diff options
Diffstat (limited to 'assets/js/src')
-rw-r--r-- | assets/js/src/App.js | 196 | ||||
-rw-r--r-- | assets/js/src/AvgTimeColumnChart.js | 39 | ||||
-rw-r--r-- | assets/js/src/BudgetChart.js | 55 | ||||
-rw-r--r-- | assets/js/src/BurndownChart.js | 48 | ||||
-rw-r--r-- | assets/js/src/CumulativeFlowDiagram.js | 48 | ||||
-rw-r--r-- | assets/js/src/LeadCycleTimeChart.js | 45 | ||||
-rw-r--r-- | assets/js/src/Markdown.js | 50 | ||||
-rw-r--r-- | assets/js/src/Popover.js | 50 | ||||
-rw-r--r-- | assets/js/src/Router.js | 34 | ||||
-rw-r--r-- | assets/js/src/Search.js | 61 | ||||
-rw-r--r-- | assets/js/src/Sidebar.js | 25 | ||||
-rw-r--r-- | assets/js/src/TaskRepartitionChart.js | 18 | ||||
-rw-r--r-- | assets/js/src/TaskTimeColumnChart.js | 39 | ||||
-rw-r--r-- | assets/js/src/Tooltip.js | 87 | ||||
-rw-r--r-- | assets/js/src/UserRepartitionChart.js | 18 | ||||
-rw-r--r-- | assets/js/src/analytic.js | 355 | ||||
-rw-r--r-- | assets/js/src/base.js | 394 | ||||
-rw-r--r-- | assets/js/src/board.js | 392 | ||||
-rw-r--r-- | assets/js/src/calendar.js | 86 | ||||
-rw-r--r-- | assets/js/src/screenshot.js | 204 | ||||
-rw-r--r-- | assets/js/src/swimlane.js | 111 |
21 files changed, 1133 insertions, 1222 deletions
diff --git a/assets/js/src/App.js b/assets/js/src/App.js new file mode 100644 index 00000000..2c3d10a7 --- /dev/null +++ b/assets/js/src/App.js @@ -0,0 +1,196 @@ +function App() { + this.popover = new Popover(this); + this.markdown = new Markdown(); + this.sidebar = new Sidebar(); + this.search = new Search(); + this.tooltip = new Tooltip(this); + this.swimlane = new Swimlane(); + this.keyboardShortcuts(); + this.boardSelector(); + this.listen(); + this.poll(); + + // Alert box fadeout + $(".alert-fade-out").delay(4000).fadeOut(800, function() { + $(this).remove(); + }); + + // Reload page when a destination project is changed + var reloading_project = false; + $("select.task-reload-project-destination").change(function() { + if (! reloading_project) { + $(".loading-icon").show(); + reloading_project = true; + window.location = $(this).data("redirect").replace(/PROJECT_ID/g, $(this).val()); + } + }); +} + +App.prototype.listen = function() { + $(document).off(); + + this.popover.listen(); + this.markdown.listen(); + this.sidebar.listen(); + this.tooltip.listen(); + this.search.listen(); + this.search.focus(); + this.taskAutoComplete(); + this.datePicker(); + this.focus(); + + // Dropdown + $(".dropit-submenu").hide(); + $('.dropdown').not(".dropit").dropit({ triggerParentEl : "span" }); +}; + +App.prototype.focus = function() { + + // Autofocus fields (html5 autofocus works only with page onload) + $("[autofocus]").each(function(index, element) { + $(this).focus(); + }) + + // Auto-select input fields + $(document).on('focus', '.auto-select', function() { + $(this).select(); + }); + + // Workaround for chrome + $(document).on('mouseup', '.auto-select', function(e) { + e.preventDefault(); + }); +}; + +App.prototype.poll = function() { + window.setInterval(this.checkSession, 60000); +}; + +App.prototype.keyboardShortcuts = function() { + // Submit form + Mousetrap.bindGlobal("mod+enter", function() { + $("form").submit(); + }); + + // Open board selector + Mousetrap.bind("b", function(e) { + e.preventDefault(); + $('#board-selector').trigger('chosen:open'); + }); +}; + +App.prototype.checkSession = function() { + if (! $(".form-login").length) { + $.ajax({ + cache: false, + url: $("body").data("status-url"), + statusCode: { + 401: function() { + window.location = $("body").data("login-url"); + } + } + }); + } +}; + +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 + }); +}; + +App.prototype.taskAutoComplete = function() { + // Task auto-completion + if ($(".task-autocomplete").length) { + + if ($('.opposite_task_id').val() == '') { + $(".task-autocomplete").parent().find("input[type=submit]").attr('disabled','disabled'); + } + + $(".task-autocomplete").autocomplete({ + source: $(".task-autocomplete").data("search-url"), + minLength: 1, + select: function(event, ui) { + var field = $(".task-autocomplete").data("dst-field"); + $("input[name=" + field + "]").val(ui.item.id); + + $(".task-autocomplete").parent().find("input[type=submit]").removeAttr('disabled'); + } + }); + } +}; + +App.prototype.boardSelector = function() { + $(".chosen-select").chosen({ + width: "200px", + no_results_text: $(".chosen-select").data("notfound"), + disable_search_threshold: 10 + }); + + $("#board-selector").chosen({ + width: 180, + no_results_text: $("#board-selector").data("notfound") + }); + + $("#board-selector").change(function() { + window.location = $(this).attr("data-board-url").replace(/PROJECT_ID/g, $(this).val()); + }); +}; + +App.prototype.showLoadingIcon = function() { + $("body").append('<span id="app-loading-icon"> <i class="fa fa-spinner fa-spin"></i></span>'); +}; + +App.prototype.hideLoadingIcon = function() { + $("#app-loading-icon").remove(); +}; + +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; +}; + +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"; +}; diff --git a/assets/js/src/AvgTimeColumnChart.js b/assets/js/src/AvgTimeColumnChart.js new file mode 100644 index 00000000..f8b7d2ee --- /dev/null +++ b/assets/js/src/AvgTimeColumnChart.js @@ -0,0 +1,39 @@ +function AvgTimeColumnChart() { +} + +AvgTimeColumnChart.prototype.execute = function(app) { + 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: app.formatDuration + } + } + }, + legend: { + show: false + } + }); +}; diff --git a/assets/js/src/BudgetChart.js b/assets/js/src/BudgetChart.js new file mode 100644 index 00000000..9ab0d5a9 --- /dev/null +++ b/assets/js/src/BudgetChart.js @@ -0,0 +1,55 @@ +function BudgetChart() { +} + +BudgetChart.prototype.execute = function() { + var categories = []; + var metrics = $("#chart").data("metrics"); + var labels = $("#chart").data("labels"); + var inputFormat = d3.time.format("%Y-%m-%d"); + var outputFormat = d3.time.format($("#chart").data("date-format")); + + var columns = [ + [labels["in"]], + [labels["left"]], + [labels["out"]] + ]; + + var colors = {}; + colors[labels["in"]] = '#5858FA'; + colors[labels["left"]] = '#04B404'; + colors[labels["out"]] = '#DF3A01'; + + for (var i = 0; i < metrics.length; i++) { + categories.push(outputFormat(inputFormat.parse(metrics[i]["date"]))); + columns[0].push(metrics[i]["in"]); + columns[1].push(metrics[i]["left"]); + columns[2].push(metrics[i]["out"]); + } + + c3.generate({ + data: { + columns: columns, + colors: colors, + type : 'bar' + }, + bar: { + width: { + ratio: 0.25 + } + }, + grid: { + x: { + show: true + }, + y: { + show: true + } + }, + axis: { + x: { + type: 'category', + categories: categories + } + } + }); +}; diff --git a/assets/js/src/BurndownChart.js b/assets/js/src/BurndownChart.js new file mode 100644 index 00000000..79199b67 --- /dev/null +++ b/assets/js/src/BurndownChart.js @@ -0,0 +1,48 @@ +function BurndownChart() { +} + +BurndownChart.prototype.execute = function() { + 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 + } + } + }); +}; diff --git a/assets/js/src/CumulativeFlowDiagram.js b/assets/js/src/CumulativeFlowDiagram.js new file mode 100644 index 00000000..61a0847b --- /dev/null +++ b/assets/js/src/CumulativeFlowDiagram.js @@ -0,0 +1,48 @@ +function CumulativeFlowDiagram() { +} + +CumulativeFlowDiagram.prototype.execute = function() { + + 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 + } + } + }); +}; diff --git a/assets/js/src/LeadCycleTimeChart.js b/assets/js/src/LeadCycleTimeChart.js new file mode 100644 index 00000000..501ed892 --- /dev/null +++ b/assets/js/src/LeadCycleTimeChart.js @@ -0,0 +1,45 @@ +function LeadCycleTimeChart() { +} + +LeadCycleTimeChart.prototype.execute = function(app) { + 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: app.formatDuration + } + } + } + }); +}; diff --git a/assets/js/src/Markdown.js b/assets/js/src/Markdown.js new file mode 100644 index 00000000..3a51ffce --- /dev/null +++ b/assets/js/src/Markdown.js @@ -0,0 +1,50 @@ +function Markdown() { +} + +Markdown.prototype.showPreview = 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", // TODO: remoe harcoded url + 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(); + }); +}; + +Markdown.prototype.showWriter = 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(); +}; + +Markdown.prototype.listen = function() { + $(document).on("click", "#markdown-preview", this.showPreview.bind(this)); + $(document).on("click", "#markdown-write", this.showWriter.bind(this)); +}; diff --git a/assets/js/src/Popover.js b/assets/js/src/Popover.js new file mode 100644 index 00000000..217ae55e --- /dev/null +++ b/assets/js/src/Popover.js @@ -0,0 +1,50 @@ +function Popover(app) { + this.app = app; + this.router = new Router(); + this.router.addRoute('screenshot-zone', Screenshot); + Mousetrap.bindGlobal("esc", this.close); +} + +Popover.prototype.isOpen = function() { + return $('#popover-container').size() > 0; +}; + +Popover.prototype.open = function(link) { + var self = this; + + $.get(link, function(content) { + $("body").append('<div id="popover-container"><div id="popover-content">' + content + '</div></div>'); + self.router.dispatch(); + self.app.listen(); + }); +}; + +Popover.prototype.close = function(e) { + if (e) { + e.preventDefault(); + } + + $('#popover-container').remove(); +}; + +Popover.prototype.onClick = function(e) { + e.preventDefault(); + e.stopPropagation(); + + var link = e.target.getAttribute("href"); + + if (! link) { + link = e.target.getAttribute("data-href"); + } + + if (link) { + this.open(link); + } +}; + +Popover.prototype.listen = function() { + $(document).on("click", ".popover", this.onClick.bind(this)); + $(document).on("click", ".close-popover", this.close.bind(this)); + $(document).on("click", "#popover-container", this.close.bind(this)); + $(document).on("click", "#popover-content", function(e) { e.stopPropagation(); }); +}; diff --git a/assets/js/src/Router.js b/assets/js/src/Router.js new file mode 100644 index 00000000..d1e5de2a --- /dev/null +++ b/assets/js/src/Router.js @@ -0,0 +1,34 @@ +function Router() { + this.routes = {}; +} + +Router.prototype.addRoute = function(id, controller) { + this.routes[id] = controller; +}; + +Router.prototype.dispatch = function(app) { + for (var id in this.routes) { + if (document.getElementById(id)) { + var controller = Object.create(this.routes[id].prototype); + controller.execute(app); + break; + } + } +}; + +jQuery(document).ready(function() { + var app = new App(); + var router = new Router(); + router.addRoute('board', Board); + router.addRoute('calendar', Calendar); + router.addRoute('screenshot-zone', Screenshot); + router.addRoute('analytic-task-repartition', TaskRepartitionChart); + router.addRoute('analytic-user-repartition', UserRepartitionChart); + router.addRoute('analytic-cfd', CumulativeFlowDiagram); + router.addRoute('analytic-burndown', BurndownChart); + router.addRoute('budget-chart', BudgetChart); + router.addRoute('analytic-avg-time-column', AvgTimeColumnChart); + router.addRoute('analytic-task-time-column', TaskTimeColumnChart); + router.addRoute('analytic-lead-cycle-time', LeadCycleTimeChart); + router.dispatch(app); +}); diff --git a/assets/js/src/Search.js b/assets/js/src/Search.js new file mode 100644 index 00000000..d38ffd2b --- /dev/null +++ b/assets/js/src/Search.js @@ -0,0 +1,61 @@ +function Search() { + this.keyboardShortcuts(); +} + +Search.prototype.focus = function() { + // Place cursor at the end when focusing on the search box + $(document).on("focus", "#form-search", function() { + if ($("#form-search")[0].setSelectionRange) { + $('#form-search')[0].setSelectionRange($('#form-search').val().length, $('#form-search').val().length); + } + }); +}; + +Search.prototype.listen = function() { + // Filter helper for search + $(document).on("click", ".filter-helper", function (e) { + e.preventDefault(); + $("#form-search").val($(this).data("filter")); + $("form.search").submit(); + }); +}; + +Search.prototype.keyboardShortcuts = function() { + // Switch view mode for projects: go to the board + Mousetrap.bind("v b", function(e) { + var link = $(".view-board"); + + if (link.length) { + window.location = link.attr('href'); + } + }); + + // Switch view mode for projects: go to the calendar + Mousetrap.bind("v c", function(e) { + var link = $(".view-calendar"); + + if (link.length) { + window.location = link.attr('href'); + } + }); + + // Switch view mode for projects: go to the listing + Mousetrap.bind("v l", function(e) { + var link = $(".view-listing"); + + if (link.length) { + window.location = link.attr('href'); + } + }); + + // Focus to the search field + Mousetrap.bind("f", function(e) { + e.preventDefault(); + var input = document.getElementById("form-search"); + + if (input) { + input.focus(); + } + }); +}; + diff --git a/assets/js/src/Sidebar.js b/assets/js/src/Sidebar.js new file mode 100644 index 00000000..0794d6b3 --- /dev/null +++ b/assets/js/src/Sidebar.js @@ -0,0 +1,25 @@ +function Sidebar() { +} + +Sidebar.prototype.expand = function(e) { + e.preventDefault(); + $(".sidebar-container").removeClass("sidebar-collapsed"); + $(".sidebar-collapse").show(); + $(".sidebar h2").show(); + $(".sidebar ul").show(); + $(".sidebar-expand").hide(); +}; + +Sidebar.prototype.collapse = function(e) { + e.preventDefault(); + $(".sidebar-container").addClass("sidebar-collapsed"); + $(".sidebar-expand").show(); + $(".sidebar h2").hide(); + $(".sidebar ul").hide(); + $(".sidebar-collapse").hide(); +}; + +Sidebar.prototype.listen = function() { + $(document).on("click", ".sidebar-collapse", this.collapse); + $(document).on("click", ".sidebar-expand", this.expand); +}; diff --git a/assets/js/src/TaskRepartitionChart.js b/assets/js/src/TaskRepartitionChart.js new file mode 100644 index 00000000..1b1368dc --- /dev/null +++ b/assets/js/src/TaskRepartitionChart.js @@ -0,0 +1,18 @@ +function TaskRepartitionChart() { +} + +TaskRepartitionChart.prototype.execute = 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' + } + }); +}; diff --git a/assets/js/src/TaskTimeColumnChart.js b/assets/js/src/TaskTimeColumnChart.js new file mode 100644 index 00000000..a95424a0 --- /dev/null +++ b/assets/js/src/TaskTimeColumnChart.js @@ -0,0 +1,39 @@ +function TaskTimeColumnChart() { +} + +TaskTimeColumnChart.prototype.execute = function(app) { + 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: app.formatDuration + } + } + }, + legend: { + show: false + } + }); +}; diff --git a/assets/js/src/Tooltip.js b/assets/js/src/Tooltip.js new file mode 100644 index 00000000..48102da6 --- /dev/null +++ b/assets/js/src/Tooltip.js @@ -0,0 +1,87 @@ +function Tooltip(app) { + this.app = app; +} + +Tooltip.prototype.listen = function() { + var self = this; + + $(".tooltip").tooltip({ + track: false, + show: 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); + + // Toggle subtasks status + $('#tooltip-subtasks a').not(".popover").click(function(e) { + + e.preventDefault(); + e.stopPropagation(); + + if ($(this).hasClass("popover-subtask-restriction")) { + self.app.popover.open($(this).attr('href')); + $(_this).tooltip('close'); + } + else { + $.get($(this).attr('href'), setTooltipContent); + } + }); + }); + + 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); + }); +};
\ No newline at end of file diff --git a/assets/js/src/UserRepartitionChart.js b/assets/js/src/UserRepartitionChart.js new file mode 100644 index 00000000..4255130a --- /dev/null +++ b/assets/js/src/UserRepartitionChart.js @@ -0,0 +1,18 @@ +function UserRepartitionChart() { +} + +UserRepartitionChart.prototype.execute = 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' + } + }); +}; diff --git a/assets/js/src/analytic.js b/assets/js/src/analytic.js deleted file mode 100644 index a5df0ac6..00000000 --- a/assets/js/src/analytic.js +++ /dev/null @@ -1,355 +0,0 @@ -(function() { - - // CFD diagram - function drawCfd() - { - 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 - } - } - }); - } - - // Burndown chart - function drawBurndown() - { - 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 - } - } - }); - } - - // Draw task repartition chart - function drawTaskRepartition() - { - 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' - } - }); - } - - // Draw user repartition chart - function drawUserRepartition() - { - 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' - } - }); - } - - // Draw budget chart - function drawBudget() - { - var categories = []; - var metrics = $("#chart").data("metrics"); - var labels = $("#chart").data("labels"); - var inputFormat = d3.time.format("%Y-%m-%d"); - var outputFormat = d3.time.format($("#chart").data("date-format")); - - var columns = [ - [labels["in"]], - [labels["left"]], - [labels["out"]] - ]; - - var colors = {}; - colors[labels["in"]] = '#5858FA'; - colors[labels["left"]] = '#04B404'; - colors[labels["out"]] = '#DF3A01'; - - for (var i = 0; i < metrics.length; i++) { - categories.push(outputFormat(inputFormat.parse(metrics[i]["date"]))); - columns[0].push(metrics[i]["in"]); - columns[1].push(metrics[i]["left"]); - columns[2].push(metrics[i]["out"]); - } - - c3.generate({ - data: { - columns: columns, - colors: colors, - type : 'bar' - }, - bar: { - width: { - ratio: 0.25 - } - }, - grid: { - x: { - show: true - }, - y: { - show: true - } - }, - axis: { - x: { - type: 'category', - categories: categories - } - } - }); - } - - // Draw chart for average time spent into each column - function drawAvgTimeColumn() - { - 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: formatDuration - } - } - }, - legend: { - show: false - } - }); - } - - // Draw chart for average time spent into each column - function drawTaskTimeColumn() - { - 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: formatDuration - } - } - }, - legend: { - show: false - } - }); - } - - // Draw lead and cycle time for the project - function drawLeadAndCycleTime() - { - 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: formatDuration - } - } - } - }); - } - - function formatDuration(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"; - } - - jQuery(document).ready(function() { - - if (Kanboard.Exists("analytic-task-repartition")) { - drawTaskRepartition(); - } - else if (Kanboard.Exists("analytic-user-repartition")) { - drawUserRepartition(); - } - else if (Kanboard.Exists("analytic-cfd")) { - drawCfd(); - } - else if (Kanboard.Exists("analytic-burndown")) { - drawBurndown(); - } - else if (Kanboard.Exists("budget-chart")) { - drawBudget(); - } - else if (Kanboard.Exists("analytic-avg-time-column")) { - drawAvgTimeColumn(); - } - else if (Kanboard.Exists("analytic-task-time-column")) { - drawTaskTimeColumn(); - } - else if (Kanboard.Exists("analytic-lead-cycle-time")) { - drawLeadAndCycleTime(); - } - }); - -})(); diff --git a/assets/js/src/base.js b/assets/js/src/base.js deleted file mode 100644 index ffad93b8..00000000 --- a/assets/js/src/base.js +++ /dev/null @@ -1,394 +0,0 @@ -var Kanboard = (function() { - - jQuery(document).ready(function() { - Kanboard.Init(); - }); - - return { - - ShowLoadingIcon: function() { - $("body").append('<span id="app-loading-icon"> <i class="fa fa-spinner fa-spin"></i></span>'); - }, - - HideLoadingIcon: function() { - $("#app-loading-icon").remove(); - }, - - // Return true if the element#id exists - Exists: function(id) { - if (document.getElementById(id)) { - return true; - } - - return false; - }, - - // Open a popup on a link click - Popover: function(e, callback) { - e.preventDefault(); - e.stopPropagation(); - - var link = e.target.getAttribute("href"); - - if (! link) { - link = e.target.getAttribute("data-href"); - } - - if (link) { - Kanboard.OpenPopover(link, callback); - } - }, - - // Display a popup - OpenPopover: function(link, callback) { - - $.get(link, function(content) { - - $("body").append('<div id="popover-container"><div id="popover-content">' + content + '</div></div>'); - - $("#popover-container").click(function() { - Kanboard.ClosePopover(); - }); - - $("#popover-content").click(function(e) { - e.stopPropagation(); - }); - - $(".close-popover").click(function(e) { - e.preventDefault(); - Kanboard.ClosePopover(); - }); - - Mousetrap.bindGlobal("esc", function() { - Kanboard.ClosePopover(); - }); - - if (callback) { - callback(); - } - }); - }, - - ClosePopover: function() { - $('#popover-container').remove(); - Kanboard.Screenshot.Destroy(); - }, - - // 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() { - window.location = $("body").data("login-url"); - } - } - }); - } - }, - - Init: function() { - - // Chosen select - $(".chosen-select").chosen({ - width: "200px", - no_results_text: $(".chosen-select").data("notfound"), - disable_search_threshold: 10 - }); - - // Project select box - $("#board-selector").chosen({ - width: 180, - no_results_text: $("#board-selector").data("notfound") - }); - - $("#board-selector").change(function() { - window.location = $(this).attr("data-board-url").replace(/PROJECT_ID/g, $(this).val()); - }); - - // Check the session every 60s - window.setInterval(Kanboard.CheckSession, 60000); - - // Submit form - Mousetrap.bindGlobal("mod+enter", function() { - $("form").submit(); - }); - - // Open board selector - Mousetrap.bind("b", function(e) { - e.preventDefault(); - $('#board-selector').trigger('chosen:open'); - }); - - // Focus to the search box - Mousetrap.bind("f", function(e) { - e.preventDefault(); - - var input = document.getElementById("form-search"); - - if (input) { - input.focus(); - } - }); - - // Switch view mode for projects: go to the board - Mousetrap.bind("v b", function(e) { - var link = $(".view-board"); - - if (link.length) { - window.location = link.attr('href'); - } - }); - - // Switch view mode for projects: go to the calendar - Mousetrap.bind("v c", function(e) { - var link = $(".view-calendar"); - - if (link.length) { - window.location = link.attr('href'); - } - }); - - // Switch view mode for projects: go to the listing - Mousetrap.bind("v l", function(e) { - var link = $(".view-listing"); - - if (link.length) { - window.location = link.attr('href'); - } - }); - - // Place cursor at the end when focusing on the search box - $(document).on("focus", "#form-search", function() { - if ($("#form-search")[0].setSelectionRange) { - $('#form-search')[0].setSelectionRange($('#form-search').val().length, $('#form-search').val().length); - } - }); - - // Filter helper for search - $(document).on("click", ".filter-helper", function (e) { - e.preventDefault(); - $("#form-search").val($(this).data("filter")); - $("form.search").submit(); - }); - - // Collapse sidebar - $(document).on("click", ".sidebar-collapse", function (e) { - e.preventDefault(); - $(".sidebar-container").addClass("sidebar-collapsed"); - $(".sidebar-expand").show(); - $(".sidebar h2").hide(); - $(".sidebar ul").hide(); - $(".sidebar-collapse").hide(); - }); - - // Expand sidebar - $(document).on("click", ".sidebar-expand", function (e) { - e.preventDefault(); - $(".sidebar-container").removeClass("sidebar-collapsed"); - $(".sidebar-collapse").show(); - $(".sidebar h2").show(); - $(".sidebar ul").show(); - $(".sidebar-expand").hide(); - }); - - // Reload page when a destination project is changed - var reloading_project = false; - $("select.task-reload-project-destination").change(function() { - if (! reloading_project) { - $(".loading-icon").show(); - reloading_project = true; - window.location = $(this).data("redirect").replace(/PROJECT_ID/g, $(this).val()); - } - }); - - // Datepicker translation - $.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]); - - // Alert box fadeout - $(".alert-fade-out").delay(4000).fadeOut(800, function() { - $(this).remove(); - }); - - Kanboard.InitAfterAjax(); - }, - - InitAfterAjax: function() { - - // Popover - $(document).on("click", ".popover", Kanboard.Popover); - - // Autofocus fields (html5 autofocus works only with page onload) - $("[autofocus]").each(function(index, element) { - $(this).focus(); - }) - - // 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 - }); - - // Markdown Preview for textareas - $("#markdown-preview").click(Kanboard.MarkdownPreview); - $("#markdown-write").click(Kanboard.MarkdownWriter); - - // Auto-select input fields - $(".auto-select").focus(function() { - $(this).select(); - }); - - // Dropdown - $(".dropit-submenu").hide(); - $('.dropdown').not(".dropit").dropit({ triggerParentEl : "span" }); - - // Task auto-completion - if ($(".task-autocomplete").length) { - - if ($('.opposite_task_id').val() == '') { - $(".task-autocomplete").parent().find("input[type=submit]").attr('disabled','disabled'); - } - - $(".task-autocomplete").autocomplete({ - source: $(".task-autocomplete").data("search-url"), - minLength: 1, - select: function(event, ui) { - var field = $(".task-autocomplete").data("dst-field"); - $("input[name=" + field + "]").val(ui.item.id); - - $(".task-autocomplete").parent().find("input[type=submit]").removeAttr('disabled'); - } - }); - } - - // Tooltip for column description - $(".tooltip").tooltip({ - content: function() { - return '<div class="markdown">' + $(this).attr("title") + '</div>'; - }, - 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); - } - } - }); - - // Screenshot - if (Kanboard.Exists("screenshot-zone")) { - Kanboard.Screenshot.Init(); - } - } - }; - -})(); diff --git a/assets/js/src/board.js b/assets/js/src/board.js index 40f140bf..7015f1c6 100644 --- a/assets/js/src/board.js +++ b/assets/js/src/board.js @@ -1,278 +1,164 @@ -(function() { - - var checkInterval = null; - - function on_popover(e) - { - e.preventDefault(); - e.stopPropagation(); - - Kanboard.Popover(e, Kanboard.InitAfterAjax); - } - - function keyboard_shortcuts() - { - Mousetrap.bind("n", function() { - Kanboard.OpenPopover( - $("#board").data("task-creation-url"), - Kanboard.InitAfterAjax - ); - }); - - Mousetrap.bind("s", function() { - Kanboard.ShowLoadingIcon(); - - $.ajax({ - cache: false, - url: $('.filter-display-mode:not([style="display: none;"]) a').attr('href'), - success: function(data) { - $("#board-container").remove(); - $("#main").append(data); - Kanboard.InitAfterAjax(); - board_unload_events(); - board_load_events(); - compactview_reload(); - $('.filter-display-mode').toggle(); - Kanboard.HideLoadingIcon(); - } - }); - }); - - Mousetrap.bind("c", function() { - compactview_toggle(); - }); +function Board() { + this.app = null; + this.checkInterval = null; +} + +Board.prototype.execute = function(app) { + this.app = app; + this.app.swimlane.refresh(); + this.app.swimlane.listen(); + this.poll(); + this.keyboardShortcuts(); + this.resizeColumnHeight(); + this.listen(); + this.dragAndDrop(); + this.compactView(); + + $(window).resize(this.resizeColumnHeight); +}; + +Board.prototype.poll = function() { + var interval = parseInt($("#board").attr("data-check-interval")); + + if (interval > 0) { + this.checkInterval = window.setInterval(this.check.bind(this), interval * 1000); } +}; - function resize_column() - { - var position = $(".board-swimlane").position(); - $(".board-task-list").height($(window).height() - position.top); - } +Board.prototype.check = function() { + if (this.app.isVisible()) { - // Setup the board - function board_load_events() - { - // Resize column height - resize_column(); + var self = this; + this.app.showLoadingIcon(); - // Drag and drop - $(".board-task-list").sortable({ - delay: 300, - distance: 5, - connectWith: ".column", - placeholder: "draggable-placeholder", - items: ".draggable-item", - 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') - ); + $.ajax({ + cache: false, + url: $("#board").attr("data-check-url"), + statusCode: { + 200: function(data) { self.refresh(data); }, + 304: function () { self.app.hideLoadingIcon(); } } }); + } +}; + +Board.prototype.save = function(taskId, columnId, position, swimlaneId) { + this.app.showLoadingIcon(); + + $.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: this.refresh.bind(this), + error: this.app.hideLoadingIcon.bind(this) + }); +}; - // Task popover - $("#board").on("click", ".task-board-popover", on_popover); - - // Redirect to the task details page - $("#board").on("click", ".task-board", function() { - window.location = $(this).data("task-url"); - }); - - // Tooltips for tasks - $(".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 < 1 ? "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').not(".popover").click(function(e) { - - e.preventDefault(); - e.stopPropagation(); - - if ($(this).hasClass("popover-subtask-restriction")) { - Kanboard.OpenPopover($(this).attr('href')); - $(_this).tooltip('close'); - } - else { - $.get($(this).attr('href'), setTooltipContent); - } - }); - }); - - 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; +Board.prototype.refresh = function(data) { + $("#board-container").replaceWith(data); - setTimeout(function () { - if (! $(".ui-tooltip:hover").length) { - $(_this).tooltip("close"); - } - }, 100); - }); + this.app.listen(); + this.app.swimlane.refresh(); + this.app.swimlane.listen(); + this.resizeColumnHeight(); + this.app.hideLoadingIcon(); + this.listen(); + this.dragAndDrop(); + this.compactView(); +}; - // Automatic refresh - var interval = parseInt($("#board").attr("data-check-interval")); +Board.prototype.resizeColumnHeight = function() { + var position = $(".board-swimlane").position(); - if (interval > 0) { - checkInterval = window.setInterval(board_check, interval * 1000); - } + if (position) { + $(".board-task-list").height($(window).height() - position.top); } +}; + +Board.prototype.dragAndDrop = function() { + var self = this; + $(".board-task-list").sortable({ + delay: 300, + distance: 5, + connectWith: ".board-task-list", + placeholder: "draggable-placeholder", + items: ".draggable-item", + stop: function(event, ui) { + self.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') + ); + } + }); +}; - // Stop events - function board_unload_events() - { - clearInterval(checkInterval); - } +Board.prototype.listen = function() { + var self = this; - // Save and refresh the board - function board_save(taskId, columnId, position, swimlaneId) - { - board_unload_events(); - Kanboard.ShowLoadingIcon(); + $(document).on("click", ".task-board", function() { + window.location = $(this).data("task-url"); + }); - $.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-container").remove(); - $("#main").append(data); - Kanboard.InitAfterAjax(); - board_load_events(); - compactview_reload(); - Kanboard.HideLoadingIcon(); - } - }); - } + $(document).on('click', ".filter-toggle-scrolling", function(e) { + e.preventDefault(); + self.toggleCompactView(); + }); +}; - // Check if the 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-container").remove(); - $("#main").append(data); - Kanboard.InitAfterAjax(); - board_unload_events(); - board_load_events(); - compactview_reload(); - } - } - }); - } - } +Board.prototype.toggleCompactView = function() { + var scrolling = localStorage.getItem("horizontal_scroll") || 1; + localStorage.setItem("horizontal_scroll", scrolling == 0 ? 1 : 0); + this.compactView(); +}; - function compactview_load_events() - { - jQuery(document).on('click', ".filter-toggle-scrolling", function(e) { - e.preventDefault(); - compactview_toggle(); - }); +Board.prototype.compactView = function() { + if (localStorage.getItem("horizontal_scroll") == 0) { + $(".filter-wide").show(); + $(".filter-compact").hide(); - compactview_reload(); + $("#board-container").addClass("board-container-compact"); + $("#board th").addClass("board-column-compact"); } + else { + $(".filter-wide").hide(); + $(".filter-compact").show(); - function compactview_toggle() - { - var scrolling = Kanboard.GetStorageItem("horizontal_scroll") || 1; - Kanboard.SetStorageItem("horizontal_scroll", scrolling == 0 ? 1 : 0); - compactview_reload(); + $("#board-container").removeClass("board-container-compact"); + $("#board th").removeClass("board-column-compact"); } - - function compactview_reload() - { - if (Kanboard.GetStorageItem("horizontal_scroll") == 0) { - - $(".filter-wide").show(); - $(".filter-compact").hide(); - - $("#board-container").addClass("board-container-compact"); - $("#board th").addClass("board-column-compact"); - } - else { - - $(".filter-wide").hide(); - $(".filter-compact").show(); - - $("#board-container").removeClass("board-container-compact"); - $("#board th").removeClass("board-column-compact"); +}; + +Board.prototype.toggleCollapsedMode = 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.refresh(data); } - } + }); +}; - jQuery(document).ready(function() { +Board.prototype.keyboardShortcuts = function() { + var self = this; - if (Kanboard.Exists("board")) { - board_load_events(); - compactview_load_events(); - keyboard_shortcuts(); + Mousetrap.bind("c", function() { self.toggleCompactView(); }); + Mousetrap.bind("s", function() { self.toggleCollapsedMode(); }); - $(window).resize(resize_column); - } + Mousetrap.bind("n", function() { + self.app.popover.open($("#board").data("task-creation-url")); }); - -})(); +}; diff --git a/assets/js/src/calendar.js b/assets/js/src/calendar.js index 68da57ee..ffb00dcd 100644 --- a/assets/js/src/calendar.js +++ b/assets/js/src/calendar.js @@ -1,51 +1,49 @@ -(function() { +function Calendar() { - jQuery(document).ready(function() { - if (Kanboard.Exists("calendar")) { - var calendar = $('#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() - }; +Calendar.prototype.execute = function() { + var calendar = $('#calendar'); - for (var key in params) { - url += "&" + key + "=" + params[key]; - } + 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'); - }); - } + $.getJSON(url, function(events) { + calendar.fullCalendar('removeEvents'); + calendar.fullCalendar('addEventSource', events); + calendar.fullCalendar('rerenderEvents'); }); } }); - -})(); +}; diff --git a/assets/js/src/screenshot.js b/assets/js/src/screenshot.js index dce49c77..fd50f8e7 100644 --- a/assets/js/src/screenshot.js +++ b/assets/js/src/screenshot.js @@ -1,145 +1,131 @@ -Kanboard.Screenshot = (function() { +function Screenshot() { + this.pasteCatcher = null; +} - var pasteCatcher = null; +Screenshot.prototype.execute = function() { + this.initialize(); +}; - // Setup event listener and workarounds - function init() - { - destroy(); +// Setup event listener and workarounds +Screenshot.prototype.initialize = function() { + this.destroy(); - if (! window.Clipboard) { + if (! window.Clipboard) { - // Create a contenteditable element - pasteCatcher = document.createElement("div"); - pasteCatcher.id = "screenshot-pastezone"; - pasteCatcher.contentEditable = "true"; + // 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 - pasteCatcher.style.opacity = 0; - pasteCatcher.style.position = "fixed"; - pasteCatcher.style.top = 0; - pasteCatcher.style.right = 0; - pasteCatcher.style.width = 0; + // 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(pasteCatcher, document.body.firstChild); + document.body.insertBefore(this.pasteCatcher, document.body.firstChild); - // Set focus on the contenteditable element - pasteCatcher.focus(); + // Set focus on the contenteditable element + this.pasteCatcher.focus(); - // Set the focus when clicked anywhere in the document - document.addEventListener("click", setFocus); + // 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", setFocus); - } - - window.addEventListener("paste", pasteHandler); + // Set the focus when clicked in screenshot dropzone (popover) + document.getElementById("screenshot-zone").addEventListener("click", this.setFocus.bind(this)); } - // Set focus on the contentEditable element - function setFocus() - { - if (pasteCatcher !== null) { - pasteCatcher.focus(); - } + window.addEventListener("paste", this.pasteHandler.bind(this)); +}; + +// Destroy contentEditable element +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")); } - // Destroy contenteditable - function destroy() - { - if (pasteCatcher != null) { - document.body.removeChild(pasteCatcher); - } - else if (document.getElementById("screenshot-pastezone")) { - document.body.removeChild(document.getElementById("screenshot-pastezone")); - } + document.removeEventListener("click", this.setFocus.bind(this)); + this.pasteCatcher = null; +}; - document.removeEventListener("click", setFocus); - pasteCatcher = null; +// Set focus on contentEditable element +Screenshot.prototype.setFocus = function() { + if (this.pasteCatcher !== null) { + this.pasteCatcher.focus(); } +}; - // Paste event callback - function pasteHandler(e) - { - // Firefox doesn't have the property e.clipboardData.items (only Chrome) - if (e.clipboardData && e.clipboardData.items) { +// Paste event callback +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; + var items = e.clipboardData.items; - if (items) { + if (items) { - for (var i = 0; i < items.length; i++) { + for (var i = 0; i < items.length; i++) { - // Find an image in pasted elements - if (items[i].type.indexOf("image") !== -1) { + // Find an image in pasted elements + if (items[i].type.indexOf("image") !== -1) { - var blob = items[i].getAsFile(); + var blob = items[i].getAsFile(); - // Get the image as base64 data - var reader = new FileReader(); - reader.onload = function(event) { - createImage(event.target.result); - }; + // 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); - } + reader.readAsDataURL(blob); } } } - else { + } + else { - // Handle Firefox - setTimeout(checkInput, 100); - } + // Handle Firefox + setTimeout(this.checkInput.bind(this), 100); } +}; - // Parse the input in the paste catcher element - function checkInput() - { - var child = pasteCatcher.childNodes[0]; +// Parse the input in the paste catcher element +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") { - createImage(child.src); - } + 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); } - - pasteCatcher.innerHTML = ""; - } - - // Creates a new image from a given source - function createImage(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); - - destroy(); - init(); } - jQuery(document).ready(function() { + pasteCatcher.innerHTML = ""; +}; - if (Kanboard.Exists("screenshot-zone")) { - init(); - } - }); +// Creates a new image from a given source +Screenshot.prototype.createImage = function(blob) { + var pastedImage = new Image(); + pastedImage.src = blob; - return { - Init: init, - Destroy: destroy + // 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); }; -})();
\ No newline at end of file + + var zone = document.getElementById("screenshot-zone"); + zone.innerHTML = ""; + zone.className = "screenshot-pasted"; + zone.appendChild(pastedImage); + + this.destroy(); + this.initialize(); +}; diff --git a/assets/js/src/swimlane.js b/assets/js/src/swimlane.js index 212d6d36..ce18dbfa 100644 --- a/assets/js/src/swimlane.js +++ b/assets/js/src/swimlane.js @@ -1,90 +1,67 @@ -(function() {
-
- // Expand a Swimlane via display attributes
- function expand(swimlaneId)
- {
- $('.swimlane-row-' + swimlaneId).css('display', 'table-row');
- $('.show-icon-swimlane-' + swimlaneId).css('display', 'none');
- $('.hide-icon-swimlane-' + swimlaneId).css('display', 'inline');
- }
-
- // Collapse a Swimlane via display attributes
- function collapse(swimlaneId)
- {
- $('.swimlane-row-' + swimlaneId).css('display', 'none');
- $('.show-icon-swimlane-' + swimlaneId).css('display', 'inline');
- $('.hide-icon-swimlane-' + swimlaneId).css('display', 'none');
- }
+function Swimlane() {
+}
- // Add swimlane Id to the hidden list and stores the list to localStorage
- function hide(id)
- {
- var storageKey = "hidden_swimlanes_" + $("#board").data("project-id");
- var hiddenSwimlaneIds = JSON.parse(Kanboard.GetStorageItem(storageKey)) || [];
+Swimlane.prototype.getStorageKey = function() {
+ return "hidden_swimlanes_" + $("#board").data("project-id");
+};
- hiddenSwimlaneIds.push(id);
+Swimlane.prototype.expand = function(swimlaneId) {
+ var swimlaneIds = this.getAllCollapsed();
+ var index = swimlaneIds.indexOf(swimlaneId);
- Kanboard.SetStorageItem(storageKey, JSON.stringify(hiddenSwimlaneIds));
+ if (index > -1) {
+ swimlaneIds.splice(index, 1);
}
- // Remove swimlane Id from the hidden list and stores the list to
- // localStorage
- function unhide(id)
- {
- var storageKey = "hidden_swimlanes_" + $("#board").data("project-id");
- var hiddenSwimlaneIds = JSON.parse(Kanboard.GetStorageItem(storageKey)) || [];
- var index = hiddenSwimlaneIds.indexOf(id);
+ localStorage.setItem(this.getStorageKey(), JSON.stringify(swimlaneIds));
- if (index > -1) {
- hiddenSwimlaneIds.splice(index, 1);
- }
+ $('.swimlane-row-' + swimlaneId).css('display', 'table-row');
+ $('.show-icon-swimlane-' + swimlaneId).css('display', 'none');
+ $('.hide-icon-swimlane-' + swimlaneId).css('display', 'inline');
+};
- Kanboard.SetStorageItem(storageKey, JSON.stringify(hiddenSwimlaneIds));
- }
+Swimlane.prototype.collapse = function(swimlaneId) {
+ var swimlaneIds = this.getAllCollapsed();
- // Check if swimlane Id is hidden. Anything > -1 means hidden.
- function isHidden(id)
- {
- return getAllHidden().indexOf(id) > -1;
+ if (swimlaneIds.indexOf(swimlaneId) < 0) {
+ swimlaneIds.push(swimlaneId);
+ localStorage.setItem(this.getStorageKey(), JSON.stringify(swimlaneIds));
}
- // Gets all swimlane Ids that are hidden
- function getAllHidden()
- {
- var storageKey = "hidden_swimlanes_" + $("#board").data("project-id");
- return JSON.parse(Kanboard.GetStorageItem(storageKey)) || [];
- }
+ $('.swimlane-row-' + swimlaneId).css('display', 'none');
+ $('.show-icon-swimlane-' + swimlaneId).css('display', 'inline');
+ $('.hide-icon-swimlane-' + swimlaneId).css('display', 'none');
+};
- // Reload the swimlane states (shown/hidden) after an ajax call
- jQuery(document).ajaxComplete(function() {
+Swimlane.prototype.isCollapsed = function(swimlaneId) {
+ return this.getAllCollapsed().indexOf(swimlaneId) > -1;
+};
- getAllHidden().map(function(swimlaneId) {
- collapse(swimlaneId);
- });
- });
+Swimlane.prototype.getAllCollapsed = function() {
+ return JSON.parse(localStorage.getItem(this.getStorageKey())) || [];
+};
- // Reload the swimlane states (shown/hidden) after page refresh
- jQuery(document).ready(function() {
+Swimlane.prototype.refresh = function() {
+ var swimlaneIds = this.getAllCollapsed();
- getAllHidden().map(function(swimlaneId) {
- collapse(swimlaneId);
- });
- });
+ for (var i = 0; i < swimlaneIds.length; i++) {
+ this.collapse(swimlaneIds[i]);
+ }
+};
- // Clicking on Show/Hide icon fires this.
- jQuery(document).on('click', ".board-swimlane-toggle", function(e) {
+Swimlane.prototype.listen = function() {
+ var self = this;
+
+ $(document).on('click', ".board-swimlane-toggle", function(e) {
e.preventDefault();
var swimlaneId = $(this).data('swimlane-id');
- if (isHidden(swimlaneId)) {
- unhide(swimlaneId);
- expand(swimlaneId);
+ if (self.isCollapsed(swimlaneId)) {
+ self.expand(swimlaneId);
}
else {
- hide(swimlaneId);
- collapse(swimlaneId);
+ self.collapse(swimlaneId);
}
});
-
-})();
+};
|