Mercurial > gemma
comparison client/src/components/fairway/Profiles.vue @ 1558:0ded4c56978e
refac: component filestructure. remove admin/map hierarchy
author | Thomas Junk <thomas.junk@intevation.de> |
---|---|
date | Wed, 12 Dec 2018 09:22:20 +0100 |
parents | client/src/components/map/fairway/Profiles.vue@35f85da41fdb |
children | f2d24dceecc7 |
comparison
equal
deleted
inserted
replaced
1557:62171cd9a42b | 1558:0ded4c56978e |
---|---|
1 <template> | |
2 <div | |
3 :class="[ | |
4 'box ui-element rounded bg-white text-nowrap', | |
5 { expanded: showProfiles } | |
6 ]" | |
7 > | |
8 <div> | |
9 <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center"> | |
10 <font-awesome-icon icon="chart-area" class="mr-2"></font-awesome-icon> | |
11 <translate>Profiles</translate> | |
12 <font-awesome-icon | |
13 icon="times" | |
14 class="ml-auto text-muted" | |
15 @click="$store.commit('application/showProfiles', false)" | |
16 ></font-awesome-icon> | |
17 </h6> | |
18 <div | |
19 class="d-flex flex-column p-3 flex-grow-1 text-left position-relative" | |
20 > | |
21 <div | |
22 class="loading d-flex justify-content-center align-items-center" | |
23 v-if="surveysLoading || profileLoading" | |
24 > | |
25 <font-awesome-icon icon="spinner" spin /> | |
26 </div> | |
27 <select | |
28 @click="moveToBottleneck" | |
29 v-model="selectedBottleneck" | |
30 class="form-control font-weight-bold" | |
31 > | |
32 <option :value="null"> | |
33 <translate>Select Bottleneck</translate> | |
34 </option> | |
35 <option | |
36 v-for="bn in bottlenecks" | |
37 :key="bn.properties.name" | |
38 :value="bn.properties.name" | |
39 >{{ bn.properties.name }}</option | |
40 > | |
41 </select> | |
42 <div v-if="selectedBottleneck"> | |
43 <div class="d-flex mt-2"> | |
44 <div class="flex-fill"> | |
45 <small class="text-muted"> | |
46 <translate>Sounding Result</translate>: | |
47 </small> | |
48 <select | |
49 v-model="selectedSurvey" | |
50 class="form-control form-control-sm" | |
51 > | |
52 <option | |
53 v-for="survey in surveys" | |
54 :key="survey.date_info" | |
55 :value="survey" | |
56 >{{ formatSurveyDate(survey.date_info) }}</option | |
57 > | |
58 </select> | |
59 </div> | |
60 <div | |
61 class="flex-fill ml-3" | |
62 v-if="selectedSurvey && surveys.length > 1" | |
63 > | |
64 <small class="text-muted mt-1"> | |
65 <translate>Compare with</translate>: | |
66 </small> | |
67 <select | |
68 v-model="additionalSurvey" | |
69 class="form-control form-control-sm" | |
70 > | |
71 <option :value="null">None</option> | |
72 <option | |
73 v-for="survey in additionalSurveys" | |
74 :key="survey.date_info" | |
75 :value="survey" | |
76 >{{ formatSurveyDate(survey.date_info) }}</option | |
77 > | |
78 </select> | |
79 </div> | |
80 </div> | |
81 <hr class="w-100 mb-0" /> | |
82 <small class="text-muted d-block mt-2"> | |
83 <translate>Saved cross profiles</translate>: | |
84 </small> | |
85 <div class="d-flex"> | |
86 <select | |
87 :class="[ | |
88 'form-control form-control-sm flex-fill', | |
89 { 'rounded-left-only': selectedCut } | |
90 ]" | |
91 v-model="selectedCut" | |
92 > | |
93 <option></option> | |
94 <option | |
95 v-for="(cut, index) in previousCuts" | |
96 :value="cut" | |
97 :key="index" | |
98 >{{ cut.label }}</option | |
99 > | |
100 </select> | |
101 <button | |
102 class="btn btn-sm btn-danger input-button-right" | |
103 @click="confirmDeleteSelectedCut = true" | |
104 v-if="selectedCut && !confirmDeleteSelectedCut" | |
105 > | |
106 <font-awesome-icon icon="trash" /> | |
107 </button> | |
108 <button | |
109 class="btn btn-sm btn-info rounded-0" | |
110 @click="confirmDeleteSelectedCut = false" | |
111 v-if="selectedCut && confirmDeleteSelectedCut" | |
112 > | |
113 <font-awesome-icon icon="times" /> | |
114 </button> | |
115 <button | |
116 class="btn btn-sm btn-danger input-button-right" | |
117 @click="deleteSelectedCut" | |
118 v-if="selectedCut && confirmDeleteSelectedCut" | |
119 > | |
120 <font-awesome-icon icon="check" /> | |
121 </button> | |
122 </div> | |
123 <small class="text-muted d-block mt-2"> | |
124 <translate>Enter coordinates manually</translate>: | |
125 </small> | |
126 <div class="position-relative"> | |
127 <input | |
128 class="form-control form-control-sm pr-5" | |
129 placeholder="Lat,Lon,Lat,Lon" | |
130 v-model="coordinatesInput" | |
131 /> | |
132 <button | |
133 class="btn btn-sm btn-info position-absolute input-button-right" | |
134 @click="applyManualCoordinates" | |
135 style="top: 0; right: 0;" | |
136 v-if="coordinatesInputIsValid" | |
137 > | |
138 <font-awesome-icon icon="check" /> | |
139 </button> | |
140 </div> | |
141 <small class="d-flex text-left mt-2" v-if="startPoint && endPoint"> | |
142 <div class="text-nowrap mr-3"> | |
143 <b> <translate>Start</translate>: </b> <br /> | |
144 Lat: {{ startPoint[1] }} <br /> | |
145 Lon: {{ startPoint[0] }} | |
146 </div> | |
147 <div class="text-nowrap"> | |
148 <b>End:</b> <br /> | |
149 Lat: {{ endPoint[1] }} <br /> | |
150 Lon: {{ endPoint[0] }} | |
151 </div> | |
152 <button | |
153 v-clipboard:copy="coordinatesForClipboard" | |
154 v-clipboard:success="onCopyCoordinates" | |
155 class="btn btn-info btn-sm ml-auto mt-auto" | |
156 > | |
157 <font-awesome-icon icon="copy" /> | |
158 </button> | |
159 </small> | |
160 <div class="d-flex mt-3"> | |
161 <div | |
162 class="pr-3 w-50" | |
163 v-if="startPoint && endPoint && !selectedCut" | |
164 > | |
165 <button | |
166 class="btn btn-info btn-sm w-100" | |
167 @click="showLabelInput = !showLabelInput" | |
168 > | |
169 <font-awesome-icon :icon="showLabelInput ? 'times' : 'check'" /> | |
170 {{ showLabelInput ? "Cancel" : "Save" }} | |
171 </button> | |
172 </div> | |
173 <div | |
174 :class="startPoint && endPoint && !selectedCut ? 'w-50' : 'w-100'" | |
175 > | |
176 <button class="btn btn-info btn-sm w-100" @click="toggleCutTool"> | |
177 <font-awesome-icon | |
178 :icon="cutTool && cutTool.getActive() ? 'times' : 'plus'" | |
179 ></font-awesome-icon> | |
180 {{ cutTool && cutTool.getActive() ? "Cancel" : "New" }} | |
181 </button> | |
182 </div> | |
183 </div> | |
184 <div v-if="showLabelInput" class="mt-2"> | |
185 <small class="text-muted"> | |
186 <translate>Enter label for cross profile</translate>: | |
187 </small> | |
188 <div class="position-relative"> | |
189 <input | |
190 class="form-control form-control-sm pr-5" | |
191 v-model="cutLabel" | |
192 /> | |
193 <button | |
194 class="btn btn-sm btn-info position-absolute input-button-right" | |
195 @click="saveCut" | |
196 v-if="cutLabel" | |
197 style="top: 0; right: 0;" | |
198 > | |
199 <font-awesome-icon icon="check" /> | |
200 </button> | |
201 </div> | |
202 </div> | |
203 </div> | |
204 </div> | |
205 </div> | |
206 </div> | |
207 </template> | |
208 | |
209 <style lang="scss" scoped> | |
210 .loading { | |
211 background: rgba(255, 255, 255, 0.9); | |
212 position: absolute; | |
213 z-index: 99; | |
214 top: 0; | |
215 right: 0; | |
216 bottom: 0; | |
217 left: 0; | |
218 } | |
219 | |
220 .input-button-right { | |
221 border-top-right-radius: $border-radius; | |
222 border-bottom-right-radius: $border-radius; | |
223 border-top-left-radius: 0 !important; | |
224 border-bottom-left-radius: 0 !important; | |
225 } | |
226 | |
227 .rounded-left-only { | |
228 border-top-right-radius: 0 !important; | |
229 border-bottom-right-radius: 0 !important; | |
230 border-top-left-radius: $border-radius; | |
231 border-bottom-left-radius: $border-radius; | |
232 } | |
233 </style> | |
234 | |
235 <script> | |
236 /* This is Free Software under GNU Affero General Public License v >= 3.0 | |
237 * without warranty, see README.md and license for details. | |
238 * | |
239 * SPDX-License-Identifier: AGPL-3.0-or-later | |
240 * License-Filename: LICENSES/AGPL-3.0.txt | |
241 * | |
242 * Copyright (C) 2018 by via donau | |
243 * – Österreichische Wasserstraßen-Gesellschaft mbH | |
244 * Software engineering by Intevation GmbH | |
245 * | |
246 * Author(s): | |
247 * Markus Kottländer <markus.kottlaender@intevation.de> | |
248 */ | |
249 import { mapState, mapGetters } from "vuex"; | |
250 import Feature from "ol/Feature"; | |
251 import LineString from "ol/geom/LineString"; | |
252 import { displayError, displayInfo } from "../../lib/errors.js"; | |
253 import { formatSurveyDate } from "../../lib/date.js"; | |
254 | |
255 export default { | |
256 name: "profiles", | |
257 data() { | |
258 return { | |
259 coordinatesInput: "", | |
260 cutLabel: "", | |
261 showLabelInput: false, | |
262 confirmDeleteSelectedCut: false | |
263 }; | |
264 }, | |
265 computed: { | |
266 ...mapGetters("map", ["getVSourceByName"]), | |
267 ...mapState("application", ["showProfiles"]), | |
268 ...mapState("map", ["lineTool", "polygonTool", "cutTool"]), | |
269 ...mapState("bottlenecks", ["bottlenecks", "surveys", "surveysLoading"]), | |
270 ...mapState("fairwayprofile", [ | |
271 "previousCuts", | |
272 "startPoint", | |
273 "endPoint", | |
274 "profileLoading" | |
275 ]), | |
276 selectedBottleneck: { | |
277 get() { | |
278 return this.$store.state.bottlenecks.selectedBottleneck; | |
279 }, | |
280 set(name) { | |
281 this.$store | |
282 .dispatch("bottlenecks/setSelectedBottleneck", name) | |
283 .then(() => { | |
284 this.$store.commit("bottlenecks/setFirstSurveySelected"); | |
285 }); | |
286 } | |
287 }, | |
288 selectedSurvey: { | |
289 get() { | |
290 return this.$store.state.bottlenecks.selectedSurvey; | |
291 }, | |
292 set(survey) { | |
293 this.$store.commit("fairwayprofile/additionalSurvey", null); | |
294 this.$store.commit("bottlenecks/selectedSurvey", survey); | |
295 } | |
296 }, | |
297 additionalSurvey: { | |
298 get() { | |
299 return this.$store.state.fairwayprofile.additionalSurvey; | |
300 }, | |
301 set(survey) { | |
302 this.$store.commit("fairwayprofile/additionalSurvey", survey); | |
303 } | |
304 }, | |
305 selectedCut: { | |
306 get() { | |
307 return this.$store.state.fairwayprofile.selectedCut; | |
308 }, | |
309 set(cut) { | |
310 this.$store.commit("fairwayprofile/selectedCut", cut); | |
311 if (!cut) { | |
312 this.$store.commit("fairwayprofile/clearCurrentProfile"); | |
313 this.$store.commit("application/showSplitscreen", false); | |
314 this.getVSourceByName("Cut Tool").clear(); | |
315 } | |
316 } | |
317 }, | |
318 additionalSurveys() { | |
319 return this.surveys.filter(survey => survey !== this.selectedSurvey); | |
320 }, | |
321 coordinatesForClipboard() { | |
322 return ( | |
323 this.startPoint[1] + | |
324 "," + | |
325 this.startPoint[0] + | |
326 "," + | |
327 this.endPoint[1] + | |
328 "," + | |
329 this.endPoint[0] | |
330 ); | |
331 }, | |
332 coordinatesInputIsValid() { | |
333 const coordinates = this.coordinatesInput | |
334 .split(",") | |
335 .map(coord => parseFloat(coord.trim())) | |
336 .filter(c => Number(c) === c); | |
337 return coordinates.length === 4; | |
338 } | |
339 }, | |
340 watch: { | |
341 selectedBottleneck() { | |
342 this.$store.dispatch("fairwayprofile/previousCuts"); | |
343 this.cutLabel = | |
344 this.selectedBottleneck + " (" + new Date().toISOString() + ")"; | |
345 }, | |
346 selectedSurvey(survey) { | |
347 this.loadProfile(survey); | |
348 }, | |
349 additionalSurvey(survey) { | |
350 this.loadProfile(survey); | |
351 }, | |
352 selectedCut(cut) { | |
353 if (cut) { | |
354 this.confirmDeleteSelectedCut = false; | |
355 this.applyCoordinates(cut.coordinates); | |
356 } | |
357 } | |
358 }, | |
359 methods: { | |
360 formatSurveyDate(date) { | |
361 return formatSurveyDate(date); | |
362 }, | |
363 loadProfile(survey) { | |
364 if (survey) { | |
365 this.$store.commit("fairwayprofile/profileLoading", true); | |
366 this.$store | |
367 .dispatch("fairwayprofile/loadProfile", survey) | |
368 .finally(() => | |
369 this.$store.commit("fairwayprofile/profileLoading", false) | |
370 ); | |
371 } | |
372 }, | |
373 toggleCutTool() { | |
374 this.cutTool.setActive(!this.cutTool.getActive()); | |
375 this.lineTool.setActive(false); | |
376 this.polygonTool.setActive(false); | |
377 this.$store.commit("map/setCurrentMeasurement", null); | |
378 }, | |
379 onCopyCoordinates() { | |
380 displayInfo({ | |
381 title: this.$gettext("Success"), | |
382 message: this.$gettext("Coordinates copied to clipboard!") | |
383 }); | |
384 }, | |
385 applyManualCoordinates() { | |
386 const coordinates = this.coordinatesInput | |
387 .split(",") | |
388 .map(coord => parseFloat(coord.trim())); | |
389 this.selectedCut = null; | |
390 this.coordinatesInput = ""; | |
391 this.applyCoordinates([ | |
392 coordinates[1], | |
393 coordinates[0], | |
394 coordinates[3], | |
395 coordinates[2] | |
396 ]); | |
397 }, | |
398 applyCoordinates(coordinates) { | |
399 // allow only numbers | |
400 coordinates = coordinates.filter(c => Number(c) === c); | |
401 if (coordinates.length === 4) { | |
402 // draw line on map | |
403 this.getVSourceByName("Cut Tool").clear(); | |
404 const cut = new Feature({ | |
405 geometry: new LineString([ | |
406 [coordinates[0], coordinates[1]], | |
407 [coordinates[2], coordinates[3]] | |
408 ]).transform("EPSG:4326", "EPSG:3857") | |
409 }); | |
410 this.getVSourceByName("Cut Tool").addFeature(cut); | |
411 | |
412 // draw diagram | |
413 this.$store.dispatch("fairwayprofile/cut", cut); | |
414 } else { | |
415 displayError({ | |
416 title: this.$gettext("Invalid input"), | |
417 message: this.$gettext( | |
418 "Please enter correct coordinates in the format: Lat,Lon,Lat,Lon" | |
419 ) | |
420 }); | |
421 } | |
422 }, | |
423 saveCut() { | |
424 const previousCuts = | |
425 JSON.parse(localStorage.getItem("previousCuts")) || []; | |
426 const newEntry = { | |
427 label: this.cutLabel, | |
428 bottleneckName: this.selectedBottleneck, | |
429 coordinates: [...this.startPoint, ...this.endPoint], | |
430 timestamp: new Date().getTime() | |
431 }; | |
432 const existingEntry = previousCuts.find(cut => { | |
433 return JSON.stringify(cut) === JSON.stringify(newEntry); | |
434 }); | |
435 if (!existingEntry) previousCuts.push(newEntry); | |
436 if (previousCuts.length > 100) previousCuts.shift(); | |
437 localStorage.setItem("previousCuts", JSON.stringify(previousCuts)); | |
438 this.$store.dispatch("fairwayprofile/previousCuts"); | |
439 | |
440 this.showLabelInput = false; | |
441 displayInfo({ | |
442 title: this.$gettext("Profile saved!"), | |
443 message: this.$gettext( | |
444 'You can now select these coordinates from the "Saved cross profiles" menu to restore this cross profile.' | |
445 ) | |
446 }); | |
447 }, | |
448 deleteSelectedCut() { | |
449 let previousCuts = JSON.parse(localStorage.getItem("previousCuts")) || []; | |
450 previousCuts = previousCuts.filter(cut => { | |
451 return JSON.stringify(cut) !== JSON.stringify(this.selectedCut); | |
452 }); | |
453 localStorage.setItem("previousCuts", JSON.stringify(previousCuts)); | |
454 this.$store.commit("fairwayprofile/selectedCut", null); | |
455 this.$store.dispatch("fairwayprofile/previousCuts"); | |
456 displayInfo({ title: this.$gettext("Profile deleted!") }); | |
457 }, | |
458 moveToBottleneck() { | |
459 const bottleneck = this.bottlenecks.find( | |
460 bn => bn.properties.name === this.selectedBottleneck | |
461 ); | |
462 if (!bottleneck) return; | |
463 this.$store.commit("map/moveMap", { | |
464 coordinates: bottleneck.geometry.coordinates, | |
465 zoom: 17, | |
466 preventZoomOut: true | |
467 }); | |
468 } | |
469 } | |
470 }; | |
471 </script> |