@osfarm/itineraire-technique 1.1.8 → 1.1.10

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.
@@ -1 +1 @@
1
- div.mainITKContainer{container-type:inline-size;container-name:myparent;line-height:1rem}div.mainITKContainer .left-transcript{display:none;width:50%;font-family:Segoe UI}div.mainITKContainer .chart-div{width:100%}div.mainITKContainer .bottom-transcript{display:none}div.mainITKContainer.withTranscript .bottom-transcript{display:block;font-family:Segoe UI}@container myparent (min-width: 800px){div.mainITKContainer.withTranscript .left-transcript{display:block}div.mainITKContainer.withTranscript .chart-div{width:50%}div.mainITKContainer.withTranscript .bottom-transcript{display:none}}@container myparent (min-width: 1200px){div.mainITKContainer.withTranscript .left-transcript{display:block;width:40%}div.mainITKContainer.withTranscript .chart-div{width:60%}div.mainITKContainer.withTranscript .bottom-transcript{display:none}}div.mainITKContainer div.rotation_item{background-color:#f8fafc;border-left:#fff 14px solid;border-radius:7px;padding:8px 3px 5px 9px;margin:12px 0;overflow-x:hidden}div.mainITKContainer div.rotation_item.highlighted{box-shadow:0px 4px 4px 0px var(--UI-Shadow, rgba(17, 36, 69, 0.16)),0px 1px 16px 0px var(--UI-Shadow, rgba(17, 36, 69, 0.16));background-color:#f0f3f5}div.mainITKContainer div.rotation_item div.step-header h4{margin-bottom:0;margin-top:0}div.mainITKContainer div.rotation_item div.step-header div.step_dates{font-size:11px;background-color:#cdcccc;color:#fff;margin:5px;padding:2px 5px;border-radius:5px;height:20px;float:right}div.mainITKContainer div.rotation_item div.step-header div.collapse-button{border-radius:50%;width:30px;height:30px;text-align:center;padding:5px 0px;margin:0 5px 3px 0;cursor:pointer;background:#dfe6f7;color:#7a8bad;float:right;transition:transform .3s ease-in-out}div.mainITKContainer div.rotation_item .step_description{clear:both}div.mainITKContainer div.rotation_item.show-all div.collapse-button{transform:rotate(180deg)}div.mainITKContainer div.rotation_item.show-all div.details{max-height:10000px}div.mainITKContainer div.rotation_item div.details{max-height:0px;overflow:hidden;transition:max-height .3s ease-in-out}div.mainITKContainer div.rotation_item div.details div.intervention{background-color:#fff;border-radius:5px;margin-bottom:11px;padding:13px;cursor:pointer}div.mainITKContainer div.rotation_item div.details div.intervention span.intervention_title{font-weight:bold}div.mainITKContainer div.rotation_item div.details div.intervention span.intervention_date{color:#707070;background-color:#f0f3f5;float:right}div.mainITKContainer div.rotation_item div.details div.intervention div.intervention_description{margin-top:5px}div.mainITKContainer div.rotation_item .step-edit{display:none}div.mainITKContainer .charts{width:100%;height:500px;display:inline-block}div.mainITKContainer .transcript{font-size:80%;width:100%;max-height:450px;overflow-y:scroll;scroll-behavior:smooth;padding:3px}.rotation-tooltip{max-width:400px}/*# sourceMappingURL=styles-rendering.css.map */
1
+ div.mainITKContainer{container-type:inline-size;container-name:myparent;line-height:1rem}div.mainITKContainer .left-transcript{display:none;width:50%;font-family:Segoe UI}div.mainITKContainer .chart-div{width:100%}div.mainITKContainer .bottom-transcript{display:none}div.mainITKContainer.withTranscript .bottom-transcript{display:block;font-family:Segoe UI}@container myparent (min-width: 800px){div.mainITKContainer.withTranscript .left-transcript{display:block}div.mainITKContainer.withTranscript .chart-div{width:50%}div.mainITKContainer.withTranscript .bottom-transcript{display:none}}@container myparent (min-width: 1000px){div.mainITKContainer.withTranscript .left-transcript{display:block;width:40%}div.mainITKContainer.withTranscript .chart-div{width:60%}div.mainITKContainer.withTranscript .bottom-transcript{display:none}}div.mainITKContainer div.rotation_item{background-color:#f8fafc;border-left:#fff 14px solid;border-radius:7px;padding:8px 3px 5px 9px;margin:12px 0;overflow-x:hidden}div.mainITKContainer div.rotation_item.highlighted{box-shadow:0px 4px 4px 0px var(--UI-Shadow, rgba(17, 36, 69, 0.16)),0px 1px 16px 0px var(--UI-Shadow, rgba(17, 36, 69, 0.16));background-color:#f0f3f5}div.mainITKContainer div.rotation_item div.step-header h4{font-size:20px;margin-bottom:0;margin-top:0}div.mainITKContainer div.rotation_item div.step-header div.step_dates{font-size:11px;background-color:#cdcccc;color:#000;margin:5px;padding:2px 5px;border-radius:5px;height:20px;float:right}div.mainITKContainer div.rotation_item div.step-header div.collapse-button{border-radius:50%;width:30px;height:30px;text-align:center;padding:5px 0px;margin:0 5px 3px 0;cursor:pointer;background:#dfe6f7;color:#7a8bad;float:right;transition:transform .3s ease-in-out}div.mainITKContainer div.rotation_item .step_description{clear:both}div.mainITKContainer div.rotation_item.show-all div.collapse-button{transform:rotate(180deg)}div.mainITKContainer div.rotation_item.show-all div.details{max-height:10000px}div.mainITKContainer div.rotation_item div.details{max-height:0px;overflow:hidden;transition:max-height .3s ease-in-out}div.mainITKContainer div.rotation_item div.details div.intervention{background-color:#fff;border-radius:5px;margin-bottom:11px;padding:13px;cursor:pointer}div.mainITKContainer div.rotation_item div.details div.intervention span.intervention_title{font-weight:bold}div.mainITKContainer div.rotation_item div.details div.intervention span.intervention_date{color:#707070;background-color:#f0f3f5;float:right}div.mainITKContainer div.rotation_item div.details div.intervention div.intervention_description{margin-top:5px}div.mainITKContainer div.rotation_item .step-edit{display:none}div.mainITKContainer .charts{width:100%;height:500px;display:inline-block}div.mainITKContainer .transcript{font-size:80%;width:100%;max-height:450px;overflow-y:scroll;scroll-behavior:smooth;padding:3px}.rotation-tooltip{max-width:400px;text-align:left}.rotation-tooltip div.step_dates{font-size:9px;background-color:#cdcccc;color:#000;margin:5px;padding:0px 5px;border-radius:5px;float:right}/*# sourceMappingURL=styles-rendering.css.map */
@@ -1 +1 @@
1
- {"version":3,"sourceRoot":"","sources":["../scss/styles-rendering.scss"],"names":[],"mappings":"AAAA,qBACI,2BACA,wBACA,iBAEA,sCACI,aACA,UACA,qBAGJ,gCACI,WAGJ,wCACI,aAIA,uDACI,cACA,qBAIR,uCAEQ,qDACI,cAGJ,+CACI,UAGJ,uDACI,cAKZ,wCAEQ,qDACI,cACA,UAGJ,+CACI,UAGJ,uDACI,cAKZ,uCACI,yBACA,4BACA,kBACA,wBACA,cACA,kBAEA,mDACI,8HAEA,yBAIA,0DACI,gBACA,aAGJ,sEACI,eACA,yBACA,WACA,WACA,gBACA,kBACA,YACA,YAGJ,2EACI,kBACA,WACA,YACA,kBACA,gBACA,mBACA,eACA,mBACA,cACA,YACA,qCAIR,yDACI,WAIA,oEACI,yBAGJ,4DACI,mBAKR,mDACI,eACA,gBACA,sCAEA,oEACI,sBACA,kBACA,mBACA,aACA,eAEA,4FACI,iBAGJ,2FACI,cACA,yBACA,YAGJ,iGACI,eAKZ,kDACI,aAIR,6BACI,WACA,aACA,qBAGJ,iCACI,cACA,WACA,iBACA,kBACA,uBACA,YAIR,kBACI","file":"styles-rendering.css"}
1
+ {"version":3,"sourceRoot":"","sources":["../scss/styles-rendering.scss"],"names":[],"mappings":"AAAA,qBACI,2BACA,wBACA,iBAEA,sCACI,aACA,UACA,qBAGJ,gCACI,WAGJ,wCACI,aAIA,uDACI,cACA,qBAIR,uCAEQ,qDACI,cAGJ,+CACI,UAGJ,uDACI,cAKZ,wCAEQ,qDACI,cACA,UAGJ,+CACI,UAGJ,uDACI,cAKZ,uCACI,yBACA,4BACA,kBACA,wBACA,cACA,kBAEA,mDACI,8HAEA,yBAIA,0DACI,eACA,gBACA,aAGJ,sEACI,eACA,yBACA,WACA,WACA,gBACA,kBACA,YACA,YAGJ,2EACI,kBACA,WACA,YACA,kBACA,gBACA,mBACA,eACA,mBACA,cACA,YACA,qCAIR,yDACI,WAIA,oEACI,yBAGJ,4DACI,mBAKR,mDACI,eACA,gBACA,sCAEA,oEACI,sBACA,kBACA,mBACA,aACA,eAEA,4FACI,iBAGJ,2FACI,cACA,yBACA,YAGJ,iGACI,eAKZ,kDACI,aAIR,6BACI,WACA,aACA,qBAGJ,iCACI,cACA,WACA,iBACA,kBACA,uBACA,YAIR,kBACI,gBACA,gBAEA,iCACI,cACA,yBACA,WACA,WACA,gBACA,kBACA","file":"styles-rendering.css"}
@@ -39,17 +39,35 @@ class RotationRenderer {
39
39
  }
40
40
 
41
41
  fixRotationData(rotationData) {
42
+ if (!rotationData || typeof rotationData !== 'object') return rotationData;
43
+
42
44
  if (rotationData.options == undefined)
43
45
  rotationData.options = {};
44
46
 
45
47
  if (rotationData.options?.view == undefined || rotationData?.options?.view == '')
46
48
  rotationData.options.view = 'horizontal';
47
49
 
50
+ // Safety check for steps array
51
+ if (!rotationData.steps || !Array.isArray(rotationData.steps)) {
52
+ rotationData.steps = [];
53
+ return rotationData;
54
+ }
55
+
48
56
  // Map rotationData items to make sure that the startDate and endDate are proper Date objects
49
57
  rotationData.steps.map((item) => {
58
+ if (!item) return item;
59
+
50
60
  item.startDate = new Date(item.startDate);
51
61
  item.endDate = new Date(item.endDate);
52
62
 
63
+ // Safety check for invalid dates
64
+ if (isNaN(item.startDate.getTime())) {
65
+ item.startDate = new Date();
66
+ }
67
+ if (isNaN(item.endDate.getTime()) || item.endDate <= item.startDate) {
68
+ item.endDate = new Date(item.startDate.getTime() + (30 * 24 * 60 * 60 * 1000)); // Default to 30 days later
69
+ }
70
+
53
71
  // Add a duration in months
54
72
  item.duration = Math.round((item.endDate - item.startDate) / (30 * 1000 * 60 * 60 * 24));
55
73
 
@@ -142,7 +160,7 @@ class RotationRenderer {
142
160
  self.noFocusUpdate = false;
143
161
  }, 1500);
144
162
 
145
- element[0].scrollIntoView({ block: "start" });
163
+ element[0].scrollIntoView({ block: "start", block: "nearest" });
146
164
 
147
165
  self.getVisibleTranscriptDiv().find('.' + params.data.divId).closest('.rotation_item').addClass("show-all");
148
166
 
@@ -256,11 +274,14 @@ class RotationRenderer {
256
274
  let maxDate = null;
257
275
 
258
276
  steps.forEach((item) => {
259
- if (!minDate || minDate > item.startDate.valueOf())
260
- minDate = item.startDate.valueOf();
277
+ const stepStart = item.startDate.valueOf() - 86400000 * 30; // Subtract some space for the start of the arrow
278
+ const stepEnd = item.endDate.valueOf() + 86400000 * 50; // Add some space for the end of the arrow
261
279
 
262
- if (!maxDate || maxDate < item.endDate.valueOf())
263
- maxDate = item.endDate.valueOf() + 86400000 * 30; // Add some space for the end of the arrow
280
+ if (!minDate || minDate > stepStart)
281
+ minDate = stepStart;
282
+
283
+ if (!maxDate || maxDate < stepEnd)
284
+ maxDate = stepEnd;
264
285
  });
265
286
 
266
287
  return { min: minDate, max: maxDate };
@@ -276,6 +297,15 @@ class RotationRenderer {
276
297
  if (item.name == Number(item.name))
277
298
  item.name = "Etape " + item.name; // Force the item name to be a string
278
299
 
300
+ let description = self.getHTMLFormatedDescription(item.description);
301
+ if (item.interventions?.length > 0 || item.attributes?.length > 0) {
302
+ description += '<br/>';
303
+ item.attributes?.forEach(attr => {
304
+ description += `<br><b>${attr.name} :</b> ${attr.value}`;
305
+ });
306
+ }
307
+
308
+
279
309
  data.push({
280
310
  name: item.name,
281
311
  divId: 'Step_' + index,
@@ -283,7 +313,7 @@ class RotationRenderer {
283
313
  startDate: new Date(item.startDate.valueOf()), // Date de début
284
314
  endDate: new Date(item.endDate.valueOf()), // Date de fin
285
315
  duration: item.duration,
286
- description: self.getHTMLFormatedDescription(item.description),
316
+ description: description,
287
317
  value: [
288
318
  1, // Parcelle (index de la série)
289
319
  item.startDate.valueOf(), // Date de début
@@ -340,9 +370,14 @@ class RotationRenderer {
340
370
 
341
371
  // Avoid words that are too long
342
372
  testWidth = echarts.format.getTextRect(line).width;
343
- while (testWidth > maxWidth) {
373
+ let trimCount = 0;
374
+ const maxTrimCount = line.length; // Prevent infinite loop
375
+
376
+ while (testWidth > maxWidth && line.length > 0 && trimCount < maxTrimCount) {
344
377
  line = line.slice(0, -1);
378
+ if (line.length === 0) break; // Safety check
345
379
  testWidth = echarts.format.getTextRect(line).width;
380
+ trimCount++;
346
381
  }
347
382
  }
348
383
 
@@ -376,101 +411,221 @@ class RotationRenderer {
376
411
  let maxXPositions = new Map();
377
412
 
378
413
  function renderItem(params, api) {
414
+ // Safety checks to prevent crashes
415
+ if (!params || !api) return null;
416
+
417
+ try {
418
+ var categoryIndex = api.value(0);
419
+ var start = api.coord([api.value(1), categoryIndex]);
420
+ var end = api.coord([api.value(2), categoryIndex]);
421
+ var name = api.value(3);
422
+ var type = api.value(4);
423
+ let secondary_crop = api.value(5);
424
+ let bHasSecondaryCrops = api.value(6);
425
+
426
+ // Safety check for invalid coordinates
427
+ if (!start || !end || start.length < 2 || end.length < 2) return null;
428
+ if (isNaN(start[0]) || isNaN(start[1]) || isNaN(end[0]) || isNaN(end[1])) return null;
429
+
430
+ const x = start[0];
431
+ let y = start[1];
432
+
433
+ const style = api.style();
434
+ style.opacity = 0.5;
435
+
436
+ if (params.context.rendered == undefined) {
437
+ // Start of a new rendering round
438
+ maxXPositions = new Map();
439
+
440
+ for (let catIndex = 0; catIndex < 3; catIndex++) {
441
+ for (let track = 0; track < 3; track++) {
442
+ maxXPositions.set('track_left_' + catIndex + '_' + track, params.coordSys.width);
443
+ }
444
+ }
445
+ }
379
446
 
380
- var categoryIndex = api.value(0);
381
- var start = api.coord([api.value(1), categoryIndex]);
382
- var end = api.coord([api.value(2), categoryIndex]);
383
- var name = api.value(3);
384
- var type = api.value(4);
385
- let secondary_crop = api.value(5);
386
- let bHasSecondaryCrops = api.value(6);
447
+ params.context.rendered = true;
387
448
 
388
- const x = start[0];
389
- let y = start[1];
449
+ // Remove the default emphasis style
450
+ api.styleEmphasis({});
390
451
 
391
- const style = api.style();
392
- style.opacity = 0.5;
452
+ if (type == 'rotation_item') {
393
453
 
394
- if (params.context.rendered == undefined) {
395
- // Start of a new rendering round
396
- maxXPositions = new Map();
454
+ // start[0] // abscisse gauche de l'élément (après zoom)
455
+ // start[1] // ordonnée gauche de l'élément
456
+ // end[0] // abscisse droite de l'élément (après zoom)
457
+ // end[1] // ordonnée droite de l'élément
458
+ // height // Hauteur de l'élément
397
459
 
398
- for (let catIndex = 0; catIndex < 3; catIndex++) {
399
- for (let track = 0; track < 3; track++) {
400
- maxXPositions.set('track_left_' + catIndex + '_' + track, params.coordSys.width);
460
+ // params.coordSys.x // début du canva
461
+ // params.coordSys.y // début du canva
462
+ // params.coordSys.width, // largeur du canva
463
+ // params.coordSys.height // hauteur du canva
464
+
465
+ let height = self.barHeight - 20; // 20 px margin top and bottom
466
+ let top = y - height / 2;
467
+ let textXMargin = 2;
468
+ let textYMargin = 10;
469
+
470
+ if (bHasSecondaryCrops) {
471
+ height = self.barHeight - 40; // 20 px margin top and bottom
472
+ top = y - height / 2 - 15;
473
+ textXMargin = 2;
474
+ textYMargin = 10;
401
475
  }
402
- }
403
- }
404
476
 
405
- params.context.rendered = true;
477
+ if (secondary_crop) {
478
+ // Move secondary crops a bit down and reduce their size
479
+ top = top + height + 5;
480
+ height = height / 3;
481
+ textXMargin = 5;
482
+ textYMargin = 5;
483
+ }
406
484
 
407
- // Remove the default emphasis style
408
- api.styleEmphasis({});
485
+ const arrowWidth = height / 3;
486
+ const border = 3;
409
487
 
410
- if (type == 'rotation_item') {
488
+ var points = [
489
+ [x, top],
490
+ [end[0] - border, top],
491
+ [end[0] + arrowWidth - border, top + height / 2],
492
+ [end[0] - border, top + height],
493
+ [x, top + height],
494
+ [x + arrowWidth, top + height / 2],
495
+ ];
411
496
 
412
- // start[0] // abscisse gauche de l'élément (après zoom)
413
- // start[1] // ordonnée gauche de l'élément
414
- // end[0] // abscisse droite de l'élément (après zoom)
415
- // end[1] // ordonnée droite de l'élément
416
- // height // Hauteur de l'élément
497
+ //const itemLabelWidth = echarts.format.getTextRect(name).width + textMargin * 2;
498
+ const itemWidth = end[0] - x;
417
499
 
418
- // params.coordSys.x // début du canva
419
- // params.coordSys.y // début du canva
420
- // params.coordSys.width, // largeur du canva
421
- // params.coordSys.height // hauteur du canva
500
+ // if (itemLabelWidth > itemWidth)
501
+ // name = ''; // Hide the label as we won't have the room to show it
422
502
 
423
- let height = self.barHeight - 20; // 20 px margin top and bottom
424
- let top = y - height / 2;
425
- let textXMargin = 2;
426
- let textYMargin = 10;
503
+ name = wrapText(echarts, name, itemWidth - arrowWidth, height);
427
504
 
428
- if (bHasSecondaryCrops) {
429
- height = self.barHeight - 40; // 20 px margin top and bottom
430
- top = y - height / 2 - 15;
431
- textXMargin = 2;
432
- textYMargin = 10;
433
- }
505
+ // See this for clip regions : https://stackoverflow.com/questions/71735038/setting-border-and-label-in-custom-apache-echarts
506
+ // https://stackoverflow.com/questions/73653691/how-to-draw-a-custom-triangle-in-renderitem-in-apache-echarts
434
507
 
435
- if (secondary_crop) {
436
- // Move secondary crops a bit down and reduce their size
437
- top = top + height + 5;
438
- height = height / 3;
439
- textXMargin = 5;
440
- textYMargin = 5;
508
+ return (
509
+ {
510
+ type: 'polygon',
511
+ transition: ['shape'],
512
+ shape: {
513
+ points: points
514
+ },
515
+ style: style,
516
+ emphasis: {
517
+ style: {
518
+ shadowBlur: 4,
519
+ shadowOffsetX: 1,
520
+ shadowOffsetY: 2,
521
+ shadowColor: 'rgba(0, 0, 0, 0.2)'
522
+ },
523
+ },
524
+ textConfig: {
525
+ position: [arrowWidth + textXMargin, textYMargin]
526
+ },
527
+ textContent: {
528
+ style: {
529
+ text: name,
530
+ fill: '#000',
531
+ width: 80,
532
+ fontWeight: 'bold'
533
+ }
534
+ }
535
+ }
536
+ );
441
537
  }
442
538
 
443
- const arrowWidth = height / 3;
444
- const border = 3;
539
+ if (type == 'intervention_bottom' || type == 'intervention_top') {
540
+
541
+ const height = 20;
542
+ const margin = 10;
543
+ const textMargin = 5;
544
+
545
+ // Maintain a list of max x for each row. If the max x is further right than the label we try to push,
546
+ // use another row. If for all rows the space is taken, just drop this item
547
+
548
+ let trackToUse = null;
549
+ const itemLabelWidth = echarts.format.getTextRect(name).width + textMargin * 2;
550
+
551
+ for (let track = 0; track < 3; track++) {
552
+ if (!maxXPositions.has('track_right_' + categoryIndex + '_' + track)) {
553
+ // Situation where the track is empty
554
+ trackToUse = track;
555
+ break;
556
+ }
557
+
558
+ let trackLeft = maxXPositions.get('track_left_' + categoryIndex + '_' + track);
559
+ if (trackLeft > (x + itemLabelWidth)) {
560
+ // Situation where the drawing has started right of the current element
561
+ trackToUse = track;
562
+ break;
563
+ }
564
+
565
+ let trackRight = maxXPositions.get('track_right_' + categoryIndex + '_' + track);
566
+ if (trackRight < x) {
567
+ // Situation where the last painted element is sufficiently far on the left
568
+ trackToUse = track;
569
+ break;
570
+ }
571
+ }
572
+
573
+ if (trackToUse == null)
574
+ return null;
575
+
576
+ let currentRight = maxXPositions.get('track_right_' + categoryIndex + '_' + trackToUse);
577
+ if (currentRight == undefined || currentRight < x + itemLabelWidth)
578
+ maxXPositions.set('track_right_' + categoryIndex + '_' + trackToUse, x + itemLabelWidth);
445
579
 
446
- var points = [
447
- [x, top],
448
- [end[0] - border, top],
449
- [end[0] + arrowWidth - border, top + height / 2],
450
- [end[0] - border, top + height],
451
- [x, top + height],
452
- [x + arrowWidth, top + height / 2],
453
- ];
580
+ let currentLeft = maxXPositions.get('track_left_' + categoryIndex + '_' + trackToUse);
581
+ if (currentLeft > x)
582
+ maxXPositions.set('track_left_' + categoryIndex + '_' + trackToUse, x);
454
583
 
455
- //const itemLabelWidth = echarts.format.getTextRect(name).width + textMargin * 2;
456
- const itemWidth = end[0] - x;
584
+ // A nicer solution could be to draw large items in a reduced format until there is enough space for
585
+ // drawing them fully. Unfortunately that would require a two pass drawing which does not exist with Echarts ?
457
586
 
458
- // if (itemLabelWidth > itemWidth)
459
- // name = ''; // Hide the label as we won't have the room to show it
587
+ const arrowWidth = 3;
460
588
 
461
- name = wrapText(echarts, name, itemWidth - arrowWidth, height);
589
+ let arrowTop = y + 55;
590
+ let arrowBottom = y - 55;
462
591
 
463
- // See this for clip regions : https://stackoverflow.com/questions/71735038/setting-border-and-label-in-custom-apache-echarts
464
- // https://stackoverflow.com/questions/73653691/how-to-draw-a-custom-triangle-in-renderitem-in-apache-echarts
592
+ y = margin + y + trackToUse * (height + margin) - (self.barHeight / 2);
465
593
 
466
- return (
467
- {
594
+ var points = [];
595
+ var textPosition = [];
596
+
597
+ if (type == 'intervention_top') {
598
+ textPosition = [textMargin, textMargin];
599
+ points = [
600
+ [x, y],
601
+ [x + itemLabelWidth, y],
602
+ [x + itemLabelWidth, y + height],
603
+ [x + arrowWidth, y + height],
604
+ [x, arrowTop]
605
+ ];
606
+ }
607
+ else {
608
+ textPosition = [textMargin, textMargin + y - arrowBottom];
609
+ points = [
610
+ [x, arrowBottom],
611
+ [x + arrowWidth, y],
612
+ [x + itemLabelWidth, y],
613
+ [x + itemLabelWidth, y + height],
614
+ [x, y + height]
615
+ ];
616
+ }
617
+
618
+ return ({
468
619
  type: 'polygon',
469
620
  transition: ['shape'],
470
621
  shape: {
471
622
  points: points
472
623
  },
473
- style: style,
624
+ style: api.style({
625
+ fill: style.fill,
626
+ stroke: style.fill,
627
+ textFill: '#000',
628
+ }),
474
629
  emphasis: {
475
630
  style: {
476
631
  shadowBlur: 4,
@@ -480,130 +635,21 @@ class RotationRenderer {
480
635
  },
481
636
  },
482
637
  textConfig: {
483
- position: [arrowWidth + textXMargin, textYMargin]
638
+ position: textPosition
484
639
  },
485
640
  textContent: {
486
641
  style: {
487
642
  text: name,
488
643
  fill: '#000',
489
- width: 80,
490
- fontWeight: 'bold'
491
644
  }
492
645
  }
493
646
  }
494
- );
495
- }
496
-
497
- if (type == 'intervention_bottom' || type == 'intervention_top') {
498
-
499
- const height = 20;
500
- const margin = 10;
501
- const textMargin = 5;
502
-
503
- // Maintain a list of max x for each row. If the max x is further right than the label we try to push,
504
- // use another row. If for all rows the space is taken, just drop this item
505
-
506
- let trackToUse = null;
507
- const itemLabelWidth = echarts.format.getTextRect(name).width + textMargin * 2;
508
-
509
- for (let track = 0; track < 3; track++) {
510
- if (!maxXPositions.has('track_right_' + categoryIndex + '_' + track)) {
511
- // Situation where the track is empty
512
- trackToUse = track;
513
- break;
514
- }
515
-
516
- let trackLeft = maxXPositions.get('track_left_' + categoryIndex + '_' + track);
517
- if (trackLeft > (x + itemLabelWidth)) {
518
- // Situation where the drawing has started right of the current element
519
- trackToUse = track;
520
- break;
521
- }
522
-
523
- let trackRight = maxXPositions.get('track_right_' + categoryIndex + '_' + track);
524
- if (trackRight < x) {
525
- // Situation where the last painted element is sufficiently far on the left
526
- trackToUse = track;
527
- break;
528
- }
529
- }
530
-
531
- if (trackToUse == null)
532
- return null;
533
-
534
- let currentRight = maxXPositions.get('track_right_' + categoryIndex + '_' + trackToUse);
535
- if (currentRight == undefined || currentRight < x + itemLabelWidth)
536
- maxXPositions.set('track_right_' + categoryIndex + '_' + trackToUse, x + itemLabelWidth);
537
-
538
- let currentLeft = maxXPositions.get('track_left_' + categoryIndex + '_' + trackToUse);
539
- if (currentLeft > x)
540
- maxXPositions.set('track_left_' + categoryIndex + '_' + trackToUse, x);
541
-
542
- // A nicer solution could be to draw large items in a reduced format until there is enough space for
543
- // drawing them fully. Unfortunately that would require a two pass drawing which does not exist with Echarts ?
647
+ );
544
648
 
545
- const arrowWidth = 3;
546
-
547
- let arrowTop = y + 55;
548
- let arrowBottom = y - 55;
549
-
550
- y = margin + y + trackToUse * (height + margin) - (self.barHeight / 2);
551
-
552
- var points = [];
553
- var textPosition = [];
554
-
555
- if (type == 'intervention_top') {
556
- textPosition = [textMargin, textMargin];
557
- points = [
558
- [x, y],
559
- [x + itemLabelWidth, y],
560
- [x + itemLabelWidth, y + height],
561
- [x + arrowWidth, y + height],
562
- [x, arrowTop]
563
- ];
564
- }
565
- else {
566
- textPosition = [textMargin, textMargin + y - arrowBottom];
567
- points = [
568
- [x, arrowBottom],
569
- [x + arrowWidth, y],
570
- [x + itemLabelWidth, y],
571
- [x + itemLabelWidth, y + height],
572
- [x, y + height]
573
- ];
574
- }
575
-
576
- return ({
577
- type: 'polygon',
578
- transition: ['shape'],
579
- shape: {
580
- points: points
581
- },
582
- style: api.style({
583
- fill: style.fill,
584
- stroke: style.fill,
585
- textFill: '#000',
586
- }),
587
- emphasis: {
588
- style: {
589
- shadowBlur: 4,
590
- shadowOffsetX: 1,
591
- shadowOffsetY: 2,
592
- shadowColor: 'rgba(0, 0, 0, 0.2)'
593
- },
594
- },
595
- textConfig: {
596
- position: textPosition
597
- },
598
- textContent: {
599
- style: {
600
- text: name,
601
- fill: '#000',
602
- }
603
- }
604
649
  }
605
- );
606
-
650
+ } catch (error) {
651
+ console.error('Error in renderItem:', error);
652
+ return null;
607
653
  }
608
654
  }
609
655
 
@@ -823,8 +869,29 @@ class RotationRenderer {
823
869
  ];
824
870
 
825
871
  let monthsPerYear = new Map();
872
+
873
+ // Safety check to prevent infinite loops
874
+ if (!steps || steps.length === 0) {
875
+ series.push(months);
876
+ return series;
877
+ }
878
+
826
879
  let startMonth = new Date(steps.at(0).startDate.valueOf());
827
- while (startMonth < steps.at(-1).endDate) {
880
+ let endDate = steps.at(-1).endDate;
881
+
882
+ // Safety check for valid dates and reasonable duration (max 20 years)
883
+ if (isNaN(startMonth.getTime()) ||
884
+ isNaN(endDate.getTime()) ||
885
+ startMonth >= endDate ||
886
+ (endDate - startMonth) > (20 * 365 * 24 * 60 * 60 * 1000)) {
887
+ series.push(months);
888
+ return series;
889
+ }
890
+
891
+ let loopCount = 0;
892
+ const maxLoops = 240; // Max 20 years * 12 months
893
+
894
+ while (startMonth < endDate && loopCount < maxLoops) {
828
895
  const monthName = startMonth.toLocaleDateString(undefined, { month: 'short' });
829
896
  const year = startMonth.getFullYear();
830
897
 
@@ -843,6 +910,7 @@ class RotationRenderer {
843
910
 
844
911
  // increment the current month
845
912
  startMonth.setMonth(startMonth.getMonth() + 1);
913
+ loopCount++;
846
914
  }
847
915
 
848
916
  series.push(months);
@@ -912,13 +980,21 @@ class RotationRenderer {
912
980
 
913
981
  option.tooltip = {
914
982
  extraCssText: "text-wrap: wrap;",
983
+ position: function (pos, params, el, elRect, size) {
984
+ var obj = { top: 10 };
985
+ obj[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = 30;
986
+ return obj;
987
+ },
915
988
  className: "rotation-tooltip",
916
989
  formatter: function (params) {
917
990
  if (params.data.type == 'rotation_item') {
918
991
  let start = params.data.startDate.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short', year: '2-digit' });
919
992
  let end = params.data.endDate.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short', year: '2-digit' });
993
+ let duration = params.data.duration + ' mois';
994
+ if (params.data.duration > 20)
995
+ duration = (Math.round(params.data.duration / 1.2)/10 + ' années').replace('.', ',');
920
996
 
921
- return params.marker + params.name + ' : ' + params.data.duration + ' mois (' + start + ' ➜ ' + end + ')<br>' + params.data.description;
997
+ return params.marker + ' <b>' + params.name + '</b><div class="step_dates"><b>' + duration + '</b> (' + start + ' ➜ ' + end + ')</div><br style="clear:both">' + params.data.description.replace('’', '\'');
922
998
  }
923
999
  else {
924
1000
  let interventionDate = params.data.interventionDate;
@@ -930,13 +1006,13 @@ class RotationRenderer {
930
1006
  else
931
1007
  dateString += ' (J' + days + ')';
932
1008
 
933
- return params.marker + params.name + ' - ' + dateString + '<br>' + params.data.description;
1009
+ return params.marker + ' <b>'+ params.name + '</b> - ' + dateString + '<br>' + params.data.description.replace('’', '\'');;
934
1010
  }
935
1011
  }
936
1012
  };
937
1013
 
938
1014
  option.toolbox = {
939
- "itemSize": 25,
1015
+ "itemSize": 20,
940
1016
  "iconStyle": {
941
1017
  "borderColor": "#AAA",
942
1018
  "borderWidth": 1
@@ -984,8 +1060,6 @@ class RotationRenderer {
984
1060
 
985
1061
  // If a line has a column in it, split the line in two and add bold to the first part:
986
1062
  description = description.replace(/^([^:\n]+):/gm, "<b>$1:</b>");
987
- description = description.replace(/\n([^:\n]+):/gm, "\n<b>$1:</b>");
988
-
989
1063
  description = description.replace(/\n/g, '<br/>');
990
1064
 
991
1065
  return description;
@@ -103,5 +103,12 @@ class WikiEditor {
103
103
 
104
104
  const editData = await editResp.json();
105
105
  console.log(editData);
106
+
107
+ if (editData.edit && editData.edit.result === 'Success') {
108
+ return Promise.resolve();
109
+ } else {
110
+ return Promise.reject(editData);
111
+ }
106
112
  }
113
+
107
114
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@osfarm/itineraire-technique",
3
- "version": "1.1.8",
3
+ "version": "1.1.10",
4
4
  "description": "A visualisation tool to show agricultural technical itineraries based on Echarts",
5
5
  "main": "editor.html",
6
6
  "scripts": {