@workiom/frappe-gantt 1.0.21 → 1.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/arrow.js CHANGED
@@ -9,6 +9,7 @@ export default class Arrow {
9
9
  this.is_critical = this.check_critical_path();
10
10
  this.is_invalid = this.check_invalid_dependency();
11
11
  this.is_hovered = false;
12
+ this.is_active = false;
12
13
 
13
14
  this.calculate_path();
14
15
  this.draw();
@@ -76,47 +77,64 @@ export default class Arrow {
76
77
  this.path = this._path_finish_to_start(
77
78
  right_A, left_A, right_B, left_B, y_A, y_B, y_mid, padding, curve
78
79
  );
80
+ this.label_pos = { x: left_B, y: y_B, side: 'left' };
79
81
  break;
80
82
  case 'start-to-start':
81
83
  this.path = this._path_start_to_start(
82
84
  left_A, left_B, y_A, y_B, padding, curve
83
85
  );
86
+ this.label_pos = { x: left_B, y: y_B, side: 'left' };
84
87
  break;
85
88
  case 'finish-to-finish':
86
89
  this.path = this._path_finish_to_finish(
87
90
  right_A, right_B, y_A, y_B, padding, curve
88
91
  );
92
+ this.label_pos = { x: right_B, y: y_B, side: 'right' };
89
93
  break;
90
94
  case 'start-to-finish':
91
95
  this.path = this._path_start_to_finish(
92
96
  left_A, right_B, y_A, y_B, y_mid, padding, curve
93
97
  );
98
+ this.label_pos = { x: right_B, y: y_B, side: 'right' };
94
99
  break;
95
100
  default:
96
101
  this.path = this._path_finish_to_start(
97
102
  right_A, left_A, right_B, left_B, y_A, y_B, y_mid, padding, curve
98
103
  );
104
+ this.label_pos = { x: left_B, y: y_B, side: 'left' };
99
105
  }
100
106
  }
101
107
 
