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,70 @@
$(function () {
$('[data-toggle="tooltip"]').tooltip({'delay' : {'show': 1250, 'hide': 250}});
$('[data-toggle="popover"]').popover();
})
function NavbarTransparent() {
var startingpoint = 50;
$(window).on("load scroll", function () {
if ($(this).scrollTop() > startingpoint) {
$('.navbar-overlay, .navbar-transparent').addClass('is-active');
if(document.getElementById("project_context-header") !== null) {
$('#project_context-header').addClass('is-offset');
}
} else {
$('.navbar-overlay, .navbar-transparent').removeClass('is-active');
if(document.getElementById("project_context-header") !== null) {
$('#project_context-header').removeClass('is-offset');
}
};
});
};
NavbarTransparent();
/* Status Bar */
function statusBarSet(classes, html, icon_name, time){
/* Utility to notify the user by temporarily flashing text on the project header
Usage:
'classes' can be: success, error, warning, info, default
'html': the text to display, can contain html tags
(in case of errors, it's better to use data.status + data.statusText instead )
'icon_name': optional, sets a custom icon (otherwise an icon based on the class will be used)
'time': optional, custom time in milliseconds for the text to be displayed
*/
var icon = '';
if (!time) { time = 3000 };
if (!icon_name) {
if (classes == 'error') {
icon_name = 'pi-attention';
} else if (classes == 'success') {
icon_name = 'pi-check';
} else if (classes == 'warning') {
icon_name = 'pi-warning';
} else if (classes == 'info') {
icon_name = 'pi-info';
} else {
icon = '<i class="' + icon_name + '"></i>';
};
} else {
icon = '<i class="' + icon_name + '"></i>';
};
var text = icon + html;
$("#project-statusbar").addClass('active ' + classes);
$("#project-statusbar").html(text);
/* Back to normal */
setTimeout(function(){
$("#project-statusbar").removeAttr('class');
$("#project-statusbar").html();
}, time);
};

View File

@@ -0,0 +1,182 @@
function projectNavCollapse() {
$("#project-side-container").addClass('collapsed');
$("ul.breadcrumb.context").addClass('active');
if (typeof Ps !== 'undefined'){
Ps.destroy(document.getElementById('project_tree'));
};
};
function projectNavExpand() {
$("#project-side-container").removeClass('collapsed');
$("ul.breadcrumb.context").removeAttr('class');
if (typeof Ps !== 'undefined'){
Ps.initialize(document.getElementById('project_tree'), {suppressScrollX: true});
}
};
function projectNavCheck(){
/* Only run if there is a tree */
if(document.getElementById("project_tree") !== null) {
var nav_status = Cookies.getJSON('bcloud_ui');
if (nav_status && nav_status.nav_collapsed) {
if (nav_status.nav_collapsed == 'expanded') {
projectNavExpand();
} else if ( nav_status.nav_collapsed == 'collapsed' ) {
projectNavCollapse();
}
} else {
projectNavExpand();
}
}
}
function projectNavToggle(){
var nav_status = Cookies.getJSON('bcloud_ui');
if (nav_status && nav_status.nav_collapsed) {
if (nav_status.nav_collapsed == 'expanded') {
projectNavCollapse();
setJSONCookie('bcloud_ui', 'nav_collapsed', 'collapsed');
} else if ( nav_status.nav_collapsed == 'collapsed' ) {
projectNavExpand();
setJSONCookie('bcloud_ui', 'nav_collapsed', 'expanded');
}
} else {
projectNavCollapse();
setJSONCookie('bcloud_ui', 'nav_collapsed', 'collapsed');
}
$('#project_context-header').width($('#project_context-container').width());
}
$(function () {
/* Check on first load */
projectNavCheck();
$('.project_split, .project_nav-toggle-btn').on('click', function (e) {
projectNavToggle();
});
/* Only run if there is a tree */
if(document.getElementById("project_tree") !== null) {
$(document).keypress(function(e) {
var tag = e.target.tagName.toLowerCase();
/* Toggle when pressing [T] key */
if(e.which == 116 && tag != 'input' && tag != 'textarea' && !e.ctrlKey && !e.metaKey && !e.altKey) {
projectNavToggle();
}
});
}
});
/* Small utility to enable specific node_types under the Add New dropdown */
/* It takes:
* empty: Enable every item
* false: Disable every item
* array: Disable every item except a list of node_types, e.g: ['asset', 'group']
*/
function addMenuEnable(node_types){
$("#item_add").parent().removeClass('disabled');
$("ul.add_new-menu li[class^='button-']").hide().addClass('disabled');
if (node_types === undefined) {
$("ul.add_new-menu li[class^='button-']").show().removeClass('disabled');
} else if (node_types == false) {
$("#item_add").parent().addClass('disabled');
} else {
$.each(node_types, function(index, value) {
$("ul.add_new-menu li[class*='button-" + value +"']").show().removeClass('disabled');
});
}
}
function addMenuDisable(node_types){
$.each(node_types, function(index, value) {
$("ul.add_new-menu li[class*='button-" + value +"']").addClass('disabled');
});
}
/* Completely hide specific items (like Texture when on project root) */
function addMenuHide(node_types){
$.each(node_types, function(index, value) {
$("ul.add_new-menu li[class*='button-" + value +"']").hide().addClass('disabled');
});
}
/* Jump to the top of the page! */
function hopToTop(limit){
if (limit == null) {
limit = 500;
}
document.getElementById("hop").onclick = function(e){ window.scrollTo(0, 0);}
$(window).scroll(function() {
if ($(window).scrollTop() >= limit) {$("#hop").addClass("active")} else {$("#hop").removeAttr("class")}
});
}
/* Utility to replace a single item on a JSON cookie */
function setJSONCookie(cookieToChange, cookieItem, cookieData){
/* Get cookie to change, and its list if it has any */
var cookieList = Cookies.getJSON(cookieToChange);
/* Create an empty list if there's no cookie */
if (!cookieList){ cookieList = {}; }
cookieList[cookieItem] = cookieData;
/* Set (or create) cookie */
Cookies.set(cookieToChange, cookieList);
}
function containerResizeY(window_height){
var container_offset = $('#project-container').offset();
var container_height = window_height - container_offset.top;
var container_height_wheader = window_height - container_offset.top - $('#project_nav-header').height();
var window_height_minus_nav = $('#project_nav').height() - $('#project_nav-header').height();
$('#project_context-header').width($('#project_context-container').width());
$('#project_nav-container, .project_split').css(
{'max-height': window_height_minus_nav + 'px',
'height': window_height_minus_nav + 'px'}
);
if ($(window).width() > 768) {
if (container_height > parseInt($('#project-container').css("min-height"))) {
if (projectTree){
$(projectTree).css(
{'max-height': container_height_wheader + 'px',
'height': container_height_wheader + 'px'}
);
}
};
};
if (projectTree){ Ps.update(projectTree) }
};

