summaryrefslogtreecommitdiff
path: root/assets/js/src
diff options
context:
space:
mode:
Diffstat (limited to 'assets/js/src')
-rw-r--r--assets/js/src/analytic.js159
-rw-r--r--assets/js/src/base.js182
-rw-r--r--assets/js/src/board.js256
-rw-r--r--assets/js/src/calendar.js103
-rw-r--r--assets/js/src/init.js18
-rw-r--r--assets/js/src/task.js13
6 files changed, 731 insertions, 0 deletions
diff --git a/assets/js/src/analytic.js b/assets/js/src/analytic.js
new file mode 100644
index 00000000..14cbc815
--- /dev/null
+++ b/assets/js/src/analytic.js
@@ -0,0 +1,159 @@
+
+Kanboard.Analytic = (function() {
+
+ return {
+ Init: function() {
+
+ if (Kanboard.Exists("analytic-task-repartition")) {
+ Kanboard.Analytic.TaskRepartition.Init();
+ }
+ else if (Kanboard.Exists("analytic-user-repartition")) {
+ Kanboard.Analytic.UserRepartition.Init();
+ }
+ else if (Kanboard.Exists("analytic-cfd")) {
+ Kanboard.Analytic.CFD.Init();
+ }
+ }
+ };
+
+})();
+
+Kanboard.Analytic.CFD = (function() {
+
+ function fetchData()
+ {
+ jQuery.getJSON($("#chart").attr("data-url"), function(data) {
+ drawGraph(data.metrics, data.labels, data.columns);
+ });
+ }
+
+ function drawGraph(metrics, labels, columns)
+ {
+ var series = prepareSeries(metrics, labels);
+
+ var svg = dimple.newSvg("#chart", "100%", 380);
+ var chart = new dimple.chart(svg, series);
+
+ var x = chart.addCategoryAxis("x", labels['day']);
+ x.addOrderRule("Date");
+
+ chart.addMeasureAxis("y", labels['total']);
+
+ var s = chart.addSeries(labels['column'], dimple.plot.area);
+ s.addOrderRule(columns.reverse());
+
+ chart.addLegend(10, 10, 500, 30, "left");
+ chart.draw();
+ }
+
+ function prepareSeries(metrics, labels)
+ {
+ var series = [];
+
+ for (var i = 0; i < metrics.length; i++) {
+
+ var row = {};
+ row[labels['column']] = metrics[i]['column_title'];
+ row[labels['day']] = metrics[i]['day'];
+ row[labels['total']] = metrics[i]['total'];
+ series.push(row);
+ }
+
+ return series;
+ }
+
+ return {
+ Init: fetchData
+ };
+
+})();
+
+Kanboard.Analytic.TaskRepartition = (function() {
+
+ function fetchData()
+ {
+ jQuery.getJSON($("#chart").attr("data-url"), function(data) {
+ drawGraph(data.metrics, data.labels);
+ });
+ }
+
+ function drawGraph(metrics, labels)
+ {
+ var series = prepareSeries(metrics, labels);
+
+ var svg = dimple.newSvg("#chart", "100%", 350);
+
+ var chart = new dimple.chart(svg, series);
+ chart.addMeasureAxis("p", labels["nb_tasks"]);
+ var ring = chart.addSeries(labels["column_title"], dimple.plot.pie);
+ ring.innerRadius = "50%";
+ chart.addLegend(0, 0, 100, "100%", "left");
+ chart.draw();
+ }
+
+ function prepareSeries(metrics, labels)
+ {
+ var series = [];
+
+ for (var i = 0; i < metrics.length; i++) {
+
+ var serie = {};
+ serie[labels["nb_tasks"]] = metrics[i]["nb_tasks"];
+ serie[labels["column_title"]] = metrics[i]["column_title"];
+
+ series.push(serie);
+ }
+
+ return series;
+ }
+
+ return {
+ Init: fetchData
+ };
+
+})();
+
+Kanboard.Analytic.UserRepartition = (function() {
+
+ function fetchData()
+ {
+ jQuery.getJSON($("#chart").attr("data-url"), function(data) {
+ drawGraph(data.metrics, data.labels);
+ });
+ }
+
+ function drawGraph(metrics, labels)
+ {
+ var series = prepareSeries(metrics, labels);
+
+ var svg = dimple.newSvg("#chart", "100%", 350);
+
+ var chart = new dimple.chart(svg, series);
+ chart.addMeasureAxis("p", labels["nb_tasks"]);
+ var ring = chart.addSeries(labels["user"], dimple.plot.pie);
+ ring.innerRadius = "50%";
+ chart.addLegend(0, 0, 100, "100%", "left");
+ chart.draw();
+ }
+
+ function prepareSeries(metrics, labels)
+ {
+ var series = [];
+
+ for (var i = 0; i < metrics.length; i++) {
+
+ var serie = {};
+ serie[labels["nb_tasks"]] = metrics[i]["nb_tasks"];
+ serie[labels["user"]] = metrics[i]["user"];
+
+ series.push(serie);
+ }
+
+ return series;
+ }
+
+ return {
+ Init: fetchData
+ };
+
+})();
diff --git a/assets/js/src/base.js b/assets/js/src/base.js
new file mode 100644
index 00000000..75d23b88
--- /dev/null
+++ b/assets/js/src/base.js
@@ -0,0 +1,182 @@
+// Common functions
+var Kanboard = (function() {
+
+ return {
+
+ // Return true if the element#id exists
+ Exists: function(id) {
+ if (document.getElementById(id)) {
+ return true;
+ }
+
+ return false;
+ },
+
+ // Display a popup
+ Popover: function(e, callback) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ var link = e.target.getAttribute("href");
+
+ if (! link) {
+ link = e.target.getAttribute("data-href");
+ }
+
+ if (link) {
+ $.get(link, function(content) {
+
+ $("body").append('<div id="popover-container"><div id="popover-content">' + content + '</div></div>');
+
+ $("#popover-container").click(function() {
+ $(this).remove();
+ });
+
+ $("#popover-content").click(function(e) {
+ e.stopPropagation();
+ });
+
+ if (callback) {
+ callback();
+ }
+ });
+ }
+ },
+
+ // Return true if the page is visible
+ 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;
+ },
+
+ // Save preferences in local storage
+ SetStorageItem: function(key, value) {
+ if (typeof(Storage) !== "undefined") {
+ localStorage.setItem(key, value);
+ }
+ },
+
+ GetStorageItem: function(key) {
+
+ if (typeof(Storage) !== "undefined") {
+ return localStorage.getItem(key);
+ }
+
+ return '';
+ },
+
+ // Generate Markdown preview
+ MarkdownPreview: function(e) {
+
+ e.preventDefault();
+
+ var link = $(this);
+ var nav = $(this).closest("ul");
+ var write = $(".write-area");
+ var preview = $(".preview-area");
+ var textarea = $("textarea");
+
+ var request = $.ajax({
+ url: "?controller=app&action=preview",
+ contentType: "application/json",
+ type: "POST",
+ processData: false,
+ dataType: "html",
+ data: JSON.stringify({
+ "text": textarea.val()
+ })
+ });
+
+ request.done(function(data) {
+
+ nav.find("li").removeClass("form-tab-selected");
+ link.parent().addClass("form-tab-selected");
+
+ preview.find(".markdown").html(data)
+ preview.css("height", textarea.css("height"));
+ preview.css("width", textarea.css("width"));
+
+ write.hide();
+ preview.show();
+ });
+ },
+
+ // Show the Markdown textarea
+ MarkdownWriter: function(e) {
+
+ e.preventDefault();
+
+ $(this).closest("ul").find("li").removeClass("form-tab-selected")
+ $(this).parent().addClass("form-tab-selected");
+
+ $(".write-area").show();
+ $(".preview-area").hide();
+ },
+
+ // Check session and redirect to the login page if not logged
+ CheckSession: function() {
+
+ if (! $(".form-login").length) {
+ $.ajax({
+ cache: false,
+ url: $("body").data("status-url"),
+ statusCode: {
+ 401: function(data) {
+ window.location = $("body").data("login-url");
+ }
+ }
+ });
+ }
+ },
+
+ // Common init
+ Init: function() {
+
+ // Datepicker
+ $(".form-date").datepicker({
+ showOtherMonths: true,
+ selectOtherMonths: true,
+ dateFormat: 'yy-mm-dd',
+ constrainInput: false
+ });
+
+ // Project select box
+ $("#board-selector").chosen({
+ width: 180
+ });
+
+ $("#board-selector").change(function() {
+ window.location = $(this).attr("data-board-url").replace(/%d/g, $(this).val());
+ });
+
+ // Markdown Preview for textareas
+ $("#markdown-preview").click(Kanboard.MarkdownPreview);
+ $("#markdown-write").click(Kanboard.MarkdownWriter);
+
+ // Check the session every 60s
+ window.setInterval(Kanboard.CheckSession, 60000);
+
+ // Auto-select input fields
+ $(".auto-select").focus(function() {
+ $(this).select();
+ });
+ }
+ };
+
+})();
diff --git a/assets/js/src/board.js b/assets/js/src/board.js
new file mode 100644
index 00000000..98365486
--- /dev/null
+++ b/assets/js/src/board.js
@@ -0,0 +1,256 @@
+Kanboard.Board = (function() {
+
+ var checkInterval = null;
+
+ function on_popover(e)
+ {
+ Kanboard.Popover(e, Kanboard.Init);
+ }
+
+ // Setup the board
+ function board_load_events()
+ {
+ // Drag and drop
+ $(".column").sortable({
+ delay: 300,
+ distance: 5,
+ connectWith: ".column",
+ placeholder: "draggable-placeholder",
+ stop: function(event, ui) {
+ board_save(
+ ui.item.attr('data-task-id'),
+ ui.item.parent().attr("data-column-id"),
+ ui.item.index() + 1,
+ ui.item.parent().attr('data-swimlane-id')
+ );
+ }
+ });
+
+ // Assignee change
+ $(".assignee-popover").click(Kanboard.Popover);
+
+ // Category change
+ $(".category-popover").click(Kanboard.Popover);
+
+ // Task edit popover
+ $(".task-edit-popover").click(on_popover);
+ $(".task-creation-popover").click(on_popover);
+
+ // Description popover
+ $(".task-description-popover").click(on_popover);
+
+ // Tooltips
+ $(".task-board-tooltip").tooltip({
+ track: 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 == 0 ? "align-left" : "align-right")
+ .appendTo(this);
+ }
+ },
+ content: function(e) {
+ var href = $(this).attr('data-href');
+
+ if (! href) {
+ return;
+ }
+
+ var _this = this;
+ $.get(href, function setTooltipContent(data) {
+
+ $('.ui-tooltip-content:visible').html(data);
+
+ var tooltip = $('.ui-tooltip:visible');
+
+ // 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);
+
+ // Toggle subtasks status
+ $('#tooltip-subtasks a').click(function(e) {
+
+ e.preventDefault();
+ e.stopPropagation();
+
+ $.get($(this).attr('href'), setTooltipContent);
+ });
+ });
+
+ return '<i class="fa fa-refresh fa-spin fa-2x"></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);
+ });
+
+ // Redirect to the task details page
+ $("[data-task-url]").each(function() {
+ $(this).click(function() {
+ window.location = $(this).attr("data-task-url");
+ });
+ });
+
+ // Automatic refresh
+ var interval = parseInt($("#board").attr("data-check-interval"));
+
+ if (interval > 0) {
+ checkInterval = window.setInterval(board_check, interval * 1000);
+ }
+ }
+
+ // Stop events
+ function board_unload_events()
+ {
+ $("[data-task-url]").off();
+ clearInterval(checkInterval);
+ }
+
+ // Save and refresh the board
+ function board_save(taskId, columnId, position, swimlaneId)
+ {
+ board_unload_events();
+
+ $.ajax({
+ cache: false,
+ url: $("#board").attr("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) {
+ $("#board").remove();
+ $("#main").append(data);
+ board_load_events();
+ filter_apply();
+ }
+ });
+ }
+
+ // Check if a board have been changed by someone else
+ function board_check()
+ {
+ if (Kanboard.IsVisible()) {
+ $.ajax({
+ cache: false,
+ url: $("#board").attr("data-check-url"),
+ statusCode: {
+ 200: function(data) {
+ $("#board").remove();
+ $("#main").append(data);
+ board_unload_events();
+ board_load_events();
+ filter_apply();
+ }
+ }
+ });
+ }
+ }
+
+ // Apply user or date filter (change tasks opacity)
+ function filter_apply()
+ {
+ var selectedUserId = $("#form-user_id").val();
+ var selectedCategoryId = $("#form-category_id").val();
+ var filterDueDate = $("#filter-due-date").hasClass("filter-on");
+ var projectId = $('#board').data('project-id');
+
+ $("[data-task-id]").each(function(index, item) {
+
+ var ownerId = item.getAttribute("data-owner-id");
+ var dueDate = item.getAttribute("data-due-date");
+ var categoryId = item.getAttribute("data-category-id");
+
+ if (ownerId != selectedUserId && selectedUserId != -1) {
+ item.style.opacity = "0.2";
+ }
+ else {
+ item.style.opacity = "1.0";
+ }
+
+ if (filterDueDate && (dueDate == "" || dueDate == "0")) {
+ item.style.opacity = "0.2";
+ }
+
+ if (categoryId != selectedCategoryId && selectedCategoryId != -1) {
+ item.style.opacity = "0.2";
+ }
+ });
+
+ // Save filter settings
+ Kanboard.SetStorageItem("board_filter_" + projectId + "_form-user_id", selectedUserId);
+ Kanboard.SetStorageItem("board_filter_" + projectId + "_form-category_id", selectedCategoryId);
+ Kanboard.SetStorageItem("board_filter_" + projectId + "_filter-due-date", ~~(filterDueDate));
+ }
+
+ // Load filter events
+ function filter_load_events()
+ {
+ var projectId = $('#board').data('project-id');
+
+ $("#form-user_id").change(filter_apply);
+ $("#form-category_id").change(filter_apply);
+ $("#filter-due-date").click(function(e) {
+ $(this).toggleClass("filter-on");
+ filter_apply();
+ e.preventDefault();
+ });
+
+ // Get and set filters from localStorage
+ $("#form-user_id").val(Kanboard.GetStorageItem("board_filter_" + projectId + "_form-user_id") || -1);
+ $("#form-category_id").val(Kanboard.GetStorageItem("board_filter_" + projectId + "_form-category_id") || -1);
+
+ if (+Kanboard.GetStorageItem("board_filter_" + projectId + "_filter-due-date")) {
+ $("#filter-due-date").addClass("filter-on");
+ } else {
+ $("#filter-due-date").removeClass("filter-on");
+ }
+
+ filter_apply();
+ }
+
+ return {
+ Init: function() {
+ board_load_events();
+ filter_load_events();
+ }
+ };
+
+})();
diff --git a/assets/js/src/calendar.js b/assets/js/src/calendar.js
new file mode 100644
index 00000000..ca76377e
--- /dev/null
+++ b/assets/js/src/calendar.js
@@ -0,0 +1,103 @@
+Kanboard.Calendar = (function() {
+
+ // Show the empty calendar
+ function show_calendar()
+ {
+ var calendar = $("#calendar");
+ var translations = calendar.data("translations");
+
+ calendar.fullCalendar({
+ editable: true,
+ eventLimit: true,
+ header: {
+ left: 'prev,next today',
+ center: 'title',
+ right: ''
+ },
+ eventDrop: move_calendar_event,
+ monthNames: [translations.January, translations.February, translations.March, translations.April, translations.May, translations.June, translations.July, translations.August, translations.September, translations.October, translations.November, translations.December],
+ monthNamesShort: [translations.Jan, translations.Feb, translations.Mar, translations.Apr, translations.May, translations.Jun, translations.Jul, translations.Aug, translations.Sep, translations.Oct, translations.Nov, translations.Dec],
+ buttonText: {today: translations.Today},
+ dayNames: [translations.Sunday, translations.Monday, translations.Tuesday, translations.Wednesday, translations.Thursday, translations.Friday, translations.Saturday],
+ dayNamesShort: [translations.Sun, translations.Mon, translations.Tue, translations.Wed, translations.Thu, translations.Fri, translations.Sat]
+ });
+ }
+
+ // Save the new due date for a moved task
+ function move_calendar_event(calendar_event)
+ {
+ $.ajax({
+ cache: false,
+ url: $("#calendar").data("save-url"),
+ contentType: "application/json",
+ type: "POST",
+ processData: false,
+ data: JSON.stringify({
+ "task_id": calendar_event.id,
+ "date_due": calendar_event.start.format()
+ })
+ });
+ }
+
+ // Refresh the calendar events
+ function refresh_calendar(filters)
+ {
+ var calendar = $("#calendar");
+ var url = calendar.data("check-url");
+ var params = {
+ "start": calendar.fullCalendar('getView').start.format(),
+ "end": calendar.fullCalendar('getView').end.format()
+ }
+
+ jQuery.extend(params, filters);
+
+ for (var key in params) {
+ url += "&" + key + "=" + params[key];
+ }
+
+ $.getJSON(url, function(events) {
+ calendar.fullCalendar('removeEvents');
+ calendar.fullCalendar('addEventSource', events);
+ calendar.fullCalendar('rerenderEvents');
+ });
+ }
+
+ // Restore saved filters
+ function load_filters()
+ {
+ var filters = Kanboard.GetStorageItem('calendar_filters');
+
+ if (filters !== "undefined" && filters !== "") {
+ filters = JSON.parse(filters);
+
+ for (var filter in filters) {
+ $("select[name=" + filter + "]").val(filters[filter]);
+ }
+ }
+
+ refresh_calendar(filters || {});
+
+ $('.calendar-filter').change(apply_filters);
+ }
+
+ // Apply filters on change
+ function apply_filters()
+ {
+ var filters = {};
+
+ $('.calendar-filter').each(function(index, element) {
+ filters[$(this).attr("name")] = $(this).val();
+ });
+
+ Kanboard.SetStorageItem("calendar_filters", JSON.stringify(filters));
+ refresh_calendar(filters);
+ }
+
+ return {
+ Init: function() {
+ show_calendar();
+ load_filters();
+ }
+ };
+
+})();
diff --git a/assets/js/src/init.js b/assets/js/src/init.js
new file mode 100644
index 00000000..e6d16085
--- /dev/null
+++ b/assets/js/src/init.js
@@ -0,0 +1,18 @@
+// Initialization
+$(function() {
+
+ Kanboard.Init();
+
+ if (Kanboard.Exists("board")) {
+ Kanboard.Board.Init();
+ }
+ else if (Kanboard.Exists("calendar")) {
+ Kanboard.Calendar.Init();
+ }
+ else if (Kanboard.Exists("task-section")) {
+ Kanboard.Task.Init();
+ }
+ else if (Kanboard.Exists("analytic-section")) {
+ Kanboard.Analytic.Init();
+ }
+}); \ No newline at end of file
diff --git a/assets/js/src/task.js b/assets/js/src/task.js
new file mode 100644
index 00000000..968f25db
--- /dev/null
+++ b/assets/js/src/task.js
@@ -0,0 +1,13 @@
+// Task related functions
+Kanboard.Task = (function() {
+
+ return {
+
+ Init: function() {
+
+ // Image preview for attachments
+ $(".file-popover").click(Kanboard.Popover);
+ }
+ };
+
+})();