Javascript refactoring

This commit is contained in:
Frederic Guillot 2015-08-04 22:51:44 -04:00
parent f04ec0700c
commit e13872fc2e
28 changed files with 1210 additions and 1293 deletions

View File

@ -167,8 +167,10 @@ class ProjectAnalytic extends Base
$sums[$task['column_id']] += ($task['date_completed'] ?: time()) - $task['date_moved'];
foreach ($sums as $column_id => $time_spent) {
$stats[$column_id]['count']++;
$stats[$column_id]['time_spent'] += $time_spent;
if (isset($stats[$column_id])) {
$stats[$column_id]['count']++;
$stats[$column_id]['time_spent'] += $time_spent;
}
}
}

View File

@ -17,7 +17,7 @@
<th class="board-column-header">
<?php if (! $not_editable): ?>
<div class="board-add-icon">
<?= $this->url->link('+', 'taskcreation', 'create', array('project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']), false, 'task-board-popover', t('Add a new task')) ?>
<?= $this->url->link('+', 'taskcreation', 'create', array('project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']), false, 'popover', t('Add a new task')) ?>
</div>
<?php endif ?>

View File

@ -10,7 +10,7 @@
'changeCategory',
array('task_id' => $task['id'], 'project_id' => $task['project_id']),
false,
'task-board-popover' . (! empty($task['category_description']) ? ' tooltip' : ''),
'popover' . (! empty($task['category_description']) ? ' tooltip' : ''),
! empty($task['category_description']) ? $this->text->markdown($task['category_description']) : t('Change category')
) ?>
<?php endif ?>
@ -27,31 +27,31 @@
<?php endif ?>
<?php if ($task['recurrence_status'] == \Model\Task::RECURRING_STATUS_PENDING): ?>
<span title="<?= t('Recurrence') ?>" class="task-board-tooltip" data-href="<?= $this->url->href('board', 'recurrence', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-refresh fa-rotate-90"></i></span>
<span title="<?= t('Recurrence') ?>" class="tooltip" data-href="<?= $this->url->href('board', 'recurrence', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-refresh fa-rotate-90"></i></span>
<?php endif ?>
<?php if ($task['recurrence_status'] == \Model\Task::RECURRING_STATUS_PROCESSED): ?>
<span title="<?= t('Recurrence') ?>" class="task-board-tooltip" data-href="<?= $this->url->href('board', 'recurrence', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-refresh fa-rotate-90 fa-inverse"></i></span>
<span title="<?= t('Recurrence') ?>" class="tooltip" data-href="<?= $this->url->href('board', 'recurrence', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-refresh fa-rotate-90 fa-inverse"></i></span>
<?php endif ?>
<?php if (! empty($task['nb_links'])): ?>
<span title="<?= t('Links') ?>" class="task-board-tooltip" data-href="<?= $this->url->href('board', 'tasklinks', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-code-fork"></i>&nbsp;<?= $task['nb_links'] ?></span>
<span title="<?= t('Links') ?>" class="tooltip" data-href="<?= $this->url->href('board', 'tasklinks', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-code-fork"></i>&nbsp;<?= $task['nb_links'] ?></span>
<?php endif ?>
<?php if (! empty($task['nb_subtasks'])): ?>
<span title="<?= t('Sub-Tasks') ?>" class="task-board-tooltip" data-href="<?= $this->url->href('board', 'subtasks', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-bars"></i>&nbsp;<?= round($task['nb_completed_subtasks']/$task['nb_subtasks']*100, 0).'%' ?></span>
<span title="<?= t('Sub-Tasks') ?>" class="tooltip" data-href="<?= $this->url->href('board', 'subtasks', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-bars"></i>&nbsp;<?= round($task['nb_completed_subtasks']/$task['nb_subtasks']*100, 0).'%' ?></span>
<?php endif ?>
<?php if (! empty($task['nb_files'])): ?>
<span title="<?= t('Attachments') ?>" class="task-board-tooltip" data-href="<?= $this->url->href('board', 'attachments', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-paperclip"></i>&nbsp;<?= $task['nb_files'] ?></span>
<span title="<?= t('Attachments') ?>" class="tooltip" data-href="<?= $this->url->href('board', 'attachments', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-paperclip"></i>&nbsp;<?= $task['nb_files'] ?></span>
<?php endif ?>
<?php if (! empty($task['nb_comments'])): ?>
<span title="<?= p($task['nb_comments'], t('%d comment', $task['nb_comments']), t('%d comments', $task['nb_comments'])) ?>" class="task-board-tooltip" data-href="<?= $this->url->href('board', 'comments', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-comment-o"></i>&nbsp;<?= $task['nb_comments'] ?></span>
<span title="<?= p($task['nb_comments'], t('%d comment', $task['nb_comments']), t('%d comments', $task['nb_comments'])) ?>" class="tooltip" data-href="<?= $this->url->href('board', 'comments', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-comment-o"></i>&nbsp;<?= $task['nb_comments'] ?></span>
<?php endif ?>
<?php if (! empty($task['description'])): ?>
<span title="<?= t('Description') ?>" class="task-board-tooltip" data-href="<?= $this->url->href('board', 'description', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>">
<span title="<?= t('Description') ?>" class="tooltip" data-href="<?= $this->url->href('board', 'description', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>">
<i class="fa fa-file-text-o"></i>
</span>
<?php endif ?>