View File

@@ -0,0 +1,109 @@
/* Reply */
$(document).on('click','body .comment-action-reply',function(e){
e.preventDefault();
// container of the comment we are replying to
var parentDiv = $(this).parent().parent();
// container of the first-level comment in the thread
var parentDivFirst = $(this).parent().parent().prevAll('.is-first:first');
// Get the id of the comment
if (parentDiv.hasClass('is-reply')) {
parentNodeId = parentDivFirst.data('node_id');
} else {
parentNodeId = parentDiv.data('node_id');
}
// Get the textarea and set its parent_id data
var commentField = document.getElementById('comment_field');
commentField.setAttribute('data-parent_id', parentNodeId);
// Start the comment field with @authorname:
var replyAuthor = $(this).parent().parent().find('.comment-author:first').html();
$(commentField).val("**@" + replyAuthor + ":** ");
// Add class for styling
$('.comment-container').removeClass('is-replying');
parentDiv.addClass('is-replying');
// Rename Post Comment button to Reply
var commentSubmitButton = document.getElementById('comment_submit');
$(commentSubmitButton).text('Post Reply');
// Move comment-reply container field after the parent container
var commentForm = $('.comment-reply-container').detach();
parentDiv.after(commentForm);
// document.getElementById('comment_field').focus();
$(commentField).focus();
// Convert Markdown
var convert = new Markdown.getSanitizingConverter().makeHtml;
var preview = $('.comment-reply-preview');
preview.html(convert($(commentField).val()));
$('.comment-reply-form').addClass('filled');
});
/* Cancel Reply */
$(document).on('click','body .comment-action-cancel',function(e){
e.preventDefault();
$('.comment-reply-container').detach().prependTo('#comments-list');
var commentField = document.getElementById('comment_field');
$(commentField).val('');
// Convert Markdown
var convert = new Markdown.getSanitizingConverter().makeHtml;
var preview = $('.comment-reply-preview');
preview.html(convert($(commentField).val()));
var commentSubmitButton = document.getElementById('comment_submit');
$(commentSubmitButton).text('Post Comment');
$('.comment-reply-form').removeClass('filled');
$('.comment-container').removeClass('is-replying');
});
/* Rate */
$(document).on('click','body .comment-action-rating',function(e){
e.preventDefault();
var $this = $(this);
var nodeId = $this.parent().parent().parent().data('node_id');
var is_positive = !$this.hasClass('down');
var parentDiv = $this.parent();
var rated_positive = parentDiv.hasClass('positive');
var op;
if (parentDiv.hasClass('rated') && is_positive == rated_positive) {
op = 'revoke';
} else if (is_positive) {
op = 'upvote';
} else {
op = 'downvote';
}
$.post("/nodes/comments/" + nodeId + "/rate/" + op)
.done(function(data){
// Add/remove styles for rated statuses
switch(op) {
case 'revoke':
parentDiv.removeClass('rated');
break;
case 'upvote':
parentDiv.addClass('rated');
parentDiv.addClass('positive');
break;
case 'downvote':
parentDiv.addClass('rated');
parentDiv.removeClass('positive');
break;
}
var rating = data['data']['rating_positive'] - data['data']['rating_negative'];
$this.siblings('.comment-rating-value').text(rating);
});
});

