feat: replace tooltip with popup
This commit is contained in:
parent
a3c9f15cf9
commit
350dc88785
126
src/bar.js
126
src/bar.js
@ -136,7 +136,6 @@ export default class Bar {
|
||||
draw_progress_bar() {
|
||||
if (this.invalid) return;
|
||||
this.progress_width = this.calculate_progress_width();
|
||||
|
||||
this.$bar_progress = createSVG('rect', {
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
@ -157,7 +156,7 @@ export default class Bar {
|
||||
this.gantt.config.column_width;
|
||||
|
||||
let $date_highlight = this.gantt.create_el({
|
||||
classes: `date-highlight highlight-${this.task.id}`,
|
||||
classes: `date-highlight hide highlight-${this.task.id}`,
|
||||
width: this.width,
|
||||
left: x,
|
||||
});
|
||||
@ -172,11 +171,18 @@ export default class Bar {
|
||||
const ignored_end = this.x + width;
|
||||
const total_ignored_area =
|
||||
this.gantt.config.ignored_positions.reduce((acc, val) => {
|
||||
if (this.task._index === 2)
|
||||
console.log('IN', val >= this.x, val < ignored_end);
|
||||
return acc + (val >= this.x && val < ignored_end);
|
||||
}, 0) * this.gantt.config.column_width;
|
||||
let progress_width =
|
||||
((width - total_ignored_area) * this.task.progress) / 100;
|
||||
|
||||
console.log(
|
||||
this.task,
|
||||
this.gantt.config.ignored_positions.reduce((acc, val) => {
|
||||
return acc + (val >= this.x && val < ignored_end);
|
||||
}, 0),
|
||||
);
|
||||
const progress_end = this.x + progress_width;
|
||||
const total_ignored_progress =
|
||||
this.gantt.config.ignored_positions.reduce((acc, val) => {
|
||||
@ -304,6 +310,22 @@ export default class Bar {
|
||||
this.setup_click_event();
|
||||
}
|
||||
|
||||
toggle_popup(e) {
|
||||
if (
|
||||
!this.gantt.popup ||
|
||||
this.gantt.popup.parent.classList.contains('hide')
|
||||
) {
|
||||
this.gantt.show_popup({
|
||||
x: e.offsetX || e.layerX,
|
||||
y: e.offsetY || e.layerY,
|
||||
task: this.task,
|
||||
target: this.$bar,
|
||||
});
|
||||
} else {
|
||||
this.gantt.popup.hide();
|
||||
}
|
||||
}
|
||||
|
||||
setup_click_event() {
|
||||
let task_id = this.task.id;
|
||||
$.on(this.group, 'mouseover', (e) => {
|
||||
@ -316,40 +338,38 @@ export default class Bar {
|
||||
});
|
||||
|
||||
if (this.gantt.options.popup_on === 'click') {
|
||||
let opened = false;
|
||||
$.on(this.group, 'click', (e) => {
|
||||
if (!opened) {
|
||||
this.show_popup(e.offsetX || e.layerX);
|
||||
this.gantt.$container.querySelector(
|
||||
`.highlight-${task_id}`,
|
||||
).style.display = 'block';
|
||||
} else {
|
||||
this.gantt.hide_popup();
|
||||
}
|
||||
opened = !opened;
|
||||
console.log('CLICKED');
|
||||
this.toggle_popup(e);
|
||||
this.gantt.$container
|
||||
.querySelector(`.highlight-${task_id}`)
|
||||
.classList.toggle('hide');
|
||||
});
|
||||
} else {
|
||||
let timeout;
|
||||
$.on(
|
||||
this.group,
|
||||
'mouseenter',
|
||||
(e) =>
|
||||
(timeout = setTimeout(() => {
|
||||
this.show_popup(e.offsetX || e.layerX);
|
||||
this.gantt.$container.querySelector(
|
||||
`.highlight-${task_id}`,
|
||||
).style.display = 'block';
|
||||
}, 200)),
|
||||
);
|
||||
|
||||
$.on(this.group, 'mouseleave', () => {
|
||||
clearTimeout(timeout);
|
||||
this.gantt.popup?.hide?.();
|
||||
|
||||
this.gantt.$container.querySelector(
|
||||
`.highlight-${task_id}`,
|
||||
).style.display = 'none';
|
||||
});
|
||||
// let timeout;
|
||||
// $.on(
|
||||
// this.group,
|
||||
// 'mouseenter',
|
||||
// (e) =>
|
||||
// (timeout = setTimeout(() => {
|
||||
// this.gantt.show_popup({
|
||||
// x: e.offsetX || e.layerX,
|
||||
// y: e.offsetY || e.layerY,
|
||||
// task: this.task,
|
||||
// target: this.$bar,
|
||||
// });
|
||||
// this.gantt.$container.querySelector(
|
||||
// `.highlight-${task_id}`,
|
||||
// ).style.display = 'block';
|
||||
// }, 200)),
|
||||
// );
|
||||
// $.on(this.group, 'mouseleave', () => {
|
||||
// clearTimeout(timeout);
|
||||
// this.gantt.popup?.hide?.();
|
||||
// this.gantt.$container.querySelector(
|
||||
// `.highlight-${task_id}`,
|
||||
// ).style.display = 'none';
|
||||
// });
|
||||
}
|
||||
|
||||
$.on(this.group, 'click', () => {
|
||||
@ -363,36 +383,12 @@ export default class Bar {
|
||||
}
|
||||
this.group.classList.remove('active');
|
||||
if (this.gantt.popup)
|
||||
this.gantt.popup.parent.classList.remove('hidden');
|
||||
this.gantt.popup.parent.classList.remove('hide');
|
||||
|
||||
this.gantt.trigger_event('double_click', [this.task]);
|
||||
});
|
||||
}
|
||||
|
||||
show_popup(x) {
|
||||
if (this.gantt.bar_being_dragged) return;
|
||||
|
||||
const start_date = date_utils.format(
|
||||
this.task._start,
|
||||
'MMM D',
|
||||
this.gantt.options.language,
|
||||
);
|
||||
const end_date = date_utils.format(
|
||||
date_utils.add(this.task._end, -1, 'second'),
|
||||
'MMM D',
|
||||
this.gantt.options.language,
|
||||
);
|
||||
|
||||
const subtitle = `${start_date} - ${end_date} (${this.actual_duration_in_days} days)<br/>Progress: ${this.task.progress}`;
|
||||
this.gantt.show_popup({
|
||||
x,
|
||||
target_element: this.$bar,
|
||||
title: this.task.name,
|
||||
subtitle: subtitle,
|
||||
task: this.task,
|
||||
});
|
||||
}
|
||||
|
||||
update_bar_position({ x = null, width = null }) {
|
||||
const bar = this.$bar;
|
||||
|
||||
@ -541,7 +537,7 @@ export default class Bar {
|
||||
if (progress < 0) return 0;
|
||||
const total =
|
||||
this.$bar.getWidth() -
|
||||
this.ignored_duration * this.gantt.config.column_width;
|
||||
this.ignored_duration_raw * this.gantt.config.column_width;
|
||||
return parseInt((progress / total) * 100, 10);
|
||||
}
|
||||
|
||||
@ -617,7 +613,8 @@ export default class Bar {
|
||||
actual_duration_in_days++;
|
||||
}
|
||||
}
|
||||
this.actual_duration_in_days = actual_duration_in_days;
|
||||
this.task.actual_duration = actual_duration_in_days;
|
||||
this.task.ignored_duration = duration_in_days - actual_duration_in_days;
|
||||
|
||||
this.duration =
|
||||
date_utils.convert_scales(
|
||||
@ -625,12 +622,13 @@ export default class Bar {
|
||||
this.gantt.config.unit,
|
||||
) / this.gantt.config.step;
|
||||
|
||||
this.actual_duration =
|
||||
this.actual_duration_raw =
|
||||
date_utils.convert_scales(
|
||||
actual_duration_in_days + 'd',
|
||||
this.gantt.config.unit,
|
||||
) / this.gantt.config.step;
|
||||
this.ignored_duration = this.duration - this.actual_duration;
|
||||
|
||||
this.ignored_duration_raw = this.duration - this.actual_duration_raw;
|
||||
}
|
||||
|
||||
update_attr(element, attr, value) {
|
||||
@ -648,7 +646,7 @@ export default class Bar {
|
||||
this.$expected_bar_progress.setAttribute(
|
||||
'width',
|
||||
this.gantt.config.column_width *
|
||||
this.actual_duration *
|
||||
this.actual_duration_raw *
|
||||
(this.expected_progress / 100) || 0,
|
||||
);
|
||||
}
|
||||
|
||||
@ -122,8 +122,47 @@ const DEFAULT_OPTIONS = {
|
||||
lines: 'both',
|
||||
move_dependencies: true,
|
||||
padding: 18,
|
||||
popup: null,
|
||||
popup_on: 'hover',
|
||||
popup: (ctx) => {
|
||||
ctx.set_title(ctx.task.name);
|
||||
if (ctx.task.description) ctx.set_subtitle(ctx.task.description);
|
||||
else ctx.set_subtitle('');
|
||||
|
||||
const start_date = date_utils.format(
|
||||
ctx.task._start,
|
||||
'MMM D',
|
||||
ctx.chart.options.language,
|
||||
);
|
||||
const end_date = date_utils.format(
|
||||
date_utils.add(ctx.task._end, -1, 'second'),
|
||||
'MMM D',
|
||||
ctx.chart.options.language,
|
||||
);
|
||||
|
||||
ctx.set_details(
|
||||
`${start_date} - ${end_date} (${ctx.task.actual_duration} days${ctx.task.ignored_duration ? ' + ' + ctx.task.ignored_duration + ' excluded' : ''})<br/>Progress: ${ctx.task.progress}%`,
|
||||
);
|
||||
|
||||
ctx.add_action('Toggle Priority', (task, chart) => {
|
||||
task.important = !task.important;
|
||||
chart.refresh(
|
||||
chart.tasks.map((t) => (t.id !== task.id ? t : task)),
|
||||
);
|
||||
});
|
||||
|
||||
ctx.add_action('+', (task, chart) => {
|
||||
task.progress += (1 / task.actual_duration) * 100;
|
||||
chart.refresh(
|
||||
chart.tasks.map((t) => (t.id !== task.id ? t : task)),
|
||||
);
|
||||
});
|
||||
ctx.add_action('-', (task, chart) => {
|
||||
task.progress -= (1 / task.actual_duration) * 100;
|
||||
chart.refresh(
|
||||
chart.tasks.map((t) => (t.id !== task.id ? t : task)),
|
||||
);
|
||||
});
|
||||
},
|
||||
popup_on: 'click',
|
||||
readonly_progress: false,
|
||||
readonly_dates: false,
|
||||
readonly: false,
|
||||
|
||||
16
src/index.js
16
src/index.js
@ -669,6 +669,7 @@ export default class Gantt {
|
||||
|
||||
make_grid_highlights() {
|
||||
this.highlightHolidays();
|
||||
this.config.ignored_positions = [];
|
||||
|
||||
const height =
|
||||
(this.options.bar_height + this.options.padding) *
|
||||
@ -1028,7 +1029,7 @@ export default class Gantt {
|
||||
|
||||
bar_wrapper.classList.add('active');
|
||||
|
||||
if (this.popup) this.popup.parent.classList.add('hidden');
|
||||
if (this.popup) this.popup.hide();
|
||||
|
||||
x_on_start = e.offsetX || e.layerX;
|
||||
y_on_start = e.offsetY || e.layerY;
|
||||
@ -1360,7 +1361,6 @@ export default class Gantt {
|
||||
this.options.snap_at || this.config.view_mode.default_snap || '1d';
|
||||
|
||||
if (default_snap !== 'unit') {
|
||||
console.log(default_snap);
|
||||
const { duration, scale } = date_utils.parse_duration(default_snap);
|
||||
unit_length =
|
||||
date_utils.convert_scales(this.config.view_mode.step, scale) /
|
||||
@ -1404,7 +1404,7 @@ export default class Gantt {
|
||||
[...this.$svg.querySelectorAll('.bar-wrapper')].forEach((el) => {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
if (this.popup) this.popup.parent.classList.remove('hidden');
|
||||
if (this.popup) this.popup.parent.classList.remove('hide');
|
||||
}
|
||||
|
||||
view_is(modes) {
|
||||
@ -1431,12 +1431,16 @@ export default class Gantt {
|
||||
});
|
||||
}
|
||||
|
||||
show_popup(options) {
|
||||
show_popup(opts) {
|
||||
if (this.options.popup === false) return;
|
||||
if (!this.popup) {
|
||||
this.popup = new Popup(this.$popup_wrapper, this.options.popup);
|
||||
this.popup = new Popup(
|
||||
this.$popup_wrapper,
|
||||
this.options.popup,
|
||||
this,
|
||||
);
|
||||
}
|
||||
this.popup.show(options);
|
||||
this.popup.show(opts);
|
||||
}
|
||||
|
||||
hide_popup() {
|
||||
|
||||
72
src/popup.js
72
src/popup.js
@ -1,7 +1,9 @@
|
||||
export default class Popup {
|
||||
constructor(parent, custom_html) {
|
||||
constructor(parent, popup_func, gantt) {
|
||||
this.parent = parent;
|
||||
this.custom_html = custom_html;
|
||||
this.popup_func = popup_func;
|
||||
this.gantt = gantt;
|
||||
|
||||
this.make();
|
||||
}
|
||||
|
||||
@ -9,55 +11,53 @@ export default class Popup {
|
||||
this.parent.innerHTML = `
|
||||
<div class="title"></div>
|
||||
<div class="subtitle"></div>
|
||||
<div class="pointer"></div>
|
||||
<div class="details"></div>
|
||||
<div class="actions"></div>
|
||||
`;
|
||||
|
||||
this.hide();
|
||||
|
||||
this.title = this.parent.querySelector('.title');
|
||||
this.subtitle = this.parent.querySelector('.subtitle');
|
||||
this.pointer = this.parent.querySelector('.pointer');
|
||||
this.details = this.parent.querySelector('.details');
|
||||
this.actions = this.parent.querySelector('.actions');
|
||||
}
|
||||
|
||||
show(options) {
|
||||
if (!options.target_element) {
|
||||
throw new Error('target_element is required to show popup');
|
||||
}
|
||||
const target_element = options.target_element;
|
||||
show({ x, y, task, target }) {
|
||||
this.actions.innerHTML = '';
|
||||
let html = this.popup_func({
|
||||
task,
|
||||
chart: this.gantt,
|
||||
set_title: (title) => (this.title.innerHTML = title),
|
||||
set_subtitle: (subtitle) => (this.subtitle.innerHTML = subtitle),
|
||||
set_details: (details) => (this.details.innerHTML = details),
|
||||
add_action: (html, func) => {
|
||||
let action = this.gantt.create_el({
|
||||
classes: 'action-btn',
|
||||
type: 'button',
|
||||
append_to: this.actions,
|
||||
});
|
||||
if (typeof html === 'function') html = html(task);
|
||||
action.innerHTML = html;
|
||||
action.onclick = (e) => func(task, this.gantt, e);
|
||||
},
|
||||
});
|
||||
|
||||
if (this.custom_html) {
|
||||
let html = this.custom_html(options.task);
|
||||
html += '<div class="pointer"></div>';
|
||||
this.parent.innerHTML = html;
|
||||
this.pointer = this.parent.querySelector('.pointer');
|
||||
} else {
|
||||
// set data
|
||||
this.title.innerHTML = options.title;
|
||||
this.subtitle.innerHTML = options.subtitle;
|
||||
}
|
||||
if (html) this.parent.innerHTML = html;
|
||||
|
||||
// set position
|
||||
let position_meta;
|
||||
if (target_element instanceof HTMLElement) {
|
||||
position_meta = target_element.getBoundingClientRect();
|
||||
} else if (target_element instanceof SVGElement) {
|
||||
position_meta = options.target_element.getBBox();
|
||||
if (target instanceof HTMLElement) {
|
||||
position_meta = target.getBoundingClientRect();
|
||||
} else if (target instanceof SVGElement) {
|
||||
position_meta = target.getBBox();
|
||||
}
|
||||
|
||||
this.parent.style.left = options.x - this.parent.clientWidth / 2 + 'px';
|
||||
this.parent.style.top =
|
||||
position_meta.y + position_meta.height + 10 + 'px';
|
||||
|
||||
this.parent.classList.remove('hidden');
|
||||
this.pointer.style.left = this.parent.clientWidth / 2 + 'px';
|
||||
this.pointer.style.top = '-10px';
|
||||
|
||||
// show
|
||||
this.parent.style.opacity = 1;
|
||||
this.parent.style.left = x + 10 + 'px';
|
||||
this.parent.style.top = y - 10 + 'px';
|
||||
this.parent.classList.remove('hide');
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.parent.style.opacity = 0;
|
||||
this.parent.style.left = 0;
|
||||
this.parent.classList.add('hide');
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,9 +91,5 @@
|
||||
& .title {
|
||||
border-color: lighten(var(--g-blue-dark, 5));
|
||||
}
|
||||
|
||||
& .pointer {
|
||||
border-top-color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,32 +13,56 @@
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: #171b1f;
|
||||
background: #fff;
|
||||
box-shadow: 0px 10px 24px -3px rgba(0, 0, 0, 0.2);
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
width: max-content;
|
||||
z-index: 1000;
|
||||
|
||||
&.hidden {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
||||
& .title {
|
||||
margin-bottom: 5px;
|
||||
text-align: -webkit-center;
|
||||
text-align: center;
|
||||
color: var(--g-text-light);
|
||||
margin-bottom: 2px;
|
||||
color: var(--g-text-dark);
|
||||
font-size: 0.85rem;
|
||||
font-weight: 650;
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
& .subtitle {
|
||||
color: var(--g-text-secondary);
|
||||
color: var(--g-text-dark);
|
||||
font-size: 0.8rem;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
& .pointer {
|
||||
position: absolute;
|
||||
height: 5px;
|
||||
border: 5px solid transparent;
|
||||
border-bottom-color: rgba(0, 0, 0, 0.8);
|
||||
& .details {
|
||||
color: var(--g-text-muted);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
& .actions {
|
||||
margin-top: 10px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
& .action-btn {
|
||||
border: none;
|
||||
padding: 5px 8px;
|
||||
background-color: #dbeafe;
|
||||
border-right: 1px solid var(--g-text-light);
|
||||
|
||||
&:hover {
|
||||
background-color: #93c5fd;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-top-left-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,7 +164,6 @@
|
||||
var(--gv-upper-header-height) + var(--gv-lower-header-height) * 0.1
|
||||
);
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
|
||||
& .current-highlight {
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
--g-border-color: #ebeff2;
|
||||
--g-text-muted: #7c7c7c;
|
||||
--g-text-light: #fff;
|
||||
--g-text-secondary: #98a1a9;
|
||||
--g-text-dark: #171717;
|
||||
--g-progress-color: #ebeef0;
|
||||
--g-handle-color: #dcdce4;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user