102
108
  _path_finish_to_start(right_A, left_A, right_B, left_B, y_A, y_B, y_mid, padding, curve) {
103
109
  const x_right = right_A + padding;
110
+ const going_up = y_B < y_A;
104
111
 
105
112
  if (x_right < left_B) {
106
- // Case 1: space between tasks — 3 segments: right, down, right
113
+ // Case 1: gap between tasks — 3 segments: right, vertical, right
114
+ if (!going_up) {
115
+ return `
116
+ M ${right_A} ${y_A}
117
+ H ${x_right - curve}
118
+ a ${curve} ${curve} 0 0 1 ${curve} ${curve}
119
+ V ${y_B - curve}
120
+ a ${curve} ${curve} 0 0 0 ${curve} ${curve}
121
+ H ${left_B}
122
+ m -5 -5 l 5 5 l -5 5`;
123
+ }
107
124
  return `
108
- M ${right_A} ${y_A}
109
- H ${x_right - curve}
110
- a ${curve} ${curve} 0 0 1 ${curve} ${curve}
111
- V ${y_B - curve}
112
- a ${curve} ${curve} 0 0 0 ${curve} ${curve}
113
- H ${left_B}
114
- m -5 -5 l 5 5 l -5 5`;
125
+ M ${right_A} ${y_A}
126
+ H ${x_right - curve}
127
+ a ${curve} ${curve} 0 0 0 ${curve} ${-curve}
128
+ V ${y_B + curve}
129
+ a ${curve} ${curve} 0 0 1 ${curve} ${-curve}
130
+ H ${left_B}
131
+ m -5 -5 l 5 5 l -5 5`;
115
132
  }
116
133
 
117
- // Case 2: overlap — 5 segments: right, down, left, down, right
134
+ // Case 2: overlap — 5 segments: right, vertical, left, vertical, right
118
135
  const x_left = left_B - padding;
119
- return `
136
+ if (!going_up) {
137
+ return `
120
138
  M ${right_A} ${y_A}
121
139
  H ${x_right - curve}
122
140
  a ${curve} ${curve} 0 0 1 ${curve} ${curve}
@@ -128,12 +146,27 @@ export default class Arrow {
128
146
  a ${curve} ${curve} 0 0 0 ${curve} ${curve}
129
147
  H ${left_B}
130
148
  m -5 -5 l 5 5 l -5 5`;
149
+ }
150
+ return `
151
+ M ${right_A} ${y_A}
152
+ H ${x_right - curve}
153
+ a ${curve} ${curve} 0 0 0 ${curve} ${-curve}
154
+ V ${y_mid + curve}
155
+ a ${curve} ${curve} 0 0 0 ${-curve} ${-curve}
156
+ H ${x_left + curve}
157
+ a ${curve} ${curve} 0 0 1 ${-curve} ${-curve}
158
+ V ${y_B + curve}
159
+ a ${curve} ${curve} 0 0 1 ${curve} ${-curve}
160
+ H ${left_B}
161
+ m -5 -5 l 5 5 l -5 5`;
131
162
  }
132
163
 
133
164
  _path_start_to_start(left_A, left_B, y_A, y_B, padding, curve) {
134
165
  const x_left = Math.min(left_A, left_B) - padding;
166
+ const going_up = y_B < y_A;
135
167
 
136
- return `
168
+ if (!going_up) {
169
+ return `
137
170
  M ${left_A} ${y_A}
138
171
  H ${x_left + curve}
139
172
  a ${curve} ${curve} 0 0 0 ${-curve} ${curve}
@@ -141,12 +174,23 @@ export default class Arrow {
141
174
  a ${curve} ${curve} 0 0 0 ${curve} ${curve}
142
175
  H ${left_B}
143
176
  m -5 -5 l 5 5 l -5 5`;
177
+ }
178
+ return `
179
+ M ${left_A} ${y_A}
180
+ H ${x_left + curve}
181
+ a ${curve} ${curve} 0 0 1 ${-curve} ${-curve}
182
+ V ${y_B + curve}
183
+ a ${curve} ${curve} 0 0 1 ${curve} ${-curve}
184
+ H ${left_B}
185
+ m -5 -5 l 5 5 l -5 5`;
144
186
  }
145
187
 
146
188
  _path_finish_to_finish(right_A, right_B, y_A, y_B, padding, curve) {
147
189
  const x_right = Math.max(right_A, right_B) + padding;
190
+ const going_up = y_B < y_A;
148
191
 
149
- return `
192
+ if (!going_up) {
193
+ return `
150
194
  M ${right_A} ${y_A}
151
195
  H ${x_right - curve}
152
196
  a ${curve} ${curve} 0 0 1 ${curve} ${curve}
@@ -154,13 +198,27 @@ export default class Arrow {
154
198
  a ${curve} ${curve} 0 0 1 ${-curve} ${curve}
155
199
  H ${right_B}
156
200
  m 5 -5 l -5 5 l 5 5`;
201
+ }
202
+ return `
203
+ M ${right_A} ${y_A}
204
+ H ${x_right - curve}
205
+ a ${curve} ${curve} 0 0 0 ${curve} ${-curve}
206
+ V ${y_B + curve}
207
+ a ${curve} ${curve} 0 0 0 ${-curve} ${-curve}
208
+ H ${right_B}
209
+ m 5 -5 l -5 5 l 5 5`;
157
210
  }
158
211
 
159
212
  _path_start_to_finish(left_A, right_B, y_A, y_B, y_mid, padding, curve) {
160
213
  const x_left = left_A - padding;
161
214
  const x_right = right_B + padding;
215
+ const going_up = y_B < y_A;
216
+ // crossed: from-task is to the right of to-task — not enough room for mid-column detour
217
+ const crossed = x_right < x_left + 2 * curve;
162
218
 
163
- return `
219
+ if (!crossed) {
220
+ if (!going_up) {
221
+ return `
164
222
  M ${left_A} ${y_A}
165
223
  H ${x_left + curve}
166
224
  a ${curve} ${curve} 0 0 0 ${-curve} ${curve}
@@ -172,6 +230,39 @@ export default class Arrow {
172
230
  a ${curve} ${curve} 0 0 1 ${-curve} ${curve}
173
231
  H ${right_B}
174
232
  m 5 -5 l -5 5 l 5 5`;
233
+ }
234
+ return `
235
+ M ${left_A} ${y_A}
236
+ H ${x_left + curve}
237
+ a ${curve} ${curve} 0 0 1 ${-curve} ${-curve}
238
+ V ${y_mid + curve}
239
+ a ${curve} ${curve} 0 0 1 ${curve} ${-curve}
240
+ H ${x_right - curve}
241
+ a ${curve} ${curve} 0 0 0 ${curve} ${-curve}
242
+ V ${y_B + curve}
243
+ a ${curve} ${curve} 0 0 0 ${-curve} ${-curve}
244
+ H ${right_B}
245
+ m 5 -5 l -5 5 l 5 5`;
246
+ }
247
+
248
+ if (!going_up) {
249
+ return `
250
+ M ${left_A} ${y_A}
251
+ H ${x_left + curve}
252
+ a ${curve} ${curve} 0 0 0 ${-curve} ${curve}
253
+ V ${y_B - curve}
254
+ a ${curve} ${curve} 0 0 1 ${-curve} ${curve}
255
+ H ${right_B}
256
+ m 5 -5 l -5 5 l 5 5`;
257
+ }
258
+ return `
259
+ M ${left_A} ${y_A}
260
+ H ${x_left + curve}
261
+ a ${curve} ${curve} 0 0 1 ${-curve} ${-curve}
262
+ V ${y_B + curve}
263
+ a ${curve} ${curve} 0 0 0 ${-curve} ${-curve}
264
+ H ${right_B}
265
+ m 5 -5 l -5 5 l 5 5`;
175
266
  }
176
267
 
177
268
  _get_connected_bars() {
@@ -220,6 +311,7 @@ export default class Arrow {
220
311
  const bar = el.querySelector('.bar');
221
312
  if (bar) bar.classList.add(bar_class);
222
313
  });
314
+ this._show_label();
223
315
  });
224
316
 
225
317
  this.hit_element.addEventListener('mouseleave', () => {
@@ -227,8 +319,106 @@ export default class Arrow {
227
319
  this.element.classList.remove('arrow-hover');
228
320
  this._get_connected_bars().forEach(el => {
229
321
  const bar = el.querySelector('.bar');
230
- if (bar) bar.classList.remove('bar-arrow-hover', 'bar-arrow-critical', 'bar-arrow-invalid');
322
+ if (bar) {
323
+ bar.classList.remove('bar-arrow-hover');
324
+ if (!this.is_active) {
325
+ bar.classList.remove('bar-arrow-critical', 'bar-arrow-invalid');
326
+ }
327
+ }
231
328
  });
329
+ if (!this.is_active && !this.is_hovered) this._hide_label();
330
+ });
331
+
332
+ this.hit_element.addEventListener('click', (e) => {
333
+ e.stopPropagation();
334
+ if (this.is_active) {
335
+ this.gantt.set_active_arrow(null);
336
+ } else {
337
+ this.gantt.set_active_arrow(this);
338
+ }
339
+ });
340
+ }
341
+
342
+ _get_type_abbr() {
343
+ const map = {
344
+ 'finish-to-start': 'FS',
345
+ 'start-to-start': 'SS',
346
+ 'finish-to-finish': 'FF',
347
+ 'start-to-finish': 'SF',
348
+ };
349
+ return map[this.dependency_type] || 'FS';
350
+ }
351
+
352
+ _show_label() {
353
+ if (this.label_element) return;
354
+ const abbr = this._get_type_abbr();
355
+ const w = 21;
356
+ const h = 20;
357
+ const { x: tip_x, y: tip_y, side } = this.label_pos;
358
+ const cx = side === 'left'
359
+ ? tip_x - 10 - w / 2
360
+ : tip_x + 10 + w / 2;
361
+ const cy = tip_y;
362
+
363
+ this.label_element = createSVG('g', { class: 'arrow-type-label' });
364
+ const bg = createSVG('rect', {
365
+ x: cx - w / 2,
366
+ y: cy - h / 2,
367
+ width: w,
368
+ height: h,
369
+ rx: 3,
370
+ });
371
+ const text = createSVG('text', {
372
+ x: cx,
373
+ y: cy,
374
+ 'dominant-baseline': 'middle',
375
+ 'text-anchor': 'middle',
376
+ });
377
+ text.textContent = abbr;
378
+ this.label_element.appendChild(bg);
379
+ this.label_element.appendChild(text);
380
+ this.label_element.addEventListener('mouseenter', () => {
381
+ this.hit_element.dispatchEvent(new MouseEvent('mouseenter', { bubbles: false }));
382
+ });
383
+ this.label_element.addEventListener('mouseleave', () => {
384
+ this.hit_element.dispatchEvent(new MouseEvent('mouseleave', { bubbles: false }));
385
+ });
386
+ this.label_element.addEventListener('click', (e) => {
387
+ e.stopPropagation();
388
+ this.hit_element.dispatchEvent(new MouseEvent('click', { bubbles: false }));
389
+ });
390
+ this.gantt.layers.arrow.appendChild(this.label_element);
391
+ }
392
+
393
+ _hide_label() {
394
+ if (this.label_element) {
395
+ this.label_element.remove();
396
+ this.label_element = null;
397
+ }
398
+ }
399
+
400
+ activate() {
401
+ this.is_active = true;
402
+ this.element.classList.add('arrow-active');
403
+ this._show_label();
404
+ const bar_class = this.is_invalid
405
+ ? 'bar-arrow-invalid'
406
+ : this.is_critical
407
+ ? 'bar-arrow-critical'
408
+ : 'bar-arrow-active';
409
+ this._get_connected_bars().forEach(el => {
410
+ const bar = el.querySelector('.bar');
411
+ if (bar) bar.classList.add(bar_class);
412
+ });
413
+ }
414
+
415
+ deactivate() {
416
+ this.is_active = false;
417
+ this.element.classList.remove('arrow-active');
418
+ this._hide_label();
419
+ this._get_connected_bars().forEach(el => {
420
+ const bar = el.querySelector('.bar');
421
+ if (bar) bar.classList.remove('bar-arrow-active', 'bar-arrow-critical', 'bar-arrow-invalid');
232
422
  });
233
423
  }
234
424
 
@@ -248,6 +438,7 @@ export default class Arrow {
248
438
  arrowClass = 'arrow-critical';
249
439
  }
250
440
  if (this.is_hovered) arrowClass += ' arrow-hover';
441
+ if (this.is_active) arrowClass += ' arrow-active';
251
442
  this.element.setAttribute('class', arrowClass.trim());
252
443
  }
253
444
  }
package/src/bar.js CHANGED
@@ -115,6 +115,7 @@ export default class Bar {
115
115
  if (this.gantt.options.task_add_icon_position) {
116
116
  this.draw_add_task_icon();
117
117
  }
118
+ this.draw_connector_circles();
118
119
  }
119
120
 
120
121
  draw_bar() {
@@ -439,6 +440,48 @@ export default class Bar {
439
440
  }
440
441
  }
441
442
 
443
+ draw_connector_circles() {
444
+ this.$connector_start = null;
445
+ this.$connector_end = null;
446
+ if (!this.gantt.options.allow_dependency_creation) return;
447
+ if (this.gantt.options.readonly) return;
448
+ const isRTL = this.gantt.options.isRTL;
449
+ const cy = this.y + this.height / 2;
450
+ const start_cx = isRTL ? this.x + this.width : this.x;
451
+ const end_cx = isRTL ? this.x : this.x + this.width;
452
+
453
+ this.$connector_start = createSVG('circle', {
454
+ class: 'connector-circle connector-start',
455
+ 'data-endpoint': 'start',
456
+ cx: start_cx,
457
+ cy,
458
+ r: 4,
459
+ append_to: this.handle_group,
460
+ });
461
+ this.$connector_end = createSVG('circle', {
462
+ class: 'connector-circle connector-end',
463
+ 'data-endpoint': 'end',
464
+ cx: end_cx,
465
+ cy,
466
+ r: 4,
467
+ append_to: this.handle_group,
468
+ });
469
+ }
470
+
471
+ update_connector_circles() {
472
+ if (!this.$connector_start) return;
473
+ const isRTL = this.gantt.options.isRTL;
474
+ const bx = this.$bar.getX();
475
+ const bw = this.$bar.getWidth();
476
+ const cy = this.y + this.height / 2;
477
+ const start_cx = isRTL ? bx + bw : bx;
478
+ const end_cx = isRTL ? bx : bx + bw;
479
+ this.$connector_start.setAttribute('cx', start_cx);
480
+ this.$connector_start.setAttribute('cy', cy);
481
+ this.$connector_end.setAttribute('cx', end_cx);
482
+ this.$connector_end.setAttribute('cy', cy);
483
+ }
484
+
442
485
  bind() {
443
486
  if (this.invalid) return;
444
487
  this.setup_click_event();
@@ -653,6 +696,7 @@ export default class Bar {
653
696
 
654
697
  this.update_progressbar_position();
655
698
  this.update_arrow_position();
699
+ this.update_connector_circles();
656
700
  }
657
701
 
658
702
  update_label_position_on_horizontal_scroll({ x, sx }) {
package/src/defaults.js CHANGED
@@ -108,6 +108,7 @@ const DEFAULT_VIEW_MODES = [
108
108
  ];
109
109
 
110
110
  const DEFAULT_OPTIONS = {
111
+ allow_dependency_creation: true,
111
112
  arrow_curve: 5,
112
113
  auto_move_label: false,
113
114
  bar_corner_radius: 3,