@osfarm/itineraire-technique 1.1.12 → 1.1.14

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
- .main-header{background-color:#6fa76f;color:#fff;height:3rem;display:flex;align-items:center}.main-header .btn.show,.main-header .btn:first-child:active,.main-header :not(.btn-check)+.btn:active{background-color:#026602}.editor-view{overflow-y:auto;height:calc(100vh - 4rem)}.rotation_item .step-edit{color:#878787;cursor:pointer;display:none;margin-left:10px;font-size:70%;vertical-align:super}.rotation_item:hover .step-edit{display:inline !important}.welcome-view{background-color:#6fa76f;color:#fff;padding:1rem;margin-top:1rem;border-radius:1rem}.welcome-view #cropsContainer{color:green}.card-white{background-color:#fff;padding:1rem;border-radius:1rem}.card-holder{background-color:#f5f5f5;padding:1rem;border-radius:1rem;margin:auto}.editable-row{background-color:#f7f7f7;min-height:3rem;display:flex;align-items:center;border-radius:.5rem}.editable-row .edit-buttons{visibility:hidden}.editable-row:hover>.edit-buttons{visibility:visible}.intervention-row{background:#e0e0e0}.primary-button{color:#fff;background-color:green;border:green}.primary-button:hover{background-color:#026602}.close-step-times{background:none;border:none;color:#878787;font-size:120%}.close-step-times:hover{color:#494949}#cropsContainer .drag-handle{color:#ccc;font-weight:normal;font-size:86%;margin-right:10px;vertical-align:middle;cursor:grab}.form-control.text-right{text-align:right}.modal .form-label{font-weight:600}/*# sourceMappingURL=styles-editor.css.map */
1
+ .main-header{background-color:#6fa76f;color:#fff;height:3rem;display:flex;align-items:center}.main-header .btn.show,.main-header .btn:first-child:active,.main-header :not(.btn-check)+.btn:active{background-color:#026602}.editor-view{overflow-y:auto;height:calc(100vh - 4rem)}.rotation_item .step-edit{color:#878787;cursor:pointer;display:none;margin-left:10px;font-size:70%;vertical-align:super}.rotation_item:hover .step-edit{display:inline !important}.welcome-view{background-color:#6fa76f;color:#fff;padding:1rem;margin-top:1rem;border-radius:1rem}.welcome-view #cropsContainer{color:green}.card-white{background-color:#fff;padding:1rem;border-radius:1rem}.card-holder{background-color:#f5f5f5;padding:1rem;border-radius:1rem;margin:auto}.editable-row{background-color:#f7f7f7;min-height:3rem;display:flex;align-items:center;border-radius:.5rem}.editable-row .edit-buttons{visibility:hidden}.editable-row:hover>.edit-buttons{visibility:visible}.intervention-row{background:#e0e0e0}.primary-button{color:#fff;background-color:green;border:green}.primary-button:hover{background-color:#026602}.close-step-times{background:none;border:none;color:#878787;font-size:120%}.close-step-times:hover{color:#494949}#cropsContainer .drag-handle{color:#ccc;font-weight:normal;font-size:86%;margin-right:10px;vertical-align:middle;cursor:grab}.form-control.text-right{text-align:right}.modal .form-label{font-weight:600}#code-snippet{font-family:monospace;font-size:13px;text-align:left;border:1px inset;background-color:#f1f1f1}/*# sourceMappingURL=styles-editor.css.map */
@@ -1 +1 @@
1
- {"version":3,"sourceRoot":"","sources":["../scss/styles-editor.scss"],"names":[],"mappings":"AAKA,aACI,yBACA,WACA,OALY,KAOZ,aACA,mBAEA,sGACI,yBAIR,aACI,gBAGA,0BAKA,0BACI,cACA,eACA,aACA,iBACA,cACA,qBAGJ,gCACI,0BAIR,cACI,yBACA,WACA,aACA,gBACA,mBAEA,8BACI,YAIR,YACI,sBACA,aACA,mBAGJ,aACI,yBACA,aACA,mBACA,YAGJ,cACI,yBACA,gBAEA,aACA,mBAEA,oBAEA,4BACI,kBAGJ,kCACI,mBAIR,kBACI,mBAGJ,gBACI,MAvFgB,KAwFhB,iBA1FW,MA2FX,OA3FW,MA6FX,sBACI,iBA7Fa,QAiGrB,kBACI,gBACA,YACA,cACA,eAEA,wBACI,cAIR,6BACI,WACA,mBACA,cACA,kBACA,sBACA,YAIA,yBACI,iBAKJ,mBACI","file":"styles-editor.css"}
1
+ {"version":3,"sourceRoot":"","sources":["../scss/styles-editor.scss"],"names":[],"mappings":"AAKA,aACI,yBACA,WACA,OALiB,KAOjB,aACA,mBAEA,sGAGI,yBAIR,aACI,gBAGA,0BAKA,0BACI,cACA,eACA,aACA,iBACA,cACA,qBAGJ,gCACI,0BAIR,cACI,yBACA,WACA,aACA,gBACA,mBAEA,8BACI,YAIR,YACI,sBACA,aACA,mBAGJ,aACI,yBACA,aACA,mBACA,YAGJ,cACI,yBACA,gBAEA,aACA,mBAEA,oBAEA,4BACI,kBAGJ,kCACI,mBAIR,kBACI,mBAGJ,gBACI,MAzFiB,KA0FjB,iBA5FiB,MA6FjB,OA7FiB,MA+FjB,sBACI,iBA/Fa,QAmGrB,kBACI,gBACA,YACA,cACA,eAEA,wBACI,cAIR,6BACI,WACA,mBACA,cACA,kBACA,sBACA,YAIA,yBACI,iBAKJ,mBACI,gBAIR,cACI,sBACA,eACA,gBACA,iBACA","file":"styles-editor.css"}
package/editor.html CHANGED
@@ -51,6 +51,7 @@
51
51
  <span class="visually-hidden">Autres options de chargement</span>
52
52
  </button>
53
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>
54
55
  <li><a class="dropdown-item" href="#" onclick="exportToJsonFile()"><i class="fa fa-download" aria-hidden="true"></i> Exporter</a></li>
55
56
  </ul>
56
57
  </div>
@@ -271,6 +272,47 @@
271
272
  </div>
272
273
  </div>
273
274
 
275
+ <!-- Save As Modal -->
276
+ <div class="modal fade" id="saveAsModal" tabindex="-1" aria-labelledby="saveAsModalLabel" aria-hidden="true">
277
+ <div class="modal-dialog">
278
+ <div class="modal-content">
279
+ <div class="modal-header">
280
+ <h5 class="modal-title" id="saveAsModalLabel">Enregistrer sous</h5>
281
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
282
+ </div>
283
+ <div class="modal-body">
284
+ <form>
285
+ <div class="mb-3 form-check">
286
+ <input type="checkbox" class="form-check-input" id="saveAsUseExistingPage">
287
+ <label class="form-check-label" for="saveAsUseExistingPage">
288
+ Enregistrer sous une page existante
289
+ </label>
290
+ </div>
291
+ <div class="mb-3" id="saveAsPageSelectContainer">
292
+ <label for="saveAsPageSelect" class="form-label">Page parent</label>
293
+ <select class="form-select" id="saveAsPageSelect" disabled>
294
+ <option value="">Sélectionner une page...</option>
295
+ </select>
296
+ <div class="form-text">Si non coché, le fichier sera enregistré sous "Non classified"</div>
297
+ </div>
298
+ <div class="mb-3">
299
+ <label for="saveAsFilename" class="form-label">Nom du fichier</label>
300
+
301
+ <div class="input-group mb-3">
302
+ <input type="text" class="form-control" id="saveAsFilename" placeholder="Nom du fichier (sans extension)">
303
+ <span class="input-group-text" id="basic-addon2">.json</span>
304
+ </div>
305
+ </div>
306
+ </form>
307
+ </div>
308
+ <div class="modal-footer">
309
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
310
+ <button type="button" class="btn btn-primary" id="saveAsConfirmBtn">Enregistrer</button>
311
+ </div>
312
+ </div>
313
+ </div>
314
+ </div>
315
+
274
316
  <!-- Modal -->
275
317
  <div class="modal fade" id="modalParams" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
276
318
  <div class="modal-dialog">
@@ -318,6 +360,10 @@
318
360
  <label for="stepsTitle" class="form-label">Titre des étapes de la rotation</label>
319
361
  <input type="text" class="form-control" id="stepsTitle" value="Étapes de la rotation dans la parcelle">
320
362
  </div>
363
+ <div class="mb-3 d-none" id="codeSnippetDiv">
364
+ <label for="code-snippet" class="form-label">Code à insérer dans la page</label>
365
+ <textarea readonly class="form-control-plaintext" id="code-snippet" rows="5"></textarea>
366
+ </div>
321
367
  </div>
322
368
 
323
369
  <!-- Plot context -->
@@ -645,6 +691,17 @@
645
691
 
646
692
  we = new WikiEditor();
647
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
+ });
648
705
  } else {
649
706
  document.getElementById("WikiButtons").classList.add("d-none");
650
707
  document.getElementById("NonWikiButtons").classList.remove("d-none");
@@ -670,12 +727,20 @@
670
727
  }
