@osfarm/itineraire-technique 1.2.0 → 1.2.1
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/.github/copilot-instructions.md +56 -0
- package/.github/workflows/publish.yml +34 -0
- package/README.md +2 -34
- package/css/styles-editor.css +1 -1
- package/css/styles-editor.css.map +1 -1
- package/editor.html +38 -748
- package/js/chart-render.js +16 -11
- package/js/editor-interventions.js +315 -189
- package/js/editor-loader-default.js +238 -0
- package/js/editor-loader-itinera.js +135 -0
- package/js/{editor-wiki-editor.js → editor-loader-wiki.js} +99 -11
- package/js/editor-main.js +752 -0
- package/js/intervention.js +12 -0
- package/js/step-model.js +69 -0
- package/package.json +6 -59
- package/scss/styles-editor.scss +145 -0
- package/scss/styles-rendering.scss +184 -0
- package/examples/README.md +0 -137
- package/examples/nextjs-_document.tsx +0 -66
- package/examples/nextjs-api-route.ts +0 -122
- package/examples/nextjs-app-router-editor.tsx +0 -304
- package/examples/nextjs-app-router-viewer.tsx +0 -90
- package/js/editor-attributes.js +0 -99
- package/js/editor-crops.js +0 -136
- package/js/editor-export.js +0 -118
- package/react/QUICKSTART.md +0 -172
- package/react/README.md +0 -305
- package/react/TikaEditor.jsx +0 -212
- package/react/TikaRenderer.jsx +0 -116
- package/react/hooks.ts +0 -217
- package/react/index.ts +0 -19
- package/react/types.ts +0 -152
package/editor.html
CHANGED
|
@@ -10,18 +10,20 @@
|
|
|
10
10
|
|
|
11
11
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
|
|
12
12
|
<script src="https://cdn.jsdelivr.net/npm/jquery-ui@1.14.1/dist/jquery-ui.min.js"></script>
|
|
13
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-ui@1.14.1/themes/base/
|
|
13
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-ui@1.14.1/themes/base/all.min.css">
|
|
14
14
|
|
|
15
15
|
<script src="https://cdn.jsdelivr.net/npm/underscore@1.13.7/underscore-umd-min.js"></script>
|
|
16
16
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
|
|
17
17
|
integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
|
|
18
18
|
|
|
19
|
+
<script src="./js/intervention.js"></script>
|
|
20
|
+
<script src="./js/step-model.js"></script>
|
|
19
21
|
<script src="./js/chart-render.js"></script>
|
|
20
|
-
<script src="./js/editor-attributes.js"></script>
|
|
21
22
|
<script src="./js/editor-interventions.js"></script>
|
|
22
|
-
<script src="./js/editor-
|
|
23
|
-
<script src="./js/editor-
|
|
24
|
-
<script src="./js/editor-
|
|
23
|
+
<script src="./js/editor-loader-default.js"></script>
|
|
24
|
+
<script src="./js/editor-loader-wiki.js"></script>
|
|
25
|
+
<script src="./js/editor-loader-itinera.js"></script>
|
|
26
|
+
<script src="./js/editor-main.js"></script>
|
|
25
27
|
|
|
26
28
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"
|
|
27
29
|
integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
|
@@ -34,41 +36,8 @@
|
|
|
34
36
|
<body>
|
|
35
37
|
<div class="container-fluid">
|
|
36
38
|
<div class="row">
|
|
37
|
-
<div class="col-auto text-left main-header file-icons mb-2">
|
|
38
|
-
|
|
39
|
-
<div class="btn-group me-2" role="group">
|
|
40
|
-
<button type="button" class="btn btn-outline-primary primary-button" onclick="loadFromWiki()"><i class="fa fa-upload" aria-hidden="true"></i> Charger une rotation</a></button>
|
|
41
|
-
<button type="button" class="btn btn-outline-primary primary-button dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
|
|
42
|
-
<span class="visually-hidden">Autres options de chargement</span>
|
|
43
|
-
</button>
|
|
44
|
-
<ul class="dropdown-menu">
|
|
45
|
-
<li><a class="dropdown-item" href="#" onclick="importFromTestJson()"><i class="fa fa-lightbulb-o" aria-hidden="true"></i> Charger un exemple</a></li>
|
|
46
|
-
<li><a class="dropdown-item" href="#" onclick="importFromJsonFile()"><i class="fa fa-upload" aria-hidden="true"></i> Importer (JSON)</a></li>
|
|
47
|
-
</ul>
|
|
48
|
-
</div><div class="btn-group me-2" role="group">
|
|
49
|
-
<button type="button" onclick="saveToWiki()" class="btn btn-outline-primary primary-button"><i class="fa fa-download" aria-hidden="true"></i> Enregistrer dans le wiki</button>
|
|
50
|
-
<button type="button" class="btn btn-outline-primary primary-button dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
|
|
51
|
-
<span class="visually-hidden">Autres options de chargement</span>
|
|
52
|
-
</button>
|
|
53
|
-
<ul class="dropdown-menu">
|
|
54
|
-
<li><a class="dropdown-item" href="#" onclick="showSaveAsModal()"><i class="fa fa-save" aria-hidden="true"></i> Enregistrer sous</a></li>
|
|
55
|
-
<li><a class="dropdown-item" href="#" onclick="doExportToJsonFile()"><i class="fa fa-download" aria-hidden="true"></i> Exporter</a></li>
|
|
56
|
-
</ul>
|
|
57
|
-
</div>
|
|
58
|
-
</div>
|
|
59
|
-
<div id="NonWikiButtons" class="">
|
|
60
|
-
<div class="btn-group me-2" role="group">
|
|
61
|
-
<button type="button" class="btn btn-outline-primary primary-button" onclick="importFromJsonFile()"><i class="fa fa-upload" aria-hidden="true"></i> Charger une rotation</a></button>
|
|
62
|
-
<button type="button" class="btn btn-outline-primary primary-button dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
|
|
63
|
-
<span class="visually-hidden">Autres options de chargement</span>
|
|
64
|
-
</button>
|
|
65
|
-
<ul class="dropdown-menu">
|
|
66
|
-
<li><a class="dropdown-item" href="#" onclick="importFromTestJson()"><i class="fa fa-lightbulb-o" aria-hidden="true"></i> Charger un exemple</a></li>
|
|
67
|
-
</ul>
|
|
68
|
-
</div><button type="button" onclick="doExportToJsonFile()" class="btn btn-outline-primary primary-button me-2"><i class="fa fa-download" aria-hidden="true"></i> Exporter (JSON)</button>
|
|
69
|
-
</div>
|
|
70
|
-
|
|
71
|
-
<button type="button" onclick="wipe(crops)" class="btn btn-outline-primary primary-button"><i class="fa fa-trash" aria-hidden="true"></i> Tout effacer</button>
|
|
39
|
+
<div class="col-auto text-left main-header file-icons mb-2" id="toolbar-buttons-container">
|
|
40
|
+
<!-- Wiki/Itinera/NonWiki buttons and wipe button will be inserted here dynamically -->
|
|
72
41
|
</div>
|
|
73
42
|
<div class="col text-center main-header mb-2">
|
|
74
43
|
<h1 id="title" contenteditable="true" style="width: 100%">Itinéraire technique</h1>
|
|
@@ -100,7 +69,7 @@
|
|
|
100
69
|
|
|
101
70
|
</div>
|
|
102
71
|
|
|
103
|
-
<div class="
|
|
72
|
+
<div class="welcome-view container px-4" id="cropListView" style="display:none;">
|
|
104
73
|
<div class="row">
|
|
105
74
|
<div class="col-12">
|
|
106
75
|
<p>Liste des étapes</p>
|
|
@@ -117,8 +86,8 @@
|
|
|
117
86
|
</div>
|
|
118
87
|
</div>
|
|
119
88
|
|
|
120
|
-
<div
|
|
121
|
-
<div class="row card-holder mb-2">
|
|
89
|
+
<div id="cropEditorView" style="display:none">
|
|
90
|
+
<div id="cropDetailView" class="row card-holder mb-2">
|
|
122
91
|
<div class="col-12">
|
|
123
92
|
<button class="float-end close-step-button close-step-times" ><i class="fa fa-times" aria-hidden="true"></i></button>
|
|
124
93
|
<h4>Étape</h4>
|
|
@@ -162,37 +131,9 @@
|
|
|
162
131
|
</form>
|
|
163
132
|
</div>
|
|
164
133
|
</div>
|
|
165
|
-
<div class="row card-holder attributes-view mb-2">
|
|
166
|
-
<div class="col-12">
|
|
167
|
-
<h5>Attributs</h5>
|
|
168
|
-
<div class="row">
|
|
169
|
-
<div id="attributesContainer" class="container"></div>
|
|
170
|
-
</div>
|
|
171
|
-
<button id="newAttributeButton" type="button" onclick="createAttributForm()"
|
|
172
|
-
class="btn btn-primary primary-button w-100"><i class="fa fa-plus-square"
|
|
173
|
-
aria-hidden="true"></i> Ajouter</button>
|
|
174
|
-
<div id="newAttributeContainer"></div>
|
|
175
|
-
</div>
|
|
176
|
-
</div>
|
|
177
|
-
<div class="row card-holder">
|
|
178
|
-
<div class="col-12">
|
|
179
|
-
<h5>Interventions</h5>
|
|
180
|
-
<div class="row">
|
|
181
|
-
<p id="interventionsTopName" class="h6"></p>
|
|
182
|
-
<div id="interventionsTopContainer" class="container"></div>
|
|
183
|
-
</div>
|
|
184
|
-
<hr class="my-2">
|
|
185
|
-
<div class="row">
|
|
186
|
-
<p id="interventionsBottomName" class="h6"></p>
|
|
187
|
-
<div id="interventionsBottomContainer" class="container"></div>
|
|
188
|
-
</div>
|
|
189
134
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
aria-hidden="true"></i> Ajouter</button>
|
|
193
|
-
<div id="newInterventionContainer"></div>
|
|
194
|
-
</div>
|
|
195
|
-
</div>
|
|
135
|
+
<!-- Interventions table will be inserted here dynamically by InterventionTable.setupDiv() -->
|
|
136
|
+
|
|
196
137
|
<div class="row">
|
|
197
138
|
<div class="col-12">
|
|
198
139
|
<button type="button"
|
|
@@ -248,6 +189,17 @@
|
|
|
248
189
|
</div>
|
|
249
190
|
</div>
|
|
250
191
|
</div>
|
|
192
|
+
|
|
193
|
+
<div class="toast-container position-fixed bottom-0 end-0 p-3">
|
|
194
|
+
<div id="liveToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
|
195
|
+
<div class="toast-header">
|
|
196
|
+
<strong class="me-auto">Itinéra</strong>
|
|
197
|
+
<small>11 mins ago</small>
|
|
198
|
+
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
|
199
|
+
</div>
|
|
200
|
+
<div class="toast-body"></div>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
251
203
|
|
|
252
204
|
<!-- Wiki Files Modal -->
|
|
253
205
|
<div class="modal fade" id="wikiFilesModal" tabindex="-1" aria-labelledby="wikiFilesModalLabel" aria-hidden="true">
|
|
@@ -360,7 +312,7 @@
|
|
|
360
312
|
<label for="stepsTitle" class="form-label">Titre des étapes de la rotation</label>
|
|
361
313
|
<input type="text" class="form-control" id="stepsTitle" value="Étapes de la rotation dans la parcelle">
|
|
362
314
|
</div>
|
|
363
|
-
<div class="mb-3
|
|
315
|
+
<div class="mb-3" id="codeSnippetDiv" style="display:none;">
|
|
364
316
|
<label for="code-snippet" class="form-label">Code à insérer dans la page</label>
|
|
365
317
|
<textarea readonly class="form-control-plaintext" id="code-snippet" rows="5"></textarea>
|
|
366
318
|
</div>
|
|
@@ -418,686 +370,24 @@
|
|
|
418
370
|
</div>
|
|
419
371
|
</div>
|
|
420
372
|
</div>
|
|
421
|
-
</body>
|
|
422
|
-
|
|
423
|
-
</html>
|
|
424
|
-
|
|
425
373
|
<script>
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
"title": DEFAULT_TITLE,
|
|
431
|
-
"options": {
|
|
432
|
-
"view": "horizontal",
|
|
433
|
-
"show_transcript": true,
|
|
434
|
-
"title_top_interventions": "Contrôle adventices",
|
|
435
|
-
"title_bottom_interventions": "Autres interventions",
|
|
436
|
-
"title_steps": "Étapes de la rotation dans la parcelle",
|
|
437
|
-
"region": "France"
|
|
438
|
-
},
|
|
439
|
-
"steps": []
|
|
440
|
-
};
|
|
441
|
-
|
|
442
|
-
let selectedStep; // an instance of Crop
|
|
443
|
-
let we; // WikiEditor instance
|
|
444
|
-
|
|
445
|
-
initializeOptions();
|
|
446
|
-
|
|
447
|
-
function initializeOptions() {
|
|
448
|
-
document.getElementById("title").textContent = crops.title;
|
|
449
|
-
document.getElementById("interventionsTopName").textContent = crops.options.title_top_interventions;
|
|
450
|
-
document.getElementById("interventionsBottomName").textContent = crops.options.title_bottom_interventions;
|
|
451
|
-
|
|
452
|
-
attachCropInputListeners();
|
|
453
|
-
enableTitleEditing();
|
|
454
|
-
|
|
455
|
-
$('.close-step-button').click(function() {
|
|
456
|
-
// Set the current step to be fully edited now:
|
|
457
|
-
selectedStep.setAsEdited();
|
|
458
|
-
|
|
459
|
-
displayCropListView();
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
$(function() {
|
|
463
|
-
$('#cropForm').on('keydown', function(e) {
|
|
464
|
-
// Prevent form submit on Enter, but allow Enter in textarea
|
|
465
|
-
if (e.key === 'Enter' && e.target.tagName !== 'TEXTAREA') {
|
|
466
|
-
e.preventDefault();
|
|
467
|
-
return false;
|
|
468
|
-
}
|
|
469
|
-
});
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
// Make cropsContainer sortable
|
|
473
|
-
$("#cropsContainer").sortable({
|
|
474
|
-
handle: '.drag-handle',
|
|
475
|
-
axis: 'y',
|
|
476
|
-
update: function (event, ui) {
|
|
477
|
-
// Set new order
|
|
478
|
-
let movedStepId = ui.item.data('id');
|
|
479
|
-
let lastStepEnd = null;
|
|
480
|
-
|
|
481
|
-
let newOrder = [];
|
|
482
|
-
$(event.target).children('.step-row').each(function () {
|
|
483
|
-
let id = $(this).data('id');
|
|
484
|
-
|
|
485
|
-
let step = crops.steps.find(function (crop) { return crop.id == id });
|
|
486
|
-
|
|
487
|
-
if (movedStepId == id) {
|
|
488
|
-
let movedStep = new StepModel(step);
|
|
489
|
-
let duration = movedStep.getDurationInDays();
|
|
490
|
-
|
|
491
|
-
if (lastStepEnd != null) {
|
|
492
|
-
lastStepEnd.setDate(lastStepEnd.getDate() + 1);
|
|
493
|
-
|
|
494
|
-
movedStep.setStartDate(lastStepEnd);
|
|
495
|
-
movedStep.setDurationInDays(duration);
|
|
496
|
-
} else {
|
|
497
|
-
// This is the first step in the list, set its start date before the previously first step start date
|
|
498
|
-
let nextStep = crops.steps[0];
|
|
499
|
-
let newStartDate = new Date(nextStep.startDate.valueOf());
|
|
500
|
-
newStartDate.setDate(newStartDate.getDate() - duration);
|
|
501
|
-
movedStep.setStartDate(newStartDate);
|
|
502
|
-
movedStep.setDurationInDays(duration);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
step = movedStep.getStep();
|
|
506
|
-
}
|
|
507
|
-
else {
|
|
508
|
-
lastStepEnd = new Date(step.endDate.valueOf());
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
newOrder.push(step);
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
crops.steps = newOrder;
|
|
515
|
-
|
|
516
|
-
refreshAllTables();
|
|
517
|
-
}
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
$('#modalParams').on('show.bs.modal', function (event) {
|
|
522
|
-
|
|
523
|
-
// Set the modal form inputs values from crops.options
|
|
524
|
-
$("#viewSelect").val(crops.options.view);
|
|
525
|
-
$("#showTranscriptCheckbox").prop("checked", crops.options.show_transcript);
|
|
526
|
-
$("#topInterventionsTitle").val(crops.options.title_top_interventions);
|
|
527
|
-
$("#bottomInterventionsTitle").val(crops.options.title_bottom_interventions);
|
|
528
|
-
$("#stepsTitle").val(crops.options.title_steps);
|
|
529
|
-
$("#regionInput").val(crops.options.region ?? "France");
|
|
530
|
-
$("#addressInput").val(crops.options.address ?? "");
|
|
531
|
-
$("#latitudeInput").val(crops.options.latitude ?? "");
|
|
532
|
-
$("#longitudeInput").val(crops.options.longitude ?? "");
|
|
533
|
-
|
|
534
|
-
// Load ombrothermic data from climate_data if present
|
|
535
|
-
let hasClimateData = crops.options.climate_data &&
|
|
536
|
-
crops.options.climate_data.temperatures &&
|
|
537
|
-
crops.options.climate_data.precipitations;
|
|
538
|
-
|
|
539
|
-
// Check the checkbox if show_climate_diagram is explicitly true OR if climate_data exists
|
|
540
|
-
let showDiagram = crops.options.show_climate_diagram === true;
|
|
541
|
-
$("#ombroCheck").prop("checked", showDiagram);
|
|
542
|
-
|
|
543
|
-
if (hasClimateData) {
|
|
544
|
-
let tempLine = crops.options.climate_data.temperatures.join(' ');
|
|
545
|
-
let precipLine = crops.options.climate_data.precipitations.join(' ');
|
|
546
|
-
$("#ombroData").val(tempLine + '\n' + precipLine);
|
|
547
|
-
} else {
|
|
548
|
-
$("#ombroData").val("");
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// Enable/disable textarea based on checkbox state
|
|
552
|
-
$("#ombroData").prop("disabled", !showDiagram);
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
// Add event listener to toggle textarea when checkbox changes
|
|
556
|
-
$("#ombroCheck").on("change", function() {
|
|
557
|
-
$("#ombroData").prop("disabled", !this.checked);
|
|
558
|
-
});
|
|
559
|
-
|
|
560
|
-
// Update Google Maps link when coordinates change
|
|
561
|
-
function updateGoogleMapsLink() {
|
|
562
|
-
const lat = $("#latitudeInput").val().trim();
|
|
563
|
-
const lon = $("#longitudeInput").val().trim();
|
|
564
|
-
|
|
565
|
-
if (lat && lon && !isNaN(parseFloat(lat)) && !isNaN(parseFloat(lon))) {
|
|
566
|
-
const mapsUrl = `https://www.google.com/maps?q=${lat},${lon}`;
|
|
567
|
-
$("#googleMapsLink a").attr("href", mapsUrl);
|
|
568
|
-
$("#googleMapsLink").show();
|
|
569
|
-
} else {
|
|
570
|
-
$("#googleMapsLink").hide();
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// Attach listeners to lat/long inputs
|
|
575
|
-
$("#latitudeInput, #longitudeInput").on("input change", updateGoogleMapsLink);
|
|
576
|
-
|
|
577
|
-
// Update link when modal opens
|
|
578
|
-
$('#modalParams').on('shown.bs.modal', function() {
|
|
579
|
-
updateGoogleMapsLink();
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
// Location search button handler
|
|
583
|
-
$("#searchLocationBtn").on("click", function() {
|
|
584
|
-
const address = $("#addressInput").val().trim();
|
|
585
|
-
|
|
586
|
-
if (!address) {
|
|
587
|
-
$("#locationSearchStatus").html('<span class="text-warning">Veuillez entrer une adresse</span>');
|
|
588
|
-
return;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// Show loading state
|
|
592
|
-
$("#searchLocationBtn").prop("disabled", true);
|
|
593
|
-
$("#locationSearchStatus").html('<i class="fa fa-spinner fa-spin"></i> Recherche en cours...');
|
|
594
|
-
|
|
595
|
-
$.ajax({
|
|
596
|
-
url: "https://itk-info.tripleperformance.fr/api/location",
|
|
597
|
-
method: "POST",
|
|
598
|
-
contentType: "application/json",
|
|
599
|
-
data: JSON.stringify({ address: address }),
|
|
600
|
-
success: function(data) {
|
|
601
|
-
console.log("Location data received:", data);
|
|
602
|
-
|
|
603
|
-
// Populate latitude and longitude
|
|
604
|
-
if (data.latitude && data.longitude) {
|
|
605
|
-
$("#latitudeInput").val(data.latitude);
|
|
606
|
-
$("#longitudeInput").val(data.longitude);
|
|
607
|
-
updateGoogleMapsLink();
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
// Populate climate data if available
|
|
611
|
-
if (data.monthly_temperatures && data.monthly_rainfall) {
|
|
612
|
-
let tempLine = data.monthly_temperatures.join(' ');
|
|
613
|
-
let precipLine = data.monthly_rainfall.join(' ');
|
|
614
|
-
$("#ombroData").val(tempLine + '\n' + precipLine);
|
|
615
|
-
$("#ombroCheck").prop("checked", true);
|
|
616
|
-
$("#ombroData").prop("disabled", false);
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
// Show success message
|
|
620
|
-
let message = '<span class="text-success">✓ Coordonnées trouvées';
|
|
621
|
-
if (data.source_explanation) {
|
|
622
|
-
message += ' — ' + data.source_explanation;
|
|
623
|
-
}
|
|
624
|
-
message += '</span>';
|
|
625
|
-
$("#locationSearchStatus").html(message);
|
|
626
|
-
},
|
|
627
|
-
error: function(err) {
|
|
628
|
-
console.error("Location search error:", err);
|
|
629
|
-
$("#locationSearchStatus").html('<span class="text-danger">Erreur lors de la recherche</span>');
|
|
630
|
-
},
|
|
631
|
-
complete: function() {
|
|
632
|
-
$("#searchLocationBtn").prop("disabled", false);
|
|
633
|
-
}
|
|
634
|
-
});
|
|
635
|
-
});
|
|
636
|
-
|
|
637
|
-
$("#paramsModalSaveButton").click(function () {
|
|
638
|
-
crops.options.view = $("#viewSelect").val();
|
|
639
|
-
crops.options.show_transcript = $("#showTranscriptCheckbox").prop("checked");
|
|
640
|
-
crops.options.title_top_interventions = $("#topInterventionsTitle").val();
|
|
641
|
-
crops.options.title_bottom_interventions = $("#bottomInterventionsTitle").val();
|
|
642
|
-
crops.options.title_steps = $("#stepsTitle").val();
|
|
643
|
-
crops.options.region = $("#regionInput").val();
|
|
644
|
-
crops.options.address = $("#addressInput").val().trim();
|
|
645
|
-
crops.options.latitude = $("#latitudeInput").val().trim();
|
|
646
|
-
crops.options.longitude = $("#longitudeInput").val().trim();
|
|
647
|
-
|
|
648
|
-
// Convert ombrothermic data to climate_data object
|
|
649
|
-
let ombroEnabled = $("#ombroCheck").prop("checked");
|
|
650
|
-
|
|
651
|
-
if (ombroEnabled) {
|
|
652
|
-
crops.options.show_climate_diagram = true;
|
|
653
|
-
|
|
654
|
-
let ombroText = $("#ombroData").val().trim();
|
|
655
|
-
let lines = ombroText.split('\n');
|
|
656
|
-
|
|
657
|
-
if (lines.length >= 2) {
|
|
658
|
-
let temperatures = lines[0].trim().split(/\s+/).map(v => parseFloat(v)).filter(v => !isNaN(v));
|
|
659
|
-
let precipitations = lines[1].trim().split(/\s+/).map(v => parseFloat(v)).filter(v => !isNaN(v));
|
|
660
|
-
|
|
661
|
-
if (temperatures.length > 0 && precipitations.length > 0) {
|
|
662
|
-
crops.options.climate_data = {
|
|
663
|
-
temperatures: temperatures,
|
|
664
|
-
precipitations: precipitations
|
|
665
|
-
};
|
|
666
|
-
} else {
|
|
667
|
-
delete crops.options.climate_data;
|
|
668
|
-
}
|
|
669
|
-
} else {
|
|
670
|
-
delete crops.options.climate_data;
|
|
671
|
-
}
|
|
672
|
-
} else {
|
|
673
|
-
crops.options.show_climate_diagram = false;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
// Close the modal:
|
|
677
|
-
let modal = bootstrap.Modal.getInstance(document.getElementById('modalParams'));
|
|
678
|
-
modal.hide();
|
|
679
|
-
|
|
680
|
-
refreshAllTables();
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
// On page load, create WikiEditor instance
|
|
374
|
+
// Initialize the TikaEditor
|
|
375
|
+
let tikaEditor;
|
|
376
|
+
|
|
377
|
+
// Initialize editor when DOM is ready
|
|
685
378
|
$(document).ready(function() {
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
if (window.location.hostname.includes("tripleperformance.ag") || window.location.hostname.includes("tripleperformance.fr")) {
|
|
689
|
-
document.getElementById("WikiButtons").classList.remove("d-none");
|
|
690
|
-
document.getElementById("NonWikiButtons").classList.add("d-none");
|
|
691
|
-
|
|
692
|
-
we = new WikiEditor();
|
|
693
|
-
we.loadPageFromURL();
|
|
694
|
-
|
|
695
|
-
// Set up Save As modal event listeners
|
|
696
|
-
$('#saveAsUseExistingPage').on('change', function() {
|
|
697
|
-
$('#saveAsPageSelect').prop('disabled', !this.checked);
|
|
698
|
-
});
|
|
699
|
-
|
|
700
|
-
$('#saveAsConfirmBtn').on('click', function() {
|
|
701
|
-
if (we) {
|
|
702
|
-
we.saveAs();
|
|
703
|
-
}
|
|
704
|
-
});
|
|
705
|
-
} else {
|
|
706
|
-
document.getElementById("WikiButtons").classList.add("d-none");
|
|
707
|
-
document.getElementById("NonWikiButtons").classList.remove("d-none");
|
|
708
|
-
}
|
|
379
|
+
tikaEditor = new TikaEditor();
|
|
380
|
+
tikaEditor.initialize();
|
|
709
381
|
});
|
|
710
382
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
showConfirmationModal(() => {
|
|
715
|
-
we.loadFromWiki();
|
|
716
|
-
});
|
|
717
|
-
} else {
|
|
718
|
-
console.error('WikiEditor instance not available');
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
function saveToWiki() {
|
|
723
|
-
if (typeof we !== 'undefined') {
|
|
724
|
-
we.saveToWiki();
|
|
725
|
-
} else {
|
|
726
|
-
console.error('WikiEditor instance not available');
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
function showSaveAsModal() {
|
|
731
|
-
if (typeof we !== 'undefined') {
|
|
732
|
-
we.showSaveAsModal();
|
|
733
|
-
} else {
|
|
734
|
-
console.error('WikiEditor instance not available');
|
|
383
|
+
function addNewStepClickEvent() {
|
|
384
|
+
if (tikaEditor) {
|
|
385
|
+
tikaEditor.addNewStepClickEvent();
|
|
735
386
|
}
|
|
736
387
|
}
|
|
737
388
|
|
|
738
|
-
|
|
739
|
-
constructor(step) {
|
|
740
|
-
this.step = step; // keep a reference to the original object
|
|
741
|
-
|
|
742
|
-
this.step.id = step.id ?? crypto.randomUUID();
|
|
743
|
-
this.step.name = step.name ?? "";
|
|
744
|
-
this.step.color = step.color ?? "#0db3bf";
|
|
745
|
-
this.step.startDate = step.startDate ? new Date(step.startDate) : new Date();
|
|
746
|
-
this.step.endDate = step.endDate ? new Date(step.endDate) : new Date();
|
|
747
|
-
this.step.description = step.description ?? "";
|
|
748
|
-
this.step.interventions = step.interventions?.map(i => new Intervention(i.day, i.name, i.type, i.description)) || [];
|
|
749
|
-
this.step.attributes = step.attributes?.map(a => new Attribute(a.name, a.value)) || [];
|
|
750
|
-
this.step.secondary_crop = step.secondary_crop ?? false;
|
|
751
|
-
|
|
752
|
-
this.step.useDefaultColor = step.useDefaultColor ?? true;
|
|
753
|
-
this.step.useDefaultStartDate = step.useDefaultStartDate ?? true;
|
|
754
|
-
this.step.useDefaultEndDate = step.useDefaultEndDate ?? true;
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
setAsEdited() {
|
|
758
|
-
this.step.useDefaultColor = false;
|
|
759
|
-
this.step.useDefaultStartDate = false;
|
|
760
|
-
this.step.useDefaultEndDate = false;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
getStep() {
|
|
764
|
-
return this.step;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
setStartDate(date) {
|
|
768
|
-
this.step.startDate = new Date(date);
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
setDurationInMonths(durationInMonths) {
|
|
772
|
-
this.step.endDate = new Date(this.step.startDate);
|
|
773
|
-
this.step.endDate.setMonth(this.step.startDate.getMonth() + durationInMonths);
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
setDurationInDays(durationInDays) {
|
|
777
|
-
this.step.endDate = new Date(this.step.startDate);
|
|
778
|
-
this.step.endDate.setDate(this.step.startDate.getDate() + durationInDays);
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
getDurationInDays() {
|
|
782
|
-
const diffTime = Math.abs(this.step.endDate - this.step.startDate);
|
|
783
|
-
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
addIntervention(day, name, type, description) {
|
|
787
|
-
let intervention = new Intervention(day, name, type, description);
|
|
788
|
-
this.step.interventions.push(intervention);
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
addAttribute(key, value) {
|
|
792
|
-
let attribute = new Attribute(key, value);
|
|
793
|
-
this.step.attributes.push(attribute);
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
updateAttribute(attributeId, key, value) {
|
|
797
|
-
this.step.attributes = this.step.attributes.map(function (attribute) {
|
|
798
|
-
if (attribute.id == attributeId) {
|
|
799
|
-
attribute.name = key;
|
|
800
|
-
attribute.value = value;
|
|
801
|
-
}
|
|
802
|
-
return attribute;
|
|
803
|
-
});
|
|
804
|
-
}
|
|
389
|
+
</script>
|
|
805
390
|
|
|
806
|
-
|
|
807
|
-
this.step.attributes = this.step.attributes.filter(function (attribute) { return attribute.id != attributeId });
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
updateIntervention(interventionId, day, name, type, description) {
|
|
811
|
-
let intervention = this.step.interventions.find((it) => it.id == interventionId);
|
|
812
|
-
|
|
813
|
-
if (intervention != undefined) {
|
|
814
|
-
intervention.day = day;
|
|
815
|
-
intervention.name = name;
|
|
816
|
-
intervention.type = type;
|
|
817
|
-
intervention.description = description;
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
removeIntervention(interventionId) {
|
|
822
|
-
this.step.interventions = this.step.interventions.filter(function (intervention) { return intervention.id != interventionId });
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
updateFromForm() {
|
|
826
|
-
this.step.name = getInputValue("cropName");
|
|
827
|
-
this.step.description = getInputValue("cropDescription");
|
|
828
|
-
this.step.startDate = new Date(getInputValue("cropStartDate"));
|
|
829
|
-
this.step.endDate = new Date(getInputValue("cropEndDate"));
|
|
830
|
-
this.step.color = getInputValue("cropColor");
|
|
831
|
-
this.step.secondary_crop = document.getElementById("cropSecondary").checked;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
class Intervention {
|
|
836
|
-
constructor(day, name, type, description) {
|
|
837
|
-
this.id = crypto.randomUUID();
|
|
838
|
-
this.day = day;
|
|
839
|
-
this.name = name || "";
|
|
840
|
-
this.type = type;
|
|
841
|
-
this.description = description || "";
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
class Attribute {
|
|
846
|
-
constructor(name, value) {
|
|
847
|
-
this.id = crypto.randomUUID();
|
|
848
|
-
this.name = name || "";
|
|
849
|
-
this.value = value || "";
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
function updateSelectedStepColor() {
|
|
854
|
-
selectedStep.step.useDefaultColor = false;
|
|
855
|
-
|
|
856
|
-
selectedStep.updateFromForm();
|
|
857
|
-
|
|
858
|
-
refreshAllTables();
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
function updateSelectedStepStartDate() {
|
|
862
|
-
selectedStep.step.useDefaultStartDate = false;
|
|
863
|
-
|
|
864
|
-
selectedStep.updateFromForm();
|
|
865
|
-
|
|
866
|
-
refreshAllTables();
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
function updateSelectedStepEndDate() {
|
|
871
|
-
selectedStep.step.useDefaultEndDate = false;
|
|
872
|
-
|
|
873
|
-
selectedStep.updateFromForm();
|
|
874
|
-
|
|
875
|
-
refreshAllTables();
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
function updateSelectedStep() {
|
|
879
|
-
selectedStep.updateFromForm();
|
|
880
|
-
|
|
881
|
-
refreshAllTables();
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
function getInputValue(elementId) {
|
|
885
|
-
return document.getElementById(elementId).value;
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
function setInputValue(elementId, value) {
|
|
889
|
-
document.getElementById(elementId).value = value;
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
function refreshAllTables() {
|
|
893
|
-
refreshStepsButtonList();
|
|
894
|
-
refreshAttributesTable();
|
|
895
|
-
refreshInterventionsTable();
|
|
896
|
-
renderChart();
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
function displayView(viewsToShow, viewsToHide) {
|
|
900
|
-
viewsToShow.forEach(view => document.querySelector(view).classList.remove("d-none"));
|
|
901
|
-
viewsToHide.forEach(view => document.querySelector(view).classList.add("d-none"));
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
function displayCropDetailView() {
|
|
905
|
-
displayView(["#cropDetailView"], ["#welcomeView", "#cropListView"]);
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
function displayCropListView() {
|
|
909
|
-
displayView(["#cropListView"], ["#welcomeView", "#cropDetailView"]);
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
function renderChart() {
|
|
913
|
-
let renderer = new RotationRenderer('itk', crops);
|
|
914
|
-
renderer.render();
|
|
915
|
-
|
|
916
|
-
$('.step-edit').click(function (event) {
|
|
917
|
-
event.stopPropagation();
|
|
918
|
-
let stepId = renderer.getElementID(event.target.closest('.rotation_item'));;
|
|
919
|
-
|
|
920
|
-
let index = stepId.split('_')[1];
|
|
921
|
-
selectedStep = new StepModel(crops.steps[index]);
|
|
922
|
-
// populateCropDetailView(selectedStep);
|
|
923
|
-
// displayCropDetailView();
|
|
924
|
-
SelectStep(selectedStep);
|
|
925
|
-
});
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
function getRotationEndDate() {
|
|
929
|
-
let latestEndDate = null;
|
|
930
|
-
crops.steps.forEach(function (crop) {
|
|
931
|
-
if (latestEndDate == null || crop.endDate > latestEndDate) {
|
|
932
|
-
latestEndDate = crop.endDate;
|
|
933
|
-
}
|
|
934
|
-
});
|
|
935
|
-
|
|
936
|
-
// Clone the date to avoid reference issues
|
|
937
|
-
if (latestEndDate == null)
|
|
938
|
-
latestEndDate = new Date();
|
|
939
|
-
else {
|
|
940
|
-
latestEndDate = new Date(latestEndDate.valueOf());
|
|
941
|
-
|
|
942
|
-
// Move to the next day
|
|
943
|
-
latestEndDate.setDate(latestEndDate.getDate() + 1);
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
return latestEndDate;
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
function addEditAndRemoveButtons(rowDiv, deleteId, editFunction, deleteFunction, duplicateFunction, style="btn-group") {
|
|
950
|
-
rowDiv = $(rowDiv);
|
|
951
|
-
|
|
952
|
-
let actionContainer = $(`<div class="col-auto edit-buttons m-1 ${style}" role="group"></div>`);
|
|
953
|
-
|
|
954
|
-
rowDiv.append(actionContainer);
|
|
955
|
-
|
|
956
|
-
actionContainer.append($('<button class="edit-button btn btn-outline-primary p-2"><i class="fa fa-pencil"></i></button>').click(function(event) {
|
|
957
|
-
event.stopPropagation(); // Prevent other onclick events from triggering
|
|
958
|
-
editFunction();
|
|
959
|
-
}));
|
|
960
|
-
|
|
961
|
-
rowDiv.find('.col').click(function(event) {
|
|
962
|
-
event.stopPropagation(); // Prevent other onclick events from triggering
|
|
963
|
-
editFunction();
|
|
964
|
-
});
|
|
965
|
-
|
|
966
|
-
if (duplicateFunction != null) {
|
|
967
|
-
actionContainer.append($('<button class="btn btn-outline-secondary p-2"><i class="fa fa-copy"></i></button>').click(function (event) {
|
|
968
|
-
event.stopPropagation(); // Prevent other onclick events from triggering
|
|
969
|
-
duplicateFunction(deleteId);
|
|
970
|
-
}));
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
actionContainer.append($('<button class="btn btn-outline-danger p-2"><i class="fa fa-trash"></i></button>').click(function (event) {
|
|
974
|
-
event.stopPropagation(); // Prevent other onclick events from triggering
|
|
975
|
-
deleteFunction(deleteId);
|
|
976
|
-
}));
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
function getAndCleanElement(elementId) {
|
|
980
|
-
let element = document.getElementById(elementId);
|
|
981
|
-
element.innerHTML = "";
|
|
982
|
-
return element;
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
function reloadCropsFromJson(cropsFromJson) {
|
|
986
|
-
console.log("Données JSON importées :", cropsFromJson);
|
|
987
|
-
|
|
988
|
-
crops = cropsFromJson;
|
|
989
|
-
|
|
990
|
-
initializeOptions();
|
|
991
|
-
refreshAllTables();
|
|
992
|
-
displayCropListView();
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
function getChatGPTInfoFromCropName() {
|
|
996
|
-
|
|
997
|
-
if (selectedStep.step.useDefaultColor == false &&
|
|
998
|
-
selectedStep.step.useDefaultStartDate == false &&
|
|
999
|
-
selectedStep.step.useDefaultEndDate == false) {
|
|
1000
|
-
// If the user has modified all fields, do not call the API again
|
|
1001
|
-
$('#itk-api-comment').text('');
|
|
1002
|
-
console.log("All fields modified by user, skipping API call");
|
|
1003
|
-
return;
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
let newCropName = $("#cropName").val();
|
|
1007
|
-
|
|
1008
|
-
$('#itk-api-comment').html('<i class="fa fa-spinner fa-spin"></i> ...');
|
|
1009
|
-
|
|
1010
|
-
$.ajax({
|
|
1011
|
-
url: "https://itk-info.tripleperformance.fr/api/culture",
|
|
1012
|
-
method: "POST",
|
|
1013
|
-
contentType: "application/json",
|
|
1014
|
-
data: JSON.stringify({
|
|
1015
|
-
culture: newCropName,
|
|
1016
|
-
region: crops.options.region ?? "France"
|
|
1017
|
-
}),
|
|
1018
|
-
success: function(data) {
|
|
1019
|
-
console.log("Réponse:", data);
|
|
1020
|
-
|
|
1021
|
-
if (data.color_hex && selectedStep.step.useDefaultColor) {
|
|
1022
|
-
setInputValue("cropColor", data.color_hex);
|
|
1023
|
-
updateSelectedStep();
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
let startDate = getRotationEndDate();
|
|
1027
|
-
if (data.average_sowing_date && selectedStep.step.useDefaultStartDate) {
|
|
1028
|
-
// data.average_sowing_date is in MM-DD format, we need to convert it to YYYY-MM-DD
|
|
1029
|
-
// for the current year
|
|
1030
|
-
let currentYear = startDate.getFullYear();
|
|
1031
|
-
startDate = new Date(`${currentYear}-${data.average_sowing_date}`);
|
|
1032
|
-
// If the start date is before today, it means the crop starts next year
|
|
1033
|
-
if (startDate < new Date()) {
|
|
1034
|
-
startDate.setFullYear(startDate.getFullYear() + 1);
|
|
1035
|
-
}
|
|
1036
|
-
setInputValue("cropStartDate", startDate.toISOString().split('T')[0]);
|
|
1037
|
-
updateSelectedStep();
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
if (data.end_of_season && selectedStep.step.useDefaultEndDate) {
|
|
1041
|
-
// data.end_of_season is in MM-DD format, we need to convert it to YYYY-MM-DD
|
|
1042
|
-
// for the current year
|
|
1043
|
-
let currentYear = startDate.getFullYear();
|
|
1044
|
-
let endDate = new Date(`${currentYear}-${data.end_of_season}`);
|
|
1045
|
-
// If the end date is before the start date, it means the crop ends the next year
|
|
1046
|
-
if (endDate < startDate) {
|
|
1047
|
-
endDate.setFullYear(startDate.getFullYear() + 1);
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
setInputValue("cropEndDate", endDate.toISOString().split('T')[0]);
|
|
1051
|
-
updateSelectedStep();
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
if (data.source_explanation) {
|
|
1055
|
-
$('#itk-api-comment').text(data.source_explanation);
|
|
1056
|
-
} else {
|
|
1057
|
-
$('#itk-api-comment').text('');
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
// {
|
|
1061
|
-
// "culture": "Colza",
|
|
1062
|
-
// "region": "France",
|
|
1063
|
-
// "average_sowing_date": "08-15",
|
|
1064
|
-
// "end_of_season": "07-15",
|
|
1065
|
-
// "color_hex": "#FFD700",
|
|
1066
|
-
// "confidence": "high",
|
|
1067
|
-
// "source_explanation": "Colza is typically sown in late summer and harvested in mid-summer in France."
|
|
1068
|
-
// }
|
|
1069
|
-
|
|
1070
|
-
},
|
|
1071
|
-
error: function(err) {
|
|
1072
|
-
console.error("Erreur:", err);
|
|
1073
|
-
}
|
|
1074
|
-
});
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
function attachCropInputListeners() {
|
|
1078
|
-
$("#cropName").on("input", _.debounce(getChatGPTInfoFromCropName, 1000));
|
|
1079
|
-
|
|
1080
|
-
$("#cropName").on("input", _.debounce(updateSelectedStep, 500));
|
|
1081
|
-
$("#cropDescription").on("input", _.debounce(updateSelectedStep, 500));
|
|
1082
|
-
$("#cropStartDate").on("change", _.debounce(updateSelectedStepStartDate, 500));
|
|
1083
|
-
$("#cropEndDate").on("change", _.debounce(updateSelectedStepEndDate, 500));
|
|
1084
|
-
$("#cropColor").on("input", _.debounce(updateSelectedStepColor, 500));
|
|
1085
|
-
$("#cropSecondary").on("change", _.debounce(updateSelectedStep, 500));
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
function enableTitleEditing() {
|
|
1089
|
-
document.getElementById("title").addEventListener("blur", function () {
|
|
1090
|
-
if (this.textContent.trim() === "") {
|
|
1091
|
-
this.textContent = DEFAULT_TITLE;
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
crops.title = this.textContent;
|
|
1095
|
-
crops.defaultTitle = false;
|
|
1096
|
-
});
|
|
1097
|
-
}
|
|
391
|
+
</body>
|
|
1098
392
|
|
|
1099
|
-
|
|
1100
|
-
let jsonName = crops.title.replace(/\s+/g, '-').toLowerCase() + ".json";
|
|
1101
|
-
exportToJsonFile(crops, jsonName);
|
|
1102
|
-
}
|
|
1103
|
-
</script>
|
|
393
|
+
</html>
|