@abi-software/mapintegratedvuer 1.22.0 → 1.24.0

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.
Files changed (39) hide show
  1. package/dist/{ConnectivityGraph-BBZ3Dgat.js → ConnectivityGraph-CCxk8vcE.js} +6 -6
  2. package/dist/{ContentMixin-B-O_IwPg.js → ContentMixin-Db8nqZax.js} +303 -247
  3. package/dist/{DynamicMarkerMixin-B9iqtP_f.js → DynamicMarkerMixin-LB2RYdSY.js} +1 -1
  4. package/dist/{Flatmap-BoEFI50b.js → Flatmap-CqUzOkdl.js} +5 -5
  5. package/dist/{Iframe-Slj-LQTF.js → Iframe-I-xdZ2FP.js} +2 -2
  6. package/dist/{MultiFlatmap-l6aBn6x3.js → MultiFlatmap-11L97ycJ.js} +6 -6
  7. package/dist/{Plot-CIhEip5b.js → Plot-Bd9RqyVD.js} +2 -2
  8. package/dist/{Scaffold-D5ZGCDvt.js → Scaffold-CySWXQxI.js} +46 -36
  9. package/dist/Simulation-ChUbGudZ.js +32 -0
  10. package/dist/{connectivities-Br3MpoAv.js → connectivities-LtlFwLOp.js} +6399 -5790
  11. package/dist/es-D-YkSGXX.js +436 -0
  12. package/dist/mapintegratedvuer.js +13061 -11664
  13. package/dist/mapintegratedvuer.umd.cjs +191 -190
  14. package/dist/style.css +1 -1
  15. package/package.json +7 -7
  16. package/src/assets/header-icon.scss +9 -3
  17. package/src/components/ContentBar.vue +172 -92
  18. package/src/components/ContentVuer.vue +41 -1
  19. package/src/components/ContextCard.vue +15 -3
  20. package/src/components/DatasetHeader.vue +4 -1
  21. package/src/components/DialogToolbarContent.vue +45 -4
  22. package/src/components/EventBus.js +8 -0
  23. package/src/components/MapContent.vue +85 -31
  24. package/src/components/ResizeSensor.vue +7 -9
  25. package/src/components/ScreenshotPopover.vue +526 -0
  26. package/src/components/SplitDialog.vue +94 -93
  27. package/src/components/SplitFlow.vue +158 -96
  28. package/src/components/scripts/utilities.js +56 -1
  29. package/src/components/viewers/Flatmap.vue +1 -0
  30. package/src/components/viewers/MultiFlatmap.vue +3 -0
  31. package/src/components/viewers/Scaffold.vue +25 -13
  32. package/src/components/viewers/Simulation.vue +23 -1
  33. package/src/components.d.ts +2 -0
  34. package/src/mixins/ContentMixin.js +23 -10
  35. package/src/services/screenshot.js +1186 -0
  36. package/src/stores/connectivities.js +38 -5
  37. package/src/stores/settings.js +18 -0
  38. package/vite.config.js +27 -4
  39. package/dist/Simulation-C5o5DXRb.js +0 -25
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abi-software/mapintegratedvuer",
3
- "version": "1.22.0",
3
+ "version": "1.24.0",
4
4
  "license": "Apache-2.0",
5
5
  "engines": {
6
6
  "node": "^24.20.0"
@@ -58,18 +58,18 @@
58
58
  "*.js"
59
59
  ],