671
728
  }
672
729
 
730
+ function showSaveAsModal() {
731
+ if (typeof we !== 'undefined') {
732
+ we.showSaveAsModal();
733
+ } else {
734
+ console.error('WikiEditor instance not available');
735
+ }
736
+ }
737
+
673
738
  class StepModel {
674
739
  constructor(step) {
675
740
  this.step = step; // keep a reference to the original object
676
741
 
677
742
  this.step.id = step.id ?? crypto.randomUUID();
678
- this.step.name = step.name ?? "Nouvelle culture";
743
+ this.step.name = step.name ?? "";
679
744
  this.step.color = step.color ?? "#0db3bf";
680
745
  this.step.startDate = step.startDate ? new Date(step.startDate) : new Date();
681
746
  this.step.endDate = step.endDate ? new Date(step.endDate) : new Date();
@@ -1013,7 +1078,9 @@
1013
1078
  if (this.textContent.trim() === "") {
1014
1079
  this.textContent = DEFAULT_TITLE;
1015
1080
  }
1081
+
1016
1082
  crops.title = this.textContent;
1083
+ crops.defaultTitle = false;
1017
1084
  });
1018
1085
  }
1019
1086
 
@@ -100,7 +100,7 @@ function showJsonErrorModal(errorMessage) {
100
100
  function showConfirmationModal(onConfirm) {
101
101
 
102
102
  if (crops?.steps.length === 0)
103
- return true;
103
+ return onConfirm();
104
104
 
105
105
  const confirmationModal = new bootstrap.Modal(document.getElementById('confirmationModal'));
106
106
  const confirmButton = document.getElementById('confirmImport');
@@ -36,6 +36,14 @@ class WikiEditor {
36
36
  sm.setAsEdited();
37
37
  });
38
38
  reloadCropsFromJson(content);
39
+
40
+ let codeSnippet = `{{Graphique Triple Performance \n| title=${content.title} \n| json=${self.pageTitle} \n| type=Rotation }}`;
41
+ $('#code-snippet').val(codeSnippet).on('focus', function() {
42
+ $(this).select();
43
+ });
44
+ $('#codeSnippetDiv').removeClass('d-none');
45
+
46
+
39
47
  } catch (e) {
40
48
  console.error("Erreur lors de l'analyse du JSON de la page :", e);
41
49
  $('#jsonErrorMessage').text("Le contenu de la page n'est pas un JSON valide.");
@@ -53,7 +61,6 @@ class WikiEditor {
53
61
  const jsonErrorModal = new bootstrap.Modal(document.getElementById('jsonErrorModal'));
54
62
  jsonErrorModal.show();
55
63
  }
56
-
57
64
  }
