2682 lines
75 KiB
JavaScript
2682 lines
75 KiB
JavaScript
'use strict';
|
|
|
|
var Kanboard = {};
|
|
|
|
Kanboard.Accordion = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.Accordion.prototype.listen = function() {
|
|
$(document).on("click", ".accordion-toggle", function(e) {
|
|
var section = $(this).parents(".accordion-section");
|
|
e.preventDefault();
|
|
|
|
if (section.hasClass("accordion-collapsed")) {
|
|
section.find(".accordion-content").show();
|
|
section.removeClass("accordion-collapsed");
|
|
} else {
|
|
section.find(".accordion-content").hide();
|
|
section.addClass("accordion-collapsed");
|
|
}
|
|
});
|
|
};
|
|
|
|
Kanboard.App = function() {
|
|
this.controllers = {};
|
|
};
|
|
|
|
Kanboard.App.prototype.get = function(controller) {
|
|
return this.controllers[controller];
|
|
};
|
|
|
|
Kanboard.App.prototype.execute = function() {
|
|
for (var className in Kanboard) {
|
|
if (className !== "App") {
|
|
var controller = new Kanboard[className](this);
|
|
this.controllers[className] = controller;
|
|
|
|
if (typeof controller.execute === "function") {
|
|
controller.execute();
|
|
}
|
|
|
|
if (typeof controller.listen === "function") {
|
|
controller.listen();
|
|
}
|
|
|
|
if (typeof controller.focus === "function") {
|
|
controller.focus();
|
|
}
|
|
|
|
if (typeof controller.keyboardShortcuts === "function") {
|
|
controller.keyboardShortcuts();
|
|
}
|
|
}
|
|
}
|
|
|
|
this.focus();
|
|
this.chosen();
|
|
this.keyboardShortcuts();
|
|
this.datePicker();
|
|
this.autoComplete();
|
|
};
|
|
|
|
Kanboard.App.prototype.keyboardShortcuts = function() {
|
|
var self = this;
|
|
|
|
// Submit form
|
|
Mousetrap.bindGlobal("mod+enter", function() {
|
|
var forms = $("form");
|
|
|
|
if (forms.length == 1) {
|
|
forms.submit();
|
|
} else if (forms.length > 1) {
|
|
if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') {
|
|
$(document.activeElement).parents("form").submit();
|
|
} else if (self.get("Popover").isOpen()) {
|
|
$("#popover-container form").submit();
|
|
}
|
|
}
|
|
});
|
|
|
|
// Open board selector
|
|
Mousetrap.bind("b", function(e) {
|
|
e.preventDefault();
|
|
$('#board-selector').trigger('chosen:open');
|
|
});
|
|
|
|
// Close popover and dropdown
|
|
Mousetrap.bindGlobal("esc", function() {
|
|
self.get("Popover").close();
|
|
self.get("Dropdown").close();
|
|
});
|
|
|
|
// Show keyboard shortcut
|
|
Mousetrap.bind("?", function() {
|
|
self.get("Popover").open($("body").data("keyboard-shortcut-url"));
|
|
});
|
|
};
|
|
|
|
Kanboard.App.prototype.focus = function() {
|
|
// Auto-select input fields
|
|
$(document).on('focus', '.auto-select', function() {
|
|
$(this).select();
|
|
});
|
|
|
|
// Workaround for chrome
|
|
$(document).on('mouseup', '.auto-select', function(e) {
|
|
e.preventDefault();
|
|
});
|
|
};
|
|
|
|
Kanboard.App.prototype.chosen = function() {
|
|
$(".chosen-select").each(function() {
|
|
var searchThreshold = $(this).data("search-threshold");
|
|
|
|
if (searchThreshold === undefined) {
|
|
searchThreshold = 10;
|
|
}
|
|
|
|
$(this).chosen({
|
|
width: "180px",
|
|
no_results_text: $(this).data("notfound"),
|
|
disable_search_threshold: searchThreshold
|
|
});
|
|
});
|
|
|
|
$(".select-auto-redirect").change(function() {
|
|
var regex = new RegExp($(this).data('redirect-regex'), 'g');
|
|
window.location = $(this).data('redirect-url').replace(regex, $(this).val());
|
|
});
|
|
};
|
|
|
|
Kanboard.App.prototype.datePicker = function() {
|
|
// Datepicker translation
|
|
$.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]);
|
|
|
|
// Datepicker
|
|
$(".form-date").datepicker({
|
|
showOtherMonths: true,
|
|
selectOtherMonths: true,
|
|
dateFormat: 'yy-mm-dd',
|
|
constrainInput: false
|
|
});
|
|
|
|
// Datetime picker
|
|
$(".form-datetime").datetimepicker({
|
|
controlType: 'select',
|
|
oneLine: true,
|
|
dateFormat: 'yy-mm-dd',
|
|
// timeFormat: 'h:mm tt',
|
|
constrainInput: false
|
|
});
|
|
};
|
|
|
|
Kanboard.App.prototype.autoComplete = function() {
|
|
$(".autocomplete").each(function() {
|
|
var input = $(this);
|
|
var field = input.data("dst-field");
|
|
var extraField = input.data("dst-extra-field");
|
|
|
|
if ($('#form-' + field).val() == '') {
|
|
input.parent().find("button[type=submit]").attr('disabled','disabled');
|
|
}
|
|
|
|
input.autocomplete({
|
|
source: input.data("search-url"),
|
|
minLength: 1,
|
|
select: function(event, ui) {
|
|
$("input[name=" + field + "]").val(ui.item.id);
|
|
|
|
if (extraField) {
|
|
$("input[name=" + extraField + "]").val(ui.item[extraField]);
|
|
}
|
|
|
|
input.parent().find("button[type=submit]").removeAttr('disabled');
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
Kanboard.App.prototype.hasId = function(id) {
|
|
return !!document.getElementById(id);
|
|
};
|
|
|
|
Kanboard.App.prototype.showLoadingIcon = function() {
|
|
$("body").append('<span id="app-loading-icon"> <i class="fa fa-spinner fa-spin"></i></span>');
|
|
};
|
|
|
|
Kanboard.App.prototype.hideLoadingIcon = function() {
|
|
$("#app-loading-icon").remove();
|
|
};
|
|
|
|
Kanboard.App.prototype.formatDuration = function(d) {
|
|
if (d >= 86400) {
|
|
return Math.round(d/86400) + "d";
|
|
}
|
|
else if (d >= 3600) {
|
|
return Math.round(d/3600) + "h";
|
|
}
|
|
else if (d >= 60) {
|
|
return Math.round(d/60) + "m";
|
|
}
|
|
|
|
return d + "s";
|
|
};
|
|
|
|
Kanboard.App.prototype.isVisible = function() {
|
|
var property = "";
|
|
|
|
if (typeof document.hidden !== "undefined") {
|
|
property = "visibilityState";
|
|
} else if (typeof document.mozHidden !== "undefined") {
|
|
property = "mozVisibilityState";
|
|
} else if (typeof document.msHidden !== "undefined") {
|
|
property = "msVisibilityState";
|
|
} else if (typeof document.webkitHidden !== "undefined") {
|
|
property = "webkitVisibilityState";
|
|
}
|
|
|
|
if (property != "") {
|
|
return document[property] == "visible";
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
Kanboard.AvgTimeColumnChart = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.AvgTimeColumnChart.prototype.execute = function() {
|
|
if (this.app.hasId("analytic-avg-time-column")) {
|
|
this.show();
|
|
}
|
|
};
|
|
|
|
Kanboard.AvgTimeColumnChart.prototype.show = function() {
|
|
var chart = $("#chart");
|
|
var metrics = chart.data("metrics");
|
|
var plots = [chart.data("label")];
|
|
var categories = [];
|
|
|
|
for (var column_id in metrics) {
|
|
plots.push(metrics[column_id].average);
|
|
categories.push(metrics[column_id].title);
|
|
}
|
|
|
|
c3.generate({
|
|
data: {
|
|
columns: [plots],
|
|
type: 'bar'
|
|
},
|
|
bar: {
|
|
width: {
|
|
ratio: 0.5
|
|
}
|
|
},
|
|
axis: {
|
|
x: {
|
|
type: 'category',
|
|
categories: categories
|
|
},
|
|
y: {
|
|
tick: {
|
|
format: this.app.formatDuration
|
|
}
|
|
}
|
|
},
|
|
legend: {
|
|
show: false
|
|
}
|
|
});
|
|
};
|
|
|
|
Kanboard.BoardCollapsedMode = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.BoardCollapsedMode.prototype.keyboardShortcuts = function() {
|
|
var self = this;
|
|
|
|
if (self.app.hasId("board")) {
|
|
Mousetrap.bind("s", function() {
|
|
self.toggle();
|
|
});
|
|
}
|
|
};
|
|
|
|
Kanboard.BoardCollapsedMode.prototype.toggle = function() {
|
|
var self = this;
|
|
this.app.showLoadingIcon();
|
|
|
|
$.ajax({
|
|
cache: false,
|
|
url: $('.filter-display-mode:not([style="display: none;"]) a').attr('href'),
|
|
success: function(data) {
|
|
$('.filter-display-mode').toggle();
|
|
self.app.get("BoardDragAndDrop").refresh(data);
|
|
}
|
|
});
|
|
};
|
|
Kanboard.BoardColumnScrolling = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.BoardColumnScrolling.prototype.execute = function() {
|
|
if (this.app.hasId("board")) {
|
|
this.render();
|
|
|
|
$(window).on("load", this.render);
|
|
$(window).resize(this.render);
|
|
}
|
|
};
|
|
|
|
Kanboard.BoardColumnScrolling.prototype.listen = function() {
|
|
var self = this;
|
|
|
|
$(document).on('click', ".filter-toggle-height", function(e) {
|
|
e.preventDefault();
|
|
self.toggle();
|
|
});
|
|
};
|
|
|
|
Kanboard.BoardColumnScrolling.prototype.onBoardRendered = function() {
|
|
this.render();
|
|
};
|
|
|
|
Kanboard.BoardColumnScrolling.prototype.toggle = function() {
|
|
var scrolling = localStorage.getItem("column_scroll");
|
|
|
|
if (scrolling == undefined) {
|
|
scrolling = 1;
|
|
}
|
|
|
|
localStorage.setItem("column_scroll", scrolling == 0 ? 1 : 0);
|
|
this.render();
|
|
};
|
|
|
|
Kanboard.BoardColumnScrolling.prototype.render = function() {
|
|
var taskList = $(".board-task-list");
|
|
var rotationWrapper = $(".board-rotation-wrapper");
|
|
var filterMax = $(".filter-max-height");
|
|
var filterMin = $(".filter-min-height");
|
|
|
|
if (localStorage.getItem("column_scroll") == 0) {
|
|
var height = 80;
|
|
|
|
filterMax.show();
|
|
filterMin.hide();
|
|
rotationWrapper.css("min-height", '');
|
|
|
|
taskList.each(function() {
|
|
var columnHeight = $(this).height();
|
|
|
|
if (columnHeight > height) {
|
|
height = columnHeight;
|
|
}
|
|
});
|
|
|
|
taskList.css("min-height", height);
|
|
taskList.css("height", '');
|
|
}
|
|
else {
|
|
|
|
filterMax.hide();
|
|
filterMin.show();
|
|
|
|
if ($(".board-swimlane").length > 1) {
|
|
taskList.each(function() {
|
|
if ($(this).height() > 500) {
|
|
$(this).css("height", 500);
|
|
}
|
|
else {
|
|
$(this).css("min-height", 320); // Height of the dropdown menu
|
|
rotationWrapper.css("min-height", 320);
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
var height = $(window).height() - 170;
|
|
|
|
taskList.css("height", height);
|
|
rotationWrapper.css("min-height", height);
|
|
}
|
|
}
|
|
};
|
|
|
|
Kanboard.BoardColumnView = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.BoardColumnView.prototype.execute = function() {
|
|
if (this.app.hasId("board")) {
|
|
this.render();
|
|
}
|
|
};
|
|
|
|
Kanboard.BoardColumnView.prototype.listen = function() {
|
|
var self = this;
|
|
|
|
$(document).on("click", ".board-toggle-column-view", function() {
|
|
self.toggle($(this).data("column-id"));
|
|
});
|
|
};
|
|
|
|
Kanboard.BoardColumnView.prototype.onBoardRendered = function() {
|
|
this.render();
|
|
};
|
|
|
|
Kanboard.BoardColumnView.prototype.render = function() {
|
|
var self = this;
|
|
|
|
$(".board-column-header").each(function() {
|
|
var columnId = $(this).data('column-id');
|
|
if (localStorage.getItem("hidden_column_" + columnId)) {
|
|
self.hideColumn(columnId);
|
|
}
|
|
});
|
|
};
|
|
|
|
Kanboard.BoardColumnView.prototype.toggle = function(columnId) {
|
|
if (localStorage.getItem("hidden_column_" + columnId)) {
|
|
this.showColumn(columnId);
|
|
}
|
|
else {
|
|
this.hideColumn(columnId);
|
|
}
|
|
};
|
|
|
|
Kanboard.BoardColumnView.prototype.hideColumn = function(columnId) {
|
|
$(".board-column-" + columnId + " .board-column-expanded").hide();
|
|
$(".board-column-" + columnId + " .board-column-collapsed").show();
|
|
$(".board-column-header-" + columnId + " .board-column-expanded").hide();
|
|
$(".board-column-header-" + columnId + " .board-column-collapsed").show();
|
|
|
|
$(".board-column-header-" + columnId).each(function() {
|
|
$(this).removeClass("board-column-compact");
|
|
$(this).addClass("board-column-header-collapsed");
|
|
});
|
|
|
|
$(".board-column-" + columnId).each(function() {
|
|
$(this).addClass("board-column-task-collapsed");
|
|
});
|
|
|
|
$(".board-column-" + columnId + " .board-rotation").each(function() {
|
|
$(this).css("width", $(".board-column-" + columnId + "").height());
|
|
});
|
|
|
|
localStorage.setItem("hidden_column_" + columnId, 1);
|
|
};
|
|
|
|
Kanboard.BoardColumnView.prototype.showColumn = function(columnId) {
|
|
$(".board-column-" + columnId + " .board-column-expanded").show();
|
|
$(".board-column-" + columnId + " .board-column-collapsed").hide();
|
|
$(".board-column-header-" + columnId + " .board-column-expanded").show();
|
|
$(".board-column-header-" + columnId + " .board-column-collapsed").hide();
|
|
|
|
$(".board-column-header-" + columnId).removeClass("board-column-header-collapsed");
|
|
$(".board-column-" + columnId).removeClass("board-column-task-collapsed");
|
|
|
|
if (localStorage.getItem("horizontal_scroll") == 0) {
|
|
$(".board-column-header-" + columnId).addClass("board-column-compact");
|
|
}
|
|
|
|
localStorage.removeItem("hidden_column_" + columnId);
|
|
};
|
|
|
|
Kanboard.BoardDragAndDrop = function(app) {
|
|
this.app = app;
|
|
this.savingInProgress = false;
|
|
};
|
|
|
|
Kanboard.BoardDragAndDrop.prototype.execute = function() {
|
|
if (this.app.hasId("board")) {
|
|
this.dragAndDrop();
|
|
this.executeListeners();
|
|
}
|
|
};
|
|
|
|
Kanboard.BoardDragAndDrop.prototype.dragAndDrop = function() {
|
|
var self = this;
|
|
var params = {
|
|
forcePlaceholderSize: true,
|
|
tolerance: "pointer",
|
|
connectWith: ".board-task-list",
|
|
placeholder: "draggable-placeholder",
|
|
items: ".draggable-item",
|
|
stop: function(event, ui) {
|
|
var task = ui.item;
|
|
var taskId = task.attr('data-task-id');
|
|
var taskPosition = task.attr('data-position');
|
|
var taskColumnId = task.attr('data-column-id');
|
|
var taskSwimlaneId = task.attr('data-swimlane-id');
|
|
|
|
var newColumnId = task.parent().attr("data-column-id");
|
|
var newSwimlaneId = task.parent().attr('data-swimlane-id');
|
|
var newPosition = task.index() + 1;
|
|
|
|
task.removeClass("draggable-item-selected");
|
|
|
|
if (newColumnId != taskColumnId || newSwimlaneId != taskSwimlaneId || newPosition != taskPosition) {
|
|
self.changeTaskState(taskId);
|
|
self.save(taskId, newColumnId, newPosition, newSwimlaneId);
|
|
}
|
|
},
|
|
start: function(event, ui) {
|
|
ui.item.addClass("draggable-item-selected");
|
|
ui.placeholder.height(ui.item.height());
|
|
}
|
|
};
|
|
|
|
if ($.support.touch) {
|
|
$(".task-board-sort-handle").css("display", "inline");
|
|
params["handle"] = ".task-board-sort-handle";
|
|
}
|
|
|
|
$(".board-task-list").sortable(params);
|
|
};
|
|
|
|
Kanboard.BoardDragAndDrop.prototype.changeTaskState = function(taskId) {
|
|
var task = $("div[data-task-id=" + taskId + "]");
|
|
task.addClass('task-board-saving-state');
|
|
task.find('.task-board-saving-icon').show();
|
|
};
|
|
|
|
Kanboard.BoardDragAndDrop.prototype.save = function(taskId, columnId, position, swimlaneId) {
|
|
var self = this;
|
|
self.app.showLoadingIcon();
|
|
self.savingInProgress = true;
|
|
|
|
$.ajax({
|
|
cache: false,
|
|
url: $("#board").data("save-url"),
|
|
contentType: "application/json",
|
|
type: "POST",
|
|
processData: false,
|
|
data: JSON.stringify({
|
|
"task_id": taskId,
|
|
"column_id": columnId,
|
|
"swimlane_id": swimlaneId,
|
|
"position": position
|
|
}),
|
|
success: function(data) {
|
|
self.refresh(data);
|
|
self.savingInProgress = false;
|
|
},
|
|
error: function() {
|
|
self.app.hideLoadingIcon();
|
|
self.savingInProgress = false;
|
|
}
|
|
});
|
|
};
|
|
|
|
Kanboard.BoardDragAndDrop.prototype.refresh = function(data) {
|
|
$("#board-container").replaceWith(data);
|
|
|
|
this.app.hideLoadingIcon();
|
|
this.dragAndDrop();
|
|
this.executeListeners();
|
|
};
|
|
|
|
Kanboard.BoardDragAndDrop.prototype.executeListeners = function() {
|
|
for (var className in this.app.controllers) {
|
|
var controller = this.app.get(className);
|
|
|
|
if (typeof controller.onBoardRendered === "function") {
|
|
controller.onBoardRendered();
|
|
}
|
|
}
|
|
};
|
|
|
|
Kanboard.BoardHorizontalScrolling = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.BoardHorizontalScrolling.prototype.execute = function() {
|
|
if (this.app.hasId("board")) {
|
|
this.render();
|
|
}
|
|
};
|
|
|
|
Kanboard.BoardHorizontalScrolling.prototype.listen = function() {
|
|
var self = this;
|
|
|
|
$(document).on('click', ".filter-toggle-scrolling", function(e) {
|
|
e.preventDefault();
|
|
self.toggle();
|
|
});
|
|
};
|
|
|
|
Kanboard.BoardHorizontalScrolling.prototype.keyboardShortcuts = function() {
|
|
var self = this;
|
|
|
|
if (self.app.hasId("board")) {
|
|
Mousetrap.bind("c", function () {
|
|
self.toggle();
|
|
});
|
|
}
|
|
};
|
|
|
|
Kanboard.BoardHorizontalScrolling.prototype.onBoardRendered = function() {
|
|
this.render();
|
|
};
|
|
|
|
Kanboard.BoardHorizontalScrolling.prototype.toggle = function() {
|
|
var scrolling = localStorage.getItem("horizontal_scroll") || 1;
|
|
localStorage.setItem("horizontal_scroll", scrolling == 0 ? 1 : 0);
|
|
this.render();
|
|
};
|
|
|
|
Kanboard.BoardHorizontalScrolling.prototype.render = function() {
|
|
if (localStorage.getItem("horizontal_scroll") == 0) {
|
|
$(".filter-wide").show();
|
|
$(".filter-compact").hide();
|
|
|
|
$("#board-container").addClass("board-container-compact");
|
|
$("#board th:not(.board-column-header-collapsed)").addClass("board-column-compact");
|
|
}
|
|
else {
|
|
$(".filter-wide").hide();
|
|
$(".filter-compact").show();
|
|
|
|
$("#board-container").removeClass("board-container-compact");
|
|
$("#board th").removeClass("board-column-compact");
|
|
}
|
|
};
|
|
|
|
Kanboard.BoardPolling = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.BoardPolling.prototype.execute = function() {
|
|
if (this.app.hasId("board")) {
|
|
var interval = parseInt($("#board").attr("data-check-interval"));
|
|
|
|
if (interval > 0) {
|
|
window.setInterval(this.check.bind(this), interval * 1000);
|
|
}
|
|
}
|
|
};
|
|
|
|
Kanboard.BoardPolling.prototype.check = function() {
|
|
if (this.app.isVisible() && !this.app.get("BoardDragAndDrop").savingInProgress) {
|
|
var self = this;
|
|
this.app.showLoadingIcon();
|
|
|
|
$.ajax({
|
|
cache: false,
|
|
url: $("#board").data("check-url"),
|
|
statusCode: {
|
|
200: function(data) {
|
|
self.app.get("BoardDragAndDrop").refresh(data);
|
|
},
|
|
304: function () {
|
|
self.app.hideLoadingIcon();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
Kanboard.BoardTask = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.BoardTask.prototype.listen = function() {
|
|
var self = this;
|
|
|
|
$(document).on("click", ".task-board-change-assignee", function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
self.app.get("Popover").open($(this).data('url'));
|
|
});
|
|
|
|
$(document).on("click", ".task-board", function(e) {
|
|
if (e.target.tagName != "A" && e.target.tagName != "IMG") {
|
|
window.location = $(this).data("task-url");
|
|
}
|
|
});
|
|
};
|
|
|
|
Kanboard.BoardTask.prototype.keyboardShortcuts = function() {
|
|
var self = this;
|
|
|
|
if (self.app.hasId("board")) {
|
|
Mousetrap.bind("n", function () {
|
|
self.app.get("Popover").open($("#board").data("task-creation-url"));
|
|
});
|
|
}
|
|
};
|
|
|
|
Kanboard.BurndownChart = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.BurndownChart.prototype.execute = function() {
|
|
if (this.app.hasId("analytic-burndown")) {
|
|
this.show();
|
|
}
|
|
};
|
|
|
|
Kanboard.BurndownChart.prototype.show = function() {
|
|
var chart = $("#chart");
|
|
var metrics = chart.data("metrics");
|
|
var columns = [[chart.data("label-total")]];
|
|
var categories = [];
|
|
var inputFormat = d3.time.format("%Y-%m-%d");
|
|
var outputFormat = d3.time.format(chart.data("date-format"));
|
|
|
|
for (var i = 0; i < metrics.length; i++) {
|
|
|
|
for (var j = 0; j < metrics[i].length; j++) {
|
|
|
|
if (i == 0) {
|
|
columns.push([metrics[i][j]]);
|
|
}
|
|
else {
|
|
columns[j + 1].push(metrics[i][j]);
|
|
|
|
if (j > 0) {
|
|
|
|
if (columns[0][i] == undefined) {
|
|
columns[0].push(0);
|
|
}
|
|
|
|
columns[0][i] += metrics[i][j];
|
|
}
|
|
|
|
if (j == 0) {
|
|
categories.push(outputFormat(inputFormat.parse(metrics[i][j])));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
c3.generate({
|
|
data: {
|
|
columns: columns
|
|
},
|
|
axis: {
|
|
x: {
|
|
type: 'category',
|
|
categories: categories
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
Kanboard.Calendar = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.Calendar.prototype.execute = function() {
|
|
var calendar = $('#calendar');
|
|
|
|
if (calendar.length == 1) {
|
|
this.show(calendar);
|
|
}
|
|
};
|
|
|
|
Kanboard.Calendar.prototype.show = function(calendar) {
|
|
calendar.fullCalendar({
|
|
lang: $("body").data("js-lang"),
|
|
editable: true,
|
|
eventLimit: true,
|
|
defaultView: "month",
|
|
header: {
|
|
left: 'prev,next today',
|
|
center: 'title',
|
|
right: 'month,agendaWeek,agendaDay'
|
|
},
|
|
eventDrop: function(event) {
|
|
$.ajax({
|
|
cache: false,
|
|
url: calendar.data("save-url"),
|
|
contentType: "application/json",
|
|
type: "POST",
|
|
processData: false,
|
|
data: JSON.stringify({
|
|
"task_id": event.id,
|
|
"date_due": event.start.format()
|
|
})
|
|
});
|
|
},
|
|
viewRender: function() {
|
|
var url = calendar.data("check-url");
|
|
var params = {
|
|
"start": calendar.fullCalendar('getView').start.format(),
|
|
"end": calendar.fullCalendar('getView').end.format()
|
|
};
|
|
|
|
for (var key in params) {
|
|
url += "&" + key + "=" + params[key];
|
|
}
|
|
|
|
$.getJSON(url, function(events) {
|
|
calendar.fullCalendar('removeEvents');
|
|
calendar.fullCalendar('addEventSource', events);
|
|
calendar.fullCalendar('rerenderEvents');
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
Kanboard.Column = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.Column.prototype.listen = function() {
|
|
this.dragAndDrop();
|
|
};
|
|
|
|
Kanboard.Column.prototype.dragAndDrop = function() {
|
|
var self = this;
|
|
|
|
$(".draggable-row-handle").mouseenter(function() {
|
|
$(this).parent().parent().addClass("draggable-item-hover");
|
|
}).mouseleave(function() {
|
|
$(this).parent().parent().removeClass("draggable-item-hover");
|
|
});
|
|
|
|
$(".columns-table tbody").sortable({
|
|
forcePlaceholderSize: true,
|
|
handle: "td:first i",
|
|
helper: function(e, ui) {
|
|
ui.children().each(function() {
|
|
$(this).width($(this).width());
|
|
});
|
|
|
|
return ui;
|
|
},
|
|
stop: function(event, ui) {
|
|
var column = ui.item;
|
|
column.removeClass("draggable-item-selected");
|
|
self.savePosition(column.data("column-id"), column.index() + 1);
|
|
},
|
|
start: function(event, ui) {
|
|
ui.item.addClass("draggable-item-selected");
|
|
}
|
|
}).disableSelection();
|
|
};
|
|
|
|
Kanboard.Column.prototype.savePosition = function(columnId, position) {
|
|
var url = $(".columns-table").data("save-position-url");
|
|
var self = this;
|
|
|
|
this.app.showLoadingIcon();
|
|
|
|
$.ajax({
|
|
cache: false,
|
|
url: url,
|
|
contentType: "application/json",
|
|
type: "POST",
|
|
processData: false,
|
|
data: JSON.stringify({
|
|
"column_id": columnId,
|
|
"position": position
|
|
}),
|
|
complete: function() {
|
|
self.app.hideLoadingIcon();
|
|
}
|
|
});
|
|
};
|
|
|
|
Kanboard.CompareHoursColumnChart = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.CompareHoursColumnChart.prototype.execute = function() {
|
|
if (this.app.hasId("analytic-compare-hours")) {
|
|
this.show();
|
|
}
|
|
};
|
|
|
|
Kanboard.CompareHoursColumnChart.prototype.show = function() {
|
|
var chart = $("#chart");
|
|
var metrics = chart.data("metrics");
|
|
var labelOpen = chart.data("label-open");
|
|
var labelClosed = chart.data("label-closed");
|
|
var spent = [chart.data("label-spent")];
|
|
var estimated = [chart.data("label-estimated")];
|
|
var categories = [];
|
|
|
|
for (var status in metrics) {
|
|
spent.push(parseFloat(metrics[status].time_spent));
|
|
estimated.push(parseFloat(metrics[status].time_estimated));
|
|
categories.push(status == 'open' ? labelOpen : labelClosed);
|
|
}
|
|
|
|
c3.generate({
|
|
data: {
|
|
columns: [spent, estimated],
|
|
type: 'bar'
|
|
},
|
|
bar: {
|
|
width: {
|
|
ratio: 0.2
|
|
}
|
|
},
|
|
axis: {
|
|
x: {
|
|
type: 'category',
|
|
categories: categories
|
|
}
|
|
},
|
|
legend: {
|
|
show: true
|
|
}
|
|
});
|
|
};
|
|
|
|
Kanboard.CumulativeFlowDiagram = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.CumulativeFlowDiagram.prototype.execute = function() {
|
|
if (this.app.hasId("analytic-cfd")) {
|
|
this.show();
|
|
}
|
|
};
|
|
|
|
Kanboard.CumulativeFlowDiagram.prototype.show = function() {
|
|
var chart = $("#chart");
|
|
var metrics = chart.data("metrics");
|
|
var columns = [];
|
|
var groups = [];
|
|
var categories = [];
|
|
var inputFormat = d3.time.format("%Y-%m-%d");
|
|
var outputFormat = d3.time.format(chart.data("date-format"));
|
|
|
|
for (var i = 0; i < metrics.length; i++) {
|
|
|
|
for (var j = 0; j < metrics[i].length; j++) {
|
|
|
|
if (i == 0) {
|
|
columns.push([metrics[i][j]]);
|
|
|
|
if (j > 0) {
|
|
groups.push(metrics[i][j]);
|
|
}
|
|
}
|
|
else {
|
|
|
|
columns[j].push(metrics[i][j]);
|
|
|
|
if (j == 0) {
|
|
categories.push(outputFormat(inputFormat.parse(metrics[i][j])));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
c3.generate({
|
|
data: {
|
|
columns: columns,
|
|
type: 'area-spline',
|
|
groups: [groups]
|
|
},
|
|
axis: {
|
|
x: {
|
|
type: 'category',
|
|
categories: categories
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
Kanboard.Dropdown = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.Dropdown.prototype.listen = function() {
|
|
var self = this;
|
|
|
|
$(document).on('click', function() {
|
|
self.close();
|
|
});
|
|
|
|
$(document).on('click', '.dropdown-menu', function(e) {
|
|
e.preventDefault();
|
|
e.stopImmediatePropagation();
|
|
self.close();
|
|
|
|
var submenu = $(this).next('ul');
|
|
var offset = $(this).offset();
|
|
|
|
// Clone the submenu outside of the column to avoid clipping issue with overflow
|
|
$("body").append(jQuery("<div>", {"id": "dropdown"}));
|
|
submenu.clone().appendTo("#dropdown");
|
|
|
|
var clone = $("#dropdown ul");
|
|
clone.addClass('dropdown-submenu-open');
|
|
|
|
var submenuHeight = clone.outerHeight();
|
|
var submenuWidth = clone.outerWidth();
|
|
|
|
if (offset.top + submenuHeight - $(window).scrollTop() < $(window).height() || $(window).scrollTop() + offset.top < submenuHeight) {
|
|
clone.css('top', offset.top + $(this).height());
|
|
}
|
|
else {
|
|
clone.css('top', offset.top - submenuHeight - 5);
|
|
}
|
|
|
|
if (offset.left + submenuWidth > $(window).width()) {
|
|
clone.css('left', offset.left - submenuWidth + $(this).outerWidth());
|
|
}
|
|
else {
|
|
clone.css('left', offset.left);
|
|
}
|
|
});
|
|
|
|
$(document).on('click', '.dropdown-submenu-open li', function(e) {
|
|
if ($(e.target).is('li')) {
|
|
$(this).find('a:visible')[0].click(); // Calling native click() not the jQuery one
|
|
}
|
|
});
|
|
};
|
|
|
|
Kanboard.Dropdown.prototype.close = function() {
|
|
$("#dropdown").remove();
|
|
};
|
|
|
|
Kanboard.Dropdown.prototype.onPopoverOpened = function() {
|
|
this.close();
|
|
};
|
|
|
|
Kanboard.FileUpload = function(app) {
|
|
this.app = app;
|
|
this.files = [];
|
|
this.currentFile = 0;
|
|
};
|
|
|
|
Kanboard.FileUpload.prototype.onPopoverOpened = function() {
|
|
var dropzone = document.getElementById("file-dropzone");
|
|
var self = this;
|
|
|
|
if (dropzone) {
|
|
dropzone.ondragover = dropzone.ondragenter = function(e) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
};
|
|
|
|
dropzone.ondrop = function(e) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
self.files = e.dataTransfer.files;
|
|
self.show();
|
|
$("#file-error-max-size").hide();
|
|
};
|
|
|
|
$(document).on("click", "#file-browser", function(e) {
|
|
e.preventDefault();
|
|
$("#file-form-element").get(0).click();
|
|
});
|
|
|
|
$(document).on("click", "#file-upload-button", function(e) {
|
|
e.preventDefault();
|
|
self.currentFile = 0;
|
|
self.checkFiles();
|
|
});
|
|
|
|
$("#file-form-element").change(function() {
|
|
self.files = document.getElementById("file-form-element").files;
|
|
self.show();
|
|
$("#file-error-max-size").hide();
|
|
});
|
|
}
|
|
};
|
|
|
|
Kanboard.FileUpload.prototype.show = function() {
|
|
$("#file-list").remove();
|
|
|
|
if (this.files.length > 0) {
|
|
$("#file-upload-button").prop("disabled", false);
|
|
$("#file-dropzone-inner").hide();
|
|
|
|
var ul = jQuery("<ul>", {"id": "file-list"});
|
|
|
|
for (var i = 0; i < this.files.length; i++) {
|
|
var percentage = jQuery("<span>", {"id": "file-percentage-" + i}).append("(0%)");
|
|
var progress = jQuery("<progress>", {"id": "file-progress-" + i, "value": 0});
|
|
var li = jQuery("<li>", {"id": "file-label-" + i})
|
|
.append(progress)
|
|
.append(" ")
|
|
.append(this.files[i].name)
|
|
.append(" ")
|
|
.append(percentage);
|
|
|
|
ul.append(li);
|
|
}
|
|
|
|
$("#file-dropzone").append(ul);
|
|
} else {
|
|
$("#file-dropzone-inner").show();
|
|
}
|
|
};
|
|
|
|
Kanboard.FileUpload.prototype.checkFiles = function() {
|
|
var max = parseInt($("#file-dropzone").data("max-size"));
|
|
|
|
for (var i = 0; i < this.files.length; i++) {
|
|
if (this.files[i].size > max) {
|
|
$("#file-error-max-size").show();
|
|
$("#file-label-" + i).addClass("file-error");
|
|
$("#file-upload-button").prop("disabled", true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.uploadFiles();
|
|
};
|
|
|
|
Kanboard.FileUpload.prototype.uploadFiles = function() {
|
|
if (this.files.length > 0) {
|
|
this.uploadFile(this.files[this.currentFile]);
|
|
}
|
|
};
|
|
|
|
Kanboard.FileUpload.prototype.uploadFile = function(file) {
|
|
var dropzone = document.getElementById("file-dropzone");
|
|
var url = dropzone.dataset.url;
|
|
var xhr = new XMLHttpRequest();
|
|
var fd = new FormData();
|
|
|
|
xhr.upload.addEventListener("progress", this.updateProgress.bind(this));
|
|
xhr.upload.addEventListener("load", this.transferComplete.bind(this));
|
|
|
|
xhr.open("POST", url, true);
|
|
fd.append('files[]', file);
|
|
xhr.send(fd);
|
|
};
|
|
|
|
Kanboard.FileUpload.prototype.updateProgress = function(e) {
|
|
if (e.lengthComputable) {
|
|
$("#file-progress-" + this.currentFile).val(e.loaded / e.total);
|
|
$("#file-percentage-" + this.currentFile).text('(' + Math.floor((e.loaded / e.total) * 100) + '%)');
|
|
}
|
|
};
|
|
|
|
Kanboard.FileUpload.prototype.transferComplete = function() {
|
|
this.currentFile++;
|
|
|
|
if (this.currentFile < this.files.length) {
|
|
this.uploadFile(this.files[this.currentFile]);
|
|
} else {
|
|
var uploadButton = $("#file-upload-button");
|
|
uploadButton.prop("disabled", true);
|
|
uploadButton.parent().hide();
|
|
$("#file-done").show();
|
|
}
|
|
};
|
|
|
|
// Based on jQuery.ganttView v.0.8.8 Copyright (c) 2010 JC Grubbs - jc.grubbs@devmynd.com - MIT License
|
|
Kanboard.Gantt = function(app) {
|
|
this.app = app;
|
|
this.data = [];
|
|
|
|
this.options = {
|
|
container: "#gantt-chart",
|
|
showWeekends: true,
|
|
allowMoves: true,
|
|
allowResizes: true,
|
|
cellWidth: 21,
|
|
cellHeight: 31,
|
|
slideWidth: 1000,
|
|
vHeaderWidth: 200
|
|
};
|
|
};
|
|
|
|
Kanboard.Gantt.prototype.execute = function() {
|
|
if (this.app.hasId("gantt-chart")) {
|
|
this.show();
|
|
}
|
|
};
|
|
|
|
// Save record after a resize or move
|
|
Kanboard.Gantt.prototype.saveRecord = function(record) {
|
|
this.app.showLoadingIcon();
|
|
|
|
$.ajax({
|
|
cache: false,
|
|
url: $(this.options.container).data("save-url"),
|
|
contentType: "application/json",
|
|
type: "POST",
|
|
processData: false,
|
|
data: JSON.stringify(record),
|
|
complete: this.app.hideLoadingIcon.bind(this)
|
|
});
|
|
};
|
|
|
|
// Build the Gantt chart
|
|
Kanboard.Gantt.prototype.show = function() {
|
|
this.data = this.prepareData($(this.options.container).data('records'));
|
|
|
|
var minDays = Math.floor((this.options.slideWidth / this.options.cellWidth) + 5);
|
|
var range = this.getDateRange(minDays);
|
|
var startDate = range[0];
|
|
var endDate = range[1];
|
|
var container = $(this.options.container);
|
|
var chart = jQuery("<div>", { "class": "ganttview" });
|
|
|
|
chart.append(this.renderVerticalHeader());
|
|
chart.append(this.renderSlider(startDate, endDate));
|
|
container.append(chart);
|
|
|
|
jQuery("div.ganttview-grid-row div.ganttview-grid-row-cell:last-child", container).addClass("last");
|
|
jQuery("div.ganttview-hzheader-days div.ganttview-hzheader-day:last-child", container).addClass("last");
|
|
jQuery("div.ganttview-hzheader-months div.ganttview-hzheader-month:last-child", container).addClass("last");
|
|
|
|
if (! $(this.options.container).data('readonly')) {
|
|
this.listenForBlockResize(startDate);
|
|
this.listenForBlockMove(startDate);
|
|
}
|
|
else {
|
|
this.options.allowResizes = false;
|
|
this.options.allowMoves = false;
|
|
}
|
|
};
|
|
|
|
// Render record list on the left
|
|
Kanboard.Gantt.prototype.renderVerticalHeader = function() {
|
|
var headerDiv = jQuery("<div>", { "class": "ganttview-vtheader" });
|
|
var itemDiv = jQuery("<div>", { "class": "ganttview-vtheader-item" });
|
|
var seriesDiv = jQuery("<div>", { "class": "ganttview-vtheader-series" });
|
|
|
|
for (var i = 0; i < this.data.length; i++) {
|
|
var content = jQuery("<span>")
|
|
.append(jQuery("<i>", {"class": "fa fa-info-circle tooltip", "title": this.getVerticalHeaderTooltip(this.data[i])}))
|
|
.append(" ");
|
|
|
|
if (this.data[i].type == "task") {
|
|
content.append(jQuery("<a>", {"href": this.data[i].link, "title": this.data[i].title}).append(this.data[i].title));
|
|
}
|
|
else {
|
|
content
|
|
.append(jQuery("<a>", {"href": this.data[i].board_link, "target": "_blank", "title": $(this.options.container).data("label-board-link")}).append('<i class="fa fa-th"></i>'))
|
|
.append(" ")
|
|
.append(jQuery("<a>", {"href": this.data[i].gantt_link, "target": "_blank", "title": $(this.options.container).data("label-gantt-link")}).append('<i class="fa fa-sliders"></i>'))
|
|
.append(" ")
|
|
.append(jQuery("<a>", {"href": this.data[i].link, "target": "_blank"}).append(this.data[i].title));
|
|
}
|
|
|
|
seriesDiv.append(jQuery("<div>", {"class": "ganttview-vtheader-series-name"}).append(content));
|
|
}
|
|
|
|
itemDiv.append(seriesDiv);
|
|
headerDiv.append(itemDiv);
|
|
|
|
return headerDiv;
|
|
};
|
|
|
|
// Render right part of the chart (top header + grid + bars)
|
|
Kanboard.Gantt.prototype.renderSlider = function(startDate, endDate) {
|
|
var slideDiv = jQuery("<div>", {"class": "ganttview-slide-container"});
|
|
var dates = this.getDates(startDate, endDate);
|
|
|
|
slideDiv.append(this.renderHorizontalHeader(dates));
|
|
slideDiv.append(this.renderGrid(dates));
|
|
slideDiv.append(this.addBlockContainers());
|
|
this.addBlocks(slideDiv, startDate);
|
|
|
|
return slideDiv;
|
|
};
|
|
|
|
// Render top header (days)
|
|
Kanboard.Gantt.prototype.renderHorizontalHeader = function(dates) {
|
|
var headerDiv = jQuery("<div>", { "class": "ganttview-hzheader" });
|
|
var monthsDiv = jQuery("<div>", { "class": "ganttview-hzheader-months" });
|
|
var daysDiv = jQuery("<div>", { "class": "ganttview-hzheader-days" });
|
|
var totalW = 0;
|
|
|
|
for (var y in dates) {
|
|
for (var m in dates[y]) {
|
|
var w = dates[y][m].length * this.options.cellWidth;
|
|
totalW = totalW + w;
|
|
|
|
monthsDiv.append(jQuery("<div>", {
|
|
"class": "ganttview-hzheader-month",
|
|
"css": { "width": (w - 1) + "px" }
|
|
}).append($.datepicker.regional[$("body").data('js-lang')].monthNames[m] + " " + y));
|
|
|
|
for (var d in dates[y][m]) {
|
|
daysDiv.append(jQuery("<div>", { "class": "ganttview-hzheader-day" }).append(dates[y][m][d].getDate()));
|
|
}
|
|
}
|
|
}
|
|
|
|
monthsDiv.css("width", totalW + "px");
|
|
daysDiv.css("width", totalW + "px");
|
|
headerDiv.append(monthsDiv).append(daysDiv);
|
|
|
|
return headerDiv;
|
|
};
|
|
|
|
// Render grid
|
|
Kanboard.Gantt.prototype.renderGrid = function(dates) {
|
|
var gridDiv = jQuery("<div>", { "class": "ganttview-grid" });
|
|
var rowDiv = jQuery("<div>", { "class": "ganttview-grid-row" });
|
|
|
|
for (var y in dates) {
|
|
for (var m in dates[y]) {
|
|
for (var d in dates[y][m]) {
|
|
var cellDiv = jQuery("<div>", { "class": "ganttview-grid-row-cell" });
|
|
if (this.options.showWeekends && this.isWeekend(dates[y][m][d])) {
|
|
cellDiv.addClass("ganttview-weekend");
|
|
}
|
|
rowDiv.append(cellDiv);
|
|
}
|
|
}
|
|
}
|
|
var w = jQuery("div.ganttview-grid-row-cell", rowDiv).length * this.options.cellWidth;
|
|
rowDiv.css("width", w + "px");
|
|
gridDiv.css("width", w + "px");
|
|
|
|
for (var i = 0; i < this.data.length; i++) {
|
|
gridDiv.append(rowDiv.clone());
|
|
}
|
|
|
|
return gridDiv;
|
|
};
|
|
|
|
// Render bar containers
|
|
Kanboard.Gantt.prototype.addBlockContainers = function() {
|
|
var blocksDiv = jQuery("<div>", { "class": "ganttview-blocks" });
|
|
|
|
for (var i = 0; i < this.data.length; i++) {
|
|
blocksDiv.append(jQuery("<div>", { "class": "ganttview-block-container" }));
|
|
}
|
|
|
|
return blocksDiv;
|
|
};
|
|
|
|
// Render bars
|
|
Kanboard.Gantt.prototype.addBlocks = function(slider, start) {
|
|
var rows = jQuery("div.ganttview-blocks div.ganttview-block-container", slider);
|
|
var rowIdx = 0;
|
|
|
|
for (var i = 0; i < this.data.length; i++) {
|
|
var series = this.data[i];
|
|
var size = this.daysBetween(series.start, series.end) + 1;
|
|
var offset = this.daysBetween(start, series.start);
|
|
var text = jQuery("<div>", {"class": "ganttview-block-text"});
|
|
|
|
var block = jQuery("<div>", {
|
|
"class": "ganttview-block tooltip" + (this.options.allowMoves ? " ganttview-block-movable" : ""),
|
|
"title": this.getBarTooltip(series),
|
|
"css": {
|
|
"width": ((size * this.options.cellWidth) - 9) + "px",
|
|
"margin-left": (offset * this.options.cellWidth) + "px"
|
|
}
|
|
}).append(text);
|
|
|
|
if (size >= 2) {
|
|
text.append(series.progress);
|
|
}
|
|
|
|
block.data("record", series);
|
|
this.setBarColor(block, series);
|
|
|
|
if (series.progress != "0%") {
|
|
block.append(jQuery("<div>", {
|
|
"css": {
|
|
"z-index": 0,
|
|
"position": "absolute",
|
|
"top": 0,
|
|
"bottom": 0,
|
|
"background-color": series.color.border,
|
|
"width": series.progress,
|
|
"opacity": 0.4
|
|
}
|
|
}));
|
|
}
|
|
|
|
jQuery(rows[rowIdx]).append(block);
|
|
rowIdx = rowIdx + 1;
|
|
}
|
|
};
|
|
|
|
// Get tooltip for vertical header
|
|
Kanboard.Gantt.prototype.getVerticalHeaderTooltip = function(record) {
|
|
var tooltip = "";
|
|
|
|
if (record.type == "task") {
|
|
tooltip = "<strong>" + record.column_title + "</strong> (" + record.progress + ")<br/>" + record.title;
|
|
}
|
|
else {
|
|
var types = ["project-manager", "project-member"];
|
|
|
|
for (var index in types) {
|
|
var type = types[index];
|
|
if (! jQuery.isEmptyObject(record.users[type])) {
|
|
var list = jQuery("<ul>");
|
|
|
|
for (var user_id in record.users[type]) {
|
|
if (user_id) {
|
|
list.append(jQuery("<li>").append(record.users[type][user_id]));
|
|
}
|
|
}
|
|
|
|
tooltip += "<p><strong>" + $(this.options.container).data("label-" + type) + "</strong></p>" + list[0].outerHTML;
|
|
}
|
|
}
|
|
}
|
|
|
|
return tooltip;
|
|
};
|
|
|
|
// Get tooltip for bars
|
|
Kanboard.Gantt.prototype.getBarTooltip = function(record) {
|
|
var tooltip = "";
|
|
|
|
if (record.not_defined) {
|
|
tooltip = $(this.options.container).data("label-not-defined");
|
|
}
|
|
else {
|
|
if (record.type == "task") {
|
|
tooltip = "<strong>" + record.progress + "</strong><br/>" +
|
|
$(this.options.container).data("label-assignee") + " " + (record.assignee ? record.assignee : '') + "<br/>";
|
|
}
|
|
|
|
tooltip += $(this.options.container).data("label-start-date") + " " + $.datepicker.formatDate('yy-mm-dd', record.start) + "<br/>";
|
|
tooltip += $(this.options.container).data("label-end-date") + " " + $.datepicker.formatDate('yy-mm-dd', record.end);
|
|
}
|
|
|
|
return tooltip;
|
|
};
|
|
|
|
// Set bar color
|
|
Kanboard.Gantt.prototype.setBarColor = function(block, record) {
|
|
if (record.not_defined) {
|
|
block.addClass("ganttview-block-not-defined");
|
|
}
|
|
else {
|
|
block.css("background-color", record.color.background);
|
|
block.css("border-color", record.color.border);
|
|
}
|
|
};
|
|
|
|
// Setup jquery-ui resizable
|
|
Kanboard.Gantt.prototype.listenForBlockResize = function(startDate) {
|
|
var self = this;
|
|
|
|
jQuery("div.ganttview-block", this.options.container).resizable({
|
|
grid: this.options.cellWidth,
|
|
handles: "e,w",
|
|
delay: 300,
|
|
stop: function() {
|
|
var block = jQuery(this);
|
|
self.updateDataAndPosition(block, startDate);
|
|
self.saveRecord(block.data("record"));
|
|
}
|
|
});
|
|
};
|
|
|
|
// Setup jquery-ui drag and drop
|
|
Kanboard.Gantt.prototype.listenForBlockMove = function(startDate) {
|
|
var self = this;
|
|
|
|
jQuery("div.ganttview-block", this.options.container).draggable({
|
|
axis: "x",
|
|
delay: 300,
|
|
grid: [this.options.cellWidth, this.options.cellWidth],
|
|
stop: function() {
|
|
var block = jQuery(this);
|
|
self.updateDataAndPosition(block, startDate);
|
|
self.saveRecord(block.data("record"));
|
|
}
|
|
});
|
|
};
|
|
|
|
// Update the record data and the position on the chart
|
|
Kanboard.Gantt.prototype.updateDataAndPosition = function(block, startDate) {
|
|
var container = jQuery("div.ganttview-slide-container", this.options.container);
|
|
var scroll = container.scrollLeft();
|
|
var offset = block.offset().left - container.offset().left - 1 + scroll;
|
|
var record = block.data("record");
|
|
|
|
// Restore color for defined block
|
|
record.not_defined = false;
|
|
this.setBarColor(block, record);
|
|
|
|
// Set new start date
|
|
var daysFromStart = Math.round(offset / this.options.cellWidth);
|
|
var newStart = this.addDays(this.cloneDate(startDate), daysFromStart);
|
|
record.start = newStart;
|
|
|
|
// Set new end date
|
|
var width = block.outerWidth();
|
|
var numberOfDays = Math.round(width / this.options.cellWidth) - 1;
|
|
record.end = this.addDays(this.cloneDate(newStart), numberOfDays);
|
|
|
|
if (record.type === "task" && numberOfDays > 0) {
|
|
jQuery("div.ganttview-block-text", block).text(record.progress);
|
|
}
|
|
|
|
// Update tooltip
|
|
block.attr("title", this.getBarTooltip(record));
|
|
|
|
block.data("record", record);
|
|
|
|
// Remove top and left properties to avoid incorrect block positioning,
|
|
// set position to relative to keep blocks relative to scrollbar when scrolling
|
|
block
|
|
.css("top", "")
|
|
.css("left", "")
|
|
.css("position", "relative")
|
|
.css("margin-left", offset + "px");
|
|
};
|
|
|
|
// Creates a 3 dimensional array [year][month][day] of every day
|
|
// between the given start and end dates
|
|
Kanboard.Gantt.prototype.getDates = function(start, end) {
|
|
var dates = [];
|
|
dates[start.getFullYear()] = [];
|
|
dates[start.getFullYear()][start.getMonth()] = [start];
|
|
var last = start;
|
|
|
|
while (this.compareDate(last, end) == -1) {
|
|
var next = this.addDays(this.cloneDate(last), 1);
|
|
|
|
if (! dates[next.getFullYear()]) {
|
|
dates[next.getFullYear()] = [];
|
|
}
|
|
|
|
if (! dates[next.getFullYear()][next.getMonth()]) {
|
|
dates[next.getFullYear()][next.getMonth()] = [];
|
|
}
|
|
|
|
dates[next.getFullYear()][next.getMonth()].push(next);
|
|
last = next;
|
|
}
|
|
|
|
return dates;
|
|
};
|
|
|
|
// Convert data to Date object
|
|
Kanboard.Gantt.prototype.prepareData = function(data) {
|
|
for (var i = 0; i < data.length; i++) {
|
|
var start = new Date(data[i].start[0], data[i].start[1] - 1, data[i].start[2], 0, 0, 0, 0);
|
|
data[i].start = start;
|
|
|
|
var end = new Date(data[i].end[0], data[i].end[1] - 1, data[i].end[2], 0, 0, 0, 0);
|
|
data[i].end = end;
|
|
}
|
|
|
|
return data;
|
|
};
|
|
|
|
// Get the start and end date from the data provided
|
|
Kanboard.Gantt.prototype.getDateRange = function(minDays) {
|
|
var minStart = new Date();
|
|
var maxEnd = new Date();
|
|
|
|
for (var i = 0; i < this.data.length; i++) {
|
|
var start = new Date();
|
|
start.setTime(Date.parse(this.data[i].start));
|
|
|
|
var end = new Date();
|
|
end.setTime(Date.parse(this.data[i].end));
|
|
|
|
if (i == 0) {
|
|
minStart = start;
|
|
maxEnd = end;
|
|
}
|
|
|
|
if (this.compareDate(minStart, start) == 1) {
|
|
minStart = start;
|
|
}
|
|
|
|
if (this.compareDate(maxEnd, end) == -1) {
|
|
maxEnd = end;
|
|
}
|
|
}
|
|
|
|
// Insure that the width of the chart is at least the slide width to avoid empty
|
|
// whitespace to the right of the grid
|
|
if (this.daysBetween(minStart, maxEnd) < minDays) {
|
|
maxEnd = this.addDays(this.cloneDate(minStart), minDays);
|
|
}
|
|
|
|
// Always start one day before the minStart
|
|
minStart.setDate(minStart.getDate() - 1);
|
|
|
|
return [minStart, maxEnd];
|
|
};
|
|
|
|
// Returns the number of day between 2 dates
|
|
Kanboard.Gantt.prototype.daysBetween = function(start, end) {
|
|
if (! start || ! end) {
|
|
return 0;
|
|
}
|
|
|
|
var count = 0, date = this.cloneDate(start);
|
|
|
|
while (this.compareDate(date, end) == -1) {
|
|
count = count + 1;
|
|
this.addDays(date, 1);
|
|
}
|
|
|
|
return count;
|
|
};
|
|
|
|
// Return true if it's the weekend
|
|
Kanboard.Gantt.prototype.isWeekend = function(date) {
|
|
return date.getDay() % 6 == 0;
|
|
};
|
|
|
|
// Clone Date object
|
|
Kanboard.Gantt.prototype.cloneDate = function(date) {
|
|
return new Date(date.getTime());
|
|
};
|
|
|
|
// Add days to a Date object
|
|
Kanboard.Gantt.prototype.addDays = function(date, value) {
|
|
date.setDate(date.getDate() + value * 1);
|
|
return date;
|
|
};
|
|
|
|
/**
|
|
* Compares the first date to the second date and returns an number indication of their relative values.
|
|
*
|
|
* -1 = date1 is lessthan date2
|
|
* 0 = values are equal
|
|
* 1 = date1 is greaterthan date2.
|
|
*/
|
|
Kanboard.Gantt.prototype.compareDate = function(date1, date2) {
|
|
if (isNaN(date1) || isNaN(date2)) {
|
|
throw new Error(date1 + " - " + date2);
|
|
} else if (date1 instanceof Date && date2 instanceof Date) {
|
|
return (date1 < date2) ? -1 : (date1 > date2) ? 1 : 0;
|
|
} else {
|
|
throw new TypeError(date1 + " - " + date2);
|
|
}
|
|
};
|
|
|
|
Kanboard.LeadCycleTimeChart = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.LeadCycleTimeChart.prototype.execute = function() {
|
|
if (this.app.hasId("analytic-lead-cycle-time")) {
|
|
this.show();
|
|
}
|
|
};
|
|
|
|
Kanboard.LeadCycleTimeChart.prototype.show = function() {
|
|
var chart = $("#chart");
|
|
var metrics = chart.data("metrics");
|
|
var cycle = [chart.data("label-cycle")];
|
|
var lead = [chart.data("label-lead")];
|
|
var categories = [];
|
|
|
|
var types = {};
|
|
types[chart.data("label-cycle")] = 'area';
|
|
types[chart.data("label-lead")] = 'area-spline';
|
|
|
|
var colors = {};
|
|
colors[chart.data("label-lead")] = '#afb42b';
|
|
colors[chart.data("label-cycle")] = '#4e342e';
|
|
|
|
for (var i = 0; i < metrics.length; i++) {
|
|
cycle.push(parseInt(metrics[i].avg_cycle_time));
|
|
lead.push(parseInt(metrics[i].avg_lead_time));
|
|
categories.push(metrics[i].day);
|
|
}
|
|
|
|
c3.generate({
|
|
data: {
|
|
columns: [
|
|
lead,
|
|
cycle
|
|
],
|
|
types: types,
|
|
colors: colors
|
|
},
|
|
axis: {
|
|
x: {
|
|
type: 'category',
|
|
categories: categories
|
|
},
|
|
y: {
|
|
tick: {
|
|
format: this.app.formatDuration
|
|
}
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
Kanboard.Markdown = function(app) {
|
|
this.app = app;
|
|
this.editor = null;
|
|
};
|
|
|
|
Kanboard.Markdown.prototype.onPopoverOpened = function() {
|
|
this.listen();
|
|
};
|
|
|
|
Kanboard.Markdown.prototype.onPopoverClosed = function() {
|
|
this.listen();
|
|
};
|
|
|
|
Kanboard.Markdown.prototype.listen = function() {
|
|
var editors = $(".markdown-editor");
|
|
|
|
if (this.editor) {
|
|
this.destroy();
|
|
}
|
|
|
|
if (editors.length > 0) {
|
|
this.show(editors[0]);
|
|
}
|
|
};
|
|
|
|
Kanboard.Markdown.prototype.destroy = function() {
|
|
var cm = this.editor.codemirror;
|
|
var wrapper = cm.getWrapperElement();
|
|
|
|
for (var item in ["toolbar", "statusbar", "sideBySide"]) {
|
|
if (this.editor.gui[item]) {
|
|
wrapper.parentNode.removeChild(this.editor.gui[item]);
|
|
}
|
|
}
|
|
|
|
cm.toTextArea();
|
|
this.editor = null;
|
|
};
|
|
|
|
Kanboard.Markdown.prototype.show = function(textarea) {
|
|
var toolbar = ["bold", "italic", "strikethrough", "heading", "|", "unordered-list", "ordered-list", "link", "|", "code", "table"];
|
|
|
|
this.editor = new SimpleMDE({
|
|
element: textarea,
|
|
status: false,
|
|
toolbarTips: false,
|
|
autoDownloadFontAwesome: false,
|
|
spellChecker: false,
|
|
autosave: {
|
|
enabled: false
|
|
},
|
|
forceSync: true,
|
|
blockStyles: {
|
|
italic: "_"
|
|
},
|
|
toolbar: textarea.hasAttribute("data-markdown-editor-disable-toolbar") ? false : toolbar,
|
|
placeholder: textarea.getAttribute("placeholder")
|
|
});
|
|
};
|
|
|
|
Kanboard.Notification = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.Notification.prototype.execute = function() {
|
|
$(".alert-fade-out").delay(4000).fadeOut(800, function() {
|
|
$(this).remove();
|
|
});
|
|
};
|
|
|
|
Kanboard.Popover = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.Popover.prototype.listen = function() {
|
|
var self = this;
|
|
|
|
$(document).on("click", ".popover", function(e) {
|
|
self.onClick(e);
|
|
});
|
|
|
|
$(document).on("click", ".close-popover", function(e) {
|
|
self.close(e);
|
|
});
|
|
|
|
$(document).on("click", "#popover-container", function(e) {
|
|
self.close(e);
|
|
});
|
|
|
|
$(document).on("click", "#popover-content", function(e) {
|
|
e.stopPropagation();
|
|
});
|
|
};
|
|
|
|
Kanboard.Popover.prototype.onClick = function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
var target = e.currentTarget || e.target;
|
|
var link = target.getAttribute("href");
|
|
|
|
if (! link) {
|
|
link = target.getAttribute("data-href");
|
|
}
|
|
|
|
if (link) {
|
|
this.open(link);
|
|
}
|
|
};
|
|
|
|
Kanboard.Popover.prototype.isOpen = function() {
|
|
return $('#popover-container').size() > 0;
|
|
};
|
|
|
|
Kanboard.Popover.prototype.open = function(link) {
|
|
var self = this;
|
|
|
|
if (!self.isOpen()) {
|
|
$.get(link, function(content) {
|
|
$("body").prepend('<div id="popover-container"><div id="popover-content">' + content + '</div></div>');
|
|
self.executeOnOpenedListeners();
|
|
});
|
|
}
|
|
};
|
|
|
|
Kanboard.Popover.prototype.close = function(e) {
|
|
if (this.isOpen()) {
|
|
if (e) {
|
|
e.preventDefault();
|
|
}
|
|
|
|
$("#popover-container").remove();
|
|
this.executeOnClosedListeners();
|
|
}
|
|
};
|
|
|
|
Kanboard.Popover.prototype.ajaxReload = function(data, request, self) {
|
|
var redirect = request.getResponseHeader("X-Ajax-Redirect");
|
|
|
|
if (redirect === 'self') {
|
|
window.location.reload();
|
|
} else if (redirect && redirect.indexOf('#') > -1) {
|
|
window.location = redirect.split('#')[0];
|
|
} else if (redirect) {
|
|
window.location = redirect;
|
|
} else {
|
|
$("#popover-content").html(data);
|
|
$("#popover-content input[autofocus]").focus();
|
|
self.executeOnOpenedListeners();
|
|
}
|
|
};
|
|
|
|
Kanboard.Popover.prototype.executeOnOpenedListeners = function() {
|
|
for (var className in this.app.controllers) {
|
|
var controller = this.app.get(className);
|
|
|
|
if (typeof controller.onPopoverOpened === "function") {
|
|
controller.onPopoverOpened();
|
|
}
|
|
}
|
|
|
|
this.afterOpen();
|
|
};
|
|
|
|
Kanboard.Popover.prototype.executeOnClosedListeners = function() {
|
|
for (var className in this.app.controllers) {
|
|
var controller = this.app.get(className);
|
|
|
|
if (typeof controller.onPopoverClosed === "function") {
|
|
controller.onPopoverClosed();
|
|
}
|
|
}
|
|
};
|
|
|
|
Kanboard.Popover.prototype.afterOpen = function() {
|
|
var self = this;
|
|
var popoverForm = $("#popover-content .popover-form");
|
|
|
|
// Submit forms with Ajax request
|
|
if (popoverForm) {
|
|
popoverForm.on("submit", function(e) {
|
|
e.preventDefault();
|
|
|
|
$.ajax({
|
|
type: "POST",
|
|
url: popoverForm.attr("action"),
|
|
data: popoverForm.serialize(),
|
|
success: function(data, textStatus, request) {
|
|
self.ajaxReload(data, request, self);
|
|
},
|
|
beforeSend: function() {
|
|
var button = $('.popover-form button[type="submit"]');
|
|
button.html('<i class="fa fa-spinner fa-pulse"></i> ' + button.html());
|
|
button.attr("disabled", true);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Submit link with Ajax request
|
|
$(document).on("click", ".popover-link", function(e) {
|
|
e.preventDefault();
|
|
|
|
$.ajax({
|
|
type: "GET",
|
|
url: $(this).attr("href"),
|
|
success: function(data, textStatus, request) {
|
|
self.ajaxReload(data, request, self);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Autofocus fields (html5 autofocus works only with page onload)
|
|
$("[autofocus]").each(function() {
|
|
$(this).focus();
|
|
});
|
|
|
|
this.app.datePicker();
|
|
this.app.autoComplete();
|
|
};
|
|
|
|
Kanboard.ProjectCreation = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.ProjectCreation.prototype.onPopoverOpened = function() {
|
|
$('#project-creation-form #form-src_project_id').on('change', function() {
|
|
var srcProjectId = $(this).val();
|
|
|
|
if (srcProjectId == 0) {
|
|
$(".project-creation-options").hide();
|
|
} else {
|
|
$(".project-creation-options").show();
|
|
}
|
|
});
|
|
};
|
|
|
|
Kanboard.ProjectPermission = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.ProjectPermission.prototype.listen = function() {
|
|
$('.project-change-role').on('change', function () {
|
|
$.ajax({
|
|
cache: false,
|
|
url: $(this).data('url'),
|
|
contentType: "application/json",
|
|
type: "POST",
|
|
processData: false,
|
|
data: JSON.stringify({
|
|
"id": $(this).data('id'),
|
|
"role": $(this).val()
|
|
})
|
|
});
|
|
});
|
|
};
|
|
|
|
Kanboard.Screenshot = function(app) {
|
|
this.app = app;
|
|
this.pasteCatcher = null;
|
|
};
|
|
|
|
Kanboard.Screenshot.prototype.onPopoverOpened = function() {
|
|
if (this.app.hasId("screenshot-zone")) {
|
|
this.initialize();
|
|
}
|
|
};
|
|
|
|
// Setup event listener and workarounds
|
|
Kanboard.Screenshot.prototype.initialize = function() {
|
|
this.destroy();
|
|
|
|
if (! window.Clipboard) {
|
|
|
|
// Create a contenteditable element
|
|
this.pasteCatcher = document.createElement("div");
|
|
this.pasteCatcher.id = "screenshot-pastezone";
|
|
this.pasteCatcher.contentEditable = "true";
|
|
|
|
// Insert the content editable at the top to avoid scrolling down in the board view
|
|
this.pasteCatcher.style.opacity = 0;
|
|
this.pasteCatcher.style.position = "fixed";
|
|
this.pasteCatcher.style.top = 0;
|
|
this.pasteCatcher.style.right = 0;
|
|
this.pasteCatcher.style.width = 0;
|
|
|
|
document.body.insertBefore(this.pasteCatcher, document.body.firstChild);
|
|
|
|
// Set focus on the contenteditable element
|
|
this.pasteCatcher.focus();
|
|
|
|
// Set the focus when clicked anywhere in the document
|
|
document.addEventListener("click", this.setFocus.bind(this));
|
|
|
|
// Set the focus when clicked in screenshot dropzone (popover)
|
|
document.getElementById("screenshot-zone").addEventListener("click", this.setFocus.bind(this));
|
|
}
|
|
|
|
window.addEventListener("paste", this.pasteHandler.bind(this));
|
|
};
|
|
|
|
// Destroy contentEditable element
|
|
Kanboard.Screenshot.prototype.destroy = function() {
|
|
if (this.pasteCatcher != null) {
|
|
document.body.removeChild(this.pasteCatcher);
|
|
}
|
|
else if (document.getElementById("screenshot-pastezone")) {
|
|
document.body.removeChild(document.getElementById("screenshot-pastezone"));
|
|
}
|
|
|
|
document.removeEventListener("click", this.setFocus.bind(this));
|
|
this.pasteCatcher = null;
|
|
};
|
|
|
|
// Set focus on contentEditable element
|
|
Kanboard.Screenshot.prototype.setFocus = function() {
|
|
if (this.pasteCatcher !== null) {
|
|
this.pasteCatcher.focus();
|
|
}
|
|
};
|
|
|
|
// Paste event callback
|
|
Kanboard.Screenshot.prototype.pasteHandler = function(e) {
|
|
// Firefox doesn't have the property e.clipboardData.items (only Chrome)
|
|
if (e.clipboardData && e.clipboardData.items) {
|
|
|
|
var items = e.clipboardData.items;
|
|
|
|
if (items) {
|
|
|
|
for (var i = 0; i < items.length; i++) {
|
|
|
|
// Find an image in pasted elements
|
|
if (items[i].type.indexOf("image") !== -1) {
|
|
|
|
var blob = items[i].getAsFile();
|
|
|
|
// Get the image as base64 data
|
|
var reader = new FileReader();
|
|
var self = this;
|
|
reader.onload = function(event) {
|
|
self.createImage(event.target.result);
|
|
};
|
|
|
|
reader.readAsDataURL(blob);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
|
|
// Handle Firefox
|
|
setTimeout(this.checkInput.bind(this), 100);
|
|
}
|
|
};
|
|
|
|
// Parse the input in the paste catcher element
|
|
Kanboard.Screenshot.prototype.checkInput = function() {
|
|
var child = this.pasteCatcher.childNodes[0];
|
|
|
|
if (child) {
|
|
// If the user pastes an image, the src attribute
|
|
// will represent the image as a base64 encoded string.
|
|
if (child.tagName === "IMG") {
|
|
this.createImage(child.src);
|
|
}
|
|
}
|
|
|
|
this.pasteCatcher.innerHTML = "";
|
|
};
|
|
|
|
// Creates a new image from a given source
|
|
Kanboard.Screenshot.prototype.createImage = function(blob) {
|
|
var pastedImage = new Image();
|
|
pastedImage.src = blob;
|
|
|
|
// Send the image content to the form variable
|
|
pastedImage.onload = function() {
|
|
var sourceSplit = blob.split("base64,");
|
|
var sourceString = sourceSplit[1];
|
|
$("input[name=screenshot]").val(sourceString);
|
|
};
|
|
|
|
var zone = document.getElementById("screenshot-zone");
|
|
zone.innerHTML = "";
|
|
zone.className = "screenshot-pasted";
|
|
zone.appendChild(pastedImage);
|
|
|
|
this.destroy();
|
|
this.initialize();
|
|
};
|
|
|
|
Kanboard.Search = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.Search.prototype.focus = function() {
|
|
|
|
// Place cursor at the end when focusing on the search box
|
|
$(document).on("focus", "#form-search", function() {
|
|
var input = $("#form-search");
|
|
|
|
if (input[0].setSelectionRange) {
|
|
var len = input.val().length * 2;
|
|
input[0].setSelectionRange(len, len);
|
|
}
|
|
});
|
|
};
|
|
|
|
Kanboard.Search.prototype.listen = function() {
|
|
$(document).on("click", ".filter-helper", function (e) {
|
|
e.preventDefault();
|
|
|
|
var filter = $(this).data("filter");
|
|
var appendFilter = $(this).data("append-filter");
|
|
var uniqueFilter = $(this).data("unique-filter");
|
|
var input = $("#form-search");
|
|
|
|
if (uniqueFilter) {
|
|
var attribute = uniqueFilter.substr(0, uniqueFilter.indexOf(':'));
|
|
filter = input.val().replace(new RegExp('(' + attribute + ':[#a-z0-9]+)', 'g'), '');
|
|
filter = filter.replace(new RegExp('(' + attribute + ':"(.+)")', 'g'), '');
|
|
filter = filter.trim();
|
|
filter += ' ' + uniqueFilter;
|
|
} else if (appendFilter) {
|
|
filter = input.val() + " " + appendFilter;
|
|
}
|
|
|
|
input.val(filter);
|
|
$("form.search").submit();
|
|
});
|
|
};
|
|
|
|
Kanboard.Search.prototype.goToView = function(label) {
|
|
var link = $(label);
|
|
|
|
if (link.length) {
|
|
window.location = link.attr('href');
|
|
}
|
|
};
|
|
|
|
Kanboard.Search.prototype.keyboardShortcuts = function() {
|
|
var self = this;
|
|
|
|
// Switch view mode for projects: go to the overview page
|
|
Mousetrap.bind("v o", function() {
|
|
self.goToView(".view-overview");
|
|
});
|
|
|
|
// Switch view mode for projects: go to the board
|
|
Mousetrap.bind("v b", function() {
|
|
self.goToView(".view-board");
|
|
});
|
|
|
|
// Switch view mode for projects: go to the calendar
|
|
Mousetrap.bind("v c", function() {
|
|
self.goToView(".view-calendar");
|
|
});
|
|
|
|
// Switch view mode for projects: go to the listing
|
|
Mousetrap.bind("v l", function() {
|
|
self.goToView(".view-listing");
|
|
});
|
|
|
|
// Switch view mode for projects: go to the gantt chart
|
|
Mousetrap.bind("v g", function() {
|
|
self.goToView(".view-gantt");
|
|
});
|
|
|
|
// Focus to the search field
|
|
Mousetrap.bind("f", function(e) {
|
|
e.preventDefault();
|
|
var input = document.getElementById("form-search");
|
|
|
|
if (input) {
|
|
input.focus();
|
|
}
|
|
});
|
|
|
|
// Reset to the search field
|
|
Mousetrap.bind("r", function(e) {
|
|
e.preventDefault();
|
|
var reset = $(".filter-reset").data("filter");
|
|
var input = $("#form-search");
|
|
|
|
input.val(reset);
|
|
$("form.search").submit();
|
|
});
|
|
};
|
|
|
|
Kanboard.Session = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.Session.prototype.execute = function() {
|
|
window.setInterval(this.checkSession, 60000);
|
|
};
|
|
|
|
Kanboard.Session.prototype.checkSession = function() {
|
|
if (! $(".form-login").length) {
|
|
$.ajax({
|
|
cache: false,
|
|
url: $("body").data("status-url"),
|
|
statusCode: {
|
|
401: function() {
|
|
window.location = $("body").data("login-url");
|
|
}
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
Kanboard.Subtask = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.Subtask.prototype.listen = function() {
|
|
var self = this;
|
|
this.dragAndDrop();
|
|
|
|
$(document).on("click", ".subtask-toggle-status", function(e) {
|
|
var el = $(this);
|
|
e.preventDefault();
|
|
|
|
$.ajax({
|
|
cache: false,
|
|
url: el.attr("href"),
|
|
success: function(data) {
|
|
if (el.hasClass("subtask-refresh-table")) {
|
|
$(".subtasks-table").replaceWith(data);
|
|
} else {
|
|
el.replaceWith(data);
|
|
}
|
|
|
|
self.dragAndDrop();
|
|
}
|
|
});
|
|
});
|
|
|
|
$(document).on("click", ".subtask-toggle-timer", function(e) {
|
|
var el = $(this);
|
|
e.preventDefault();
|
|
|
|
$.ajax({
|
|
cache: false,
|
|
url: el.attr("href"),
|
|
success: function(data) {
|
|
$(".subtasks-table").replaceWith(data);
|
|
self.dragAndDrop();
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
Kanboard.Subtask.prototype.dragAndDrop = function() {
|
|
var self = this;
|
|
|
|
$(".draggable-row-handle").mouseenter(function() {
|
|
$(this).parent().parent().addClass("draggable-item-hover");
|
|
}).mouseleave(function() {
|
|
$(this).parent().parent().removeClass("draggable-item-hover");
|
|
});
|
|
|
|
$(".subtasks-table tbody").sortable({
|
|
forcePlaceholderSize: true,
|
|
handle: "td:first i",
|
|
helper: function(e, ui) {
|
|
ui.children().each(function() {
|
|
$(this).width($(this).width());
|
|
});
|
|
|
|
return ui;
|
|
},
|
|
stop: function(event, ui) {
|
|
var subtask = ui.item;
|
|
subtask.removeClass("draggable-item-selected");
|
|
self.savePosition(subtask.data("subtask-id"), subtask.index() + 1);
|
|
},
|
|
start: function(event, ui) {
|
|
ui.item.addClass("draggable-item-selected");
|
|
}
|
|
}).disableSelection();
|
|
};
|
|
|
|
Kanboard.Subtask.prototype.savePosition = function(subtaskId, position) {
|
|
var url = $(".subtasks-table").data("save-position-url");
|
|
var self = this;
|
|
|
|
this.app.showLoadingIcon();
|
|
|
|
$.ajax({
|
|
cache: false,
|
|
url: url,
|
|
contentType: "application/json",
|
|
type: "POST",
|
|
processData: false,
|
|
data: JSON.stringify({
|
|
"subtask_id": subtaskId,
|
|
"position": position
|
|
}),
|
|
complete: function() {
|
|
self.app.hideLoadingIcon();
|
|
}
|
|
});
|
|
};
|
|
|
|
Kanboard.Swimlane = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.Swimlane.prototype.execute = function() {
|
|
if ($(".swimlanes-table").length) {
|
|
this.dragAndDrop();
|
|
}
|
|
};
|
|
|
|
Kanboard.Swimlane.prototype.listen = function() {
|
|
var self = this;
|
|
|
|
$(document).on('click', ".board-swimlane-toggle", function(e) {
|
|
e.preventDefault();
|
|
|
|
var swimlaneId = $(this).data('swimlane-id');
|
|
|
|
if (self.isCollapsed(swimlaneId)) {
|
|
self.expand(swimlaneId);
|
|
}
|
|
else {
|
|
self.collapse(swimlaneId);
|
|
}
|
|
});
|
|
};
|
|
|
|
Kanboard.Swimlane.prototype.onBoardRendered = function() {
|
|
var swimlaneIds = this.getAllCollapsed();
|
|
|
|
for (var i = 0; i < swimlaneIds.length; i++) {
|
|
this.collapse(swimlaneIds[i]);
|
|
}
|
|
};
|
|
|
|
Kanboard.Swimlane.prototype.getStorageKey = function() {
|
|
return "hidden_swimlanes_" + $("#board").data("project-id");
|
|
};
|
|
|
|
Kanboard.Swimlane.prototype.expand = function(swimlaneId) {
|
|
var swimlaneIds = this.getAllCollapsed();
|
|
var index = swimlaneIds.indexOf(swimlaneId);
|
|
|
|
if (index > -1) {
|
|
swimlaneIds.splice(index, 1);
|
|
}
|
|
|
|
localStorage.setItem(this.getStorageKey(), JSON.stringify(swimlaneIds));
|
|
|
|
$('.board-swimlane-columns-' + swimlaneId).css('display', 'table-row');
|
|
$('.board-swimlane-tasks-' + swimlaneId).css('display', 'table-row');
|
|
$('.hide-icon-swimlane-' + swimlaneId).css('display', 'inline');
|
|
$('.show-icon-swimlane-' + swimlaneId).css('display', 'none');
|
|
};
|
|
|
|
Kanboard.Swimlane.prototype.collapse = function(swimlaneId) {
|
|
var swimlaneIds = this.getAllCollapsed();
|
|
|
|
if (swimlaneIds.indexOf(swimlaneId) < 0) {
|
|
swimlaneIds.push(swimlaneId);
|
|
localStorage.setItem(this.getStorageKey(), JSON.stringify(swimlaneIds));
|
|
}
|
|
|
|
$('.board-swimlane-columns-' + swimlaneId + ':not(:first-child)').css('display', 'none');
|
|
$('.board-swimlane-tasks-' + swimlaneId).css('display', 'none');
|
|
$('.hide-icon-swimlane-' + swimlaneId).css('display', 'none');
|
|
$('.show-icon-swimlane-' + swimlaneId).css('display', 'inline');
|
|
};
|
|
|
|
Kanboard.Swimlane.prototype.isCollapsed = function(swimlaneId) {
|
|
return this.getAllCollapsed().indexOf(swimlaneId) > -1;
|
|
};
|
|
|
|
Kanboard.Swimlane.prototype.getAllCollapsed = function() {
|
|
return JSON.parse(localStorage.getItem(this.getStorageKey())) || [];
|
|
};
|
|
|
|
Kanboard.Swimlane.prototype.dragAndDrop = function() {
|
|
var self = this;
|
|
|
|
$(".draggable-row-handle").mouseenter(function() {
|
|
$(this).parent().parent().addClass("draggable-item-hover");
|
|
}).mouseleave(function() {
|
|
$(this).parent().parent().removeClass("draggable-item-hover");
|
|
});
|
|
|
|
$(".swimlanes-table tbody").sortable({
|
|
forcePlaceholderSize: true,
|
|
handle: "td:first i",
|
|
helper: function(e, ui) {
|
|
ui.children().each(function() {
|
|
$(this).width($(this).width());
|
|
});
|
|
|
|
return ui;
|
|
},
|
|
stop: function(event, ui) {
|
|
var swimlane = ui.item;
|
|
swimlane.removeClass("draggable-item-selected");
|
|
self.savePosition(swimlane.data("swimlane-id"), swimlane.index() + 1);
|
|
},
|
|
start: function(event, ui) {
|
|
ui.item.addClass("draggable-item-selected");
|
|
}
|
|
}).disableSelection();
|
|
};
|
|
|
|
Kanboard.Swimlane.prototype.savePosition = function(swimlaneId, position) {
|
|
var url = $(".swimlanes-table").data("save-position-url");
|
|
var self = this;
|
|
|
|
this.app.showLoadingIcon();
|
|
|
|
$.ajax({
|
|
cache: false,
|
|
url: url,
|
|
contentType: "application/json",
|
|
type: "POST",
|
|
processData: false,
|
|
data: JSON.stringify({
|
|
"swimlane_id": swimlaneId,
|
|
"position": position
|
|
}),
|
|
complete: function() {
|
|
self.app.hideLoadingIcon();
|
|
}
|
|
});
|
|
};
|
|
|
|
Kanboard.Task = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.Task.prototype.keyboardShortcuts = function() {
|
|
var taskView = $("#task-view");
|
|
var self = this;
|
|
|
|
if (this.app.hasId("task-view")) {
|
|
Mousetrap.bind("e", function() {
|
|
self.app.get("Popover").open(taskView.data("edit-url"));
|
|
});
|
|
|
|
Mousetrap.bind("d", function() {
|
|
self.app.get("Popover").open(taskView.data("description-url"));
|
|
});
|
|
|
|
Mousetrap.bind("c", function() {
|
|
self.app.get("Popover").open(taskView.data("comment-url"));
|
|
});
|
|
|
|
Mousetrap.bind("s", function() {
|
|
self.app.get("Popover").open(taskView.data("subtask-url"));
|
|
});
|
|
|
|
Mousetrap.bind("l", function() {
|
|
self.app.get("Popover").open(taskView.data("internal-link-url"));
|
|
});
|
|
}
|
|
};
|
|
|
|
Kanboard.Task.prototype.onPopoverOpened = function() {
|
|
var self = this;
|
|
var reloadingProjectId = 0;
|
|
|
|
// Change color
|
|
$(document).on("click", ".color-square", function() {
|
|
$(".color-square-selected").removeClass("color-square-selected");
|
|
$(this).addClass("color-square-selected");
|
|
$("#form-color_id").val($(this).data("color-id"));
|
|
});
|
|
|
|
// Assign to me
|
|
$(document).on("click", ".assign-me", function(e) {
|
|
var currentId = $(this).data("current-id");
|
|
var dropdownId = "#" + $(this).data("target-id");
|
|
|
|
e.preventDefault();
|
|
|
|
if ($(dropdownId + ' option[value=' + currentId + ']').length) {
|
|
$(dropdownId).val(currentId);
|
|
}
|
|
});
|
|
|
|
// Reload page when a destination project is changed
|
|
$(document).on("change", "select.task-reload-project-destination", function() {
|
|
if (reloadingProjectId > 0) {
|
|
$(this).val(reloadingProjectId);
|
|
}
|
|
else {
|
|
reloadingProjectId = $(this).val();
|
|
var url = $(this).data("redirect").replace(/PROJECT_ID/g, reloadingProjectId);
|
|
|
|
$(".loading-icon").show();
|
|
|
|
$.ajax({
|
|
type: "GET",
|
|
url: url,
|
|
success: function(data, textStatus, request) {
|
|
reloadingProjectId = 0;
|
|
$(".loading-icon").hide();
|
|
self.app.get("Popover").ajaxReload(data, request, self.app.get("Popover"));
|
|
}
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
Kanboard.TaskRepartitionChart = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.TaskRepartitionChart.prototype.execute = function() {
|
|
if (this.app.hasId("analytic-task-repartition")) {
|
|
this.show();
|
|
}
|
|
};
|
|
|
|
Kanboard.TaskRepartitionChart.prototype.show = function() {
|
|
var metrics = $("#chart").data("metrics");
|
|
var columns = [];
|
|
|
|
for (var i = 0; i < metrics.length; i++) {
|
|
columns.push([metrics[i].column_title, metrics[i].nb_tasks]);
|
|
}
|
|
|
|
c3.generate({
|
|
data: {
|
|
columns: columns,
|
|
type : 'donut'
|
|
}
|
|
});
|
|
};
|
|
|
|
Kanboard.TaskTimeColumnChart = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.TaskTimeColumnChart.prototype.execute = function() {
|
|
if (this.app.hasId("analytic-task-time-column")) {
|
|
this.show();
|
|
}
|
|
};
|
|
|
|
Kanboard.TaskTimeColumnChart.prototype.show = function() {
|
|
var chart = $("#chart");
|
|
var metrics = chart.data("metrics");
|
|
var plots = [chart.data("label")];
|
|
var categories = [];
|
|
|
|
for (var i = 0; i < metrics.length; i++) {
|
|
plots.push(metrics[i].time_spent);
|
|
categories.push(metrics[i].title);
|
|
}
|
|
|
|
c3.generate({
|
|
data: {
|
|
columns: [plots],
|
|
type: 'bar'
|
|
},
|
|
bar: {
|
|
width: {
|
|
ratio: 0.5
|
|
}
|
|
},
|
|
axis: {
|
|
x: {
|
|
type: 'category',
|
|
categories: categories
|
|
},
|
|
y: {
|
|
tick: {
|
|
format: this.app.formatDuration
|
|
}
|
|
}
|
|
},
|
|
legend: {
|
|
show: false
|
|
}
|
|
});
|
|
};
|
|
|
|
Kanboard.Tooltip = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.Tooltip.prototype.onBoardRendered = function() {
|
|
this.execute();
|
|
};
|
|
|
|
Kanboard.Tooltip.prototype.execute = function() {
|
|
$(".tooltip").tooltip({
|
|
track: false,
|
|
show: false,
|
|
hide: false,
|
|
position: {
|
|
my: 'left-20 top',
|
|
at: 'center bottom+9',
|
|
using: function(position, feedback) {
|
|
|
|
$(this).css(position);
|
|
|
|
var arrow_pos = feedback.target.left + feedback.target.width / 2 - feedback.element.left - 20;
|
|
|
|
$("<div>")
|
|
.addClass("tooltip-arrow")
|
|
.addClass(feedback.vertical)
|
|
.addClass(arrow_pos < 1 ? "align-left" : "align-right")
|
|
.appendTo(this);
|
|
}
|
|
},
|
|
content: function() {
|
|
var _this = this;
|
|
var href = $(this).attr('data-href');
|
|
|
|
if (! href) {
|
|
return '<div class="markdown">' + $(this).attr("title") + '</div>';
|
|
}
|
|
|
|
$.get(href, function setTooltipContent(data) {
|
|
var tooltip = $('.ui-tooltip:visible');
|
|
|
|
$('.ui-tooltip-content:visible').html(data);
|
|
|
|
// Clear previous position, it interferes with the updated position computation
|
|
tooltip.css({ top: '', left: '' });
|
|
|
|
// Remove arrow, it will be added when repositionning
|
|
tooltip.children('.tooltip-arrow').remove();
|
|
|
|
// Reposition the tooltip
|
|
var position = $(_this).tooltip("option", "position");
|
|
position.of = $(_this);
|
|
tooltip.position(position);
|
|
});
|
|
|
|
return '<i class="fa fa-spinner fa-spin"></i>';
|
|
}
|
|
})
|
|
.on("mouseenter", function() {
|
|
var _this = this;
|
|
$(this).tooltip("open");
|
|
|
|
$(".ui-tooltip").on("mouseleave", function() {
|
|
$(_this).tooltip('close');
|
|
});
|
|
}).on("mouseleave focusout", function(e) {
|
|
e.stopImmediatePropagation();
|
|
var _this = this;
|
|
|
|
setTimeout(function() {
|
|
if (! $(".ui-tooltip:hover").length) {
|
|
$(_this).tooltip("close");
|
|
}
|
|
}, 100);
|
|
});
|
|
};
|
|
|
|
Kanboard.UserRepartitionChart = function(app) {
|
|
this.app = app;
|
|
};
|
|
|
|
Kanboard.UserRepartitionChart.prototype.execute = function() {
|
|
if (this.app.hasId("analytic-user-repartition")) {
|
|
this.show();
|
|
}
|
|
};
|
|
|
|
Kanboard.UserRepartitionChart.prototype.show = function() {
|
|
var metrics = $("#chart").data("metrics");
|
|
var columns = [];
|
|
|
|
for (var i = 0; i < metrics.length; i++) {
|
|
columns.push([metrics[i].user, metrics[i].nb_tasks]);
|
|
}
|
|
|
|
c3.generate({
|
|
data: {
|
|
columns: columns,
|
|
type : 'donut'
|
|
}
|
|
});
|
|
};
|
|
|
|
jQuery(document).ready(function() {
|
|
var app = new Kanboard.App();
|
|
app.execute();
|
|
});
|