60
60
  "dependencies": {
61
- "@abi-software/flatmapvuer": "1.19.0",
62
- "@abi-software/map-side-bar": "2.19.0",
63
- "@abi-software/map-utilities": "1.12.0",
61
+ "@abi-software/flatmapvuer": "1.21.0",
62
+ "@abi-software/map-side-bar": "2.20.0",
63
+ "@abi-software/map-utilities": "1.14.0",
64
64
  "@abi-software/plotvuer": "1.3.0",
65
- "@abi-software/scaffoldvuer": "2.0.0",
66
- "@abi-software/simulationvuer": "4.3.0",
65
+ "@abi-software/scaffoldvuer": "2.1.0",
66
+ "@abi-software/simulationvuer": "4.4.0",
67
67
  "@abi-software/sparc-annotation": "0.3.2",
68
68
  "@abi-software/svg-sprite": "1.4.0",
69
69
  "@element-plus/icons-vue": "^2.3.2",
70
70
  "@vitejs/plugin-vue": "^6.0.8",
71
- "css-element-queries": "^1.2.3",
72
71
  "element-plus": "^2.14.5",
72
+ "html-to-image": "^1.11.13",
73
73
  "marked": "^8.0.1",
74
74
  "mitt": "^3.0.1",
75
75
  "pinia": "^4.0.3",
@@ -16,9 +16,15 @@
16
16
  display: flex;
17
17
  flex-direction: row;
18
18
  justify-content: center;
19
- top: 4px;
20
- right: 12px;
21
- gap: 10px;
19
+ align-items: center;
20
+ top: 0;
21
+ right: 1rem;
22
+ gap: 8px;
23
+ height: 31px; // header has 1px border
24
+
25
+ svg.map-icon {
26
+ margin: 0;
27
+ }
22
28
  }
23
29
 