58
65
  });
59
66
 
@@ -66,17 +73,13 @@ class WikiEditor {
66
73
  const self = this;
67
74
 
68
75
  if (!self.pageTitle) {
69
- alert("Aucun titre de page spécifié pour l'enregistrement.");
76
+ self.showSaveAsModal();
70
77
  return;
71
78
  }
72
79
 
73
80
  // If a page title is provided, save to that page
74
81
  self.savePageToWiki(self.pageTitle, JSON.stringify(crops, null, 2))
75
- .then(() => {
76
-
77
- // TODO : Also set a few semantic properties on the page, including the chart type, the geolocation, etc.
78
-
79
-
82
+ .then(async () => {
80
83
  alert("Itinéraire technique enregistré avec succès !");
81
84
  })
82
85
  .catch(err => {
@@ -151,6 +154,25 @@ class WikiEditor {
151
154
  }
152
155
  }
153
156
 
157
+
158
+ async getWikiUserPages(username) {
159
+ try {
160
+ // Encode the username for the URL
161
+ const encodedUsername = 'User:' + username;
162
+ const query = encodeURIComponent(`[[Page author::${encodedUsername}]][[A un type de page::+]]`);
163
+ const url = `/api.php?action=ask&query=${query}|sort=Modification date|order=desc&format=json`;
164
+
165
+ const response = await fetch(url, {
166
+ credentials: 'include'
167
+ });
168
+ const data = await response.json();
169
+ return data;
170
+ } catch (error) {
171
+ console.error("Error fetching wiki files:", error);
172
+ throw error;
173
+ }
174
+ }
175
+
154
176
  async loadFromWiki() {
155
177
  // Show the modal
156
178
  const modal = new bootstrap.Modal(document.getElementById('wikiFilesModal'));
@@ -220,4 +242,121 @@ class WikiEditor {
220
242
  }
221
243
  }
222
244
 
245
+ /**
246
+ * Show the Save As modal and handle the save as functionality
247
+ */
248
+ async showSaveAsModal() {
249
+ const self = this;
250
+
251
+ try {
252
+ // Get user info to check if logged in
253
+ const userInfo = await this.getWikiUserInfo();
254
+
255
+ if (!userInfo.id || userInfo.id === 0) {
256
+ alert('Vous devez être connecté au wiki pour utiliser cette fonctionnalité.');
257
+ return;
258
+ }
259
+
260
+ // Get user's existing pages
261
+ const pagesData = await this.getWikiUserPages(userInfo.name);
262
+
263
+ // Show the modal
264
+ const modal = new bootstrap.Modal(document.getElementById('saveAsModal'));
265
+ modal.show();
266
+
267
+ // Populate the select with user's pages
268
+ const pageSelect = $('#saveAsPageSelect');
269
+ pageSelect.empty();
270
+ pageSelect.append('<option value="">Sélectionner une page...</option>');
271
+
272
+ if (pagesData.query?.results) {
273
+ for (const [pageTitle, pageData] of Object.entries(pagesData.query.results)) {
274
+ const displayTitle = pageData.displaytitle || pageTitle;
275
+ pageSelect.append(`<option value="${pageTitle}">${displayTitle}</option>`);
276
+ }
277
+ }
278
+
279
+ // Set default filename from title if it's been changed
280
+ const filenameInput = $('#saveAsFilename');
281
+ const currentTitle = crops.title || '';
282
+ if (crops.defaultTitle === false) {
283
+ filenameInput.val(currentTitle);
284
+ } else {
285
+ filenameInput.val('');
286
+ }
287
+
288
+ } catch (error) {
289
+ console.error("Error showing save as modal:", error);
290
+ alert('Erreur lors du chargement des données utilisateur.');
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Build the URL for saving based on the save as modal selections
296
+ */
297
+ buildSaveAsUrl() {
298
+ const useExistingPage = $('#saveAsUseExistingPage').prop('checked');
299
+ const selectedPage = $('#saveAsPageSelect').val();
300
+ const filename = $('#saveAsFilename').val().trim();
301
+
302
+ if (!filename) {
303
+ alert('Veuillez saisir un nom de fichier.');
304
+ return null;
305
+ }
306
+
307
+ let subpageName;
308
+
309
+ if (useExistingPage && selectedPage) {
310
+ subpageName = selectedPage;
311
+ } else {
312
+ subpageName = 'Non classified';
313
+ }
314
+
315
+ // Build the final URL: subpagename/filename.json
316
+ const finalUrl = `${subpageName}/${filename}.json`;
317
+
318
+ if (crops.defaultTitle !== false) {
319
+ crops.title = filename;
320
+ }
321
+
322
+ return finalUrl;
323
+ }
324
+
325
+ /**
326
+ * Handle the save as operation
327
+ */
328
+ async saveAs() {
329
+ const url = this.buildSaveAsUrl();
330
+
331
+ if (!url) {
332
+ return; // Error already handled in buildSaveAsUrl
333
+ }
334
+
335
+ const self = this;
336
+ const oldPageTitle = self.pageTitle;
337
+
338
+ try {
339
+ // Set the new page title
340
+ self.pageTitle = url;
341
+
342
+ // Save to the wiki first
343
+ await self.savePageToWiki(self.pageTitle, JSON.stringify(crops, null, 2));
344
+
345
+ // Close the modal
346
+ const modal = bootstrap.Modal.getInstance(document.getElementById('saveAsModal'));
347
+ modal.hide();
348
+
349
+ alert(`Itinéraire technique enregistré avec succès sous "${url}"`);
350
+
351
+ // Only navigate after successful save
352
+ const newEditorUrl = `editor.html?wiki=${encodeURIComponent(self.pageTitle)}`;
353
+ window.location.href = newEditorUrl;
354
+
355
+ } catch (error) {
356
+ // Restore the old page title if save failed
357
+ self.pageTitle = oldPageTitle;
358
+ console.error("Error saving as new file:", error);
359
+ alert('Erreur lors de l\'enregistrement du fichier.');
360
+ }
361
+ }
223
362
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@osfarm/itineraire-technique",
3
- "version": "1.1.12",
3
+ "version": "1.1.14",
4
4
  "description": "A visualisation tool to show agricultural technical itineraries based on Echarts",
5
5
  "main": "editor.html",
6
6
  "scripts": {
@@ -1,17 +1,19 @@
1
- $button-color: green;
1
+ $button-color : green;
2
2
  $button-hover-color: rgb(2, 102, 2);
3
- $button-text-color: white;
4
- $header-height: 3rem;
3
+ $button-text-color : white;
4
+ $header-height : 3rem;
5
5
 
6
6
  .main-header {
7
7
  background-color: #6fa76f;
8
- color: white;
9
- height: $header-height;
8
+ color : white;
9
+ height : $header-height;
10
10
 
11
- display: flex;
11
+ display : flex;
12
12
  align-items: center;
13
13
 
14
- .btn.show, .btn:first-child:active, :not(.btn-check)+.btn:active {
14
+ .btn.show,
15
+ .btn:first-child:active,
16
+ :not(.btn-check)+.btn:active {
15
17
  background-color: #026602;
16
18
  }
17
19
  }
@@ -26,11 +28,11 @@ $header-height: 3rem;
26
28
  // Add a button in the rotation to edit right from the transcript
27
29
  .rotation_item {
28
30
  .step-edit {
29
- color: #878787;
30
- cursor: pointer;
31
- display:none;
32
- margin-left: 10px;
33
- font-size: 70%;
31
+ color : #878787;
32
+ cursor : pointer;
33
+ display : none;
34
+ margin-left : 10px;
35
+ font-size : 70%;
34
36
  vertical-align: super;
35
37
  }
36
38
 
@@ -41,10 +43,10 @@ $header-height: 3rem;
41
43
 
42
44
  .welcome-view {
43
45
  background-color: #6fa76f;
44
- color: white;
45
- padding: 1rem;
46
- margin-top: 1rem;
47
- border-radius: 1rem;
46
+ color : white;
47
+ padding : 1rem;
48
+ margin-top : 1rem;
49
+ border-radius : 1rem;
48
50
 
49
51
  #cropsContainer {
50
52
  color: green;
@@ -53,22 +55,22 @@ $header-height: 3rem;
53
55
 
54
56
  .card-white {
55
57
  background-color: white;
56
- padding: 1rem;
57
- border-radius: 1rem;
58
+ padding : 1rem;
59
+ border-radius : 1rem;
58
60
  }
59
61
 
60
62
  .card-holder {
61
63
  background-color: whitesmoke;
62
- padding: 1rem;
63
- border-radius: 1rem;
64
- margin: auto
64
+ padding : 1rem;
65
+ border-radius : 1rem;
66
+ margin : auto
65
67
  }
66
68
 
67
69
  .editable-row {
68
70
  background-color: #f7f7f7;
69
- min-height: 3rem;
71
+ min-height : 3rem;
70
72
 
71
- display: flex;
73
+ display : flex;
72
74
  align-items: center;
73
75
 
74
76
  border-radius: 0.5rem;
@@ -77,7 +79,7 @@ $header-height: 3rem;
77
79
  visibility: hidden;
78
80
  }
79
81
 
80
- &:hover > .edit-buttons {
82
+ &:hover>.edit-buttons {
81
83
  visibility: visible;
82
84
  }
83
85
  }
@@ -87,9 +89,9 @@ $header-height: 3rem;
87
89
  }
88
90
 
89
91
  .primary-button {
90
- color: $button-text-color;
92
+ color : $button-text-color;
91
93
  background-color: $button-color;
92
- border: $button-color;
94
+ border : $button-color;
93
95
 
94
96
  &:hover {
95
97
  background-color: $button-hover-color;
@@ -98,9 +100,9 @@ $header-height: 3rem;
98
100
 
99
101
  .close-step-times {
100
102
  background: none;
101
- border: none;
102
- color: #878787;
103
- font-size: 120%;
103
+ border : none;
104
+ color : #878787;
105
+ font-size : 120%;
104
106
 
105
107
  &:hover {
106
108
  color: #494949;
@@ -108,12 +110,12 @@ $header-height: 3rem;
108
110
  }
109
111
 
110
112
  #cropsContainer .drag-handle {
111
- color: #CCC;
112
- font-weight: normal;
113
- font-size: 86%;
114
- margin-right: 10px;
113
+ color : #CCC;
114
+ font-weight : normal;
115
+ font-size : 86%;
116
+ margin-right : 10px;
115
117
  vertical-align: middle;
116
- cursor: grab;
118
+ cursor : grab;
117
119
  }
118
120
 
119
121
  .form-control {
@@ -126,4 +128,12 @@ $header-height: 3rem;
126
128
  .form-label {
127
129
  font-weight: 600;
128
130
  }
131
+ }
132
+
133
+ #code-snippet {
134
+ font-family : monospace;
135
+ font-size : 13px;
136
+ text-align : left;
137
+ border : 1px inset;
138
+ background-color: #f1f1f1;
129
139
  }