Templates & Flask end-points for managing assets.
This commit is contained in:
@@ -64,14 +64,16 @@ class AttractExtension(PillarExtension):
|
|||||||
|
|
||||||
from . import routes
|
from . import routes
|
||||||
import attract.tasks.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
|
import attract.subversion.routes
|
||||||
|
|
||||||
return [
|
return [
|
||||||
routes.blueprint,
|
routes.blueprint,
|
||||||
attract.tasks.routes.blueprint,
|
attract.tasks.routes.blueprint,
|
||||||
attract.tasks.routes.perproject_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.blueprint,
|
||||||
attract.subversion.routes.api_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 pillar.web.system_util import pillar_api
|
||||||
|
|
||||||
from attract.routes import attract_project_view
|
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 attract import current_attract, ROLES_REQUIRED_TO_VIEW_ITEMS
|
||||||
from pillar.web.utils import get_file
|
from pillar.web.utils import get_file
|
||||||
|
|
||||||
|
from . import routes_common
|
||||||
|
|
||||||
perproject_blueprint = Blueprint('attract.shots.perproject', __name__,
|
perproject_blueprint = Blueprint('attract.shots.perproject', __name__,
|
||||||
url_prefix='/<project_url>/shots')
|
url_prefix='/<project_url>/shots')
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@@ -23,40 +25,10 @@ log = logging.getLogger(__name__)
|
|||||||
@perproject_blueprint.route('/with-task/<task_id>', endpoint='with_task')
|
@perproject_blueprint.route('/with-task/<task_id>', endpoint='with_task')
|
||||||
@attract_project_view(extension_props=True)
|
@attract_project_view(extension_props=True)
|
||||||
def for_project(project, attract_props, task_id=None, shot_id=None):
|
def for_project(project, attract_props, task_id=None, shot_id=None):
|
||||||
api = pillar_api()
|
shots, tasks_for_shots, task_types_for_template = routes_common.for_project(
|
||||||
|
node_type_shot['name'],
|
||||||
found = pillarsdk.Node.all({
|
task_types,
|
||||||
'where': {
|
project, attract_props, task_id, shot_id)
|
||||||
'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]
|
|
||||||
|
|
||||||
# Some aggregated stats
|
# Some aggregated stats
|
||||||
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',
|
return render_template('attract/shots/for_project.html',
|
||||||
shots=shots,
|
shots=shots,
|
||||||
tasks_for_shots=tasks_for_shots,
|
tasks_for_shots=tasks_for_shots,
|
||||||
task_types=task_types,
|
task_types=task_types_for_template,
|
||||||
open_task_id=task_id,
|
open_task_id=task_id,
|
||||||
open_shot_id=shot_id,
|
open_shot_id=shot_id,
|
||||||
project=project,
|
project=project,
|
||||||
@@ -83,14 +55,7 @@ def view_shot(project, attract_props, shot_id):
|
|||||||
if not request.is_xhr:
|
if not request.is_xhr:
|
||||||
return for_project(project, attract_props, shot_id=shot_id)
|
return for_project(project, attract_props, shot_id=shot_id)
|
||||||
|
|
||||||
# Shot list is public, shot details are not.
|
shot, node_type = routes_common.view_node(project, shot_id, node_type_shot['name'])
|
||||||
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'])
|
|
||||||
|
|
||||||
return render_template('attract/shots/view_shot_embed.html',
|
return render_template('attract/shots/view_shot_embed.html',
|
||||||
shot=shot,
|
shot=shot,
|
@@ -34,18 +34,21 @@ function item_open(item_id, item_type, pushState, project_url)
|
|||||||
$('[id^="' + item_type + '-"]').removeClass('active');
|
$('[id^="' + item_type + '-"]').removeClass('active');
|
||||||
$('#' + item_type + '-' + item_id).addClass('active');
|
$('#' + item_type + '-' + item_id).addClass('active');
|
||||||
|
|
||||||
// Special case to highlight the shot row when opening task in shot context
|
// Special case to highlight the shot row when opening task in shot or asset context
|
||||||
if (ProjectUtils.context() == 'shot' && item_type == 'task'){
|
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^="shot-"]').removeClass('active');
|
||||||
|
$('[id^="asset-"]').removeClass('active');
|
||||||
$('#task-' + item_id).closest('.table-row').addClass('active');
|
$('#task-' + item_id).closest('.table-row').addClass('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
var item_url = '/attract/' + project_url + '/' + item_type + 's/' + item_id;
|
var item_url = '/attract/' + project_url + '/' + item_type + 's/' + item_id;
|
||||||
var push_url = item_url;
|
var push_url = item_url;
|
||||||
if (ProjectUtils.context() == 'shot' && item_type == 'task'){
|
if (pc_ctx_shot_asset && item_type == 'task'){
|
||||||
push_url = '/attract/' + project_url + '/shots/with-task/' + item_id;
|
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 + '…');
|
statusBarSet('default', 'Loading ' + item_type + '…');
|
||||||
|
|
||||||
@@ -94,6 +97,11 @@ function shot_open(shot_id)
|
|||||||
item_open(shot_id, 'shot');
|
item_open(shot_id, 'shot');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function asset_open(asset_id)
|
||||||
|
{
|
||||||
|
item_open(asset_id, 'asset');
|
||||||
|
}
|
||||||
|
|
||||||
window.onpopstate = function(event)
|
window.onpopstate = function(event)
|
||||||
{
|
{
|
||||||
var state = event.state;
|
var state = event.state;
|
||||||
@@ -102,26 +110,26 @@ window.onpopstate = function(event)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a task and show it in the #item-details div.
|
* Create a asset and show it in the #item-details div.
|
||||||
* NOTE: Not used at the moment, we're creating shots via Blender's VSE
|
* 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) {
|
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 = {
|
data = {
|
||||||
project_url: project_url
|
project_url: project_url
|
||||||
};
|
};
|
||||||
|
|
||||||
$.post(url, data, function(shot_data) {
|
$.post(url, data, function(asset_data) {
|
||||||
shot_open(shot_data.shot_id);
|
asset_open(asset_data.asset_id);
|
||||||
})
|
})
|
||||||
.fail(function(xhr) {
|
.fail(function(xhr) {
|
||||||
if (console) {
|
if (console) {
|
||||||
console.log('Error creating task');
|
console.log('Error creating asset');
|
||||||
console.log('XHR:', xhr);
|
console.log('XHR:', xhr);
|
||||||
}
|
}
|
||||||
$('#item-details').html(xhr.responseText);
|
$('#item-details').html(xhr.responseText);
|
||||||
@@ -156,17 +164,17 @@ function task_add(shot_id, task_id, task_type)
|
|||||||
<span class="due_date">-</span>\
|
<span class="due_date">-</span>\
|
||||||
</a>\
|
</a>\
|
||||||
');
|
');
|
||||||
} else if (context == 'shot') {
|
} else if (context == 'shot' || context == 'asset') {
|
||||||
if (shot_id === undefined) {
|
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 $list_cell = $('#' + context + '-' + shot_id + ' .table-cell.task-type.' + task_type);
|
||||||
var url = '/attract/' + project_url + '/shots/with-task/' + task_id;
|
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
|
/* WARNING: This is a copy of an element of attract/shots/for_project #task-list.col-list
|
||||||
* If that changes, change this too. */
|
* If that changes, change this too. */
|
||||||
$shot_cell.append('\
|
$list_cell.append('\
|
||||||
<a class="status-todo task-link active"\
|
<a class="status-todo task-link active"\
|
||||||
title="-save your task first-"\
|
title="-save your task first-"\
|
||||||
href="' + url + '"\
|
href="' + url + '"\
|
||||||
@@ -175,7 +183,9 @@ function task_add(shot_id, task_id, task_type)
|
|||||||
</a>\
|
</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) {
|
function task_delete(task_id, task_etag, task_delete_url) {
|
||||||
if (task_id === undefined || task_etag === undefined || task_delete_url === undefined) {
|
if (task_id === undefined || task_etag === undefined || task_delete_url === undefined) {
|
||||||
throw new ReferenceError("task_delete(" + task_id + ", " + task_etag + ", " + task_delete_url + ") called.");
|
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
|
li
|
||||||
a.navbar-item.shots(href="{{ url_for('attract.shots.perproject.index', project_url=project.url) }}",
|
a.navbar-item.shots(href="{{ url_for('attract.shots.perproject.index', project_url=project.url) }}",
|
||||||
title='Shots for project {{ project.name }}') S
|
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 %}
|
| {% else %}
|
||||||
| {% if current_user.is_authenticated %}
|
| {% if current_user.is_authenticated %}
|
||||||
li
|
li
|
||||||
|
Reference in New Issue
Block a user