24
30
  .icon-group {
@@ -1,100 +1,113 @@
1
1
  <template>
2
2
  <div>
3
3
  <div class="toolbar-flex-container">
4
- <el-select
5
- v-if="entries.length > 1"
6
- :teleported="false"
7
- :model-value="entry.id"
8
- placeholder="Select"
9
- class="select-box"
10
- popper-class="viewer_dropdown"
11
- @change="viewerChanged($event)"
12
- >
13
- <el-option
14
- v-for="entry in entries"
15
- :key="entry.id"
16
- :label="getTitle(entry)"
17
- :value="entry.id"
18
- />
19
- </el-select>
20
- <div v-else class="toolbar-title shrink">
21
- {{ getEntryTitle(entry) }}
4
+ <div class="toolbar-left shrink">
5
+ <el-select
6
+ v-if="entries.length > 1"
7
+ :teleported="false"
8
+ :model-value="entry.id"
9
+ placeholder="Select"
10
+ class="select-box"
11
+ popper-class="viewer_dropdown"
12
+ @change="viewerChanged($event)"
13
+ >
14
+ <el-option
15
+ v-for="entry in entries"
16
+ :key="entry.id"
17
+ :label="getTitle(entry)"
18
+ :value="entry.id"
19
+ />
20
+ </el-select>
21
+ <div v-else class="toolbar-title shrink">
22
+ {{ getEntryTitle(entry) }}
23
+ </div>
24
+ <el-button
25
+ v-if="hasSourceInfo"
26
+ round
27
+ size="small"
28
+ class="source-chip shrink"
29
+ @click="openSourceInfo"
30
+ >
31
+ {{ getSourceTitle }}
32
+ </el-button>
22
33
  </div>
23
- <el-button
24
- v-if="hasSourceInfo"
25
- round
26
- size="small"
27
- class="source-chip shrink"
28
- @click="openSourceInfo"
29
- >
30
- {{ getSourceTitle }}
31
- </el-button>
32
- <div class="information-group shrink">
34
+
35
+ <div class="toolbar-right shrink">
36
+ <div class="information-group shrink">
37
+ <el-popover
38
+ placement="bottom"
39
+ :teleported="false"
40
+ trigger="manual"
41
+ :width="setPopperWidth(slot.id)"
42
+ :offset="0"
43
+ popper-class="context-card-popover"
44
+ :popper-options="popperOptions"
45
+ :visible="contextCardVisible"
46
+ >
47
+ <template #default v-if="contextCardEntry">
48
+ <flatmap-context-card
49
+ class="flatmap-context-card"
50
+ v-if="contextCardEntry.type == 'Flatmap' || contextCardEntry.type == 'MultiFlatmap'"
51
+ :mapImpProv="contextCardEntry.mapImpProv"
52
+ />
53
+ <context-card
54
+ v-if="contextCardEntry.type.toLowerCase() == 'scaffold'"
55
+ :entry="contextCardEntry"
56
+ :envVars="envVars"
57
+ class="context-card"
58
+ @context-ready="contextCardVisible = true"
59
+ @scaffold-view-clicked="$emit('scaffold-view-clicked', $event)"
60
+ />
61
+ </template>
62
+ <template #reference>
63
+ <div v-show="contextCardEntry">
64
+ <div
65
+ v-show="contextCardVisible"
66
+ class="information"
67
+ @click="contextCardVisible = false"
68
+ >
69
+ Hide information
70
+ <el-icon><el-icon-arrow-up /></el-icon>
71
+ </div>
72
+ <div
73
+ v-show="!contextCardVisible"
74
+ class="information"
75
+ @click="contextCardVisible = true"
76
+ >
77
+ Show information
78
+ <el-icon><el-icon-arrow-down /></el-icon>
79
+ </div>
80
+ </div>
81
+ </template>
82
+ </el-popover>
83
+ </div>
84
+ <ScreenshotPopover
85
+ v-if="captureScreenshot"
86
+ :disabled="!viewerReady"
87
+ class="screenshot-button"
88
+ tooltip="Download screenshot of this view"
89
+ :capture="onCaptureScreenshot"
90
+ :getTarget="getScreenshotTarget"
91
+ />
33
92
  <el-popover
34
- placement="bottom"
93
+ class="tooltip"
94
+ content="Close and remove"
95
+ placement="bottom-end"
96
+ :show-after="helpDelay"
35
97
  :teleported="false"
36
- trigger="manual"
37
- :width="setPopperWidth(slot.id)"
38
- :offset="0"
39
- popper-class="context-card-popover"
40
- :popper-options="popperOptions"
41
- :visible="contextCardVisible"
98
+ trigger="hover"
99
+ popper-class="header-popper"
42
100
  >
43
- <template #default v-if="contextCardEntry">
44
- <flatmap-context-card
45
- class="flatmap-context-card"
46
- v-if="contextCardEntry.type == 'Flatmap' || contextCardEntry.type == 'MultiFlatmap'"
47
- :mapImpProv="contextCardEntry.mapImpProv"
48
- />
49
- <context-card
50
- v-if="contextCardEntry.type.toLowerCase() == 'scaffold'"
51
- :entry="contextCardEntry"
52
- :envVars="envVars"
53
- class="context-card"
54
- @context-ready="contextCardVisible = true"
55
- @scaffold-view-clicked="$emit('scaffold-view-clicked', $event)"
56
- />
57
- </template>
58
101
  <template #reference>
59
- <div v-show="contextCardEntry">
60
- <div
61
- v-show="contextCardVisible"
62
- class="information"
63
- @click="contextCardVisible = false"
64
- >
65
- Hide information
66
- <el-icon><el-icon-arrow-up /></el-icon>
67
- </div>
68
- <div
69
- v-show="!contextCardVisible"
70
- class="information"
71
- @click="contextCardVisible = true"
72
- >
73
- Show information
74
- <el-icon><el-icon-arrow-down /></el-icon>
75
- </div>
76
- </div>
102
+ <map-svg-icon
103
+ icon="close-no-bk"
104
+ class="header-icon rightmost"
105
+ v-show="activeView !== 'singlepanel' && (entry.mode !== 'main' || allClosable)"
106
+ @click="closeAndRemove()"
107
+ />
77
108
  </template>
78
109
  </el-popover>
79
110
  </div>
80
- <el-popover
81
- class="tooltip"
82
- content="Close and remove"
83
- placement="bottom-end"
84
- :show-after="helpDelay"
85
- :teleported="false"
86
- trigger="hover"
87
- popper-class="header-popper"
88
- >
89
- <template #reference>
90
- <map-svg-icon
91
- icon="close-no-bk"
92
- class="header-icon rightmost"
93
- v-show="activeView !== 'singlepanel' && (entry.mode !== 'main' || allClosable)"
94
- @click="closeAndRemove()"
95
- />
96
- </template>
97
- </el-popover>
98
111
  </div>
99
112
  </div>
100
113
  </template>
@@ -108,6 +121,7 @@ import { useSettingsStore } from '../stores/settings';
108
121
  import { useSplitFlowStore } from '../stores/splitFlow';
109
122
  import ContextCard from './ContextCard.vue';
110
123
  import FlatmapContextCard from './FlatmapContextCard.vue';
124
+ import ScreenshotPopover from './ScreenshotPopover.vue';
111
125
  import { ArrowDown as ElIconArrowDown, ArrowUp as ElIconArrowUp } from '@element-plus/icons-vue';
112
126
  import tagging from '../services/tagging';
113
127
 
@@ -119,9 +133,31 @@ export default {
119
133
  ContextCard,
120
134
  FlatmapContextCard,
121
135
  MapSvgIcon,
136
+ ScreenshotPopover,
122
137
  },
123
138
  props: {
124
139
  entry: Object,
140
+ /**
141
+ * Async function capturing a screenshot of this view.
142
+ */
143
+ captureScreenshot: {
144
+ type: Function,
145
+ default: undefined,
146
+ },
147
+ /**
148
+ * Function returning the element captured, used for size preview.
149
+ */
150
+ getScreenshotTarget: {
151
+ type: Function,
152
+ default: undefined,
153
+ },
154
+ /**
155
+ * Whether the viewer's content has finished loading.
156
+ */
157
+ viewerReady: {
158
+ type: Boolean,
159
+ default: false,
160
+ },
125
161
  },
126
162
  data: function () {
127
163
  return {
@@ -202,6 +238,19 @@ export default {
202
238
  },
203
239
  },
204
240
  methods: {
241
+ onCaptureScreenshot: async function (options) {
242
+ const result = await this.captureScreenshot(options);
243
+
244
+ // GA Tracking
245
+ tagging.sendEvent({
246
+ event: 'interaction_event',
247
+ event_name: 'portal_maps_screenshot',
248
+ category: `png_${options.scale}x`,
249
+ location: 'pane_header',
250
+ });
251
+
252
+ return result;
253
+ },
205
254
  closeAndRemove: function () {
206
255
  this.splitFlowStore.closeSlot({ id: this.entry.id, entries: this.entries });
207
256
  EventBus.emit('RemoveEntryRequest', this.entry.id);
@@ -269,7 +318,7 @@ export default {
269
318
  target: value,
270
319
  });
271
320
  this.$nextTick(() => {
272
- setTimeout(() => {
321
+ this._chooserTimer = setTimeout(() => {
273
322
  this.$emit('chooser-changed');
274
323
  }, 1200);
275
324
  });
@@ -316,6 +365,9 @@ export default {
316
365
  mounted: function () {
317
366
  this.setupScaffoldContextCard();
318
367
  },
368
+ beforeUnmount: function () {
369
+ clearTimeout(this._chooserTimer);
370
+ },
319
371
  };
320
372
  </script>
321
373
 
@@ -327,19 +379,38 @@ export default {
327
379
  display: flex;
328
380
  flex-direction: row;
329
381
  align-items: center;
382
+ justify-content: space-between;
330
383
  flex-wrap: nowrap;
331
384
  width: 100%;
385
+ gap: 16px;
332
386
 
333
387
  .shrink {
334
388
  flex-shrink: 1;
335
389
  min-width: 0;
336
390
  }
337
391
 
338
- .information-group {
339
- margin-left: auto;
340
- margin-right: 12px;
392
+ .toolbar-left,
393
+ .toolbar-right {
394
+ display: flex;
395
+ flex-direction: row;
396
+ align-items: center;
341
397
  height: 100%;
398
+ gap: 8px;
399
+ }
400
+
401
+ .toolbar-left {
402
+ justify-content: flex-start;
403
+ }
404
+
405
+ .toolbar-right {
406
+ justify-content: flex-end;
407
+ margin-left: auto;
342
408
  position: relative;
409
+ margin-right: 1rem; // same as header toolbar
410
+ }
411
+
412
+ .information-group {
413
+ height: 100%;
343
414
  }
344
415
 
345
416
  .toolbar-title {
@@ -396,12 +467,14 @@ export default {
396
467
  color: $lightGrey;
397
468
  }
398
469
  }
470
+
399
471
  i .select-box :deep(.el-input__icon) {
400
472
  color: rgb(48, 49, 51);
401
473
  height: 24px;
402
474
  padding-left: 8px;
403
475
  padding-right: 8px;
404
476
  }
477
+
405
478
  .text {
406
479
  margin-left: 8px;
407
480
  margin-top: 7px;
@@ -412,6 +485,7 @@ export default {
412
485
  user-select: none;
413
486
  line-height: 18px;
414
487
  }
488
+
415
489
  .title {
416
490
  width: 140px;
417
491
  color: $app-primary-color;
@@ -428,8 +502,6 @@ export default {
428
502
 
429
503
  .source-chip {
430
504
  padding: 4px !important;
431
- margin-left: 2px;
432
- margin-right: 2px;
433
505
  background-color: $app-primary-color;
434
506
  border-color: $app-primary-color;
435
507
  color: #fff;
@@ -455,6 +527,10 @@ export default {
455
527
  flex-shrink: 0;
456
528
  }
457
529
 
530
+ .screenshot-button {
531
+ flex-shrink: 0;
532
+ }
533
+
458
534
  .information {
459
535
  font-size: 12px;
460
536
  margin-top: 0;
@@ -466,6 +542,10 @@ export default {
466
542
  cursor: pointer;
467
543
  line-height: normal;
468
544
  }
545
+
546
+ svg.map-icon {
547
+ margin: 0;
548
+ }
469
549
  }
470
550
 
471
551
  .viewer_dropdown {
@@ -10,6 +10,9 @@
10
10
  ref="contentBar"
11
11
  @chooser-changed="onResize"
12
12
  @scaffold-view-clicked="scaffoldViewClicked"
13
+ :captureScreenshot="captureScreenshot"
14
+ :getScreenshotTarget="getScreenshotElement"
15
+ :viewerReady="viewerReady"
13
16
  @vue:mounted="setPanesBoundary"
14
17
  />
15
18
  <!--
@@ -31,6 +34,7 @@
31
34
  @flatmap-provenance-ready="flatmapProvenanceReady"
32
35
  @resource-selected="resourceSelected"
33
36
  @species-changed="speciesChanged"
37
+ @viewer-ready="viewerReady = $event"
34
38
  />
35
39
  </Suspense>
36
40
  </div>
@@ -42,7 +46,9 @@ import { defineAsyncComponent } from 'vue';
42
46
  import ContentBar from './ContentBar.vue';
43
47
  import { mapStores } from 'pinia';
44
48
  import { useEntriesStore } from '../stores/entries';
49
+ import { useSettingsStore } from '../stores/settings';
45
50
  import { useSplitFlowStore } from '../stores/splitFlow';
51
+ import { captureElement, findLegends } from '../services/screenshot';
46
52
 
47
53
  const Flatmap = defineAsyncComponent(() => import('./viewers/Flatmap.vue'));
48
54
  const MapIframe = defineAsyncComponent(() => import('./viewers/Iframe.vue'));
@@ -87,6 +93,38 @@ export default {
87
93
  getState: function () {
88
94
  return this.$refs.viewer?.getState();
89
95
  },
96
+ /**
97
+ * Capture this pane and download it.
98
+ * The header bar is included only if `toolbar` is set.
99
+ * @param {Object} options - { scale, legend, toolbar }
100
+ */
101
+ captureScreenshot: function (options = {}) {
102
+ const scale = options.scale || this.settingsStore.screenshot.scale;
103
+ const legend = options.legend || this.settingsStore.screenshot.legend;
104
+ const toolbar = options.toolbar ?? this.settingsStore.screenshot.toolbar;
105
+ const title = this.$refs.contentBar?.getEntryTitle(this.entry) || this.entry.type;
106
+ return captureElement(this.getScreenshotElement({ legend, toolbar }), {
107
+ scale,
108
+ legend,
109
+ toolbar,
110
+ title,
111
+ viewers: [this],
112
+ });
113
+ },
114
+ hasLegend: function () {
115
+ return findLegends(this.$refs.container).length > 0;
116
+ },
117
+ /**
118
+ * The whole pane when the header bar is included, otherwise only the viewer.
119
+ * @param {Object} options - { legend, toolbar }
120
+ */
121
+ getScreenshotElement: function (options = {}) {
122
+ const { legend, toolbar } = options;
123
+ return toolbar && legend !== 'only' ? this.$el : this.$refs.container;
124
+ },
125
+ getScreenshotSources: function (pixelRatio) {
126
+ return this.$refs.viewer?.getScreenshotSources?.(pixelRatio) || [];
127
+ },
90
128
  resourceSelected: function (payload) {
91
129
  this.$emit('resource-selected', payload);
92
130
  },
@@ -168,10 +206,12 @@ export default {
168
206
  return {
169
207
  mouseHovered: false,
170
208
  activeSpecies: 'Rat',
209
+ // Viewers without a ready event are considered ready immediately
210
+ viewerReady: !['Flatmap', 'MultiFlatmap', 'Scaffold'].includes(this.entry.type),
171
211
  };
172
212
  },
173
213
  computed: {
174
- ...mapStores(useEntriesStore, useSplitFlowStore),
214
+ ...mapStores(useEntriesStore, useSettingsStore, useSplitFlowStore),
175
215
  viewerType() {
176
216
  switch (this.entry.type) {
177
217
  case 'MapIframe':
@@ -7,7 +7,7 @@
7
7
  class="context-card"
8
8
  >
9
9
  <div class="card-left">
10
- <img :src="banner" class="context-image" />
10
+ <img :crossorigin="crossorigin" :src="banner" class="context-image" />
11
11
  </div>
12
12
  <div class="card-right scrollbar">
13
13
  <div>
@@ -54,7 +54,11 @@
54
54
  </div>
55
55
  <template v-for="(view, i) in contextData.views" :key="i + '_1'">
56
56
  <div @click="openViewFile(view)" class="context-card-view">
57
- <img class="view-image" :src="getFileFromPath(view.thumbnail)" />
57
+ <img
58
+ :crossorigin="crossorigin"
59
+ class="view-image"
60
+ :src="getFileFromPath(view.thumbnail)"
61
+ />
58
62
  <div class="view-description">{{ view.description }}</div>
59
63
  </div>
60
64
  <div class="padding" />
@@ -72,6 +76,7 @@
72
76
  :style="'background-color:' + sample.color"
73
77
  ></div>
74
78
  <img
79
+ :crossorigin="crossorigin"
75
80
  class="key-image"
76
81
  v-else-if="sample.thumbnail"
77
82
  :src="getFileFromPath(sample.thumbnail)"
@@ -99,7 +104,11 @@
99
104
  </div>
100
105
  <template v-for="(view, i) in contextData.views" :key="i + '_1'">
101
106
  <span @click="viewClicked(view, i)" class="context-card-view">
102
- <img class="view-image" :src="getFileFromPath(view.thumbnail)" />
107
+ <img
108
+ :crossorigin="crossorigin"
109
+ class="view-image"
110
+ :src="getFileFromPath(view.thumbnail)"
111
+ />
103
112
  <div class="view-description">
104
113
  {{ view.description }}
105
114
  <i class="el-icon-warning-outline info"></i>
@@ -171,6 +180,9 @@ export default {
171
180
  CopyToClipboard,
172
181
  },
173
182
  mixins: [S3Bucket],
183
+ inject: {
184
+ crossorigin: { default: 'anonymous' },
185
+ },
174
186
  props: {
175
187
  /**
176
188
  * Object containing information for
@@ -18,7 +18,7 @@
18
18
  class="dataset-card"
19
19
  ref="card"
20
20
  >
21
- <img :src="entry.datasetImage" class="image" />
21
+ <img :crossorigin="crossorigin" :src="entry.datasetImage" class="image" />
22
22
  <div style="padding: 14px">
23
23
  <span class="dataset-description">{{ entry.datasetDescription }}</span>
24
24
  <div>
@@ -33,6 +33,9 @@
33
33
  <script>
34
34
  export default {
35
35
  name: 'DatasetHeader',
36
+ inject: {
37
+ crossorigin: { default: 'anonymous' },
38
+ },
36
39
  props: {
37
40
  /**
38
41
  * Object containing information for
@@ -231,6 +231,15 @@
231
231
  />
232
232
  </template>
233
233
  </el-popover>
234
+ <ScreenshotPopover
235
+ v-if="captureScreenshot"
236
+ title="Download screenshot of all views"
237
+ tooltip="Download screenshot"
238
+ :capture="onCaptureScreenshot"
239
+ :getTarget="getScreenshotTarget"
240
+ :disabled="!mapLoaded"
241
+ shortcut
242
+ />
234
243
  <el-popover
235
244
  v-if="permalinkRef"
236
245
  ref="linkPopover"
@@ -463,7 +472,7 @@
463
472
  <el-icon
464
473
  ref="globalSettingRef"
465
474
  :disabled="!mapLoaded"
466
- class="header-icon"
475
+ class="header-icon global-settings-icon"
467
476
  :class="{ disabled: !mapLoaded }"
468
477
  >
469
478
  <el-icon-more-filled />
@@ -476,13 +485,14 @@
476
485
 
477
486
  <script>
478
487
  import { shallowRef } from 'vue';
479
- import EventBus from './EventBus';
488
+ import EventBus, { onEvents } from './EventBus';
480
489
  import { mapStores } from 'pinia';
481
490
  import { useEntriesStore } from '../stores/entries';
482
491
  import { useSettingsStore } from '../stores/settings';
483
492
  import { useSplitFlowStore } from '../stores/splitFlow';
484
493
  import { MapSvgIcon, MapSvgSpriteColor } from '@abi-software/svg-sprite';
485
494
  import SearchControls from './SearchControls.vue';
495
+ import ScreenshotPopover from './ScreenshotPopover.vue';
486
496
  import { CopyDocument as ElIconCopyDocument } from '@element-plus/icons-vue';
487
497
  import tagging from '../services/tagging';
488
498
 
@@ -494,6 +504,7 @@ export default {
494
504
  components: {
495
505
  MapSvgIcon,
496
506
  MapSvgSpriteColor,
507
+ ScreenshotPopover,
497
508
  SearchControls,
498
509
  },
499
510
  props: {
@@ -511,6 +522,20 @@ export default {
511
522
  type: Boolean,
512
523
  default: false,
513
524
  },
525
+ /**
526
+ * Async function capturing a screenshot of all visible views.
527
+ */
528
+ captureScreenshot: {
529
+ type: Function,
530
+ default: undefined,
531
+ },
532
+ /**
533
+ * Function returning the element captured, used for size preview.
534
+ */
535
+ getScreenshotTarget: {
536
+ type: Function,
537
+ default: undefined,
538
+ },
514
539
  },
515
540
  inject: ['showGlobalSettings'],
516
541
  computed: {
@@ -703,6 +728,19 @@ export default {
703
728
  this.getShareLink(false);
704
729
  }
705
730
  },
731
+ onCaptureScreenshot: async function (options) {
732
+ const result = await this.captureScreenshot(options);
733
+
734
+ // GA Tracking
735
+ tagging.sendEvent({
736
+ event: 'interaction_event',
737
+ event_name: 'portal_maps_screenshot',
738
+ category: `png_${options.scale}x`,
739
+ location: options.source === 'shortcut' ? 'keyboard_shortcut' : 'map_toolbar',
740
+ });
741
+
742
+ return result;
743
+ },
706
744
  getShareLink: function (withAnnotation) {
707
745
  this.displayShareOptions = false;
708
746
  this.loadingLink = true;
@@ -745,8 +783,10 @@ export default {
745
783
  this.permalinkRef = shallowRef(this.$refs.permalinkRef);
746
784
  this.globalSettingRef = shallowRef(this.$refs.globalSettingRef);
747
785
 
748
- EventBus.on('mapLoaded', () => {
749
- this.mapLoaded = true;
786
+ this._offBus = onEvents({
787
+ mapLoaded: () => {
788
+ this.mapLoaded = true;
789
+ },
750
790
  });
751
791
 
752
792
  document.addEventListener('fullscreenchange', this.onFullscreenEsc);
@@ -754,6 +794,7 @@ export default {
754
794
  this.loadGlobalSettings();
755
795
  },
756
796
  unmounted: function () {
797
+ this._offBus?.();
757
798
  document.removeEventListener('fullscreenchange', this.onFullscreenEsc);
758
799
  },
759
800
  };