View File

@ -2,14 +2,14 @@
<span>
<a href="#" class="dropdown-menu"><?= '#'.$task['id'] ?></a>
<ul>
<li><i class="fa fa-user"></i> <?= $this->url->link(t('Change assignee'), 'board', 'changeAssignee', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-popover') ?></li>
<li><i class="fa fa-tag"></i> <?= $this->url->link(t('Change category'), 'board', 'changeCategory', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-popover') ?></li>
<li><i class="fa fa-align-left"></i> <?= $this->url->link(t('Change description'), 'taskmodification', 'description', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-popover') ?></li>
<li><i class="fa fa-pencil-square-o"></i> <?= $this->url->link(t('Edit this task'), 'taskmodification', 'edit', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-popover') ?></li>
<li><i class="fa fa-comment-o"></i> <?= $this->url->link(t('Add a comment'), 'comment', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-popover') ?></li>
<li><i class="fa fa-code-fork"></i> <?= $this->url->link(t('Add a link'), 'tasklink', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-popover') ?></li>
<li><i class="fa fa-camera"></i> <?= $this->url->link(t('Add a screenshot'), 'board', 'screenshot', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-popover') ?></li>
<li><i class="fa fa-close"></i> <?= $this->url->link(t('Close this task'), 'taskstatus', 'close', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'redirect' => 'board'), false, 'task-board-popover') ?></li>
<li><i class="fa fa-user"></i> <?= $this->url->link(t('Change assignee'), 'board', 'changeAssignee', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?></li>
<li><i class="fa fa-tag"></i> <?= $this->url->link(t('Change category'), 'board', 'changeCategory', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?></li>
<li><i class="fa fa-align-left"></i> <?= $this->url->link(t('Change description'), 'taskmodification', 'description', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?></li>
<li><i class="fa fa-pencil-square-o"></i> <?= $this->url->link(t('Edit this task'), 'taskmodification', 'edit', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?></li>
<li><i class="fa fa-comment-o"></i> <?= $this->url->link(t('Add a comment'), 'comment', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?></li>
<li><i class="fa fa-code-fork"></i> <?= $this->url->link(t('Add a link'), 'tasklink', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?></li>
<li><i class="fa fa-camera"></i> <?= $this->url->link(t('Add a screenshot'), 'board', 'screenshot', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?></li>
<li><i class="fa fa-close"></i> <?= $this->url->link(t('Close this task'), 'taskstatus', 'close', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'redirect' => 'board'), false, 'popover') ?></li>
</ul>
</span>
</span>

View File

@ -37,7 +37,7 @@
'changeAssignee',
array('task_id' => $task['id'], 'project_id' => $task['project_id']),
false,
'task-board-popover',
'popover',
t('Change assignee')
) ?>
</span>

File diff suppressed because one or more lines are too long

196
assets/js/src/App.js Normal file
View File

@ -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">&nbsp;<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";
};

View File

@ -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
}
});
};

View File

@ -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
}
}
});
};

View File

@ -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
}
}
});
};

View File

@ -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
}
}
});
};

View File

@ -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
}
}
}
});
};

50
assets/js/src/Markdown.js Normal file
View File

@ -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));
};

50
assets/js/src/Popover.js Normal file
View File

@ -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(); });
};

34
assets/js/src/Router.js Normal file
View File

@ -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);
});

61
assets/js/src/Search.js Normal file
View File

@ -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();
}
});
};

25
assets/js/src/Sidebar.js Normal file
View File

@ -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);
};

View File

@ -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'
}
});
};

View File

@ -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
}
});
};

87
assets/js/src/Tooltip.js Normal file
View File

@ -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);
});
};

View File

@ -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'
}
});
};

View File

@ -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();
}
});
})();

View File

@ -1,394 +0,0 @@
var Kanboard = (function() {
jQuery(document).ready(function() {
Kanboard.Init();
});
return {
ShowLoadingIcon: function() {
$("body").append('<span id="app-loading-icon">&nbsp;<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();
}
}
};
})();

View File

