Templates & Flask end-points for managing assets.
This commit is contained in:
@@ -64,14 +64,16 @@ class AttractExtension(PillarExtension):
|
||||
|
||||
from . import routes
|
||||
import attract.tasks.routes
|
||||
import attract.shots_and_assets.routes
|
||||
import attract.shots_and_assets.routes_assets
|
||||
import attract.shots_and_assets.routes_shots
|
||||
import attract.subversion.routes
|
||||
|
||||
return [
|
||||
routes.blueprint,
|
||||
attract.tasks.routes.blueprint,
|
||||
attract.tasks.routes.perproject_blueprint,
|
||||
attract.shots_and_assets.routes.perproject_blueprint,
|
||||
attract.shots_and_assets.routes_assets.perproject_blueprint,
|
||||
attract.shots_and_assets.routes_shots.perproject_blueprint,
|
||||
attract.subversion.routes.blueprint,
|
||||
attract.subversion.routes.api_blueprint,
|
||||
]
|
||||
|
97
attract/shots_and_assets/routes_assets.py
Normal file
97
attract/shots_and_assets/routes_assets.py
Normal file
@@ -0,0 +1,97 @@
|
||||
import logging
|
||||
|
||||
import flask_login
|
||||
from flask import Blueprint, render_template, request
|
||||
import flask
|
||||
import werkzeug.exceptions as wz_exceptions
|
||||
|
||||
import pillarsdk
|
||||
import pillar.api.utils
|
||||
from pillar.web.system_util import pillar_api
|
||||
|
||||
from attract.routes import attract_project_view
|
||||
from attract.node_types.asset import node_type_asset, task_types
|
||||
from attract import current_attract, ROLES_REQUIRED_TO_VIEW_ITEMS
|
||||
from pillar.web.utils import get_file
|
||||
|
||||
from . import routes_common
|
||||
|
||||
perproject_blueprint = Blueprint('attract.assets.perproject', __name__,
|
||||
url_prefix='/<project_url>/assets')
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@perproject_blueprint.route('/', endpoint='index')
|
||||
@perproject_blueprint.route('/with-task/<task_id>', endpoint='with_task')
|
||||
@attract_project_view(extension_props=True)
|
||||
def for_project(project, attract_props, task_id=None, asset_id=None):
|
||||
assets, tasks_for_assets, task_types_for_template = routes_common.for_project(
|
||||
node_type_asset['name'],
|
||||
task_types,
|
||||
project, attract_props, task_id, asset_id)
|
||||
|
||||
return render_template('attract/assets/for_project.html',
|
||||
assets=assets,
|
||||
tasks_for_assets=tasks_for_assets,
|
||||
task_types=task_types_for_template,
|
||||
open_task_id=task_id,
|
||||
open_asset_id=asset_id,
|
||||
project=project,
|
||||
attract_props=attract_props)
|
||||
|
||||
|
||||
@perproject_blueprint.route('/<asset_id>')
|
||||
@attract_project_view(extension_props=True)
|
||||
def view_asset(project, attract_props, asset_id):
|
||||
if not request.is_xhr:
|
||||
return for_project(project, attract_props, asset_id=asset_id)
|
||||
|
||||
asset, node_type = routes_common.view_node(project, asset_id, node_type_asset['name'])
|
||||
|
||||
return render_template('attract/assets/view_asset_embed.html',
|
||||
asset=asset,
|
||||
project=project,
|
||||
asset_node_type=node_type,
|
||||
attract_props=attract_props)
|
||||
|
||||
|
||||
@perproject_blueprint.route('/<asset_id>', methods=['POST'])
|
||||
@attract_project_view()
|
||||
def save(project, asset_id):
|
||||
log.info('Saving asset %s', asset_id)
|
||||
log.debug('Form data: %s', request.form)
|
||||
|
||||
asset_dict = request.form.to_dict()
|
||||
current_attract.shot_manager.edit_asset(asset_id, **asset_dict)
|
||||
|
||||
# Return the patched node in all its glory.
|
||||
api = pillar_api()
|
||||
asset = pillarsdk.Node.find(asset_id, api=api)
|
||||
return pillar.api.utils.jsonify(asset.to_dict())
|
||||
|
||||
|
||||
@perproject_blueprint.route('/create', methods=['POST'])
|
||||
@attract_project_view()
|
||||
def create_asset(project):
|
||||
asset = current_attract.shot_manager.create_asset(project)
|
||||
|
||||
resp = flask.make_response()
|
||||
resp.headers['Location'] = flask.url_for('.view_asset',
|
||||
project_url=project['url'],
|
||||
asset_id=asset['_id'])
|
||||
resp.status_code = 201
|
||||
return flask.make_response(flask.jsonify({'asset_id': asset['_id']}), 201)
|
||||
|
||||
|
||||
@perproject_blueprint.route('/<asset_id>/activities')
|
||||
@attract_project_view()
|
||||
def activities(project, asset_id):
|
||||
if not request.is_xhr:
|
||||
return flask.redirect(flask.url_for('.view_asset',
|
||||
project_url=project.url,
|
||||
asset_id=asset_id))
|
||||
acts = current_attract.activities_for_node(asset_id)
|
||||
|
||||
# NOTE: this uses the 'shots' template, because it has everything we ever wanted.
|
||||
return flask.render_template('attract/shots/view_activities_embed.html',
|
||||
activities=acts)
|
69
attract/shots_and_assets/routes_common.py
Normal file
69
attract/shots_and_assets/routes_common.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import logging
|
||||
|
||||
import flask
|
||||
import flask_login
|
||||
import werkzeug.exceptions as wz_exceptions
|
||||
|
||||
import pillarsdk
|
||||
from pillar.web.system_util import pillar_api
|
||||
from pillar.web.utils import get_file
|
||||
|
||||
from attract import current_attract, ROLES_REQUIRED_TO_VIEW_ITEMS
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def for_project(node_type_name, task_types_for_nt, project, attract_props,
|
||||
task_id=None, shot_or_asset_id=None):
|
||||
"""Common view code for assets and shots /attract/<project_url>/{assets,shots}"""
|
||||
api = pillar_api()
|
||||
|
||||
found = pillarsdk.Node.all({
|
||||
'where': {
|
||||
'project': project['_id'],
|
||||
'node_type': node_type_name,
|
||||
},
|
||||
'sort': [
|
||||
('properties.cut_in_timeline_in_frames', 1),
|
||||
]
|
||||
}, api=api)
|
||||
nodes = found['_items']
|
||||
|
||||
thumb_placeholder = flask.url_for('static_attract', filename='assets/img/placeholder.jpg')
|
||||
for node in nodes:
|
||||
picture = get_file(node.picture, api=api)
|
||||
if picture:
|
||||
node._thumbnail = next((var.link for var in picture.variations
|
||||
if var.size == 't'), thumb_placeholder)
|
||||
else:
|
||||
node._thumbnail = thumb_placeholder
|
||||
|
||||
# The placeholder can be shown quite small, but otherwise the aspect ratio of
|
||||
# the actual thumbnail should be taken into account. Since it's different for
|
||||
# each project, we can't hard-code a proper height.
|
||||
node._thumbnail_height = '30px' if node._thumbnail is thumb_placeholder else 'auto'
|
||||
|
||||
tasks_for_nodes = current_attract.shot_manager.tasks_for_nodes(nodes, task_types_for_nt)
|
||||
|
||||
# Append the task type onto which 'other' tasks are mapped.
|
||||
task_types_for_template = task_types_for_nt + [None]
|
||||
|
||||
return nodes, tasks_for_nodes, task_types_for_template
|
||||
|
||||
|
||||
def view_node(project, node_id, node_type_name):
|
||||
"""Returns the node if the user has access.
|
||||
|
||||
Uses attract.ROLES_REQUIRED_TO_VIEW_ITEMS to check permissions.
|
||||
"""
|
||||
|
||||
# asset list is public, asset details are not.
|
||||
if not flask_login.current_user.has_role(*ROLES_REQUIRED_TO_VIEW_ITEMS):
|
||||
raise wz_exceptions.Forbidden()
|
||||
|
||||
api = pillar_api()
|
||||
|
||||
node = pillarsdk.Node.find(node_id, api=api)
|
||||
node_type = project.get_node_type(node_type_name)
|
||||
|
||||
return node, node_type
|
@@ -10,10 +10,12 @@ import pillar.api.utils
|
||||
from pillar.web.system_util import pillar_api
|
||||
|
||||
from attract.routes import attract_project_view
|
||||
from attract.node_types.shot import node_type_shot
|
||||
from attract.node_types.shot import node_type_shot, task_types
|
||||
from attract import current_attract, ROLES_REQUIRED_TO_VIEW_ITEMS
|
||||
from pillar.web.utils import get_file
|
||||
|
||||
from . import routes_common
|
||||
|
||||
perproject_blueprint = Blueprint('attract.shots.perproject', __name__,
|
||||
url_prefix='/<project_url>/shots')
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -23,40 +25,10 @@ log = logging.getLogger(__name__)
|
||||
@perproject_blueprint.route('/with-task/<task_id>', endpoint='with_task')
|
||||
@attract_project_view(extension_props=True)
|
||||
def for_project(project, attract_props, task_id=None, shot_id=None):
|
||||
api = pillar_api()
|
||||
|
||||
found = pillarsdk.Node.all({
|
||||
'where': {
|
||||
'project': project['_id'],
|
||||
'node_type': node_type_shot['name'],
|
||||
},
|
||||
'sort': [
|
||||
('properties.cut_in_timeline_in_frames', 1),
|
||||
]
|
||||
}, api=api)
|
||||
shots = found['_items']
|
||||
|
||||
thumb_placeholder = flask.url_for('static_attract', filename='assets/img/placeholder.jpg')
|
||||
for shot in shots:
|
||||
picture = get_file(shot.picture, api=api)
|
||||
if picture:
|
||||
shot._thumbnail = next((var.link for var in picture.variations
|
||||
if var.size == 't'), thumb_placeholder)
|
||||
else:
|
||||
shot._thumbnail = thumb_placeholder
|
||||
|
||||
# The placeholder can be shown quite small, but otherwise the aspect ratio of
|
||||
# the actual thumbnail should be taken into account. Since it's different for
|
||||
# each project, we can't hard-code a proper height.
|
||||
shot._thumbnail_height = '30px' if shot._thumbnail is thumb_placeholder else 'auto'
|
||||
|
||||
tasks_for_shots = current_attract.shot_manager.tasks_for_nodes(
|
||||
shots,
|
||||
attract_props.task_types.attract_shot,
|
||||
)
|
||||
|
||||
# Append the task type onto which 'other' tasks are mapped.
|
||||
task_types = attract_props.task_types.attract_shot + [None]
|
||||
shots, tasks_for_shots, task_types_for_template = routes_common.for_project(
|
||||
node_type_shot['name'],
|
||||
task_types,
|
||||
project, attract_props, task_id, shot_id)
|
||||
|
||||
# Some aggregated stats
|
||||
stats = {
|
||||
@@ -69,7 +41,7 @@ def for_project(project, attract_props, task_id=None, shot_id=None):
|
||||
return render_template('attract/shots/for_project.html',
|
||||
shots=shots,
|
||||
tasks_for_shots=tasks_for_shots,
|
||||
task_types=task_types,
|
||||
task_types=task_types_for_template,
|
||||
open_task_id=task_id,
|
||||
open_shot_id=shot_id,
|
||||
project=project,
|
||||
@@ -83,14 +55,7 @@ def view_shot(project, attract_props, shot_id):
|
||||
if not request.is_xhr:
|
||||
return for_project(project, attract_props, shot_id=shot_id)
|
||||
|
||||
# Shot list is public, shot details are not.
|
||||
if not flask_login.current_user.has_role(*ROLES_REQUIRED_TO_VIEW_ITEMS):
|
||||
raise wz_exceptions.Forbidden()
|
||||
|
||||
api = pillar_api()
|
||||
|
||||
shot = pillarsdk.Node.find(shot_id, api=api)
|
||||
node_type = project.get_node_type(node_type_shot['name'])
|
||||
shot, node_type = routes_common.view_node(project, shot_id, node_type_shot['name'])
|
||||
|
||||
return render_template('attract/shots/view_shot_embed.html',
|
||||
shot=shot,
|
@@ -34,18 +34,21 @@ function item_open(item_id, item_type, pushState, project_url)
|
||||
$('[id^="' + item_type + '-"]').removeClass('active');
|
||||
$('#' + item_type + '-' + item_id).addClass('active');
|
||||
|
||||
// Special case to highlight the shot row when opening task in shot context
|
||||
if (ProjectUtils.context() == 'shot' && item_type == 'task'){
|
||||
// Special case to highlight the shot row when opening task in shot or asset context
|
||||
var pu_ctx = ProjectUtils.context();
|
||||
var pc_ctx_shot_asset = (pu_ctx == 'shot' || pu_ctx == 'asset');
|
||||
if (pc_ctx_shot_asset && item_type == 'task'){
|
||||
$('[id^="shot-"]').removeClass('active');
|
||||
$('[id^="asset-"]').removeClass('active');
|
||||
$('#task-' + item_id).closest('.table-row').addClass('active');
|
||||
}
|
||||
|
||||
var item_url = '/attract/' + project_url + '/' + item_type + 's/' + item_id;
|
||||
var push_url = item_url;
|
||||
if (ProjectUtils.context() == 'shot' && item_type == 'task'){
|
||||
push_url = '/attract/' + project_url + '/shots/with-task/' + item_id;
|
||||
if (pc_ctx_shot_asset && item_type == 'task'){
|
||||
push_url = '/attract/' + project_url + '/' + pu_ctx + 's/with-task/' + item_id;
|
||||
}
|
||||
item_url += '?context=' + ProjectUtils.context();
|
||||
item_url += '?context=' + pu_ctx;
|
||||
|
||||
statusBarSet('default', 'Loading ' + item_type + '…');
|
||||
|
||||
@@ -94,6 +97,11 @@ function shot_open(shot_id)
|
||||
item_open(shot_id, 'shot');
|
||||
}
|
||||
|
||||
function asset_open(asset_id)
|
||||
{
|
||||
item_open(asset_id, 'asset');
|
||||
}
|
||||
|
||||
window.onpopstate = function(event)
|
||||
{
|
||||
var state = event.state;
|
||||
@@ -102,26 +110,26 @@ window.onpopstate = function(event)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a task and show it in the #item-details div.
|
||||
* NOTE: Not used at the moment, we're creating shots via Blender's VSE
|
||||
* Create a asset and show it in the #item-details div.
|
||||
* NOTE: Not used at the moment, we're creating assets via Blender's VSE
|
||||
*/
|
||||
function shot_create(project_url)
|
||||
function asset_create(project_url)
|
||||
{
|
||||
if (project_url === undefined) {
|
||||
throw new ReferenceError("shot_create(" + project_url+ ") called.");
|
||||
throw new ReferenceError("asset_create(" + project_url+ ") called.");
|
||||
}
|
||||
var url = '/attract/' + project_url + '/shots/create';
|
||||
var url = '/attract/' + project_url + '/assets/create';
|
||||
|
||||
data = {
|
||||
project_url: project_url
|
||||
};
|
||||
|
||||
$.post(url, data, function(shot_data) {
|
||||
shot_open(shot_data.shot_id);
|
||||
$.post(url, data, function(asset_data) {
|
||||
asset_open(asset_data.asset_id);
|
||||
})
|
||||
.fail(function(xhr) {
|
||||
if (console) {
|
||||
console.log('Error creating task');
|
||||
console.log('Error creating asset');
|
||||
console.log('XHR:', xhr);
|
||||
}
|
||||
$('#item-details').html(xhr.responseText);
|
||||
@@ -156,17 +164,17 @@ function task_add(shot_id, task_id, task_type)
|
||||
<span class="due_date">-</span>\
|
||||
</a>\
|
||||
');
|
||||
} else if (context == 'shot') {
|
||||
} else if (context == 'shot' || context == 'asset') {
|
||||
if (shot_id === undefined) {
|
||||
throw new ReferenceError("task_add(" + shot_id + ", " + task_id + ", " + task_type + ") called in shot context.");
|
||||
throw new ReferenceError("task_add(" + shot_id + ", " + task_id + ", " + task_type + ") called in " + context + " context.");
|
||||
}
|
||||
|
||||
var $shot_cell = $('#shot-' + shot_id + ' .table-cell.task-type.' + task_type);
|
||||
var url = '/attract/' + project_url + '/shots/with-task/' + task_id;
|
||||
var $list_cell = $('#' + context + '-' + shot_id + ' .table-cell.task-type.' + task_type);
|
||||
var url = '/attract/' + project_url + '/' + context + 's/with-task/' + task_id;
|
||||
|
||||
/* WARNING: This is a copy of an element of attract/shots/for_project #task-list.col-list
|
||||
* If that changes, change this too. */
|
||||
$shot_cell.append('\
|
||||
$list_cell.append('\
|
||||
<a class="status-todo task-link active"\
|
||||
title="-save your task first-"\
|
||||
href="' + url + '"\
|
||||
@@ -175,7 +183,9 @@ function task_add(shot_id, task_id, task_type)
|
||||
</a>\
|
||||
');
|
||||
|
||||
$shot_cell.find('.task-add.task-add-link').addClass('hidden');
|
||||
$list_cell.find('.task-add.task-add-link').addClass('hidden');
|
||||
} else {
|
||||
if (console) console.log('task_add: not doing much in context', context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,6 +332,36 @@ function shot_save(shot_id, shot_url) {
|
||||
});
|
||||
}
|
||||
|
||||
function asset_save(asset_id, asset_url) {
|
||||
return attract_form_save('shot_form', 'asset-' + asset_id, asset_url, {
|
||||
done: function($asset, saved_asset) {
|
||||
// Update the asset list.
|
||||
// NOTE: this is tightly linked to the HTML of the asset list in for_project.jade.
|
||||
$('.asset-name-' + saved_asset._id).text(saved_asset.name).flashOnce();
|
||||
$asset.find('span.name').text(saved_asset.name);
|
||||
$asset.find('span.due_date').text(moment().to(saved_asset.properties.due_date));
|
||||
$asset.find('span.status').text(saved_asset.properties.status.replace('_', ' '));
|
||||
|
||||
$asset
|
||||
.removeClassPrefix('status-')
|
||||
.addClass('status-' + saved_asset.properties.status)
|
||||
.flashOnce()
|
||||
;
|
||||
|
||||
asset_open(asset_id);
|
||||
},
|
||||
fail: function($item, xhr_or_response_data) {
|
||||
if (xhr_or_response_data.status == 412) {
|
||||
// TODO: implement something nice here. Just make sure we don't throw
|
||||
// away the user's edits. It's up to the user to handle this.
|
||||
} else {
|
||||
$('#item-details').html(xhr_or_response_data.responseText);
|
||||
}
|
||||
},
|
||||
type: 'asset'
|
||||
});
|
||||
}
|
||||
|
||||
function task_delete(task_id, task_etag, task_delete_url) {
|
||||
if (task_id === undefined || task_etag === undefined || task_delete_url === undefined) {
|
||||
throw new ReferenceError("task_delete(" + task_id + ", " + task_etag + ", " + task_delete_url + ") called.");
|
||||
|
142
src/templates/attract/assets/for_project.jade
Normal file
142
src/templates/attract/assets/for_project.jade
Normal file
@@ -0,0 +1,142 @@
|
||||
| {% extends 'attract/layout.html' %}
|
||||
| {% block bodyattrs %}{{ super() }} data-context='asset'{% endblock %}
|
||||
| {% block page_title %}Assets - {{ project.name }}{% endblock %}
|
||||
| {% block body %}
|
||||
#col_main
|
||||
.col_header.task-list-header
|
||||
| {{ assets | count }} assets
|
||||
a.task-project(href="{{url_for('projects.view', project_url=project.url)}}") {{ project.name }}
|
||||
a#task-add(href="javascript:asset_create('{{ project.url }}');") + Create Asset
|
||||
|
||||
#shot-list
|
||||
.table
|
||||
.table-head
|
||||
.table-row
|
||||
.table-cell.asset-status
|
||||
.table-cell.asset-thumbnail
|
||||
span.collapser.thumbnails(title="Collapse thumbnails") Thumbnail
|
||||
.table-cell.asset-name
|
||||
span.collapser(title="Collapse name column") Name
|
||||
| {% for task_type in task_types %}
|
||||
.table-cell.task-type(class="{{ task_type }}")
|
||||
span.collapser(title="Collapse {{ task_type or 'Other' }} column") {{ task_type or 'other' }}
|
||||
| {% endfor %}
|
||||
|
||||
.table-body
|
||||
| {% for asset in assets %}
|
||||
.table-row(
|
||||
id="asset-{{ asset._id }}",
|
||||
class="status-{{ asset.properties.status }} {{ asset.properties.used_in_edit | yesno(' ,not-in-edit, ') }}")
|
||||
.table-cell.asset-status(
|
||||
title="Status: {{ asset.properties.status | undertitle }}")
|
||||
.table-cell.asset-thumbnail
|
||||
a(
|
||||
data-asset-id="{{ asset._id }}",
|
||||
href="{{ url_for('attract.assets.perproject.view_asset', project_url=project.url, asset_id=asset._id) }}",
|
||||
class="status-{{ asset.properties.status }} asset-link")
|
||||
img(src="{{ asset._thumbnail }}",
|
||||
alt="Thumbnail",
|
||||
style='width: 110px; height: {{ asset._thumbnail_height }}')
|
||||
.table-cell.asset-name
|
||||
a(
|
||||
data-asset-id="{{ asset._id }}",
|
||||
href="{{ url_for('attract.assets.perproject.view_asset', project_url=project.url, asset_id=asset._id) }}",
|
||||
class="status-{{ asset.properties.status }} asset-link")
|
||||
span(class="asset-name-{{ asset._id }}") {{ asset.name }}
|
||||
| {% for task_type in task_types %}
|
||||
.table-cell.task-type(class="{{ task_type }}")
|
||||
| {% for task in tasks_for_assets[asset._id][task_type] %}
|
||||
a(
|
||||
data-task-id="{{ task._id }}",
|
||||
id="task-{{ task._id }}",
|
||||
href="{{ url_for('attract.assets.perproject.with_task', project_url=project.url, task_id=task._id) }}",
|
||||
class="status-{{ task.properties.status }} task-link",
|
||||
title="{{ task.properties.status | undertitle }} task: {{ task.name }}")
|
||||
| {# First letter of the status. Disabled until we provide the user setting to turn it off
|
||||
span {{ task.properties.status[0] }}
|
||||
| #}
|
||||
| {% endfor %}
|
||||
//- Dirty hack, assume a user can create a task for a asset if they can edit the asset.
|
||||
| {% if 'PUT' in asset.allowed_methods %}
|
||||
a.task-add(
|
||||
title="Add a new '{{ task_type }}' task",
|
||||
class="task-add-link {% if tasks_for_assets[asset._id][task_type] %}hidden{% endif %}"
|
||||
href="javascript:task_create('{{ asset._id }}', '{{ task_type }}');")
|
||||
i.pi-plus
|
||||
| Task
|
||||
| {% endif %}
|
||||
| {% endfor %}
|
||||
| {% endfor %}
|
||||
|
||||
.col-splitter
|
||||
|
||||
#col_right
|
||||
.col_header
|
||||
span.header_text
|
||||
#status-bar
|
||||
#item-details
|
||||
.item-details-empty
|
||||
| Select a asset or Task
|
||||
|
||||
| {% endblock %}
|
||||
| {% block footer_scripts %}
|
||||
script.
|
||||
{% if open_task_id %}
|
||||
$(function() { item_open('{{ open_task_id }}', 'task', false); });
|
||||
{% endif %}
|
||||
{% if open_asset_id %}
|
||||
$(function() { item_open('{{ open_asset_id }}', 'asset', false); });
|
||||
{% endif %}
|
||||
|
||||
var same_cells;
|
||||
|
||||
/* Collapse columns by clicking on the title */
|
||||
$('.table-head .table-cell span.collapser').on('click', function(e){
|
||||
e.stopPropagation();
|
||||
|
||||
/* We need to find every cell matching the same classes */
|
||||
same_cells = '.' + $(this).parent().attr('class').split(' ').join('.');
|
||||
$(same_cells).hide();
|
||||
/* Add the spacer which we later click to expand */
|
||||
$('<div class="table-cell-spacer ' + $(this).text() + '" title="Expand ' + $(this).text() + '"></div>').insertAfter(same_cells);
|
||||
});
|
||||
|
||||
$('body').on('click', '.table-cell-spacer', function(){
|
||||
|
||||
/* We need to find every cell matching the same classes */
|
||||
same_cells = '.' + $(this).prev().attr('class').split(' ').join('.');
|
||||
$(same_cells).show();
|
||||
$(same_cells).next().remove();
|
||||
});
|
||||
|
||||
$('.table-body .table-cell').mouseenter(function(){
|
||||
same_cells = '.' + $(this).attr('class').split(' ').join('.');
|
||||
$('.table-head ' + same_cells).addClass('highlight');
|
||||
}).mouseleave(function(){
|
||||
same_cells = '.' + $(this).attr('class').split(' ').join('.');
|
||||
$('.table-head ' + same_cells).removeClass('highlight');
|
||||
});
|
||||
|
||||
$('.table-head .table-cell').mouseenter(function(){
|
||||
same_cells = '.' + $(this).attr('class').split(' ').join('.');
|
||||
$('.table-body ' + same_cells).addClass('highlight');
|
||||
}).mouseleave(function(){
|
||||
same_cells = '.' + $(this).attr('class').split(' ').join('.');
|
||||
$('.table-body ' + same_cells).removeClass('highlight');
|
||||
});
|
||||
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/clipboard.min.js')}}")
|
||||
script(src="{{ url_for('static_attract', filename='assets/js/vendor/jquery-resizable.min.js')}}")
|
||||
script.
|
||||
$("#col_main").resizable({
|
||||
handleSelector: ".col-splitter",
|
||||
resizeHeight: false
|
||||
});
|
||||
|
||||
// Set height of asset-list and item details so we can scroll inside them
|
||||
$(window).on('load resize', function(){
|
||||
var window_height = $(window).height() - 50; // header is 50px
|
||||
$('#asset-list').css({'height': window_height});
|
||||
$('#item-details').css({'height': window_height});
|
||||
});
|
||||
| {% endblock footer_scripts %}
|
124
src/templates/attract/assets/view_asset_embed.jade
Normal file
124
src/templates/attract/assets/view_asset_embed.jade
Normal file
@@ -0,0 +1,124 @@
|
||||
.attract-box.shot.with-status(class="status-{{ asset.properties.status }}")
|
||||
form#shot_form(onsubmit="return asset_save('{{asset._id}}', '{{ url_for('attract.assets.perproject.save', project_url=project['url'], asset_id=asset._id) }}')")
|
||||
input(type='hidden',name='_etag',value='{{ asset._etag }}')
|
||||
| {% if 'PUT' in asset.allowed_methods %}
|
||||
input.item-name(
|
||||
name="name",
|
||||
type="text",
|
||||
placeholder='Asset name',
|
||||
value="{{ asset.name | hide_none }}")
|
||||
| {% else %}
|
||||
span.item-name {{ asset.name | hide_none }}
|
||||
| {% endif %}
|
||||
|
||||
button.copy-to-clipboard.btn.item-id(
|
||||
style="margin-left: auto",
|
||||
name="Copy to Clipboard",
|
||||
type="button",
|
||||
data-clipboard-text="{{ asset._id }}",
|
||||
title="Copy ID to clipboard")
|
||||
| ID
|
||||
|
||||
| {% if 'PUT' in asset.allowed_methods %}
|
||||
.input-group
|
||||
textarea#item-description.input-transparent(
|
||||
name="description",
|
||||
type="text",
|
||||
rows=1,
|
||||
placeholder='Description') {{ asset.description | hide_none }}
|
||||
|
||||
.input-group
|
||||
label(for="item-status") Status:
|
||||
select#item-status.input-transparent(
|
||||
name="status")
|
||||
| {% for status in asset_node_type.dyn_schema.status.allowed %}
|
||||
| <option value="{{ status }}" {% if status == asset.properties.status %}selected{% endif %}>{{ status | undertitle }}</option>
|
||||
| {% endfor %}
|
||||
|
||||
.input-group
|
||||
textarea#item-notes.input-transparent(
|
||||
name="notes",
|
||||
type="text",
|
||||
rows=1,
|
||||
placeholder='Notes') {{ asset.properties.notes | hide_none }}
|
||||
|
||||
.input-group-separator
|
||||
|
||||
.input-group
|
||||
|
||||
button#item-save.btn.btn-default.btn-block(type='submit')
|
||||
i.pi-check
|
||||
| Save Asset
|
||||
| {% else %}
|
||||
//- NOTE: read-only versions of the fields above.
|
||||
| {% if asset.description %}
|
||||
p.item-description {{ asset.description | hide_none }}
|
||||
| {% endif %}
|
||||
|
||||
.table.item-properties
|
||||
.table-body
|
||||
.table-row.properties-status.js-help(
|
||||
data-url="{{ url_for('attract.help', project_url=project.url) }}")
|
||||
.table-cell Status
|
||||
.table-cell(class="status-{{ asset.properties.status }}")
|
||||
| {{ asset.properties.status | undertitle }}
|
||||
| {% if asset.properties.notes %}
|
||||
.table-row
|
||||
.table-cell Notes
|
||||
.table-cell
|
||||
| {{ asset.properties.notes | hide_none }}
|
||||
| {% endif %}
|
||||
| {% endif %}
|
||||
|
||||
#item-view-feed
|
||||
#activities
|
||||
#comments-embed
|
||||
|
||||
| {% if config.DEBUG %}
|
||||
.debug-info
|
||||
a.debug-info-toggle(role='button',
|
||||
data-toggle='collapse',
|
||||
href='#debug-content',
|
||||
aria-expanded='false',
|
||||
aria-controls='debug-content')
|
||||
i.pi-info
|
||||
| Debug Info
|
||||
#debug-content.collapse
|
||||
pre.
|
||||
{{ asset.to_dict() | pprint }}
|
||||
| {% endif %}
|
||||
|
||||
script.
|
||||
var clipboard = new Clipboard('.copy-to-clipboard');
|
||||
|
||||
clipboard.on('success', function(e) {
|
||||
statusBarSet('info', 'Copied asset ID to clipboard', 'pi-check');
|
||||
});
|
||||
|
||||
var activities_url = "{{ url_for('.activities', project_url=project.url, asset_id=asset['_id']) }}";
|
||||
loadActivities(activities_url); // from 10_tasks.js
|
||||
loadComments("{{ url_for('nodes.comments_for_node', node_id=asset['_id']) }}");
|
||||
|
||||
$('body').on('pillar:comment-posted', function(e, comment_node_id) {
|
||||
loadActivities(activities_url)
|
||||
.done(function() {
|
||||
$('#' + comment_node_id).scrollHere();
|
||||
});
|
||||
});
|
||||
|
||||
$('.js-help').openModalUrl('Help', "{{ url_for('attract.help', project_url=project.url) }}");
|
||||
|
||||
{% if 'PUT' in asset.allowed_methods %}
|
||||
/* Resize textareas */
|
||||
var textAreaFields = $('#item-description, #item-notes');
|
||||
|
||||
textAreaFields.each(function(){
|
||||
$(this)
|
||||
.autoResize()
|
||||
.blur();
|
||||
});
|
||||
|
||||
$('#item-status').change(function(){
|
||||
$("#item-save").trigger( "click" );
|
||||
});
|
||||
{% endif %}
|
@@ -41,6 +41,9 @@ html(lang="en")
|
||||
li
|
||||
a.navbar-item.shots(href="{{ url_for('attract.shots.perproject.index', project_url=project.url) }}",
|
||||
title='Shots for project {{ project.name }}') S
|
||||
li
|
||||
a.navbar-item.shots(href="{{ url_for('attract.assets.perproject.index', project_url=project.url) }}",
|
||||
title='Assets for project {{ project.name }}') A
|
||||
| {% else %}
|
||||
| {% if current_user.is_authenticated %}
|
||||
li
|
||||
|
Reference in New Issue
Block a user