View File

@@ -0,0 +1,15 @@
// Util to handle project, node and parent properties
ProjectUtils = {
nodeId: function() { return document.body.dataset.nodeId; },
parentNodeId: function() { return document.body.dataset.parentNodeId; },
projectId: function() { return document.body.dataset.projectId; },
isProject: function() { return document.body.dataset.isProject === 'true'; },
nodeType: function() { return document.body.dataset.nodeType; },
isModified: function() { return document.body.dataset.isModified === 'true'; },
setProjectAttributes: function(props) {
for (var key in props) {
if (!props.hasOwnProperty(key)) continue;
document.body.dataset[key] = props[key];
}
}
};

View File

@@ -0,0 +1,100 @@
/*
* == Search ==
* index and algolia settings are defined in layout.jade
*/
$(document).ready(function() {
var searchInput = $('#cloud-search');
var tu = searchInput.typeahead({hint: true}, {
source: index.ttAdapter(),
displayKey: 'name',
limit: 10,
minLength: 0,
templates: {
suggestion: function(hit) {
var hitMedia = (hit.media ? ' · <span class="media">'+hit.media+'</span>' : '');
var hitFree = (hit.is_free ? '<div class="search-hit-ribbon"><span>free</span></div>' : '');
var hitPicture;
if (hit.picture){
hitPicture = '<img src="' + hit.picture + '"/>';
} else {
hitPicture = '<div class="search-hit-thumbnail-icon">';
hitPicture += (hit.media ? '<i class="pi-' + hit.media + '"></i>' : '<i class="dark pi-'+ hit.node_type + '"></i>');
hitPicture += '</div>';
};
return '' +
'<a href="/nodes/'+ hit.objectID + '/redir" class="search-site-result" id="'+ hit.objectID + '">' +
'<div class="search-hit">' +
'<div class="search-hit-thumbnail">' +
hitPicture +
hitFree +
'</div>' +
'<div class="search-hit-name" title="' + hit.name + '">' +
hit._highlightResult.name.value + ' ' +
'</div>' +
'<div class="search-hit-meta">' +
'<span class="project">' + hit._highlightResult.project.name.value + '</span> · ' +
'<span class="node_type">' + hit.node_type + '</span>' +
hitMedia +
'</div>' +
'</div>'+
'</a>';
}
}
});
$('.search-site-result.advanced, .search-icon').on('click', function(e){
e.stopPropagation();
e.preventDefault();
window.location.href = '/search#q='+ $("#cloud-search").val() + '&page=1';
});
searchInput.bind('typeahead:select', function(ev, hit) {
$('.search-icon').removeClass('pi-search').addClass('pi-spin spin');
window.location.href = '/nodes/'+ hit.objectID + '/redir';
});
searchInput.bind('typeahead:active', function() {
$('#search-overlay').addClass('active');
$('.page-body').addClass('blur');
});
searchInput.bind('typeahead:close', function() {
$('#search-overlay').removeClass('active');
$('.page-body').removeClass('blur');
});
searchInput.keyup(function(e) {
if ( $('.tt-dataset').is(':empty') ){
if(e.keyCode == 13){
window.location.href = '/search#q='+ $("#cloud-search").val() + '&page=1';
};
};
});
searchInput.bind('typeahead:render', function(event, suggestions, async, dataset) {
if( suggestions != undefined && $('.tt-all-results').length <= 0){
$('.tt-dataset').append(
'<a id="search-advanced" href="/search#q='+ $("#cloud-search").val() + '&page=1" class="search-site-result advanced tt-suggestion">' +
'<div class="search-hit">' +
'<div class="search-hit-thumbnail">' +
'<div class="search-hit-thumbnail-icon">' +
'<i class="pi-search"></i>' +
'</div>' +
'</div>' +
'<div class="search-hit-name">' +
'Use Advanced Search' +
'</div>' +
'</div>'+
'</a>');
};
});
});

