diff --git a/src/templates/_macros/_navigation.pug b/src/templates/_macros/_navigation.pug
new file mode 100644
index 0000000..46290fe
--- /dev/null
+++ b/src/templates/_macros/_navigation.pug
@@ -0,0 +1,26 @@
+include ../mixins/components
+
+| {% macro navigation_tabs(title) %}
++nav-secondary()
+ +nav-secondary-link(
+ class="{% if title == 'homepage' %}active{% endif %}",
+ href="{{ url_for('main.homepage') }}")
+ | Activity
+
+ +nav-secondary-link(
+ class="{% if title == 'home' %}active{% endif %}",
+ href="{{ url_for('projects.home_project') }}")
+ | Home
+
+ +nav-secondary-link(
+ class="{% if title == 'dashboard' %}active{% endif %}",
+ href="{{ url_for('projects.index') }}")
+ | My Projects
+
+ | {% if current_user.has_organizations() %}
+ +nav-secondary-link(
+ class="{% if title == 'organizations' %}active{% endif %}",
+ href="{{ url_for('pillar.web.organizations.index') }}")
+ | My Organizations
+ | {% endif %}
+| {% endmacro %}
diff --git a/src/templates/nodes/custom/blog/_macros.pug b/src/templates/nodes/custom/blog/_macros.pug
new file mode 100644
index 0000000..84ec904
--- /dev/null
+++ b/src/templates/nodes/custom/blog/_macros.pug
@@ -0,0 +1,164 @@
+include ../../../mixins/components
+
+| {% import 'projects/_macros.html' as projectmacros %}
+| {% macro render_blog_post(node, project=None, pages=None) %}
+
+.expand-image-links.imgs-fluid
+ | {% if node.picture %}
+ +jumbotron(
+ "{{ node.name }}",
+ "{{ node._created | pretty_date }}",
+ "{{ node.picture.thumbnail('h', api=api) }}",
+ "{{ node.url }}")(class="row")
+ | {% else %}
+ .pt-3.text-center.text-muted
+ h2
+ a.text-muted(href="{{ node.url }}")
+ | {{ node.name }}
+ ul.d-flex.list-unstyled.justify-content-center
+ | {% if node.project.name %}
+ li.pr-2 {{ node.project.name }}
+ | {% endif %}
+ | {% if node.user.full_name %}
+ li.pr-2
+ | {{ node.user.full_name }}
+ | {% endif %}
+ li
+ a.px-2.text-muted(href="{{ node.url }}",
+ title="Updated {{ node._updated | pretty_date }}")
+ | {{ node._created | pretty_date }}
+ li
+ a.px-2(href="{{ node.url }}#comments")
+ | Leave a comment
+
+ | {% if node.has_method('PUT') %}
+ li
+ a.px-2(href="{{url_for('nodes.edit', node_id=node._id)}}")
+ i.pi-edit
+ | Edit Post
+ | {% endif %}
+ | {% endif %}
+
+ | {% if project and project._id != config.MAIN_PROJECT_ID %}
+ | {{ projectmacros.render_secondary_navigation(project, pages=pages) }}
+ | {% endif %}
+
+ .row
+ .col-md-9.mx-auto
+
+ .item-content.pt-4
+ | {{ node.properties | markdowned('content') }}
+
+ hr.my-4
+| {% endmacro %}
+
+//- ******************************************************* -//
+| {% macro render_blog_list_item(node) %}
+.row.position-relative.py-2
+ .col-md-1
+ | {% if node.picture %}
+ a.imgs-fluid(href="{{ node.url }}")
+ img(src="{{ node.picture.thumbnail('s', api=api) }}")
+ | {% else %}
+ .bg-primary.rounded.h-100
+ a.d-flex.align-items-center.justify-content-center.h-100.text-white(href="{{ node.url }}")
+ i.pi-document-text
+ | {% endif %}
+
+ .col-md-11
+ h5
+ a.text-muted(href="{{ node.url }}") {{node.name}}
+
+ .text-muted.
+ #[span(title="{{node._created}}") {{node._created | pretty_date }}]
+ {% if node._created != node._updated %}
+ #[span(title="{{node._updated}}") (updated {{node._updated | pretty_date }})]
+ {% endif %}
+ {% if node.properties.category %} · {{node.properties.category}}{% endif %}
+ · {{node.user.full_name}}
+ {% if node.properties.status != 'published' %} · {{ node.properties.status}} {% endif %}
+
+| {% endmacro %}
+
+
+//- ******************************************************* -//
+| {% macro render_blog_index(project, posts, can_create_blog_posts, api, more_posts_available, posts_meta, pages=None) %}
+| {% if can_create_blog_posts %}
++nav-secondary
+ +nav-secondary-link(href="{{url_for('nodes.posts_create', project_id=project._id)}}")
+ span.text-success
+ i.pi-plus
+ | Create New Blog Post
+| {% endif %}
+
+| {% if posts %}
+| {{ render_blog_post(posts[0], project=project, pages=pages) }}
+
+.container
+ .row
+ .col-md-9.mx-auto
+ | {% for node in posts[1:] %}
+ | {% if loop.first %}
+ h5.text-muted.text-center Blasts from the past
+ | {% endif %}
+ | {{ render_blog_list_item(node) }}
+ | {% endfor %}
+
+ | {% if more_posts_available %}
+ .blog-archive-navigation
+ a(href="{{ project.blog_archive_url }}")
+ | {{posts_meta.total - posts|length}} more blog posts over here
+ i.pi-angle-right
+ | {% endif %}
+
+| {% else %}
+
+.text-center
+ p No posts... yet!
+
+| {% endif %} {# posts #}
+| {% endmacro %}
+
+
+//- Macro for rendering the navigation buttons for prev/next pages -//
+| {% macro render_archive_pagination(project) %}
+.blog-archive-navigation
+ | {% if project.blog_archive_prev %}
+ a.archive-nav-button(
+ href="{{ project.blog_archive_prev }}", rel="prev")
+ i.pi-angle-left
+ | Previous page
+ | {% else %}
+ span.archive-nav-button
+ i.pi-angle-left
+ | Previous page
+ | {% endif %}
+
+ a.archive-nav-button(
+ href="{{ url_for('main.project_blog', project_url=project.url) }}")
+ | Blog Index
+
+ | {% if project.blog_archive_next %}
+ a.archive-nav-button(
+ href="{{ project.blog_archive_next }}", rel="next")
+ | Next page
+ i.pi-angle-right
+ | {% else %}
+ span.archive-nav-button
+ | Next page
+ i.pi-angle-right
+ | {% endif %}
+
+| {% endmacro %}
+
+| {% macro render_archive(project, posts, posts_meta) %}
+
+| {{ render_archive_pagination(project) }}
+
+| {% for node in posts %}
+| {{ render_blog_list_item(node) }}
+| {% endfor %}
+
+| {{ render_archive_pagination(project) }}
+
+| {% endmacro %}
diff --git a/src/templates/organizations/index.pug b/src/templates/organizations/index.pug
new file mode 100644
index 0000000..4c3f6eb
--- /dev/null
+++ b/src/templates/organizations/index.pug
@@ -0,0 +1,213 @@
+| {% extends 'layout.html' %}
+| {% from '_macros/_navigation.html' import navigation_tabs %}
+include ../mixins/components
+
+| {% set title = 'organizations' %}
+| {% block page_title %}Organizations{% endblock %}
+
+| {% block og %}
+meta(property="og:title", content="Dashboard")
+meta(name="twitter:title", content="Blender Cloud")
+
+meta(property="og:url", content="https://cloud.blender.org/{{ request.path }}")
+meta(property="og:type", content="website")
+
+meta(property="og:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}")
+meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}")
+| {% endblock %}
+
+
+| {% block navigation_tabs %}
+| {{ navigation_tabs(title) }}
+| {% endblock navigation_tabs %}
+
+| {% block body %}
++nav-secondary
+ | {% if can_create_organization %}
+ +nav-secondary-link(
+ class="create",
+ onclick='createNewOrganization(this)')
+ span.text-success
+ i.pi-plus
+ | Create Organization
+ | {% endif %}
+
+ li#create_organization_result_panel.result
+
+.container-fluid.dashboard-container
+ .row
+ .col-md-6
+ ul.projects__list
+ | {% if organizations %}
+ | {% for organization in organizations['_items'] %}
+ | {% set link_url = url_for('pillar.web.organizations.view_embed', organization_id=organization._id) %}
+ li.projects__list-item(
+ data-url="{{ link_url }}",
+ id="organization-{{ organization._id }}")
+ a.projects__list-thumbnail(
+ href="{{ link_url }}")
+ i.pi-users
+ .projects__list-details
+ a.title(href="{{ link_url }}")
+ | {{ organization.name }}
+
+ ul.meta
+ li(title="Members")
+ | {{ organization.members|hide_none|count }} Member{{ organization.members|hide_none|count|pluralize }}
+ | {% if (organization.unknown_members|count) != 0 %}
+ | ({{ organization.unknown_members|hide_none|count }} pending)
+ | {% endif %}
+ li(title="Seats")
+ | {{ organization.seat_count }} Seat{{ organization.seat_count|pluralize }}
+
+ | {% endfor %}
+ | {% else %}
+ li.projects__list-item
+ a.projects__list-thumbnail
+ i.pi-blender-cloud
+ .projects__list-details
+ span Create an Organization to get started!
+ | {% endif %}
+
+ .col-md-6.py-1.pb-3
+ #item-details
+
+| {% endblock %}
+
+
+| {% block footer_scripts %}
+script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.typeahead-0.11.1.min.js')}}")
+script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.autocomplete-0.22.0.min.js') }}", async=true)
+
+script.
+
+ /* Returns a more-or-less reasonable message given an error response object. */
+ function xhrErrorResponseMessage(err) {
+ if (typeof err.responseJSON == 'undefined')
+ return err.statusText;
+
+ if (typeof err.responseJSON._error != 'undefined' && typeof err.responseJSON._error.message != 'undefined')
+ return err.responseJSON._error.message;
+
+ if (typeof err.responseJSON._message != 'undefined')
+ return err.responseJSON._message
+
+ return err.statusText;
+ }
+
+ /**
+ * Open an organization in the #item-details div.
+ */
+ function item_open(item_id, pushState)
+ {
+ if (item_id === undefined ) {
+ throw new ReferenceError("item_open(" + item_id + ") called.");
+ }
+
+ // Style elements starting with item_type and dash, e.g. "#job-uuid"
+ var clean_classes = 'active processing';
+ var current_item = $('#organization-' + item_id);
+
+ $('[id^="organization-"]').removeClass(clean_classes);
+ current_item
+ .removeClass(clean_classes)
+ .addClass('processing');
+
+ var item_url = '/o/' + item_id;
+
+ $.get(item_url, function(item_data) {
+ $('#item-details').html(item_data);
+
+ current_item
+ .removeClass(clean_classes)
+ .addClass('active');
+
+ }).fail(function(xhr) {
+ if (console) {
+ console.log('Error fetching organization', item_id, 'from', item_url);
+ console.log('XHR:', xhr);
+ }
+
+ current_item.removeClass(clean_classes);
+ toastr.error('Failed to open organization');
+
+ if (xhr.status) {
+ $('#item-details').html(xhr.responseText);
+ } else {
+ $('#item-details').html('
Opening ' + item_type + ' failed. There possibly was ' +
+ 'an error connecting to the server. Please check your network connection and ' +
+ 'try again.
');
+ }
+ });
+
+ // 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 = {itemId: item_id};
+
+ window.history.pushState(
+ push_state,
+ 'Organization: ' + item_id,
+ item_url
+ );
+ }
+
+ $('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');
+ });
+
+
+ {% if open_organization_id %}
+ $(function() { item_open('{{ open_organization_id }}', false); });
+ {% endif %}
+
+ {% if can_create_organization %}
+ function createNewOrganization(button) {
+ $(button)
+ .attr('disabled', 'disabled')
+ .fadeTo(200, 0.1);
+ $('#create_organization_result_panel').html('');
+
+ // TODO: create a form to get the initial info from the user.
+ $.post(
+ '{{ url_for('pillar.web.organizations.create_new') }}',
+ {
+ name: 'New Organization',
+ seat_count: 1,
+ }
+ )
+ .done(function(result) {
+ var $p = $('').text('organization created, reloading list.')
+ $('#create_organization_result_panel').html($p);
+
+ window.location.href = result.location;
+ })
+ .fail(function(err) {
+ var msg = xhrErrorResponseMessage(err);
+ $('#create_organization_result_panel').html('Error creating organization: ' + msg);
+
+ $(button)
+ .fadeTo(1000, 1.0)
+ .queue(function() {
+ $(this)
+ .removeAttr('disabled')
+ .dequeue()
+ ;
+ })
+ })
+ ;
+ return false;
+ }
+ {% endif %}
+| {% endblock %}
diff --git a/src/templates/projects/home_index.pug b/src/templates/projects/home_index.pug
new file mode 100644
index 0000000..610ff0d
--- /dev/null
+++ b/src/templates/projects/home_index.pug
@@ -0,0 +1,59 @@
+| {% extends 'projects/home_layout.html' %}
+| {% set subtab = 'blender_sync' %}
+| {% set learn_more_btn_url = '/blog/introducing-blender-sync' %}
+| {% block currenttab %}
+.container-fluid
+ 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 and keymaps once, load them anywhere.
+
+ | 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.
+
+ | {% if show_addon_download_buttons %}
+ .row
+ .col-md-6
+ a.btn.btn-block.btn-outline-success(
+ href="https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip")
+ i.pi-download
+ | Download v{{ config.BLENDER_CLOUD_ADDON_VERSION }}
+ .col-md-6
+ a.btn.btn-link(
+ href="{{ learn_more_btn_url }}")
+ | Learn More
+ i.pi-angle-right
+ | {% endif %}
+
+ .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
+ h5.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
+
+ a.download(
+ href='https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip')
+ | Download add-on
+ | {% endfor %}
+| {% endblock %}
+
diff --git a/src/templates/projects/home_layout.pug b/src/templates/projects/home_layout.pug
new file mode 100644
index 0000000..454f508
--- /dev/null
+++ b/src/templates/projects/home_layout.pug
@@ -0,0 +1,51 @@
+| {% extends 'layout.html' %}
+| {% from '_macros/_navigation.html' import navigation_tabs %}
+include ../mixins/components
+
+| {% set title = 'home' %}
+
+| {% block og %}
+meta(property="og:type", content="website")
+meta(property="og:url", content="https://cloud.blender.org{{ request.path }}")
+
+meta(property="og:title", content="Blender Cloud - Home")
+meta(name="twitter:title", content="Blender Cloud")
+
+meta(property="og:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}")
+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 navigation_tabs %}
+| {{ navigation_tabs(title) }}
+| {% endblock navigation_tabs %}
+
+| {% block body %}
+.dashboard-container
+
+ section#projects.bg-white
+ +nav-secondary()(id='sub-nav-tabs__list')
+ +nav-secondary-link(id="subtab-blender_sync", data-tab-url="{{ url_for('projects.home_project')}}")
+ | Blender Sync
+
+ +nav-secondary-link(id="subtab-images", data-tab-url="{{ url_for('projects.home_project_shared_images')}}")
+ | Images
+
+ | {% block currenttab %}{% endblock %}
+| {% endblock %}
+
+| {% block footer_scripts %}
+script.
+ $(document).ready(function () {
+ $('#subtab-{{ subtab }}').addClass('active');
+
+ var $nav_tabs = $('#sub-nav-tabs__list').find('a.nav-link');
+ $nav_tabs.on('click', function (e) {
+ console.log($(this));
+ window.location = $(this).attr('data-tab-url');
+ });
+ });
+| {% endblock %}
diff --git a/src/templates/projects/index_dashboard.pug b/src/templates/projects/index_dashboard.pug
new file mode 100644
index 0000000..4297dd1
--- /dev/null
+++ b/src/templates/projects/index_dashboard.pug
@@ -0,0 +1,300 @@
+| {% extends 'layout.html' %}
+| {% from '_macros/_navigation.html' import navigation_tabs %}
+include ../mixins/components
+
+| {% set title = 'dashboard' %}
+
+| {% block og %}
+meta(property="og:title", content="Dashboard")
+meta(name="twitter:title", content="Blender Cloud")
+
+meta(property="og:url", content="https://cloud.blender.org/{{ request.path }}")
+meta(property="og:type", content="website")
+
+meta(property="og:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}")
+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 css %}
+| {{ super() }}
+style.
+ .deleted-projects-toggle {
+ z-index: 10;
+ position: absolute;
+ right: 0;
+ font-size: 20px;
+ padding: 3px;
+ text-shadow: 0 0 2px white;
+ }
+ .deleted-projects-toggle .show-deleted {
+ color: #aaa;
+ }
+ .deleted-projects-toggle .hide-deleted {
+ color: #bbb;
+ }
+| {% endblock %}
+
+| {% block navigation_tabs %}
+| {{ navigation_tabs(title) }}
+| {% endblock navigation_tabs %}
+
+
+| {% block body %}
+.dashboard-container
+ section.dashboard-main
+ section#projects.bg-white
+ +nav-secondary()(id='sub-nav-tabs__list')
+ +nav-secondary-link(data-tab-toggle='own_projects', class="active")
+ | Own Projects
+ | {% if projects_user|length != 0 %}
+ span ({{ projects_user|length }})
+ | {% endif %}
+
+ +nav-secondary-link(data-tab-toggle='shared')
+ | Shared with me
+ | {% if projects_shared|length != 0 %}
+ span ({{ projects_shared|length }})
+ | {% endif %}
+
+ | {% if current_user.has_cap('subscriber') %}
+ +nav-secondary-link(
+ id="project-create",
+ data-url="{{ url_for('projects.create') }}",
+ href="{{ url_for('projects.create') }}")
+ span.text-success
+ | #[i.pi-plus] Create New Project
+ | {% elif current_user.has_cap('can-renew-subscription') %}
+ +nav-secondary-link(
+ id="project-create",
+ data-url="{{ url_for('projects.create') }}",
+ href="/renew",
+ target="_blank")
+ | #[i.pi-heart-filled.text-danger] Resubscribe to Create a Project
+ | {% endif %}
+
+ nav.nav-tabs__tab.active#own_projects
+ .deleted-projects-toggle
+ | {% if show_deleted_projects %}
+ a.hide-deleted(href="{{ request.base_url }}", title='Hide deleted projects')
+ i.pi-trash
+ | {% else %}
+ a.show-deleted(href="{{ request.base_url }}?deleted=1", title='Show deleted projects')
+ i.pi-trash
+ | {% endif %}
+
+ ul.projects__list
+ | {% for project in projects_deleted %}
+ li.projects__list-item.deleted
+ span.projects__list-thumbnail
+ | {% if project.picture_square %}
+ img(src="{{ project.picture_square.thumbnail('s', api=api) }}")
+ | {% else %}
+ i.pi-blender-cloud
+ | {% endif %}
+ .projects__list-details
+ span.title {{ project.name }}
+ ul.meta
+ li.status.deleted Deleted
+ li.edit
+ a(href="javascript:undelete_project('{{ project._id }}')") Restore project
+ | {% else %}
+ | {% if show_deleted_projects %}
+ li.projects__list-item.deleted You have no recenly deleted projects. Deleted projects can be restored within a month after deletion.
+ | {% endif %}
+ | {% endfor %}
+
+ | {% 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.status(
+ class="{{ project.is_private | yesno('private,public,') }}",
+ title="{{ project.is_private | yesno('Private Project,Public Project,') }}")
+ | {{ project.is_private | yesno('Private,Public,') }}
+ 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.has_cap('view-pending-nodes') %}
+ li.pending Not Published
+ | {% endif %}
+ | {% else %}
+ | {% if current_user.has_cap('subscriber') %}
+ li.projects__list-item(data-url="{{ url_for('projects.create') }}")
+ a.projects__list-thumbnail
+ i.pi-plus
+ .projects__list-details
+ a.title(href="{{ url_for('projects.create') }}")
+ | Create a project to get started!
+ | {% elif current_user.has_cap('can-renew-subscription') %}
+ li.projects__list-item(data-url="https://store.blender.org/renew-my-subscription.php")
+ a.projects__list-thumbnail
+ i.pi-plus
+ .projects__list-details
+ a.title(href="https://store.blender.org/renew-my-subscription.php")
+ | Renew your Blender Cloud subscription to create your own projects!
+ | {% else %}
+ li.projects__list-item(data-url="/join")
+ a.projects__list-thumbnail
+ i.pi-plus
+ .projects__list-details
+ a.title(href="/join")
+ | Join Blender Cloud to create your own projects!
+ | {% endif %}
+ | {% endfor %}
+
+ section.nav-tabs__tab#shared(style='display: none')
+ 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.status(
+ class="{{ project.is_private | yesno('private,public,') }}",
+ title="{{ project.is_private | yesno('Private Project,Public Project,') }}")
+ | {{ project.is_private | yesno('Private,Public,') }}
+ 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.has_cap('view-pending-nodes') %}
+ 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-broken
+ .projects__list-details
+ .title
+ | No projects shared with you... yet!
+ | {% endif %}
+| {% 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('a.nav-link');
+ $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();
+ });
+
+ // 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
+ });
+
+
+ var patch_url = '{{ url_for('projects.patch.patch_project', project_id='PROJECTID') }}';
+ function undelete_project(project_id) {
+ console.log('undeleting project', project_id);
+ $.ajax({
+ url: patch_url.replace('PROJECTID', project_id),
+ method: 'PATCH',
+ data: JSON.stringify({'op': 'undelete'}),
+ contentType: 'application/json'
+ })
+ .done(function(data, textStatus, jqXHR) {
+ location.href = jqXHR.getResponseHeader('Location');
+ })
+ .fail(function(err) {
+ toastr.error(xhrErrorResponseMessage(err), 'Undeletion failed');
+ })
+ }
+| {% endblock %}
diff --git a/src/templates/projects/view.pug b/src/templates/projects/view.pug
new file mode 100644
index 0000000..709e080
--- /dev/null
+++ b/src/templates/projects/view.pug
@@ -0,0 +1,687 @@
+| {% extends 'layout.html' %}
+| {% from '_macros/_add_new_menu.html' import add_new_menu %}
+include ../mixins/components
+
+| {% block page_title %}{{ project.name }}{% endblock%}
+| {% set title = 'project' %}
+
+| {% block og %}
+meta(property="og:type", content="website")
+
+| {% if og_picture %}
+meta(property="og:image", content="{{ og_picture.thumbnail('l', api=api) }}")
+meta(name="twitter:image", content="{{ og_picture.thumbnail('l', api=api) }}")
+| {% elif node and node.picture %}
+meta(property="og:image", content="{{ node.picture.thumbnail('l', api=api) }}")
+meta(name="twitter:image", content="{{ node.picture.thumbnail('l', api=api) }}")
+| {% elif project.picture_header %}
+meta(property="og:image", content="{{ project.picture_header.thumbnail('l', api=api) }}")
+meta(name="twitter:image", content="{{ project.picture_header.thumbnail('l', api=api) }}")
+| {% endif %}
+
+| {% if show_project %}
+meta(property="og:title", content="{{ project.name }} - Blender Cloud")
+meta(name="twitter:title", content="{{ project.name }} - Blender Cloud")
+meta(property="og:description", content="{{ project.summary }}")
+meta(name="twitter:description", content="{{ project.summary }}")
+
+meta(property="og:url", content="{{ url_for('projects.view', project_url=project.url, _external=True) }}")
+| {% else %}
+
+| {% if node %}
+meta(property="og:title", content="{{ node.name }} - Blender Cloud")
+meta(name="twitter:title", content="{{ node.name }} on Blender Cloud")
+
+| {% if node.node_type == 'post' %}
+
+| {% if node.properties.content %}
+meta(property="og:description", content="{{ node.properties.content | truncate(180) }}")
+meta(name="twitter:description", content="{{ node.properties.content | truncate(180) }}")
+| {% else %}
+meta(property="og:description", content="Blender Cloud, your source for open content and training")
+meta(name="twitter:description", content="Blender Cloud, your source for open content and training")
+| {% endif %}
+
+| {% else %}
+
+| {% if node.description %}
+meta(property="og:description", content="{{ node.description | truncate(180) }}")
+meta(name="twitter:description", content="{{ node.description | truncate(180) }}")
+| {% else %}
+meta(property="og:description", content="Blender Cloud, your source for open content and training")
+meta(name="twitter:description", content="Blender Cloud, your source for open content and training")
+| {% endif %}
+
+| {% endif %}
+
+meta(property="og:url", content="{{url_for('projects.view_node', project_url=project.url, node_id=node._id)}}")
+| {% else %}
+meta(property="og:title", content="{{ project.name }} Blog on Blender Cloud")
+meta(name="twitter:title", content="{{ project.name }} Blog on Blender Cloud")
+meta(property="og:description", content="{{ project.summary }}")
+meta(name="twitter:description", content="{{ project.summary }}")
+
+meta(property="og:url", content="{{url_for('projects.view', project_url=project.url, _external=True)}}")
+| {% endif %}
+
+| {% endif %}
+| {% endblock %}
+
+| {% block head %}
+link(href="{{ url_for('static_pillar', filename='assets/jstree/themes/default/style.min.css') }}", rel="stylesheet")
+| {% if node %}
+link(rel="amphtml", href="{{ url_for('nodes.view', node_id=node._id, _external=True, format='amp') }}")
+| {% endif %}
+
+script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-6.2.8.min.js') }}")
+script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-ga-0.4.2.min.js') }}")
+script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-hotkeys-0.2.20.min.js') }}")
+| {% endblock %}
+
+| {% block css %}
+link(href="{{ url_for('static_pillar', filename='assets/css/font-pillar.css') }}", rel="stylesheet")
+link(href="{{ url_for('static_pillar', filename='assets/css/project-main.css') }}", rel="stylesheet")
+| {% endblock %}
+
+| {% block navigation_tabs %}
++nav-secondary()(class="bg-white")
+ | {% if project.category == 'course' %}
+ li.text-capitalize
+ a.nav-link.text-muted.px-0(href="{{ url_for('cloud.courses') }}")
+ | Courses
+ | {% elif project.category == 'workshop' %}
+ li.text-capitalize
+ a.nav-link.text-muted.px-0(href="{{ url_for('cloud.workshops') }}")
+ | Workshops
+ li.px-1
+ i.pi-angle-right
+ | {% endif %}
+
+ +nav-secondary-link(
+ class="px-0",
+ href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
+ | {{ project.name }}
+
+ | {% if project.category == "open_project" %}
+ +nav-secondary-link(
+ class="active",
+ href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
+ | Explore
+ +nav-secondary-link(
+ href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
+ | Blog
+ +nav-secondary-link(
+ href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
+ | About
+ +nav-secondary-link(
+ href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
+ | Team
+ +nav-secondary-link(
+ href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
+ | Awards
+ | {% endif %}
+| {% endblock navigation_tabs %}
+
+| {% block body %}
+#project-container
+ #project-side-container
+ #project_sidebar.bg-white
+ ul.project-tabs.p-0
+ //- li.tabs-thumbnail(class="{% if project.picture_square %}image{% endif %}")
+ //- a(href="{{url_for('projects.view', project_url=project.url)}}")
+ //- #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="right",
+ class="active")
+ a(href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
+ i.pi-folder
+
+ | {% if not project.is_private %}
+ | {% if current_user_is_subscriber %}
+ li.tabs-search(
+ title="Search",
+ data-toggle="tooltip",
+ data-placement="right")
+ a(href="{{ url_for('projects.search', project_url=project.url, _external=True)}} ")
+ i.pi-search
+ | {% else %}
+ li.tabs-search(
+ title="Search (subscribers only)",
+ data-toggle="tooltip",
+ data-placement="right")
+ a(href="{{ url_for('cloud.join') }}")
+ i.pi-search
+ | {% endif %}
+ | {% endif %}
+ | {{ extension_sidebar_links }}
+
+ | {% if project.has_method('PUT') %}
+ li(
+ title="Edit Project",
+ data-toggle="tooltip",
+ data-placement="right")
+ a(href="{{ url_for('projects.edit', project_url=project.url) }}")
+ i.pi-cog
+ | {% endif %}
+
+
+ #project_nav(class="{{ title }}")
+ #project_nav-container
+ | {% if title != 'about' %}
+ //- +nav-secondary(class="bg-white")
+ //- +nav-secondary-link(
+ //- class="active",
+ //- href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
+ //- | {{ project.name }}
+ //- #project_nav-header.bg-white
+
+ //- a.project-title.p-2.font-weight-bold.text-dark(
+ //- href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
+ //- | {{ project.name }}
+
+ | {% block project_tree %}
+ #project_tree.bg-white
+ | {% endblock project_tree %}
+ | {% endif %}
+
+
+ #project_context-container.border-left
+ | {% if project.has_method('PUT') %}
+ #project_context-header.bg-white
+ span#status-bar
+
+ ul.project-edit-tools.disabled
+ li.dropdown
+ button#item_add.project-mode-view.btn.btn-sm.btn-outline-secondary.dropdown-toggle(
+ 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.btn.btn-sm.btn-outline-secondary.ml-2(
+ href="javascript:void(0);",
+ title="Edit",
+ data-project_id="{{project._id}}")
+ i.button-edit-icon.pi-edit
+ | Edit Project
+
+ li.dropdown
+ button.dropdown-toggle.project-mode-view.btn.btn-sm.btn-outline-secondary.mx-2(
+ type="button",
+ data-toggle="dropdown",
+ aria-haspopup="true",
+ aria-expanded="false")
+ i.pi-more-vertical.p-0
+
+ ul.dropdown-menu
+ | {% if current_user.has_cap('admin') %}
+ li.dropdown-item
+ a#item_featured(
+ href="javascript:void(0);",
+ title="Feature on project's homepage",
+ data-toggle="tooltip",
+ data-placement="left")
+ i.pi-star
+ | Toggle Featured
+
+ li.dropdown-item
+ a#item_toggle_public(
+ href="javascript:void(0);",
+ title="Make it accessible to anyone",
+ data-toggle="tooltip",
+ data-placement="left")
+ i.pi-lock-open
+ | Toggle Public
+ | {% endif %}
+
+ li.dropdown-item
+ a#item_toggle_projheader(
+ href="javascript:void(0);",
+ title="Feature as project's header",
+ data-toggle="tooltip",
+ data-placement="left")
+ i.pi-star
+ | Toggle Project Header video
+
+ li.dropdown-item.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.dropdown-item.button-delete
+ a#item_delete(
+ href="javascript:void(0);",
+ title="Can be undone within a month",
+ data-toggle="tooltip",
+ data-placement="left")
+ i.pi-trash
+ | Delete Project
+
+ // Edit Mode
+ li.button-cancel
+ a#item_cancel.project-mode-edit.btn.btn-outline-secondary(
+ href="javascript:void(0);",
+ title="Cancel changes")
+ i.button-cancel-icon.pi-cancel
+ | Cancel
+
+ li.button-save
+ a#item_save.project-mode-edit.btn.btn-outline-success.mx-2(
+ href="javascript:void(0);",
+ title="Save changes")
+ i.button-save-icon.pi-check
+ | Save Changes
+
+ | {% endif %}
+
+ | {% set utm_source = request.args.get('utm_source') %}
+ | {% if config.UTM_LINKS and utm_source in config.UTM_LINKS %}
+ #utm_container
+ a(href="{{config.UTM_LINKS[utm_source]['link']}}")
+ img(src="{{config.UTM_LINKS[utm_source]['image']}}", alt="gift", class="img-responsive")
+ | {% endif %}
+ #project_context
+ | {% block project_context %}
+ | {% if show_project %}
+ | {% include "projects/view_embed.html" %}
+ | {% endif %}
+ | {% endblock project_context %}
+
+ #overlay-mode-move-container
+ .overlay-container
+ .title
+ i.pi-angle-left
+ | Select the folder 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_container %}{% endblock %}
+
+| {% block footer_scripts_pre %}
+
+| {% 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') }}")
+| {% endif %}
+
+script.
+ 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').displayAs('inline-block');
+ $('.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').displayAs('inline-block');
+ } 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();
+
+ // Set the page title on the document
+ var page_title = $('#node-title').text() + " - {{ project.name }} — Blender Cloud";
+ DocumentTitleAPI.set_page_title(page_title);
+
+ // 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');
+ }
+| {% endblock %}
+
+| {% block footer_scripts %}
+script(src="{{ url_for('static_pillar', filename='assets/jstree/jstree.min.js') }}")
+
+script.
+ {% if show_project %}
+ ProjectUtils.setProjectAttributes({projectId: "{{project._id}}", isProject: true, nodeId: ''});
+ {% else %}
+ {% if node %}
+ ProjectUtils.setProjectAttributes({projectId: "{{project._id}}", isProject: false, nodeId: '{{node._id}}'});
+ {% endif %}
+ {% endif %}
+
+ var projectTree = document.getElementById('project_tree');
+
+ 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 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($(''));
+ $('#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($(''));
+ $('#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').displayAs('inline-block');
+ $('.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-document", "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-play", "max_children": 0},
+ "blog" : {"icon": "pi-newspaper", "max_children": 0},
+ "page" : {"icon": "pi-document-text", "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('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('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('Move Here').removeClass('disabled');
+ }
+ }
+
+ // Check the type of node and act accordingly
+ if (data.node.original.custom_view) {
+ window.location = data.node.a_attr.href;
+ } 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);
+ }
+ });
+ };
+
+ {% if is_embedded_edit is not defined or is_embedded_edit %}
+ // Initialize the page if we are not directly editing a node (most of the time)
+ loadContent();
+ {% endif %}
+
+ var project_container = document.getElementById('project-container');
+
+ /* UI Stuff */
+ $(window).on("load resize",function(){
+ containerResizeY($(window).height());
+
+ if ($(window).width() > 480) {
+ project_container.style.height = (window.innerHeight - project_container.offsetTop) + "px";
+ }
+ });
+
+ {% if current_user_is_subscriber %}
+ $(projectTree).addClass('is_subscriber');
+ {% endif %}
+
+| {% endblock %}
+
+| {% block comment_scripts %} {% endblock%}