diff options
Diffstat (limited to 'assets/js/src')
-rw-r--r-- | assets/js/src/analytic.js | 159 | ||||
-rw-r--r-- | assets/js/src/base.js | 182 | ||||
-rw-r--r-- | assets/js/src/board.js | 256 | ||||
-rw-r--r-- | assets/js/src/calendar.js | 103 | ||||
-rw-r--r-- | assets/js/src/init.js | 18 | ||||
-rw-r--r-- | assets/js/src/task.js | 13 |
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); + } + }; + +})(); |