Introducing Pillar Framework

Refactor of pillar-server and pillar-web into a single python package. This
simplifies the overall architecture of pillar applications.

Special thanks @sybren and @venomgfx
This commit is contained in:
2016-08-19 09:19:06 +02:00
parent a5e92e1d87
commit 2c5dc34ea2
232 changed files with 79508 additions and 2232 deletions

View File

@@ -0,0 +1,22 @@
script(type="text/javascript").
/* Convert Markdown */
var convert = new Markdown.getSanitizingConverter().makeHtml;
var convert_fields = '.node-details-description, .blog_index-item .item-content';
/* Parse description/content fields to convert markdown */
$(convert_fields).each(function(i){
$(convert_fields).eq(i).html(convert($(convert_fields).eq(i).text()));
});
ProjectUtils.setProjectAttributes({isProject: true, nodeId: '', parentNodeId: ''});
var movingMode = Cookies.getJSON('bcloud_moving_node');
if (movingMode){
$('#item_move_accept').removeClass('disabled').html('<i class="pi-check"></i> Move to Root');
if (movingMode.node_type === 'texture'){
$('#item_move_accept').addClass('disabled').html('Select a Texture Folder');
}
};

View File

@@ -0,0 +1,250 @@
| {% extends 'layout.html' %}
| {% set title = 'edit' %}
| {% block page_title %}Edit {{ project.name }}{% endblock %}
| {% block body %}
#project-container
#project-side-container
#project_sidebar
ul.project-tabs
li.tabs-thumbnail(
title="About",
data-toggle="tooltip",
data-placement="left",
class="{% if title == 'about' %}active {% endif %}{% if project.picture_square %}image{% endif %}")
a(href="{{url_for('projects.about', project_url=project.url, _external=True)}}")
#project-loading
i.pi-spin
| {% if project.picture_square %}
img(src="{{ project.picture_square.thumbnail('b', api=api) }}")
| {% else %}
i.pi-home
| {% endif %}
li.tabs-browse(
title="Browse",
data-toggle="tooltip",
data-placement="left")
a(href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
i.pi-tree-flow
| {% if not project.is_private %}
li.tabs-search(
title="Search",
data-toggle="tooltip",
data-placement="left")
a(href="{{url_for('projects.search', project_url=project.url, _external=True)}}")
i.pi-search
| {% endif %}
.project_nav-toggle-btn(
title="Expand Navigation [T]",
data-toggle="tooltip",
data-placement="right")
i.pi-angle-double-left
#project_nav
#project_nav-container
#project_nav-header
.project-title
a(href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
| {{ project.name }}
// TODO - make list a macro
#project_tree
ul.project_nav-edit-list
li(class="{% if title == 'edit' %}active{% endif %}")
a(href="{{ url_for('projects.edit', project_url=project.url) }}")
i.pi-list
| Overview
li(class="{% if title == 'sharing' %}active{% endif %}")
a(href="{{ url_for('projects.sharing', project_url=project.url) }}")
i.pi-share
| Sharing
li(class="{% if title == 'edit_node_types' %}active{% endif %}")
a(href="{{ url_for('projects.edit_node_types', project_url=project.url) }}")
i.pi-puzzle
| Node Types
.project_split(title="Toggle Navigation [T]")
#project_context-container
#project_context-header
span#project-statusbar
span#project-edit-title
| Edit Project
ul.project-edit-tools
// Edit Mode
li.button-cancel
a#item_cancel.project-mode-edit(
href="{{url_for('projects.view', project_url=project.url, _external=True)}}",
title="Cancel changes")
i.button-cancel-icon.pi-back
| Go to Project
li.button-save
a#item_save.project-mode-edit(
href="#",
title="Save changes")
i.button-save-icon.pi-check
| Save Changes
#project_context
#node-edit-container
form(
id="node-edit-form"
method='POST',
action="{{url_for('projects.edit', project_url=project.url)}}")
| {% with errors = errors %}
| {% if errors %}
| {% for field in errors %}
.alert.alert-danger(role='alert')
strong {{field}}
| {% for message in errors[field] %}
| {{message}}|
| {% endfor %}
| {% endfor %}
| {% endif %}
| {% endwith %}
| {% for field in form %}
| {% if field.name == 'csrf_token' %}
| {{ field }}
| {% else %}
| {% if field.type == 'HiddenField' %}
| {{ field }}
| {% else %}
| {% if field.name not in hidden_fields %}
.form-group(class="{{field.name}}{% if field.errors %} error{% endif %}")
| {{ field.label }}
| {% if field.name == 'picture' %}
| {% if post.picture %}
img.node-preview-thumbnail(src="{{ post.picture.thumbnail('m', api=api) }}")
a(href="#", class="file_delete", data-field-name="picture", data-file_id="{{post.picture._id}}") Delete
| {% endif %}
| {% endif %}
| {{ field(class='form-control') }}
| {% if field.errors %}
ul.error
| {% for error in field.errors %}
li {{ error }}
| {% endfor %}
| {% endif %}
| {% else %}
| {{ field(class='hidden') }}
| {% endif %}
| {% endif %}
| {% endif %}
| {% endfor %}
ul.project-edit-tools.bottom
li.button-cancel
a#item_cancel.project-mode-edit(
href="{{url_for('projects.view', project_url=project.url, _external=True)}}",
title="Cancel changes")
i.button-cancel-icon.pi-back
| Go to Project
li.button-save
a#item_save.project-mode-edit(
href="#",
title="Save changes")
i.button-save-icon.pi-check
| Save Changes
| {% endblock %}
| {% block footer_scripts %}
script(type='text/javascript', src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.ui.widget.min.js') }}")
script(type='text/javascript', src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.iframe-transport.min.js') }}")
script(type='text/javascript', src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.fileupload.min.js') }}")
script(type='text/javascript', src="{{ url_for('static_pillar', filename='assets/js/file_upload.min.js') }}")
script(type="text/javascript").
/* UI Stuff */
$(window).on("load resize",function(){
containerResizeY($(window).height());
});
/* Initialize scrollbars */
if ((typeof Ps !== 'undefined') && window.innerWidth > 768){
Ps.initialize(document.getElementById('project_tree'), {suppressScrollX: true});
}
$('.project-mode-edit').show();
ProjectUtils.setProjectAttributes({projectId: "{{project._id}}", isProject: true, nodeId: ''});
var convert = new Markdown.getSanitizingConverter().makeHtml;
$('.button-save').on('click', function(e){
e.preventDefault();
// Disable beforeunolad when submitting a form
$(window).off('beforeunload');
$(this).children('a').html('<i class="pi-spin spin"></i> Saving');
$('#node-edit-form').submit();
});
/* Build the markdown preview when typing in textarea */
$(function() {
var $textarea = $('.form-group.description textarea'),
$loader = $('<div class="md-preview-loading"><i class="pi-spin spin"></i></div>').insertAfter($textarea),
$preview = $('<div class="node-edit-form-md-preview" />').insertAfter($loader);
$loader.hide();
// Delay function to not start converting heavy posts immediately
var delay = (function(){
var timer = 0;
return function(callback, ms){
clearTimeout (timer);
timer = setTimeout(callback, ms);
};
})();
$textarea.keyup(function() {
/* If there's an iframe (YouTube embed), delay markdown convert 1.5s */
if (/iframe/i.test($textarea.val())) {
$loader.show();
delay(function(){
// Convert markdown
$preview.html(convert($textarea.val()));
$loader.hide();
}, 1500 );
} else {
// Convert markdown
$preview.html(convert($textarea.val()));
};
}).trigger('keyup');
$('input, textarea').keypress(function () {
// Unused: save status of the page as 'edited'
ProjectUtils.setProjectAttributes({isModified: true});
// Set the beforeunload to warn the user of unsaved changes
$(window).on('beforeunload', function () {
return 'You have unsaved changes in your project.';
});
});
});
| {% endblock %}
| {% block footer_navigation %}
| {% endblock %}
| {% block footer %}
| {% endblock %}

View File

@@ -0,0 +1,88 @@
| {% extends 'layout.html' %}
| {% set title = 'edit_node_types' %}
| {% block page_title %}Project {{ project.name }}{% endblock %}
| {% block body %}
.container.box
form(
method='POST',
action="{{url_for('projects.edit_node_type', project_url=project.url, node_type_name=node_type['name'])}}")
#blog_container.post-create
| {% with errors = errors %}
| {% if errors %}
| {% for field in errors %}
.alert.alert-danger(role='alert')
strong {{field}}
| {% for message in errors[field] %}
| {{message}}|
| {% endfor %}
| {% endfor %}
| {% endif %}
| {% endwith %}
#blog_index-sidebar
.blog_project-sidebar
input.btn.btn-default.button-create(type='submit', value="Update {{ node_type['name'] }}")
a.btn.btn-default.button-back(href="{{ url_for('projects.view', project_url=project.url) }}")
| Back to Project
#blog_post-edit-container
#blog_post-edit-title
| Edit {{ node_type['name'] }}
#blog_post-edit-form
| {% for field in form %}
| {% if field.name == 'csrf_token' %}
| {{ field }}
| {% else %}
| {% if field.type == 'HiddenField' %}
| {{ field }}
| {% else %}
.form-group(class="{{field.name}}{% if field.errors %} error{% endif %}")
| {{ field.label }}
| {{ field(class='form-control') }}
| {% if field.errors %}
ul.error
| {% for error in field.errors %}
li {{ error }}
| {% endfor %}
| {% endif %}
| {% endif %}
| {% endif %}
| {% endfor %}
| {% endblock %}
| {% block footer_scripts%}
script(src="https://cdn.jsdelivr.net/g/ace@1.2.3(noconflict/ace.js+noconflict/mode-json.js)")
script.
var dynSchemaEditorContainer = $("<div>", {id: "dyn_schema_editor"});
$(".form-group.dyn_schema").before(dynSchemaEditorContainer);
var dynSchemaEditor = ace.edit("dyn_schema_editor");
dynSchemaEditor.getSession().setValue($("#dyn_schema").val());
var formSchemaEditorContainer = $("<div>", {id: "form_schema_editor"});
$(".form-group.form_schema").before(formSchemaEditorContainer);
var formSchemaEditor = ace.edit("form_schema_editor");
formSchemaEditor.getSession().setValue($("#form_schema").val());
var permissionsEditorContainer = $("<div>", {id: "permissions_editor"});
$(".form-group.permissions").before(permissionsEditorContainer);
var permissionsEditor = ace.edit("permissions_editor");
permissionsEditor.getSession().setValue($("#permissions").val());
$("form").submit(function(e) {
$("#dyn_schema").val(dynSchemaEditor.getSession().getValue());
$("#form_schema").val(formSchemaEditor.getSession().getValue());
$("#permissions").val(permissionsEditor.getSession().getValue());
});
| {% endblock %}

View File

@@ -0,0 +1,110 @@
| {% extends 'layout.html' %}
| {% set title = 'edit_node_types' %}
| {% block page_title %}Node Types: {{ project.name }}{% endblock %}
| {% block body %}
#project-container
#project-side-container
#project_sidebar
ul.project-tabs
li.tabs-thumbnail(
title="About",
data-toggle="tooltip",
data-placement="left",
class="{% if title == 'about' %}active {% endif %}{% if project.picture_square %}image{% endif %}")
a(href="{{url_for('projects.about', project_url=project.url, _external=True)}}")
#project-loading
i.pi-spin
| {% if project.picture_square %}
img(src="{{ project.picture_square.thumbnail('b', api=api) }}")
| {% else %}
i.pi-home
| {% endif %}
li.tabs-browse(
title="Browse",
data-toggle="tooltip",
data-placement="left")
a(href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
i.pi-tree-flow
| {% if not project.is_private %}
li.tabs-search(
title="Search",
data-toggle="tooltip",
data-placement="left")
a(href="{{url_for('projects.search', project_url=project.url, _external=True)}}")
i.pi-search
| {% endif %}
.project_nav-toggle-btn(
title="Expand Navigation [T]",
data-toggle="tooltip",
data-placement="right")
i.pi-angle-double-left
#project_nav
#project_nav-container
#project_nav-header
.project-title
a(href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
| {{ project.name }}
// TODO - make list a macro
#project_tree
ul.project_nav-edit-list
li(class="{% if title == 'edit' %}active{% endif %}")
a(href="{{ url_for('projects.edit', project_url=project.url) }}")
i.pi-list
| Overview
li(class="{% if title == 'sharing' %}active{% endif %}")
a(href="{{ url_for('projects.sharing', project_url=project.url) }}")
i.pi-share
| Sharing
li(class="{% if title == 'edit_node_types' %}active{% endif %}")
a(href="{{ url_for('projects.edit_node_types', project_url=project.url) }}")
i.pi-puzzle
| Node Types
.project_split(title="Toggle Navigation [T]")
#project_context-container
#project_context-header
span#project-statusbar
span#project-edit-title
| Edit Project
#project_context
#node-edit-container
div(id="node-edit-form")
h3 Node Types (coming soon)
p.
Nodes are all the items that can be found in a project.
Everything is a node: a file, a folder, a comment. They are
defined with custom properties and properly presented to you.
When we add support for new node types in the future, it means we
allow the creation of new items (such as textures).
| {% if current_user.has_role('admin') %}
ul
| {% for node_type in project.node_types %}
li
a(href="{{ url_for('projects.edit_node_type', project_url=project.url, node_type_name=node_type.name) }}")
| {{node_type.name}}
| {% endfor %}
| {% endif %}
| {% endblock %}
| {% block footer_scripts %}
script(type="text/javascript").
$(window).on("load resize",function(){
containerResizeY($(window).height());
});
| {% endblock %}
| {% block footer_navigation %}
| {% endblock %}
| {% block footer %}
| {% endblock %}

View File

@@ -0,0 +1,139 @@
| {% extends 'projects/home_layout.html' %}
| {% set subtab = 'images' %}
| {% set learn_more_btn_url = '/blog/introducing-image-sharing' %}
| {% block currenttab %}
section.nav-tabs__tab.active#tab-images
.tab_header-container
| {% if not shared_images %}
.tab_header-intro(
style="background-image: url({{ url_for('static', filename='assets/img/backgrounds/pattern_01.jpg')}})")
.tab_header-intro_text
h2 Share what you see.
p.
Got a nice render, a Blender oddity, or a cool screenshot?
<br/>
Share it instantly from within Blender to the world!
.tab_header-intro_icons
i.pi-blender
i.pi-heart-filled
i.pi-picture-album
| {% endif %}
| {% if shared_images %}
div#home-images__list
| {% for node in shared_images %}
div.home-images__list-item
.home-images__list-details
a.title(href="{{ url_for_node(node=node) }}?t")
| {{ node.name }}
| {% if node.picture %}
a.home-images__list-thumbnail(
href="{{ url_for_node(node=node) }}?t")
img(src="{{ node.picture.thumbnail('l', api=api) }}")
| {% endif %}
.home-images__list-details
ul.meta
li.when(title="{{ node._created }}") {{ node._created | pretty_date_time }}
li.delete-image
a.delete-prompt(href='javascript:void(0);')
| Delete
span.delete-confirm
| Are you sure?
a.delete-confirm(href='javascript:void(0);',
data-image-id="{{ node._id }}")
i.pi-check
| Yes, delete
a.delete-cancel(href='javascript:void(0);')
i.pi-cancel
| No, cancel
| {% if node.short_link %}
li
a(href="{{ node.short_link }}") {{ node.short_link }}
| {% endif %}
| {% endfor %}
| {% else %}
.blender_sync-main.empty
.blender_sync-main-header
span.blender_sync-main-title
| Share some images using the
a(
href="https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip")
| Blender Cloud add-on.
| {% endif %}
| {% endblock %}
| {% block side_announcement %}
.title
a(href="https://cloud.blender.org/blog/introducing-image-sharing") Image Sharing
.lead
p.
Share your renders, painted textures, and other images, straight from Blender
to the cloud.
hr
| {% if show_addon_download_buttons %}
p.
Image Sharing requires a Blender Cloud subscription, which you have!
| {% else %}
p.
Image Sharing requires a Blender Cloud subscription.
.buttons
a.btn.btn-default.btn-outline.green(href="https://store.blender.org/product/membership/")
| Join Now
| {% endif %}
| {% endblock %}
| {% block footer_scripts %}
| {{ super() }}
script.
var urlNodeDelete = "{{url_for('projects.delete_node')}}";
$(document).ready(function() {
// 'Delete' link on images
var $home_image_list = $('#home-images__list');
$home_image_list.find('a.delete-prompt').on('click', function(e){
$(this)
.hide()
.next().show();
});
// 'Cancel delete' link on images
$home_image_list.find('a.delete-cancel').on('click', function(e){
$(this).parent()
.hide()
.prev().show();
});
// 'Confirm delete' link on images
$home_image_list.find('a.delete-confirm').on('click', function (e) {
var image_id = this.dataset.imageId;
var $this = $(this);
var parent = $this.closest('.home-images__list-item');
console.log('My parent is', parent);
var error_elt = $this.parent();
$.ajax({
type: 'POST',
url: urlNodeDelete,
data: {node_id: image_id},
success: function () {
if (parent.siblings().length == 0) {
// This was the last shared image. Reload the page,
// so that we can show the correct "no images shared"
// content with Jinja2.
window.location = window.location;
}
parent.hide('slow', function() { parent.remove(); });
},
error: function (jqxhr, textStatus, errorThrown) {
error_elt.text('Unable to delete image; ' + textStatus + ': ' + errorThrown);
}
});
});
hopToTop(); // Display jump to top button
});
| {% endblock %}

View File

@@ -0,0 +1,43 @@
| {% extends 'projects/home_layout.html' %}
| {% set subtab = 'blender_sync' %}
| {% set learn_more_btn_url = '/blog/introducing-blender-sync' %}
| {% block currenttab %}
section.nav-tabs__tab.active#tab-blender_sync
.tab_header-container
.tab_header-intro(
style="background-image: url({{ url_for('static', filename='assets/img/backgrounds/pattern_01.jpg')}})")
.tab_header-intro_text
h2 Connect Blender with the Cloud
p
| Save your Blender preferences once, load them anywhere.
<br/>
| Use the
=' '
a(href='https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip') Blender Cloud add-on
=' '
| to synchronise your settings from within Blender.
.tab_header-intro_icons
i.pi-blender
i.pi-heart-filled
i.pi-blender-cloud
| {% for version in synced_versions %}
.blender_sync-main
.blender_sync-main-header
h2.blender_sync-main-title
i.pi-blender
| Blender {{ version.version }}
.blender_sync-main-last
| Last synced on: {{ version.date|pretty_date }}
| {% else %}
.blender_sync-main.empty
.blender_sync-main-header
span.blender_sync-main-title
| No settings synced yet
<hr/>
a.download(
href='https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip')
| Download add-on
| {% endfor %}
| {% endblock %}

View File

@@ -0,0 +1,79 @@
| {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_tabs %}
| {% set title = 'home' %}
| {% block og %}
meta(property="og:title", content="Blender Cloud - Home")
meta(property="og:url", content="https://cloud.blender.org{{ request.path }}")
meta(property="og:type", content="website")
| {% endblock %}
| {% block tw %}
meta(name="twitter:card", content="summary_large_image")
meta(name="twitter:site", content="@Blender_Cloud")
meta(name="twitter:title", content="Blender Cloud")
meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}")
| {% endblock %}
| {% block page_title %}
| {{current_user.full_name}}
| {% endblock %}
| {% block body %}
.dashboard-container
section#main
| {{ navigation_tabs(title) }}
section#projects
section#sub-nav-tabs.home
ul#sub-nav-tabs__list
li.nav-tabs__list-tab#subtab-blender_sync(data-tab-url='.')
i.pi-blender
| Blender Sync
li.nav-tabs__list-tab#subtab-images(data-tab-url='images')
i.pi-picture
| Images
| {% block currenttab %}{% endblock %}
section#side
section#announcement
img.header(
src="{{ url_for('static', filename='assets/img/blender_sync_header.jpg') }}")
.text
| {% block side_announcement %}
.title
a(href="https://cloud.blender.org/blog/introducing-blender-sync") Blender Sync
.lead
span.
Save your settings once. Use them anywhere.
Carry your Blender configuration with you, use our free add-on to sync your keymaps and preferences.
<hr/>
Syncing is free for everyone. No subscription required.
| {% endblock %}
| {% if show_addon_download_buttons %}
.buttons
a.btn.btn-default.btn-outline.orange(
href="https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip")
i.pi-download
| Download <small>v</small>{{ config.BLENDER_CLOUD_ADDON_VERSION }}
a.btn.btn-default.btn-outline.blue(
href="{{ learn_more_btn_url }}")
| Learn More
| {% endif %}
| {% endblock %}
| {% block footer_scripts %}
script.
$(document).ready(function () {
$('#subtab-{{ subtab }}').addClass('active');
var $nav_tabs = $('#sub-nav-tabs__list').find('li.nav-tabs__list-tab');
$nav_tabs.on('click', function (e) {
window.location = $(this).attr('data-tab-url');
});
});
| {% endblock %}

View File

@@ -0,0 +1,87 @@
| {% extends 'layout.html' %}
| {% block og %}
meta(property="og:title", content="{% if title == 'open-projects' %}Open Projects{% elif title == 'training' %}Training{% endif %}")
// XXX - Replace with actual url
meta(property="og:url", content="https://cloud.blender.org")
meta(property="og:type", content="website")
| {% endblock %}
| {% block tw %}
meta(name="twitter:card", content="summary_large_image")
meta(name="twitter:site", content="@Blender_Cloud")
meta(name="twitter:title", content="{% if title == 'open-projects' %}Open Projects{% elif title == 'training' %}Training{% endif %} on Blender Cloud")
meta(name="twitter:description", content="{% if title == 'open-projects' %}Full production data and tutorials from all open movies, for you to use freely{% elif title == 'training' %}Production quality training by 3D professionals{% endif %}")
meta(name="twitter:image", content="{% if title == 'training' %}{{ url_for('static', filename='assets/img/backgrounds/background_caminandes_3_03.jpg')}}{% else %}{{ url_for('static', filename='assets/img/backgrounds/background_agent327_01.jpg')}}{% endif %}")
| {% endblock %}
| {% block page_title %}
| {% if title == 'open-projects' %}Open Projects{% elif title == 'training' %}Training{% else %}Projects{% endif %}
| {% endblock %}
| {% block body %}
#project-container
#node_index-container
#node_index-header.collection
img.background-header(src="{% if title == 'training' %}{{ url_for('static', filename='assets/img/backgrounds/background_caminandes_3_03.jpg')}}{% else %}{{ url_for('static', filename='assets/img/backgrounds/background_agent327_01.jpg')}}{% endif %}")
#node_index-collection-info
| {% if title == 'open-projects' %}
.node_index-collection-name
span Open Projects
.node_index-collection-description
span.
The iconic Blender Institute Open Movies.
Featuring all the production files, assets, artwork, and never-seen-before content.
| {% elif title == 'training' %}
.node_index-collection-name
span Training
.node_index-collection-description
span.
Character modeling, 3D printing, VFX, rigging and more.
| {% endif %}
.node_index-collection
| {% for project in projects %}
| {% if (project.status == 'published') or (project.status == 'pending' and current_user.is_authenticated) and project._id != config.MAIN_PROJECT_ID %}
.node_index-collection-card.project(
data-url="{{ url_for('projects.view', project_url=project.url) }}",
tabindex="{{ loop.index }}")
| {% if project.picture_header %}
a.item-header(
href="{{ url_for('projects.view', project_url=project.url) }}")
img(src="{{ project.picture_header.thumbnail('m', api=api) }}")
| {% endif %}
.item-info
a.item-title(
href="{{ url_for('projects.view', project_url=project.url) }}")
| {{project.name}}
| {% if project.status == 'pending' and current_user.is_authenticated and current_user.has_role('admin') %}
small (pending)
| {% endif %}
| {% if project.summary %}
p.item-description
| {{project.summary|safe}}
| {% endif %}
a.learn-more LEARN MORE
| {% endif %}
| {% endfor %}
| {% endblock %}
| {% block footer_scripts %}
script.
$('.node_index-collection-card.project').on('click', function(e){
e.preventDefault();
window.location.href = $(this).data('url');
});
| {% endblock %}

View File

@@ -0,0 +1,232 @@
| {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_tabs %}
| {% set title = 'dashboard' %}
| {% block og %}
meta(property="og:title", content="Dashboard")
meta(property="og:url", content="https://cloud.blender.org/{{ request.path }}")
meta(property="og:type", content="website")
| {% endblock %}
| {% block tw %}
meta(name="twitter:card", content="summary_large_image")
meta(name="twitter:site", content="@Blender_Cloud")
meta(name="twitter:title", content="Blender Cloud")
meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}")
| {% endblock %}
| {% block page_title %}
| {{current_user.full_name}}
| {% endblock %}
| {% block body %}
.dashboard-container
section#main
| {{ navigation_tabs(title) }}
section#projects
section#sub-nav-tabs.projects
ul#sub-nav-tabs__list
li.nav-tabs__list-tab.active(data-tab-toggle='own_projects')
| Own Projects
| {% if projects_user|length != 0 %}
span ({{ projects_user|length }})
| {% endif %}
li.nav-tabs__list-tab(data-tab-toggle='shared')
| Shared with me
| {% if projects_shared|length != 0 %}
span ({{ projects_shared|length }})
| {% endif %}
| {% if (current_user.has_role('subscriber') or current_user.has_role('admin')) %}
li.create(
data-url="{{ url_for('projects.create') }}")
a#project-create(
href="{{ url_for('projects.create') }}")
i.pi-plus
| Create Project
| {% endif %}
section.nav-tabs__tab.active#own_projects
ul.projects__list
| {% if projects_user %}
| {% for project in projects_user %}
li.projects__list-item(
data-url="{{ url_for('projects.view', project_url=project.url) }}")
a.projects__list-thumbnail(
href="{{ url_for('projects.view', project_url=project.url) }}")
| {% if project.picture_square %}
img(src="{{ project.picture_square.thumbnail('s', api=api) }}")
| {% else %}
i.pi-blender-cloud
| {% endif %}
.projects__list-details
a.title(href="{{ url_for('projects.view', project_url=project.url) }}")
| {{ project.name }}
ul.meta
li.when(title="{{ project._created }}") {{ project._created | pretty_date }}
li.edit
a(href="{{ url_for('projects.edit', project_url=project.url) }}") Edit
| {% if project.status == 'pending' and current_user.is_authenticated and current_user.has_role('admin') %}
li.pending Not Published
| {% endif %}
| {% endfor %}
| {% else %}
li.projects__list-item
a.projects__list-thumbnail
i.pi-plus
.projects__list-details
a.title(href="{{ url_for('projects.create') }}")
| Create a project to get started!
| {% endif %}
section.nav-tabs__tab#shared
ul.projects__list
| {% if projects_shared %}
| {% for project in projects_shared %}
li.projects__list-item(
data-url="{{ url_for('projects.view', project_url=project.url) }}")
a.projects__list-thumbnail(
href="{{ url_for('projects.view', project_url=project.url) }}")
| {% if project.picture_square %}
img(src="{{ project.picture_square.thumbnail('s', api=api) }}")
| {% else %}
i.pi-blender-cloud
| {% endif %}
.projects__list-details
a.title(href="{{ url_for('projects.view', project_url=project.url) }}")
| {{ project.name }}
ul.meta
li.when {{ project._created | pretty_date }}
li.who by {{ project.user.full_name }}
li.edit
a(href="{{ url_for('projects.edit', project_url=project.url) }}") Edit
| {% if project.status == 'pending' and current_user.is_authenticated and current_user.has_role('admin') %}
li.pending Not Published
| {% endif %}
li.leave
span.user-remove-prompt
| Leave Project
span.user-remove
| Are you sure?
span.user-remove-confirm(
user-id="{{ current_user.objectid }}",
project-url="{{url_for('projects.sharing', project_url=project.url)}}")
i.pi-check
| Yes, leave
span.user-remove-cancel
i.pi-cancel
| No, cancel
| {% endfor %}
| {% else %}
li.projects__list-item
a.projects__list-thumbnail
i.pi-heart
.projects__list-details
.title
| No projects shared with you... yet!
| {% endif %}
section#side
section#announcement
img.header(
src="{{ url_for('static', filename='assets/img/backgrounds/services_projects.jpg')}}")
.text
.title Projects
.lead
span.
Create and manage your own personal projects.
Upload assets and collaborate with other Blender Cloud members.
.buttons
a.btn.btn-default.btn-outline.blue(
href="https://cloud.blender.org/blog/introducing-private-projects")
| Learn More
| {% endblock %}
| {% block footer_scripts %}
script.
$(document).ready(function() {
$('li.projects__list-item').click(function(e){
url = $(this).data('url');
if (typeof url === 'undefined') return;
window.location.href = url;
if (console) console.log(url);
$(this).addClass('active');
$(this).find('.projects__list-thumbnail i')
.removeAttr('class')
.addClass('pi-spin spin');
});
// Tabs behavior
var $nav_tabs_list = $('#sub-nav-tabs__list');
var $nav_tabs = $nav_tabs_list.find('li.nav-tabs__list-tab');
$nav_tabs.on('click', function(e){
e.preventDefault();
$nav_tabs.removeClass('active');
$(this).addClass('active');
$('.nav-tabs__tab').hide();
$('#' + $(this).attr('data-tab-toggle')).show();
});
// Create project
$nav_tabs_list.find('li.create').on('click', function(e){
e.preventDefault();
$(this).addClass('disabled');
$('a', this).html('<i class="pi-spin spin"></i> Creating project...');
window.location.href = $(this).data('url');
});
// Leave project
var $projects_list = $('ul.projects__list');
$projects_list.find('span.user-remove-prompt').on('click', function(e){
e.stopPropagation();
e.preventDefault();
$(this).next().show();
$(this).hide();
});
$projects_list.find('span.user-remove-cancel').on('click', function(e){
e.stopPropagation();
e.preventDefault();
$(this).parent().prev().show();
$(this).parent().hide();
});
$projects_list.find('span.user-remove-confirm').on('click', function(e){
e.stopPropagation();
e.preventDefault();
var parent = $(this).closest('projects__list-item');
function removeUser(userId, projectUrl){
$.post(projectUrl, {user_id: userId, action: 'remove'})
.done(function (data) {
parent.remove();
});
}
removeUser($(this).attr('user-id'), $(this).attr('project-url'));
});
hopToTop(); // Display jump to top button
});
| {% endblock %}

View File

@@ -0,0 +1,266 @@
| {% extends 'layout.html' %}
| {% set title = 'sharing' %}
| {% block page_title %}Sharing: {{ project.name }}{% endblock %}
| {% block body %}
#project-container
#project-side-container
#project_sidebar
ul.project-tabs
li.tabs-thumbnail(
title="About",
data-toggle="tooltip",
data-placement="left",
class="{% if title == 'about' %}active {% endif %}{% if project.picture_square %}image{% endif %}")
a(href="{{url_for('projects.about', project_url=project.url, _external=True)}}")
#project-loading
i.pi-spin
| {% if project.picture_square %}
img(src="{{ project.picture_square.thumbnail('b', api=api) }}")
| {% else %}
i.pi-home
| {% endif %}
li.tabs-browse(
title="Browse",
data-toggle="tooltip",
data-placement="left")
a(href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
i.pi-tree-flow
| {% if not project.is_private %}
li.tabs-search(
title="Search",
data-toggle="tooltip",
data-placement="left")
a(href="{{url_for('projects.search', project_url=project.url, _external=True)}}")
i.pi-search
| {% endif %}
.project_nav-toggle-btn(
title="Expand Navigation [T]",
data-toggle="tooltip",
data-placement="right")
i.pi-angle-double-left
#project_nav
#project_nav-container
#project_nav-header
.project-title
a(href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
| {{ project.name }}
// TODO - make list a macro
#project_tree
ul.project_nav-edit-list
li(class="{% if title == 'edit' %}active{% endif %}")
a(href="{{ url_for('projects.edit', project_url=project.url) }}")
i.pi-list
| Overview
li(class="{% if title == 'sharing' %}active{% endif %}")
a(href="{{ url_for('projects.sharing', project_url=project.url) }}")
i.pi-share
| Sharing
li(class="{% if title == 'edit_node_types' %}active{% endif %}")
a(href="{{ url_for('projects.edit_node_types', project_url=project.url) }}")
i.pi-puzzle
| Node Types
.project_split(title="Toggle Navigation [T]")
#project_context-container
#project_context-header
span#project-statusbar
span#project-edit-title
| Manage users for this project
#project_context
#node-edit-container
#node-edit-form
.col-md-6
| {% if (project.user == current_user.objectid or current_user.has_role('admin')) %}
.sharing-users-search
.form-group
input#user-select.form-control(
name='contacts',
type='text',
placeholder='Add users by name')
| {% else %}
.sharing-users-search
.disabled Only project owners can manage users
| {% endif %}
ul.sharing-users-list
| {% for user in users %}
li.sharing-users-item(
user-id="{{ user['_id'] }}",
class="{% if current_user.objectid == user['_id'] %}self{% endif %}")
.sharing-users-avatar
img(src="{{ user['avatar'] }}")
.sharing-users-details
span.sharing-users-name
| {{user['full_name']}}
| {% if project.user == user['_id'] and current_user.objectid == user['_id'] %}
small (You, owner)
| {% elif project.user == user['_id'] %}
small (Owner)
| {% elif current_user.objectid == user['_id'] %}
small (You)
| {% endif %}
span.sharing-users-extra {{user['username']}}
.sharing-users-action
| {# Only allow deletion if we are: admin, project owners, or current_user in the team #}
| {% if current_user.has_role('admin') or (project.user == current_user.objectid) or (current_user.objectid == user['_id']) %}
| {% if project.user == user['_id'] %}
span
i.pi-happy(title="Hi boss!")
| {% elif current_user.objectid == user['_id'] %}
button.user-remove(title="Leave this project") Leave
| {% else %}
button.user-remove(title="Remove this user from your project")
i.pi-trash
| {% endif %}
| {% endif %}
| {% endfor %}
.col-md-6
.sharing-users-info
h4 What can team members do?
p.
Team members are able to upload new content to the
project; as well as view, edit, and comment on the content previously created.
| {% endblock %}
| {% block footer_navigation %}
| {% endblock %}
| {% block footer_scripts %}
script(type="text/javascript").
$(window).on("load resize",function(){
containerResizeY($(window).height());
});
| {% if (project.user == current_user.objectid or current_user.has_role('admin')) %}
script(src='//cdn.jsdelivr.net/autocomplete.js/0/autocomplete.jquery.min.js')
script.
$(document).ready(function() {
var APPLICATION_ID = '{{config.ALGOLIA_USER}}'
var SEARCH_ONLY_API_KEY = '{{config.ALGOLIA_PUBLIC_KEY}}';
var INDEX_NAME = '{{config.ALGOLIA_INDEX_USERS}}';
var client = algoliasearch(APPLICATION_ID, SEARCH_ONLY_API_KEY);
var index = client.initIndex(INDEX_NAME);
$('#user-select').autocomplete({hint: false}, [
{
source: function (q, cb) {
index.search(q, {hitsPerPage: 5}, function (error, content) {
if (error) {
cb([]);
return;
}
cb(content.hits, content);
});
},
displayKey: 'full_name',
minLength: 2,
limit: 10,
templates: {
suggestion: function (hit) {
return hit._highlightResult.full_name.value + ' (' + hit._highlightResult.username.value + ')';
}
}
}
]).on('autocomplete:selected', function (event, hit, dataset) {
var lis = document.getElementsByClassName('sharing-users-item');
var has_match = false;
for (var i = 0; i < lis.length; ++i) {
// Check if the user already is in the list
if ($(lis[i]).attr('user-id') == hit.objectID){
$(lis[i]).addClass('active');
setTimeout(function(){ $('.sharing-users-item').removeClass('active');}, 350);
statusBarSet('info', 'User is already part of the project', 'pi-info');
has_match = false;
break;
} else {
has_match = true;
continue;
}
};
if (has_match){
addUser(hit.objectID);
}
});
function addUser(userId){
if (userId && userId.length > 0) {
$.post("{{url_for('projects.sharing', project_url=project.url)}}",
{user_id: userId, action: 'add'})
.done(function (data) {
$("ul.sharing-users-list").prepend('' +
'<li class="sharing-users-item" user-id="' + data._id + '">' +
'<div class="sharing-users-avatar">' +
'<img src="' + data.avatar + '">'+
'</div>' +
'<div class="sharing-users-details">' +
'<span class="sharing-users-name">' + data.full_name + '</span>' +
'<span class="sharing-users-extra">' + data.username + '</span>' +
'</div>' +
'<div class="sharing-users-action">' +
'<button title="Remove this user from your project" class="user-remove">'+
'<i class="pi-trash"></i>'+
'</button>'+
'</div>'+
'</li>');
$("ul.sharing-users-list").find("[user-id='" + userId + "']").addClass('added');
setTimeout(function(){ $('.sharing-users-item').removeClass('added');}, 350);
statusBarSet('success', 'User added to this project!', 'pi-grin');
})
.fail(function (jsxhr){
data = jsxhr.responseJSON;
statusBarSet('error', 'Could not add user (' + data.message + ')', 'pi-warning');
});
} else {
statusBarSet('error', 'Please select a user from the list', 'pi-warning');
}
};
});
| {% endif %}
script.
$(document).ready(function() {
$('body').on('click', '.user-remove', function(e) {
var userId = $(this).parent().parent().attr('user-id');
removeUser(userId);
});
function removeUser(userId){
$.post("{{url_for('projects.sharing', project_url=project.url)}}",
{user_id: userId, action: 'remove'})
.done(function (data) {
$("ul.sharing-users-list").find("[user-id='" + userId + "']").remove();
statusBarSet('success', 'User removed from this project', 'pi-trash');
})
.fail(function (data){
statusBarSet('error', 'Could not remove user (' + data._status + ')', 'pi-warning');
});
}
});
| {% endblock %}

View File

@@ -0,0 +1,586 @@
| {% extends 'layout.html' %}
| {% from '_macros/_add_new_menu.html' import add_new_menu %}
| {% block page_title %}{{project.name}}{% endblock%}
| {% block og %}
meta(property="og:type", content="website")
| {% if og_picture %}
meta(property="og:image", content="{{ og_picture.thumbnail('l', api=api) }}")
| {% endif %}
| {% if show_project %}
meta(property="og:title", content="{{project.name}} - Blender Cloud")
meta(property="og:url", content="{{url_for('projects.view', project_url=project.url, _external=True)}}")
meta(property="og:description", content="{{project.summary}}")
| {% else %}
meta(property="og:title", content="{{node.name}} - Blender Cloud")
meta(property="og:url", content="{{url_for('projects.view_node', project_url=project.url, node_id=node._id)}}")
meta(property="og:description", content="{{node.description}}")
| {% endif %}
| {% endblock %}
| {% block tw %}
| {% if og_picture %}
meta(property="twitter:image", content="{{ og_picture.thumbnail('l', api=api) }}")
| {% endif %}
| {% if show_project %}
meta(name="twitter:title", content="{{project.name}} on Blender Cloud")
meta(name="twitter:description", content="{{project.summary}}")
| {% else %}
meta(name="twitter:title", content="{{node.name}} on Blender Cloud")
meta(name="twitter:description", content="{{node.description}}")
| {% endif %}
| {% endblock %}
| {% block head %}
link(href="//cdnjs.cloudflare.com/ajax/libs/jstree/3.3.1/themes/default/style.min.css", rel="stylesheet")
| {% endblock %}
| {% block css %}
link(href="{{ url_for('static_pillar', filename='assets/css/project-main.css', v=040820161) }}", rel="stylesheet")
| {% endblock %}
| {% block body %}
#project-container
#project-side-container
#project_sidebar
ul.project-tabs
li.tabs-thumbnail(
title="About",
data-toggle="tooltip",
data-placement="left",
class="{% if title == 'about' %}active {% endif %}{% if project.picture_square %}image{% endif %}")
a(href="{{url_for('projects.about', project_url=project.url, _external=True)}}")
#project-loading
i.pi-spin
| {% if project.picture_square %}
img(src="{{ project.picture_square.thumbnail('b', api=api) }}")
| {% else %}
i.pi-home
| {% endif %}
li.tabs-browse(
title="Browse",
data-toggle="tooltip",
data-placement="left",
class="{% if title != 'about' %}active{% endif %}")
a(href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
i.pi-tree-flow
| {% if not project.is_private %}
li.tabs-search(
title="Search",
data-toggle="tooltip",
data-placement="left")
a(href="{{url_for('projects.search', project_url=project.url, _external=True)}}")
i.pi-search
| {% endif %}
.project_nav-toggle-btn(
title="Expand Navigation [T]",
data-toggle="tooltip",
data-placement="right")
i.pi-angle-double-left
#project_nav(class="{{ title }}")
#project_nav-container
| {% if title != 'about' %}
#project_nav-header
.project-title
a(href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
| {{ project.name }}
#project_tree
| {% endif %}
.project_split(title="Toggle Navigation [T]")
#project_context-container
| {% if project.has_method('PUT') %}
#project_context-header
span#project-statusbar
ul.project-edit-tools.disabled
li.button-dropdown
a#item_add.dropdown-toggle.project-mode-view(
type="button",
data-toggle="dropdown",
aria-haspopup="true",
aria-expanded="false")
i.button-add-icon.pi-collection-plus
| New...
ul.dropdown-menu.add_new-menu
| {{ add_new_menu(project.node_types) }}
li.button-edit
a#item_edit.project-mode-view(
href="javascript:void(0);",
title="Edit",
data-project_id="{{project._id}}")
i.button-edit-icon.pi-edit
| Edit Project
li.button-dropdown
a.dropdown-toggle.project-mode-view(
type="button",
data-toggle="dropdown",
aria-haspopup="true",
aria-expanded="false")
i.pi-more-vertical
ul.dropdown-menu
| {% if current_user.has_role('admin') %}
li.button-featured
a#item_featured(
href="javascript:void(0);",
title="Feature on project's homepage",
data-toggle="tooltip",
data-placement="left")
i.button-featured-icon.pi-star
| Toggle Featured
li.button-toggle-public
a#item_toggle_public(
href="javascript:void(0);",
title="Toggle public",
data-toggle="tooltip",
data-placement="left")
i.pi-lock-open
| Toggle public
| {% endif %}
li.button-toggle-projheader
a#item_toggle_projheader(
href="javascript:void(0);",
title="Feature as project's header",
data-toggle="tooltip",
data-placement="left")
i.button-featured-icon.pi-star
| Toggle Project Header video
li.button-move
a#item_move(
href="javascript:void(0);",
title="Move into a folder...",
data-toggle="tooltip",
data-placement="left")
i.button-move-icon.pi-move
| Move
li.button-delete
a#item_delete(
href="javascript:void(0);",
title="Delete",
data-toggle="tooltip",
data-placement="left")
i.pi-trash
| Delete Project
// Edit Mode
li.button-cancel
a#item_cancel.project-mode-edit(
href="javascript:void(0);",
title="Cancel changes")
i.button-cancel-icon.pi-cancel
| Cancel
li.button-save
a#item_save.project-mode-edit(
href="javascript:void(0);",
title="Save changes")
i.button-save-icon.pi-check
| Save Changes
| {% endif %}
#project_context
| {% if show_project %}
| {% include "projects/view_embed.html" %}
| {% endif %}
#overlay-mode-move-container
.overlay-container
.title
i.pi-angle-left
| Select the <strong>folder</strong> where you want to move it
.buttons
button#item_move_accept.move.disabled
| Select a Folder
button#item_move_cancel.cancel
i.pi-cancel
| Cancel
| {% endblock %}
| {% block footer_navigation %}{% endblock %}
| {% block footer %}{% endblock %}
| {% block footer_scripts %}
script(src="//cdnjs.cloudflare.com/ajax/libs/jstree/3.3.1/jstree.min.js")
script(src="//releases.flowplayer.org/6.0.5/flowplayer.min.js")
| {% if project.has_method('PUT') %}
| {# JS containing the Edit, Add, Featured, and Move functions #}
script(type="text/javascript", src="{{ url_for('static_pillar', filename='assets/js/project-edit.min.js', v=190520161) }}")
| {% endif %}
script.
{% if show_project %}
ProjectUtils.setProjectAttributes({projectId: "{{project._id}}", isProject: true, nodeId: ''});
{% else %}
ProjectUtils.setProjectAttributes({projectId: "{{project._id}}", isProject: false, nodeId: '{{node._id}}'});
{% endif %}
var projectTree = document.getElementById('project_tree');
/* Initialize project_tree scrollbar */
if ((typeof Ps !== 'undefined') && projectTree && window.innerWidth > 768){
Ps.initialize(projectTree, {suppressScrollX: true});
}
var urlNodeMove = "{{url_for('projects.move_node')}}";
var urlNodeFeature = "{{url_for('projects.add_featured_node')}}";
var urlNodeDelete = "{{url_for('projects.delete_node')}}";
var urlNodeTogglePublic = "{{url_for('projects.toggle_node_public')}}";
var urlNodeToggleProjHeader = "{{url_for('projects.toggle_node_project_header')}}";
var urlProjectDelete = "{{url_for('projects.delete')}}";
var urlProjectEdit = "{{url_for('projects.edit', project_url=project.url)}}";
function updateToggleProjHeaderMenuItem() {
var $toggle_projheader = $('#item_toggle_projheader');
if (ProjectUtils.isProject()) {
$toggle_projheader.hide();
return;
}
if (ProjectUtils.nodeType() == 'asset') {
$toggle_projheader.show();
} else {
$toggle_projheader.hide();
}
}
$(updateToggleProjHeaderMenuItem);
// Function to update the interface on loadNodeContent, and edit/saving assets
function updateUi(nodeId, mode){
if (mode === 'view') {
$('.project-mode-view').show();
$('.project-mode-edit').hide();
$("#node-edit-form").unbind( "submit" );
$("#item_save").unbind( "click" );
$("#item_cancel").unbind( "click" );
} else if (mode === 'edit') {
$('.project-mode-view').hide();
$('.project-mode-edit').show();
} else {
if (console) console.log('Invalid mode:', mode);
}
// Prevent flicker by scrolling to top
$("#project_context-container").scrollTop(0);
// Enable specific items under the Add New dropdown
if (ProjectUtils.nodeType() === 'group') {
addMenuEnable(['asset', 'group']);
} else if (ProjectUtils.nodeType() === 'group_texture') {
addMenuEnable(['group_texture', 'texture']);
} else if (ProjectUtils.nodeType() === 'group_hdri') {
addMenuEnable(['group_hdri', 'hdri']);
} else if (!ProjectUtils.isProject()) {
addMenuEnable(false);
}
updateToggleProjHeaderMenuItem();
var nodeTitle = document.getElementById('node-title');
var nodeTitleText = $(nodeTitle).text() + " - {{project.name}} - Blender Cloud";
document.title = nodeTitleText;
// TODO: Maybe remove this, now it's also in loadNodeContent(), but double-check
// it's done like that in all users of updateUi().
$('#project-loading').removeAttr('class');
}
function loadNodeContent(url, nodeId) {
$('#project-loading').addClass('active');
$.get(url, function(dataHtml) {
// Update the DOM injecting the generate HTML into the page
$('#project_context').html(dataHtml);
})
.done(function(){
updateUi(nodeId, 'view');
})
.fail(function(dataResponse) {
$('#project_context').html($('<iframe id="server_error"/>'));
$('#server_error').attr('src', url);
})
.always(function(){
$('#project-loading').removeAttr('class');
$('.button-edit-icon').addClass('pi-edit').removeClass('pi-spin spin');
});
}
function loadProjectContent(url) {
$('#project-loading').addClass('active');
$.get(url, function(dataHtml) {
// Update the DOM injecting the generated HTML into the page
$('#project_context').html(dataHtml);
})
.done(function() {
updateUi('', 'view');
addMenuEnable();
addMenuDisable(['texture']);
})
.fail(function(dataResponse) {
$('#project_context').html($('<iframe id="server_error"/>'));
$('#server_error').attr('src', url);
})
.always(function(){
$('#project-loading').removeAttr('class');
$('.button-edit-icon').addClass('pi-edit').removeClass('pi-spin spin');
});
}
function displayStorage(storageNodeId, path) {
var url = '/nodes/' + storageNodeId + '/view?path=' + path;
loadNodeContent(url);
}
function displayNode(nodeId, pushState) {
// Remove the 'n_' suffix from the id
if (nodeId.substring(0, 2) == 'n_') {
nodeId = nodeId.substr(2);
}
var url = '/nodes/' + nodeId + '/view';
loadNodeContent(url, nodeId);
// Determine whether we should push the new state or not.
pushState = (typeof pushState !== 'undefined') ? pushState : true;
if (!pushState) return;
// Push the correct URL onto the history.
var push_state = {nodeId: nodeId, url: url};
var push_url = '{{url_for("projects.view", project_url=project.url)}}' + nodeId;
// console.log('Pushing state ', push_state, ' with URL ', push_url);
window.history.pushState(
push_state,
'Node ' + nodeId, // TODO: use sensible title
push_url
);
}
function redirectToNode(nodeId) {
var generic_url = '{{ url_for("projects.view_node", project_url=project.url, node_id="theNodeId") }}';
var node_url = generic_url.replace('theNodeId', nodeId);
// This makes the user skip the current page when using the 'back' button,
// i.e. it works as a proper redirect.
location.replace(node_url);
}
window.onpopstate = function(event) {
var state = event.state;
// console.log('State popped. location:', document.location, 'state:', state);
// Deselect any selected node. We'll select the visited node (if any) later on.
var jstreeAPI = $(projectTree).jstree(true);
jstreeAPI.deselect_all(true);
if (state == null) {
// Went back to the project.
displayProject();
return;
}
// Went back to a node.
loadNodeContent(state.url, state.nodeId);
// Annoying hack because jstreeAPI.select_node() can only suppress the
// changed.jstree event, and NOT the selected_node.jstree event.
projectTree.dataset.ignoreSelectNode = true;
jstreeAPI.select_node('n_' + state.nodeId, true);
delete projectTree.dataset.ignoreSelectNode;
};
function displayProject() {
var url = "{{url_for('projects.view', project_url=project.url, embed=1)}}";
loadProjectContent(url);
}
function getHashId() {
if (console)
console.log('getHashId() should not be used any more!');
}
/* Loaded once, on page load */
function loadContent() {
var nodeId = ProjectUtils.nodeId();
var isProject = ProjectUtils.isProject();
if (isProject) {
// No need to asynchronously load the project, as it's embedded by Jinja.
// displayProject() is still needed, though, when people use 'back' to go there.
if (location.hash) {
// Handle old-style /p/{url}/#node-ID links, and redirect them to the correct spot.
redirectToNode(location.hash.substr(1));
}
$('.project-mode-view').show();
$('.project-mode-edit').hide();
} else {
displayNode(nodeId, false);
}
$(projectTree).jstree({
'core': {
'data': function (obj, callback) {
if(obj.id === '#') { //tree root
if (isProject) {
$.getJSON("{{url_for('projects.jstree', project_url=project.url)}}", function (jsonObject) {
callback.call(this, jsonObject['items']);
});
} else {
$.getJSON('/nodes/' + nodeId + '/jstree', function(jsonObject) {
callback.call(this, jsonObject['items']);
});
}
} else { //normal node
var childNodeId;
if (obj.original.type == 'group_storage') {
childNodeId = obj.original.storage_node;
$.getJSON('/nodes/' + childNodeId + '/jstree?children=1&path=' + obj.original.path, function(jsonObject) {
callback.call(this, jsonObject.children);
});
} else {
// Remove the 'n_' suffix from the id
childNodeId = obj.id.substring(2);
$.getJSON('/nodes/' + childNodeId + '/jstree?children=1', function(jsonObject) {
callback.call(this, jsonObject.children);
});
}
}
}
},
"types" : {
"#": {"valid_children": ["collection"]},
"chapter" : {"icon": "pi-folder"},
"group" : {"icon": "pi-folder"},
"group_texture" : {"icon": "pi-folder-texture"},
"group_hdri" : {"icon": "pi-folder-texture", "max_children": 0},
"group_storage" : {"icon": "pi-folder"},
"filesystem_node" : {"icon": "pi-folder"},
"file" : {"icon": "pi-file-archive", "max_children": 0},
"filesystem_file" : {"icon": "pi-document", "max_children": 0},
"image" : {"icon": "pi-image", "max_children": 0},
"hdri" : {"icon": "pi-globe", "max_children": 0},
"texture" : {"icon": "pi-texture", "max_children": 0},
"video" : {"icon": "pi-film-thick", "max_children": 0},
"blog" : {"icon": "pi-newspaper", "max_children": 0},
"default" : {"icon": "pi-document"}
},
"plugins": ["types",] //, "state", "sort"
});
var jstreeAPI = $(projectTree).jstree(true);
$(projectTree).on("select_node.jstree", function (e, data) {
var selectedNodeId = data.node.id.substr(2);
// Ignore events that can't be suppressed otherwise.
// This can be removed if jstreeAPI.select_node() allows suppressing
// the select_node.jstree event.
if (e.target.dataset.ignoreSelectNode === 'true') return;
if (typeof(data.node.original.path) === 'undefined') {
var movingMode = Cookies.getJSON('bcloud_moving_node');
// Check if we are in the process of moving a node
if (movingMode) {
// Allow moving nodes only inside of node_type group
if (data.node.original.type != 'group' || movingMode.node_id === selectedNodeId || movingMode.node_id === ProjectUtils.parentNodeId()) {
if (movingMode.node_type === 'texture') {
if (data.node.original.type === 'group_texture') {
$('#item_move_accept').html('<i class="pi-check"></i>Move Here').removeClass('disabled');
} else {
$('#item_move_accept').html('Select a Texture Folder').addClass('disabled');
}
} else if (movingMode.node_type === 'hdri') {
if (data.node.original.type === 'group_hdri') {
$('#item_move_accept').html('<i class="pi-check"></i>Move Here').removeClass('disabled');
} else {
$('#item_move_accept').html('Select an HDRi Folder').addClass('disabled');
}
} else {
$('#item_move_accept').html('Select a Folder').addClass('disabled');
}
} else {
$('#item_move_accept').html('<i class="pi-check"></i>Move Here').removeClass('disabled');
}
}
// Check the type of node and act accordingly
if (data.node.original.type == 'blog') {
window.location.replace('blog');
} else {
var currentNodeId = ProjectUtils.nodeId();
if (currentNodeId != selectedNodeId) {
displayNode(selectedNodeId);
}
jstreeAPI.open_node(data.node);
}
} else {
displayStorage(data.node.original.storage_node, data.node.original.path);
jstreeAPI.toggle_node(data.node);
}
/* Update scrollbar */
Ps.update(projectTree);
});
$(projectTree).on("open_node.jstree", function () {
/* Update scrollbar */
Ps.update(projectTree);
});
$(projectTree).on("close_node.jstree", function () {
/* Update scrollbar */
Ps.update(projectTree);
});
};
// Initialize the page
loadContent();
/* UI Stuff */
$(window).on("load resize",function(){
containerResizeY($(window).height());
});
if (projectTree){
$(projectTree).hover(function(){
Ps.update(projectTree);
});
}
| {% endblock %}
| {% block comment_scripts %} {% endblock%}

View File

@@ -0,0 +1,188 @@
| {% block head %}
| {% if header_video_file %}
script(src="//releases.flowplayer.org/6.0.5/flowplayer.min.js")
script.
$(function() {
$('#flowplayer_container').flowplayer({
key: "{{config.FLOWPLAYER_KEY}}",
embed: false,
splash: true,
clip: { sources: [
{% for var in header_video_file.variations %}
{type: "{{ var.content_type }}", src: "{{ var.link|safe }}"},
{% endfor %}
]}
});
});
| {% endif %}
| {% endblock %}
| {% block body %}
#node-container
section.node-preview.project
| {% if project.url == 'caminandes-3' %}
iframe(
style="height: 490px",
src="https://www.youtube.com/embed/SkVqJ1SGeL0?rel=0&amp;controls=0&amp;showinfo=0",
frameborder="0",
allowfullscreen)
| {% elif header_video_file %}
#flowplayer_container.is-splash.play-button(
style="{% if header_video_node.picture %}background-image:url({{header_video_node.picture.thumbnail('l', api=api)}}); background-repeat:no-repeat; {% endif %}")
.fp-startscreen.fp-toggle
a.big-play-button
i.pi-play
.fp-endscreen
a.watch-again.fp-toggle
i.pi-replay
| Watch again
.fp-waiting
i.pi-spin.spin
| {% elif project.picture_header %}
a(href="{{ url_for( 'projects.about', project_url=project.url) }}")
img.header(src="{{ project.picture_header.thumbnail('l', api=api) }}")
| {% endif %}
section.node-details-container.project
| {# Hide for now
.node-details-header
.node-title-details
.date(title="Last updated {{ project._updated | pretty_date }}") {{ project._created | pretty_date }}
| {% if project.status %}
.status {{project.status}}
| {% endif %}
| #}
.node-details-title
h1
a(href="{{ url_for( 'projects.about', project_url=project.url) }}") {{ project.name }}
| {% if title != 'about' or not project.description %}
| {% set description = project.summary %}
| {% else %}
| {% set description = project.description %}
| {% endif %}
.node-details-description
| {{ description }}
| {% if title != 'about' %}
.node-extra
a.learn-more(href="{{ url_for( 'projects.about', project_url=project.url) }}") LEARN MORE
| {% endif %}
| {% if project.nodes_featured %}
.project-featured-container
h3 Featured Content
.featured-list#featured-list
| {% for n in project.nodes_featured %}
| {% if n.picture %}
a.featured-item.hidden(href="{{ url_for_node(node=n) }}")
.featured-item-info
span.type {{ n.properties.content_type }} - {{ n.user.full_name }}
span.title {{ n.name }}
img(src="{{ n.picture.thumbnail('l', api=api) }}")
| {% endif %}
| {% endfor %}
| {% endif %}
.node-extra
| {% if project.nodes_blog %}
.node-blog
h3 Blog
ul.node-blog-list
| {% for n in project.nodes_blog %}
li.node-blog-list-item(data-node_id="{{ n._id }}")
a.image(href="{{ url_for_node(node=n) }}")
| {% if n.picture %}
img(src="{{ n.picture.thumbnail('s', api=api) }}")
| {% else %}
i.pi-chatbubble-working
| {% endif %}
.info
a.title(href="{{ url_for_node(node=n) }}") {{ n.name }}
span.details
span.when {{ n._updated | pretty_date }} by
span.who {{ n.user.full_name }}
| {% endfor %}
| {% endif %}
| {% if project.nodes_latest %}
.node-updates
h3 Latest Updates
ul.node-updates-list
| {% for n in project.nodes_latest %}
| {% if n.node_type not in ['comment'] %}
li.node-updates-list-item(data-node_id="{{ n._id }}")
a.image(href="{{ url_for_node(node=n) }}")
| {% if n.picture %}
img(src="{{ n.picture.thumbnail('s', api=api) }}")
| {% else %}
| {% if n.properties.content_type == 'video' %}
i.pi-film-thick
| {% elif n.properties.content_type == 'image' %}
i.pi-picture
| {% elif n.properties.content_type == 'file' %}
i.pi-file-archive
| {% else %}
i.pi-folder
| {% endif %}
| {% endif %}
.info
a.title(href="{{ url_for_node(node=n) }}") {{ n.name }}
span.details
span.what {% if n.properties.content_type %}{{ n.properties.content_type }}{% else %}folder{% endif %} ·
span.when {{ n._updated | pretty_date }} by
span.who {{ n.user.full_name }}
| {% endif %}
| {% endfor %}
| {% endif %}
include _scripts
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.montage.min.js') }}")
script.
function montage(){
var $container = $('#featured-list'),
$imgs = $container.find('img').hide(),
totalImgs = $imgs.length,
cnt = 0;
$imgs.each(function(i) {
var $img = $(this);
$('<img/>').load(function() {
++cnt;
if( cnt === totalImgs ) {
$imgs.show();
$container.montage({
fillLastRow : true,
alternateHeight : true,
alternateHeightRange : {
min : 180,
max : 240
},
margin : 3
});
}
}).attr('src',$img.attr('src'));
$img.parent().removeClass('hidden');
});
}
$(function() {
montage();
$(".node-updates-list-item, .node-blog-list-item")
.unbind('click')
.click(function(e) {
e.preventDefault();
displayNode($(this).data('node_id'));
});
});
| {% endblock %}

View File

@@ -0,0 +1,57 @@
| {% extends 'layout.html' %}
| {% set title = 'theatre' %}
| {% block og %}
meta(property="og:title", content="{{ node.name }}")
meta(property="og:url", content="{{ url_for('projects.view_node', project_url=project.url, node_id=node._id, t=1, _external=True) }}")
meta(property="og:type", content="website")
meta(property="og:description", content="Created on {{ node._created.strftime('%d %b %Y') }}")
| {% if og_picture %}
meta(property="og:image", content="{{ og_picture.thumbnail('l', api=api) }}")
meta(property="og:image:secure_url", content="{{ og_picture.thumbnail('l', api=api) }}")
meta(property="og:image:type", content="{{ og_picture.content_type }}")
meta(property="og:image:witdh", content="{{ og_picture.width }}")
meta(property="og:image:height", content="{{ og_picture.height }}")
| {% endif %}
| {% endblock %}
| {% block tw %}
| {% if og_picture %}
meta(property="twitter:image", content="{{ og_picture.thumbnail('l', api=api) }}")
| {% endif %}
meta(name="twitter:title", content="{{node.name}}")
meta(name="twitter:description", content="Created on {{ node._created.strftime('%d %b %Y') }}")
| {% endblock %}
| {% block header_backdrop %}{% endblock %}
| {% block navigation_search %}{% endblock %}
| {% block navigation_sections %}
li
a.navbar-item.info(
href="",
title="Toggle info & sidebar")
i.pi-info
| {% endblock %}
| {% block css %}
link(href="{{ url_for('static_pillar', filename='assets/css/theatre.css', v=2016) }}", rel="stylesheet")
| {% endblock %}
| {% block body %}
#theatre-container(class="{% if current_user.is_authenticated %}with-info{% endif %}")
| {% endblock %}
| {% block footer_scripts %}
script.
$(function(){
$.get("{{url_for('nodes.view', node_id=node._id, t=True)}}", function(dataHtml) {
$("#theatre-container").html(dataHtml);
});
});
| {% endblock %}
| {% block footer %}{% endblock %}
| {% block footer_navigation %}{% endblock %}