@ -1,278 +1,164 @@
(function() {
function Board() {
this.app = null;
this.checkInterval = null;
}
var 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();
function on_popover(e)
{
e.preventDefault();
e.stopPropagation();
$(window).resize(this.resizeColumnHeight);
};
Kanboard.Popover(e, Kanboard.InitAfterAjax);
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 keyboard_shortcuts()
{
Mousetrap.bind("n", function() {
Kanboard.OpenPopover(
$("#board").data("task-creation-url"),
Kanboard.InitAfterAjax
);
});
Board.prototype.check = function() {
if (this.app.isVisible()) {
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 resize_column()
{
var position = $(".board-swimlane").position();
$(".board-task-list").height($(window).height() - position.top);
}
// Setup the board
function board_load_events()
{
// Resize column height
resize_column();
// 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')
);
}
});
// 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;
setTimeout(function () {
if (! $(".ui-tooltip:hover").length) {
$(_this).tooltip("close");
}
}, 100);
});
// 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()
{
clearInterval(checkInterval);
}
// Save and refresh the board
function board_save(taskId, columnId, position, swimlaneId)
{
board_unload_events();
Kanboard.ShowLoadingIcon();
var self = this;
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: function(data) {
$("#board-container").remove();
$("#main").append(data);
Kanboard.InitAfterAjax();
board_load_events();
compactview_reload();
Kanboard.HideLoadingIcon();
url: $("#board").attr("data-check-url"),
statusCode: {
200: function(data) { self.refresh(data); },
304: function () { self.app.hideLoadingIcon(); }
}
});
}
};
// 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.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)
});
};
Board.prototype.refresh = function(data) {
$("#board-container").replaceWith(data);
this.app.listen();
this.app.swimlane.refresh();
this.app.swimlane.listen();
this.resizeColumnHeight();
this.app.hideLoadingIcon();
this.listen();
this.dragAndDrop();
this.compactView();
};
Board.prototype.resizeColumnHeight = function() {
var position = $(".board-swimlane").position();
if (position) {
$(".board-task-list").height($(window).height() - position.top);
}
};
function compactview_load_events()
{
jQuery(document).on('click', ".filter-toggle-scrolling", function(e) {
e.preventDefault();
compactview_toggle();
});
compactview_reload();
}
function compactview_toggle()
{
var scrolling = Kanboard.GetStorageItem("horizontal_scroll") || 1;
Kanboard.SetStorageItem("horizontal_scroll", scrolling == 0 ? 1 : 0);
compactview_reload();
}
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");
}
}
jQuery(document).ready(function() {
if (Kanboard.Exists("board")) {
board_load_events();
compactview_load_events();
keyboard_shortcuts();
$(window).resize(resize_column);
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')
);
}
});
};
})();
Board.prototype.listen = function() {
var self = this;
$(document).on("click", ".task-board", function() {
window.location = $(this).data("task-url");
});
$(document).on('click', ".filter-toggle-scrolling", function(e) {
e.preventDefault();
self.toggleCompactView();
});
};
Board.prototype.toggleCompactView = function() {
var scrolling = localStorage.getItem("horizontal_scroll") || 1;
localStorage.setItem("horizontal_scroll", scrolling == 0 ? 1 : 0);
this.compactView();
};
Board.prototype.compactView = function() {
if (localStorage.getItem("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);
}
});
};
Board.prototype.keyboardShortcuts = function() {
var self = this;
Mousetrap.bind("c", function() { self.toggleCompactView(); });
Mousetrap.bind("s", function() { self.toggleCollapsedMode(); });
Mousetrap.bind("n", function() {
self.app.popover.open($("#board").data("task-creation-url"));
});
};

View File

@ -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()
};
$.getJSON(url, function(events) {
calendar.fullCalendar('removeEvents');
calendar.fullCalendar('addEventSource', events);
calendar.fullCalendar('rerenderEvents');
});
}
for (var key in params) {
url += "&" + key + "=" + params[key];
}
$.getJSON(url, function(events) {
calendar.fullCalendar('removeEvents');
calendar.fullCalendar('addEventSource', events);
calendar.fullCalendar('rerenderEvents');
});
}
});
})();
};

View File

@ -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
Screenshot.prototype.checkInput = function() {
var child = this.pasteCatcher.childNodes[0];
if (child) {
// If the user pastes an image, the src attribute
// will represent the image as a base64 encoded string.
if (child.tagName === "IMG") {
this.createImage(child.src);
}
}
// Parse the input in the paste catcher element
function checkInput()
{
var child = pasteCatcher.childNodes[0];
pasteCatcher.innerHTML = "";
};
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);
}
}
// Creates a new image from a given source
Screenshot.prototype.createImage = function(blob) {
var pastedImage = new Image();
pastedImage.src = blob;
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() {
if (Kanboard.Exists("screenshot-zone")) {
init();
}
});
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);
};
})();
var zone = document.getElementById("screenshot-zone");
zone.innerHTML = "";
zone.className = "screenshot-pasted";
zone.appendChild(pastedImage);
this.destroy();
this.initialize();
};