View File

@@ -0,0 +1,329 @@
// Store the title, to later append notifications count
var page_title = document.title;
var unread_on_load = 0;
var unread_new = 0;
var first_load = false;
// getNotifications by fetching json every X seconds
function getNotifications(){
$.getJSON( "/notifications/", function( data ) {
if (!first_load) {
unread_on_load = data['items'].length;
first_load = true;
}
var items = [];
unread_new = 0;
// Only if there's actual data
if (data['items'][0]){
// Loop through each item
$.each(data['items'], function(i, no){
// Increase the unread_new counter
if (!no['is_read']){ unread_new++ };
// Check if the current item has been read, to style it
var is_read = no['is_read'] ? 'is_read' : '';
var read_info = 'data-id="'+ no['_id'] + '" data-read="' + no['is_read'] + '"';
// Notification list item
var content = '<li class="nc-item ' + is_read +'" data-id="'+ no['_id'] + '">';
// User's avatar
content += '<div class="nc-avatar">';
content += '<img ' + read_info + ' src="' + no['username_avatar'] + '"/> ';
content += '</div>';
// Text of the notification
content += '<div class="nc-text">';
// Username and action
content += no['username'] + ' ' + no['action'] + ' ';
// Object
content += '<a '+read_info+'" href="'+no['object_url']+'" class="nc-a">';
content += no['context_object_name'] + ' ';
content += '</a> ';
// Date
content += '<span class="nc-date">';
content += '<a '+read_info+'" href="'+no['object_url']+'" class="nc-a">';
content += no['date'];
content += '</a>';
content += '</span>';
// Read Toggle
content += '<a id="'+no['_id']+'" href="/notifications/' + no['_id'] + '/read-toggle" class="nc-button nc-read_toggle">';
if (no['is_read']){
content += '<i title="Mark as Unread" class="pi pi-circle-dot"></i>';
} else {
content += '<i title="Mark as Read" class="pi pi-circle"></i>';
};
content += '</a>';
// Subscription Toggle
content += '<a href="/notifications/' + no['_id'] + '/subscription-toggle" class="nc-button nc-subscription_toggle">';
if (no['is_subscribed']){
content += '<i title="Turn Off Notifications" class="pi-toggle-on"></i>';
} else {
content += '<i title="Turn On Notifications" class="pi-toggle-off"></i>';
};
content += '</a>';
content += '</div>';
content += '</li>';
items.push(content);
}); // each
if (unread_new > 0) {
// Set page title, display notifications and set counter
document.title = '(' + unread_new + ') ' + page_title;
$('#notifications-count').addClass('bloom');
$('#notifications-count').html('<span>' + unread_new + '</span>');
$('#notifications-toggle i').removeClass('pi-notifications-none').addClass('pi-notifications-active');
} else {
document.title = page_title;
$('#notifications-count').removeAttr('class');
$('#notifications-toggle i').removeClass('pi-notifications-active').addClass('pi-notifications-none');
};
checkPopNotification(
data['items'][0]['_id'],
data['items'][0]['username'],
data['items'][0]['username_avatar'],
data['items'][0]['action'],
data['items'][0]['date'],
data['items'][0]['context_object_name'],
data['items'][0]['object_url']);
} else {
var content = '<li class="nc-item nc-item-empty">';
content += 'No notifications... yet.';
content += '</li>';
items.push(content);
}; // if items
// Populate the list
$('ul#notifications-list').html( items.join(''));
})
.done(function(){
// clear the counter
unread_on_load = unread_new;
});
};
// Used when we click somewhere in the page
function hideNotifications(){
$('#notifications').hide();
$('#notifications-toggle').removeClass('active');
};
function popNotification(){
// pop in!
$("#notification-pop").addClass('in');
// After 10s, add a class to make it pop out
setTimeout(function(){
$("#notification-pop").addClass('out');
// And a second later, remove all classes
setTimeout(function(){
$("#notification-pop").removeAttr('class');
}, 1000);
}, 10000);
// Set them the same so it doesn't pop up again
unread_on_load = unread_new;
};
function checkPopNotification(id,username,username_avatar,action,date,context_object_name,object_url)
{
// If there's new content
if (unread_new > unread_on_load){
// Fill in the urls for redirect on click, and mark-read
$("#notification-pop").attr('data-url', object_url);
$("#notification-pop").attr('data-read-toggle', '/notifications/' + id + '/read-toggle');
// The text in the pop
var text = '<span class="nc-author">' + username + '</span> ';
text += action + ' ';
text += context_object_name + ' ';
text += '<span class="nc-date">' + date + '</span>';
// Fill the html
$('#notification-pop .nc-text').html(text);
$('#notification-pop .nc-avatar img').attr('src', username_avatar);
// pop in!
popNotification();
};
};
// Function to set #notifications flyout height and resize if needed
function notificationsResize(){
var height = $(window).height() - 80;
if ($('#notifications').height() > height){
$('#notifications').css({
'max-height' : height / 2,
'overflow-y' : 'scroll'
}
);
} else {
$('#notifications').css({
'max-height' : '1000%',
'overflow-y' : 'initial'
}
);
};
};
$(function() {
// Click anywhere in the page to hide #notifications
$(document).click(function () {
hideNotifications();
});
// ...but clicking inside #notifications shouldn't hide itself
$('#notifications').on('click', function (e) {
e.stopPropagation();
});
// Toggle the #notifications flyout
$('#notifications-toggle').on('click', function (e) {
e.stopPropagation();
$('#notifications').toggle();
$(this).toggleClass("active");
notificationsResize();
// Hide other dropdowns
$('nav .dropdown').removeClass('open');
var navbarCollapse = $('nav.navbar-collapse');
if ($(navbarCollapse).hasClass('in')){
$(navbarCollapse).addClass('show-notifications').removeClass('in');
$('.nav-notifications-icon').removeClass('pi-notifications-none').addClass('pi-cancel');
} else {
$(navbarCollapse).removeClass('show-notifications');
$('.nav-notifications-icon').addClass('pi-notifications-none').removeClass('pi-cancel');
}
});
// Hide flyout when clicking other dropdowns
$('nav').on('click', '.dropdown', function (e) {
$('#notifications').hide();
$('#notifications-toggle').removeClass('active');
});
$('#notification-pop').on('click', function (e) {
e.preventDefault();
e.stopPropagation();
var link_url = $(this).data('url');
var read_url = $(this).data('read-toggle');
$.get(read_url)
.done(function () {
window.location.href = link_url;
});
});
// Read/Subscription Toggles
$('ul#notifications-list').on('click', '.nc-button', function (e) {
e.preventDefault();
var nc = $(this);
// Swap to spin icon while we wait for the response
$('i', nc).addClass('spin');
$.get($(nc).attr('href'))
.done(function (data) {
if ($(nc).hasClass('nc-read_toggle')) {
if (data.data.is_read) {
$('i', nc).removeClass('pi-circle').addClass('pi-circle-dot');
$(nc).closest('.nc-item').addClass('is_read');
} else {
$('i', nc).removeClass('pi-circle-dot').addClass('pi-circle');
$(nc).closest('.nc-item').removeClass('is_read');
}
}
;
if ($(nc).hasClass('nc-subscription_toggle')) {
if (data.data.is_subscribed) {
$('i', nc).removeClass('pi-toggle-on').addClass('pi-toggle-off');
} else {
$('i', nc).removeClass('pi-toggle-off').addClass('pi-toggle-on');
}
}
;
$('i', nc).removeClass('spin');
});
});
// When clicking on links, toggle as read
$('ul#notifications-list').on('click', '.nc-a', function (e) {
e.preventDefault();
var is_read = $(this).data('read');
var link_url = $(this).attr('href');
var read_url = '/notifications/' + $(this).data('id') + '/read-toggle';
if (is_read) {
window.location.href = link_url;
} else {
$.get(read_url)
.done(function () {
window.location.href = link_url;
});
}
});
// Mark All as Read
$('#notifications-markallread').on('click', function (e) {
e.preventDefault();
$.get("/notifications/read-all");
$('ul#notifications-list li.nc-item:not(.is_read)').each(function () {
$(this).addClass('is_read');
});
document.title = page_title;
$('#notifications-count').removeAttr('class');
$('#notifications-toggle i').removeClass('pi-notifications-active').addClass('pi-notifications-none');
unread_on_load = unread_new;
});
});
function getNotificationsLoop() {
getNotifications();
var getLoop = setTimeout(function () {
getNotificationsLoop();
}, 30000);
}