/* global Snap */ /* Class: Bar Opts: canvas [reqd] task [reqd] column_width [reqd] x y */ export default function Bar(gt, task) { const self = {}; function init() { set_defaults(); prepare(); draw(); bind(); } function set_defaults() { self.action_completed = false; self.task = task; } function prepare() { prepare_values(); prepare_plugins(); } function prepare_values() { self.invalid = self.task.invalid; self.height = gt.config.bar.height; self.x = compute_x(); self.y = compute_y(); self.corner_radius = 3; self.duration = (self.task._end.diff(self.task._start, 'hours') + 24) / gt.config.step; self.width = gt.config.column_width * self.duration; self.progress_width = gt.config.column_width * self.duration * (self.task.progress / 100) || 0; self.group = gt.canvas.group().addClass('bar-wrapper'); self.bar_group = gt.canvas.group().addClass('bar-group').appendTo(self.group); self.handle_group = gt.canvas.group().addClass('handle-group').appendTo(self.group); } function prepare_plugins() { Snap.plugin(function (Snap, Element, Paper, global, Fragment) { Element.prototype.getX = function () { return +this.attr('x'); }; Element.prototype.getY = function () { return +this.attr('y'); }; Element.prototype.getWidth = function () { return +this.attr('width'); }; Element.prototype.getHeight = function () { return +this.attr('height'); }; Element.prototype.getEndX = function () { return this.getX() + this.getWidth(); }; }); } function draw() { draw_bar(); draw_progress_bar(); draw_label(); draw_resize_handles(); } function draw_bar() { self.$bar = gt.canvas.rect(self.x, self.y, self.width, self.height, self.corner_radius, self.corner_radius) .addClass('bar') .appendTo(self.bar_group); if (self.invalid) { self.$bar.addClass('bar-invalid'); } } function draw_progress_bar() { if (self.invalid) return; self.$bar_progress = gt.canvas.rect(self.x, self.y, self.progress_width, self.height, self.corner_radius, self.corner_radius) .addClass('bar-progress') .appendTo(self.bar_group); } function draw_label() { gt.canvas.text(self.x + self.width / 2, self.y + self.height / 2, self.task.name) .addClass('bar-label') .appendTo(self.bar_group); update_label_position(); } function draw_resize_handles() { if (self.invalid) return; const bar = self.$bar, bar_progress = self.$bar_progress, handle_width = 8; gt.canvas.rect(bar.getX() + bar.getWidth() - 9, bar.getY() + 1, handle_width, self.height - 2, self.corner_radius, self.corner_radius) .addClass('handle right') .appendTo(self.handle_group); gt.canvas.rect(bar.getX() + 1, bar.getY() + 1, handle_width, self.height - 2, self.corner_radius, self.corner_radius) .addClass('handle left') .appendTo(self.handle_group); if (self.task.progress && self.task.progress < 100) { gt.canvas.polygon( bar_progress.getEndX() - 5, bar_progress.getY() + bar_progress.getHeight(), bar_progress.getEndX() + 5, bar_progress.getY() + bar_progress.getHeight(), bar_progress.getEndX(), bar_progress.getY() + bar_progress.getHeight() - 8.66 ) .addClass('handle progress') .appendTo(self.handle_group); } } function bind() { if (self.invalid) return; show_details(); bind_resize(); bind_drag(); bind_resize_progress(); setup_click_event(); } function show_details() { const popover_group = gt.element_groups.details; let details_box = popover_group.select('.details-wrapper'); if (!details_box) { details_box = gt.canvas.group() .addClass('details-wrapper') .appendTo(popover_group); gt.canvas.rect(0, 0, 0, 110, 2, 2) .addClass('details-container') .appendTo(details_box); gt.canvas.text(0, 0, '') .attr({ dx: 10, dy: 30 }) .addClass('details-heading') .appendTo(details_box); gt.canvas.text(0, 0, '') .attr({ dx: 10, dy: 65 }) .addClass('details-body') .appendTo(details_box); gt.canvas.text(0, 0, '') .attr({ dx: 10, dy: 90 }) .addClass('details-body') .appendTo(details_box); } self.group.mouseover((e, x, y) => { popover_group.removeClass('hide'); const pos = get_details_position(); details_box.transform(`t${pos.x},${pos.y}`); const start_date = self.task._start.format('MMM D'), end_date = self.task._end.format('MMM D'), heading = `${self.task.name}: ${start_date} - ${end_date}`; const $heading = popover_group .select('.details-heading') .attr('text', heading); const bbox = $heading.getBBox(); details_box.select('.details-container') .attr({ width: bbox.width + 20 }); const duration = self.task._end.diff(self.task._start, 'days'), body1 = `Duration: ${duration} days`, body2 = self.task.progress ? `Progress: ${self.task.progress}` : ''; const $body = popover_group.selectAll('.details-body'); $body[0].attr('text', body1); $body[1].attr('text', body2); }); self.group.mouseout(() => { setTimeout(() => popover_group.addClass('hide'), 500); }); } function get_details_position() { return { x: self.$bar.getEndX() + 2, y: self.$bar.getY() - 10 }; } function bind_resize() { const { left, right } = get_handles(); left.drag(onmove_left, onstart, onstop_left); right.drag(onmove_right, onstart, onstop_right); function onmove_right(dx, dy) { onmove_handle_right(dx, dy); } function onstop_right() { onstop_handle_right(); } function onmove_left(dx, dy) { onmove_handle_left(dx, dy); } function onstop_left() { onstop_handle_left(); } } function get_handles() { return { left: self.handle_group.select('.handle.left'), right: self.handle_group.select('.handle.right') }; } function bind_drag() { self.bar_group.drag(onmove, onstart, onstop); } function bind_resize_progress() { const bar = self.$bar, bar_progress = self.$bar_progress, handle = self.group.select('.handle.progress'); handle && handle.drag(on_move, on_start, on_stop); function on_move(dx, dy) { if (dx > bar_progress.max_dx) { dx = bar_progress.max_dx; } if (dx < bar_progress.min_dx) { dx = bar_progress.min_dx; } bar_progress.attr('width', bar_progress.owidth + dx); handle.transform(`t{dx},0`); bar_progress.finaldx = dx; } function on_stop() { if (!bar_progress.finaldx) return; progress_changed(); set_action_completed(); } function on_start() { bar_progress.finaldx = 0; bar_progress.owidth = bar_progress.getWidth(); bar_progress.min_dx = -bar_progress.getWidth(); bar_progress.max_dx = bar.getWidth() - bar_progress.getWidth(); } } function onstart() { const bar = self.$bar; bar.ox = bar.getX(); bar.oy = bar.getY(); bar.owidth = bar.getWidth(); bar.finaldx = 0; run_method_for_dependencies('onstart'); } self.onstart = onstart; function onmove(dx, dy) { const bar = self.$bar; bar.finaldx = get_snap_position(dx); update_bar_position(bar.ox + bar.finaldx); run_method_for_dependencies('onmove', [dx, dy]); } self.onmove = onmove; function onstop() { const bar = self.$bar; if (!bar.finaldx) return; date_changed(); set_action_completed(); run_method_for_dependencies('onstop'); } self.onstop = onstop; function onmove_handle_left(dx, dy) { const bar = self.$bar; bar.finaldx = get_snap_position(dx); update_bar_position(bar.ox + bar.finaldx, bar.owidth - bar.finaldx); run_method_for_dependencies('onmove_handle_left', [dx, dy]); } self.onmove_handle_left = onmove_handle_left; function onstop_handle_left() { const bar = self.$bar; if (bar.finaldx) date_changed(); set_action_completed(); run_method_for_dependencies('onstop_handle_left'); } self.onstop_handle_left = onstop_handle_left; function run_method_for_dependencies(fn, args) { const dm = gt.dependency_map; if (dm[self.task.id]) { for (let deptask of dm[self.task.id]) { const dt = gt.get_bar(deptask); dt[fn].apply(dt, args); } } } function onmove_handle_right(dx, dy) { const bar = self.$bar; bar.finaldx = get_snap_position(dx); update_bar_position(null, bar.owidth + bar.finaldx); } function onstop_handle_right() { const bar = self.$bar; if (bar.finaldx) date_changed(); set_action_completed(); } function update_bar_position(x, width) { const bar = self.$bar; if (x) update_attr(bar, 'x', x); if (width) update_attr(bar, 'width', width); update_label_position(); update_handle_position(); update_progressbar_position(); update_arrow_position(); update_details_position(); } function setup_click_event() { self.group.click(function () { if (self.action_completed) { // just finished a move action, wait for a few seconds return; } if (self.group.hasClass('active')) { gt.trigger_event('click', [self.task]); } unselect_all(); self.group.toggleClass('active'); }); } function date_changed() { gt.trigger_event('date_change', [self.task, compute_start_date(), compute_end_date()]); } function progress_changed() { gt.trigger_event('progress_change', [self.task, compute_progress()]); } function set_action_completed() { self.action_completed = true; setTimeout(() => self.action_completed = false, 2000); } function compute_start_date() { const bar = self.$bar, shift = (bar.getX() - compute_x()) / gt.config.column_width, new_start_date = self.task._start.clone().add(gt.config.step * shift, 'hours'); return new_start_date; } function compute_end_date() { const bar = self.$bar, og_x = compute_x() + self.duration * gt.config.column_width, final_x = bar.getEndX(), shift = (final_x - og_x) / gt.config.column_width, new_end_date = self.task._end.clone().add(gt.config.step * shift, 'hours'); return new_end_date; } function compute_progress() { return self.$bar_progress.getWidth() / self.$bar.getWidth() * 100; } function compute_x() { let x = self.task._start.diff(gt.gantt_start, 'hours') / gt.config.step * gt.config.column_width; if (gt.view_is('Month')) { x = self.task._start.diff(gt.config.start, 'days') * gt.config.column_width / 30; } return x; } function compute_y() { return gt.config.header_height + gt.config.padding + self.task._index * (self.height + gt.config.padding); } function get_snap_position(dx) { let odx = dx, rem, position; if (gt.view_is('Week')) { rem = dx % (gt.config.column_width / 7); position = odx - rem + ((rem < gt.config.column_width / 14) ? 0 : gt.config.column_width / 7); } else if (gt.view_is('Month')) { rem = dx % (gt.config.column_width / 30); position = odx - rem + ((rem < gt.config.column_width / 60) ? 0 : gt.config.column_width / 30); } else { rem = dx % gt.config.column_width; position = odx - rem + ((rem < gt.config.column_width / 2) ? 0 : gt.config.column_width); } return position; } function update_attr(element, attr, value) { value = +value; if (!isNaN(value)) { element.attr(attr, value); } return element; } function update_progressbar_position() { self.$bar_progress.attr('x', self.$bar.getX()); self.$bar_progress.attr('width', self.$bar.getWidth() * (self.task.progress / 100)); } function update_label_position() { const bar = self.$bar, label = self.group.select('.bar-label'); if (label.getBBox().width > bar.getWidth()) { label.addClass('big').attr('x', bar.getX() + bar.getWidth() + 5); } else { label.removeClass('big').attr('x', bar.getX() + bar.getWidth() / 2); } } function update_handle_position() { const bar = self.$bar; self.handle_group.select('.handle.left').attr({ 'x': bar.getX() + 1 }); self.handle_group.select('.handle.right').attr({ 'x': bar.getEndX() - 9 }); } function update_arrow_position() { for (let arrow of self.arrows) { arrow.update(); } } function update_details_position() { const details_box = gt.element_groups.details.select('.details-wrapper'); const pos = get_details_position(); details_box && details_box.transform(`t${pos.x},${pos.y}`); } function unselect_all() { gt.canvas.selectAll('.bar-wrapper').forEach(function (el) { el.removeClass('active'); }); } init(); return self; }