View File

@ -1,90 +1,67 @@
(function() {
function Swimlane() {
}
// 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');
Swimlane.prototype.getStorageKey = function() {
return "hidden_swimlanes_" + $("#board").data("project-id");
};
Swimlane.prototype.expand = function(swimlaneId) {
var swimlaneIds = this.getAllCollapsed();
var index = swimlaneIds.indexOf(swimlaneId);
if (index > -1) {
swimlaneIds.splice(index, 1);
}
// 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');
localStorage.setItem(this.getStorageKey(), JSON.stringify(swimlaneIds));
$('.swimlane-row-' + swimlaneId).css('display', 'table-row');
$('.show-icon-swimlane-' + swimlaneId).css('display', 'none');
$('.hide-icon-swimlane-' + swimlaneId).css('display', 'inline');
};
Swimlane.prototype.collapse = function(swimlaneId) {
var swimlaneIds = this.getAllCollapsed();
if (swimlaneIds.indexOf(swimlaneId) < 0) {
swimlaneIds.push(swimlaneId);
localStorage.setItem(this.getStorageKey(), JSON.stringify(swimlaneIds));
}
// 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-row-' + swimlaneId).css('display', 'none');
$('.show-icon-swimlane-' + swimlaneId).css('display', 'inline');
$('.hide-icon-swimlane-' + swimlaneId).css('display', 'none');
};
hiddenSwimlaneIds.push(id);
Swimlane.prototype.isCollapsed = function(swimlaneId) {
return this.getAllCollapsed().indexOf(swimlaneId) > -1;
};
Kanboard.SetStorageItem(storageKey, JSON.stringify(hiddenSwimlaneIds));
Swimlane.prototype.getAllCollapsed = function() {
return JSON.parse(localStorage.getItem(this.getStorageKey())) || [];
};
Swimlane.prototype.refresh = function() {
var swimlaneIds = this.getAllCollapsed();
for (var i = 0; i < swimlaneIds.length; i++) {
this.collapse(swimlaneIds[i]);
}
};
// 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);
Swimlane.prototype.listen = function() {
var self = this;
if (index > -1) {
hiddenSwimlaneIds.splice(index, 1);
}
Kanboard.SetStorageItem(storageKey, JSON.stringify(hiddenSwimlaneIds));
}
// Check if swimlane Id is hidden. Anything > -1 means hidden.
function isHidden(id)
{
return getAllHidden().indexOf(id) > -1;
}
// Gets all swimlane Ids that are hidden
function getAllHidden()
{
var storageKey = "hidden_swimlanes_" + $("#board").data("project-id");
return JSON.parse(Kanboard.GetStorageItem(storageKey)) || [];
}
// Reload the swimlane states (shown/hidden) after an ajax call
jQuery(document).ajaxComplete(function() {
getAllHidden().map(function(swimlaneId) {
collapse(swimlaneId);
});
});
// Reload the swimlane states (shown/hidden) after page refresh
jQuery(document).ready(function() {
getAllHidden().map(function(swimlaneId) {
collapse(swimlaneId);
});
});
// Clicking on Show/Hide icon fires this.
jQuery(document).on('click', ".board-swimlane-toggle", function(e) {
$(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);
}
});
})();
};

View File

@ -4,7 +4,7 @@ print_css="print links table board task comment subtask markdown"
app_css="base links title table form button alert tooltip header board task comment subtask markdown listing activity dashboard pagination popover confirm sidebar responsive dropdown screenshot filters"
vendor_css="jquery-ui.min jquery-ui-timepicker-addon.min chosen.min fullcalendar.min font-awesome.min c3.min"
app_js="base board calendar analytic swimlane screenshot"
app_js="Popover Tooltip Markdown Sidebar Search App Screenshot Calendar Board Swimlane TaskRepartitionChart UserRepartitionChart CumulativeFlowDiagram BurndownChart BudgetChart AvgTimeColumnChart TaskTimeColumnChart LeadCycleTimeChart Router"
vendor_js="jquery-1.11.1.min jquery-ui.min jquery-ui-timepicker-addon.min jquery.ui.touch-punch.min chosen.jquery.min dropit.min moment.min fullcalendar.min mousetrap.min mousetrap-global-bind.min app.min"
lang_js="da de es fi fr hu it ja nl pl pt-br ru sv sr th tr zh-cn"
@ -34,7 +34,9 @@ function minify_js {
rm -f $dst_file $tmp_file 2>/dev/null
echo "(function() { 'use strict';" > $tmp_file;
for file in $app_js; do cat "assets/js/src/${file}.js" >> $tmp_file; done
echo "})();" >> $tmp_file;
curl -s \
-d compilation_level=SIMPLE_OPTIMIZATIONS \