@abi-software/flatmapvuer 1.1.0-beta.3 → 1.1.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.
@@ -0,0 +1,360 @@
1
+ <template>
2
+ <div class="help-mode-dialog" :class="{'finish': lastItem}">
3
+ <h4>Help Mode</h4>
4
+
5
+ <template v-if="lastItem">
6
+ <p>
7
+ All caught up! <br>
8
+ Click 'Help' to restart.
9
+ </p>
10
+ <div>
11
+ <el-button class="button" @click="finishHelpMode">
12
+ Finish
13
+ </el-button>
14
+ </div>
15
+ </template>
16
+ <template v-else>
17
+ <p>Click "Next" to see the next item.</p>
18
+ <div>
19
+ <el-button class="button" @click="showNext">
20
+ Next
21
+ </el-button>
22
+ <el-button class="button secondary" @click="finishHelpMode">
23
+ Exit Help Mode
24
+ </el-button>
25
+ </div>
26
+ </template>
27
+ </div>
28
+ </template>
29
+
30
+ <script>
31
+ import {
32
+ ElButton as Button,
33
+ } from 'element-plus';
34
+
35
+ export default {
36
+ name: 'HelpModeDialog',
37
+ components: {
38
+ Button,
39
+ },
40
+ props: {
41
+ multiflatmapRef: {
42
+ type: Object,
43
+ default: null,
44
+ },
45
+ flatmapRef: {
46
+ type: Object,
47
+ default: null,
48
+ },
49
+ scaffoldRef: {
50
+ type: Object,
51
+ default: null,
52
+ },
53
+ lastItem: {
54
+ type: Boolean,
55
+ default: false,
56
+ required: false,
57
+ }
58
+ },
59
+ mounted: function () {
60
+ this.toggleHelpModeHighlight(true);
61
+ // For tooltips outside of Flatmap
62
+ this.toggleTooltipHighlight();
63
+ },
64
+ unmounted: function () {
65
+ this.toggleHelpModeHighlight(false);
66
+ },
67
+ watch: {
68
+ lastItem: function (isLastItem) {
69
+ if (isLastItem) {
70
+ this.toggleTooltipHighlight();
71
+ }
72
+ }
73
+ },
74
+ methods: {
75
+ showNext: function () {
76
+ this.$emit('show-next');
77
+ },
78
+ finishHelpMode: function () {
79
+ this.$emit('finish-help-mode');
80
+ },
81
+ /**
82
+ * This function must be called on 'shown-map-tooltip' event.
83
+ */
84
+ toggleTooltipPinHighlight: function () {
85
+ const currentFlatmapEl = this.getCurrentFlatmap();
86
+ this.resetHighlightedItems();
87
+
88
+ this.$nextTick(() => {
89
+ // Temporary solution to find the position of map marker from popover
90
+ const mapPins = currentFlatmapEl.querySelectorAll('.maplibregl-marker');
91
+ const mapPinPopover = currentFlatmapEl.querySelector('.flatmap-popup-popper');
92
+ const styleVal = mapPinPopover?.style?.transform || '';
93
+ const mapPopoverPosition = this.extractMarkerPosition(styleVal);
94
+
95
+ mapPins.forEach((mapPin) => {
96
+ const mapPinStyleVal = mapPin.style.transform;
97
+ const mapPinPosition = this.extractMarkerPosition(mapPinStyleVal);
98
+
99
+ if (mapPinPosition === mapPopoverPosition) {
100
+ mapPin.classList.add('in-help-highlight');
101
+ }
102
+ });
103
+ });
104
+ },
105
+ /**
106
+ * This function must be called on 'shown-tooltip' event.
107
+ */
108
+ toggleTooltipHighlight: function () {
109
+ this.resetHighlightedItems();
110
+
111
+ this.$nextTick(() => {
112
+ const activePoppers = document.querySelectorAll('.el-popper:not([style*="none"])');
113
+
114
+ activePoppers.forEach((activePopper) => {
115
+ const multiFlatmapTooltip = activePopper.classList.contains('flatmap-popper');
116
+ const flatmapTooltip = activePopper.classList.contains('el-fade-in-linear-enter-active');
117
+
118
+ if (multiFlatmapTooltip || flatmapTooltip) {
119
+ this.toggleHighlight(activePopper);
120
+ }
121
+ });
122
+ });
123
+ },
124
+ toggleHighlight: function (activePopper) {
125
+ const popperId = activePopper?.id || '';
126
+ const popperTrigger = document.querySelector(`[aria-describedby="${popperId}"]`);
127
+
128
+ if (popperTrigger) {
129
+ popperTrigger.classList.add('in-help-highlight');
130
+ }
131
+ },
132
+ resetHighlightedItems: function () {
133
+ const allHighlightedItems = document.querySelectorAll('.in-help-highlight');
134
+ allHighlightedItems.forEach((el) => {
135
+ el.classList.remove('in-help-highlight');
136
+ });
137
+ },
138
+ getCurrentScaffold: function () {
139
+ const scaffoldEl = this.scaffoldRef?.$el || null;
140
+ return scaffoldEl;
141
+ },
142
+ getCurrentMultiflatmap: function () {
143
+ const multiflatmapEl = this.multiflatmapRef?.$el || null;
144
+ return multiflatmapEl;
145
+ },
146
+ getCurrentFlatmap: function () {
147
+ const flatmap = this.flatmapRef || this.multiflatmapRef?.getCurrentFlatmap();
148
+ const flatmapEl = flatmap?.$el || null;
149
+ return flatmapEl;
150
+ },
151
+ toggleHelpModeHighlight: function (option) {
152
+ const currentMultiflatmapEl = this.getCurrentMultiflatmap();
153
+ const currentFlatmapEl = this.getCurrentFlatmap();
154
+ const currentScaffoldEl = this.getCurrentScaffold();
155
+ const allHighlightedItems = document.querySelectorAll('.in-help-highlight');
156
+
157
+ if (currentMultiflatmapEl) {
158
+ if (option) {
159
+ currentMultiflatmapEl.classList.add('in-help');
160
+ } else {
161
+ currentMultiflatmapEl.classList.remove('in-help');
162
+ }
163
+ }
164
+
165
+ if (currentFlatmapEl) {
166
+ if (option) {
167
+ currentFlatmapEl.classList.add('in-help');
168
+ } else {
169
+ currentFlatmapEl.classList.remove('in-help');
170
+ }
171
+ }
172
+
173
+ if (currentScaffoldEl) {
174
+ if (option) {
175
+ currentScaffoldEl.classList.add('in-help');
176
+ } else {
177
+ currentScaffoldEl.classList.remove('in-help');
178
+ }
179
+ }
180
+
181
+ if (!option) {
182
+ allHighlightedItems.forEach((el) => {
183
+ el.classList.remove('in-help-highlight');
184
+ });
185
+ }
186
+ },
187
+ /**
188
+ * Temporary solution to find the position of map marker from popover
189
+ */
190
+ extractMarkerPosition: function (str) {
191
+ const translateRegex = /translate\((.*?)\)/g;
192
+ const matches = str.match(translateRegex);
193
+ if (!matches) {
194
+ return '';
195
+ }
196
+ const lastMatch = matches[matches.length - 1];
197
+ const values = lastMatch.slice(10, -1);
198
+ return values;
199
+ },
200
+ }
201
+ }
202
+ </script>
203
+
204
+ <style lang="scss" scoped>
205
+ .help-mode-dialog {
206
+ display: flex;
207
+ flex-direction: column;
208
+ align-items: center;
209
+ justify-content: center;
210
+ gap: 1rem;
211
+ width: 300px;
212
+ padding: 1rem;
213
+ font-family: inherit;
214
+ font-size: 14px;
215
+ background: white;
216
+ border-radius: 4px 4px;
217
+ border: 1px solid $app-primary-color;
218
+ box-shadow: 0px 0px 160px 80px rgba(0,0,0,0.5);
219
+ position: absolute;
220
+ top: 0;
221
+ left: 50%;
222
+ transform: translateX(-50%);
223
+ z-index: 2;
224
+
225
+ &.finish {
226
+ animation: shake 0.35s;
227
+ animation-delay: 0.7s;
228
+ }
229
+
230
+ h4 {
231
+ color: $app-primary-color;
232
+ }
233
+
234
+ h4, p {
235
+ margin: 0;
236
+ }
237
+
238
+ p {
239
+ line-height: 1.5;
240
+ }
241
+
242
+ .button {
243
+ color: #fff;
244
+ background-color: $app-primary-color;
245
+ transform: scale(1);
246
+ transform-origin: 50% 50%;
247
+ transition: all var(--el-transition-duration);
248
+
249
+ &:hover {
250
+ color: #fff !important;
251
+ background: #ac76c5 !important;
252
+ border: 1px solid #ac76c5 !important;
253
+ }
254
+
255
+ &:active {
256
+ transform: scale(0.95);
257
+ }
258
+
259
+ &.secondary {
260
+ background: #f3e6f9;
261
+ border-color: $app-primary-color;
262
+ color: $app-primary-color;
263
+
264
+ &:hover {
265
+ color: $app-primary-color !important;
266
+ background: #fff !important;
267
+ }
268
+ }
269
+ }
270
+ }
271
+
272
+ @keyframes shake {
273
+ 0% {
274
+ transform: translateX(-50%) rotate(2deg);
275
+ }
276
+ 25% {
277
+ transform: translateX(-50%) rotate(-2deg);
278
+ }
279
+ 50% {
280
+ transform: translateX(-50%) rotate(2deg);
281
+ }
282
+ 75% {
283
+ transform: translateX(-50%) rotate(-2deg);
284
+ }
285
+ 100% {
286
+ transform: translateX(-50%) rotate(2deg);
287
+ }
288
+ }
289
+
290
+ // Just for App.vue with options popover on top
291
+ .help-mode-dialog {
292
+ .options-popover + .multi-container + & {
293
+ margin-top: 40px;
294
+ }
295
+ .options-popover:not([style*="display: none"]) + .multi-container + & {
296
+ margin-top: 175px;
297
+ }
298
+ }
299
+ </style>
300
+
301
+ <style lang="scss">
302
+ .scaffold-container.in-help,
303
+ .flatmap-container.in-help {
304
+ background: rgba(0,0,0,0.3);
305
+ }
306
+
307
+ .scaffold-container.in-help {
308
+ canvas {
309
+ opacity: 0.3;
310
+ }
311
+ }
312
+
313
+ .scaffold-container.in-help,
314
+ .flatmap-container.in-help {
315
+ .el-tooltip__trigger,
316
+ .el-popover {
317
+ opacity: 0.3;
318
+ }
319
+
320
+ .pathway-location:has(.in-help-highlight) {
321
+ opacity: 1;
322
+
323
+ .pathway-container {
324
+ background: transparent;
325
+ }
326
+
327
+ .container,
328
+ .legends-container,
329
+ .selections-container {
330
+ opacity: 0.3;
331
+ }
332
+ }
333
+
334
+ .maplibregl-canvas,
335
+ .maplibregl-ctrl-minimap {
336
+ opacity: 0.3;
337
+ }
338
+
339
+ .maplibregl-map,
340
+ .maplibregl-canvas {
341
+ pointer-events: none;
342
+ }
343
+ }
344
+
345
+ .in-help .el-popper:not([style*="none"]) {
346
+ opacity: 1 !important;
347
+ }
348
+
349
+ .in-help-highlight {
350
+ opacity: 1 !important;
351
+ background: white !important;
352
+ box-shadow: 0px 0px 128px 128px white !important;
353
+ animation: highlight-area 0.1s;
354
+
355
+ &.maplibregl-marker {
356
+ background: none !important;
357
+ box-shadow: 0px 0px 128px 128px rgba(255,255,255,0.5) !important;
358
+ }
359
+ }
360
+ </style>
@@ -8,7 +8,7 @@
8
8
  trigger="manual"
9
9
  popper-class="flatmap-popper flatmap-teleport-popper right-popper"
10
10
  width="max-content"
11
- :visible="helpMode"
11
+ :visible="activateTooltipByIndex(0)"
12
12
  :teleported="false"
13
13
  ref="selectPopover"
14
14
  >
@@ -66,6 +66,12 @@
66
66
  @pathway-selection-changed="onSelectionsDataChanged"
67
67
  :minZoom="minZoom"
68
68
  :helpMode="activeSpecies == key && helpMode"
69
+ :helpModeActiveItem="helpModeActiveItem"
70
+ :helpModeDialog="helpModeDialog"
71
+ :helpModeInitialIndex="-2"
72
+ @help-mode-last-item="onHelpModeLastItem"
73
+ @shown-tooltip="onTooltipShown"
74
+ @shown-map-tooltip="onMapTooltipShown"
69
75
  :renderAtMounted="renderAtMounted"
70
76
  :displayMinimap="displayMinimap"
71
77
  :showStarInLegend="showStarInLegend"
@@ -463,6 +469,45 @@ export default {
463
469
  })
464
470
  }
465
471
  },
472
+ /**
473
+ * @vuese
474
+ * Function to activate help mode tooltip by item index number
475
+ */
476
+ activateTooltipByIndex: function (index) {
477
+ return (
478
+ index === this.helpModeActiveItem
479
+ && this.helpMode
480
+ );
481
+ },
482
+ /**
483
+ * @vuese
484
+ * Function to check the last item of help mode
485
+ */
486
+ onHelpModeLastItem: function (isLastItem) {
487
+ if (isLastItem) {
488
+ this.$emit('help-mode-last-item', true);
489
+ }
490
+ },
491
+ /**
492
+ * @vuese
493
+ * Function to emit event after a tooltip is shown.
494
+ */
495
+ onTooltipShown: function () {
496
+ /**
497
+ * This event is emitted after a tooltip in Flatmap is shown.
498
+ */
499
+ this.$emit('shown-tooltip');
500
+ },
501
+ /**
502
+ * @vuese
503
+ * Function to emit event after a tooltip on the map is shown.
504
+ */
505
+ onMapTooltipShown: function () {
506
+ /**
507
+ * This event is emitted after a tooltip on Flatmap's map is shown.
508
+ */
509
+ this.$emit('shown-map-tooltip');
510
+ },
466
511
  },
467
512
  props: {
468
513
  /**
@@ -494,6 +539,29 @@ export default {
494
539
  type: Boolean,
495
540
  default: false,
496
541
  },
542
+ /**
543
+ * The active item index of help mode.
544
+ */
545
+ helpModeActiveItem: {
546
+ type: Number,
547
+ default: 0,
548
+ },
549
+ /**
550
+ * The option to use helpModeDialog.
551
+ * On default, `false`, clicking help will show all tooltips.
552
+ * If `true`, clicking help will show the help-mode-dialog.
553
+ */
554
+ helpModeDialog: {
555
+ type: Boolean,
556
+ default: false,
557
+ },
558
+ /**
559
+ * The last item of help mode.
560
+ */
561
+ helpModeLastItem: {
562
+ type: Boolean,
563
+ default: false,
564
+ },
497
565
  /**
498
566
  * The option to display minimap at the top-right corner of the map.
499
567
  */
@@ -251,7 +251,7 @@ export default {
251
251
  }
252
252
  },
253
253
  provSpeciesDescription: function () {
254
- let text = 'Observed in'
254
+ let text = 'Studied in'
255
255
  this.entry.provenanceTaxonomyLabel.forEach((label) => {
256
256
  text += ` ${label},`
257
257
  })
@@ -341,7 +341,7 @@ export default {
341
341
  .el-popper__arrow {
342
342
  &:before {
343
343
  border-color: $app-primary-color;
344
- background-color: #f3ecf6;
344
+ background-color: #ffffff;
345
345
  }
346
346
  }
347
347
  }
@@ -2,7 +2,21 @@
2
2
  <div class="selections-container">
3
3
  <el-row>
4
4
  <el-col :span="12">
5
- <div class="checkall-display-text">{{ title }}</div>
5
+ <span class="checkall-display-text">{{ title }}</span>
6
+ <el-popover
7
+ width="250"
8
+ trigger="hover"
9
+ :teleported="false"
10
+ popper-class="popover-origin-help"
11
+ v-if="helpMessage"
12
+ >
13
+ <template v-if="helpMessage" #reference>
14
+ <el-icon class="info"><el-icon-warning /></el-icon>
15
+ </template>
16
+ <span style="word-break: keep-all">
17
+ {{ helpMessage }}
18
+ </span>
19
+ </el-popover>
6
20
  </el-col>
7
21
  <el-col :span="12">
8
22
  <el-checkbox
@@ -59,9 +73,13 @@
59
73
 
60
74
  <script>
61
75
  /* eslint-disable no-alert, no-console */
76
+ import {
77
+ Warning as ElIconWarning,
78
+ } from '@element-plus/icons-vue'
62
79
  import {
63
80
  ElCheckbox as Checkbox,
64
81
  ElCheckboxGroup as CheckboxGroup,
82
+ ElIcon as Icon,
65
83
  ElCol as Col,
66
84
  ElRow as Row,
67
85
  } from 'element-plus'
@@ -72,7 +90,9 @@ export default {
72
90
  Checkbox,
73
91
  CheckboxGroup,
74
92
  Col,
93
+ Icon,
75
94
  Row,
95
+ ElIconWarning,
76
96
  },
77
97
  methods: {
78
98
  /**
@@ -187,6 +207,10 @@ export default {
187
207
  type: String,
188
208
  default: 'line',
189
209
  },
210
+ helpMessage: {
211
+ type: String,
212
+ default: '',
213
+ },
190
214
  identifierKey: {
191
215
  type: String,
192
216
  default: 'id',
@@ -318,4 +342,22 @@ export default {
318
342
  width: 100%;
319
343
  top: 2px;
320
344
  }
345
+
346
+ .info {
347
+ transform: rotate(180deg);
348
+ color: #8300bf;
349
+ margin-left: 8px;
350
+ }
351
+
352
+
353
+ :deep(.popover-origin-help.el-popover) {
354
+ text-transform: none !important; // need to overide the tooltip text transform
355
+ border: 1px solid $app-primary-color;
356
+ .el-popper__arrow {
357
+ &:before {
358
+ border-color: $app-primary-color;
359
+ background-color: #ffffff;
360
+ }
361
+ }
362
+ }
321
363
  </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="container">
2
+ <div class="selections-container">
3
3
  <el-row>
4
4
  <el-col :span="12">
5
5
  <div class="title-text">Systems</div>
@@ -119,6 +119,10 @@ export default {
119
119
 
120
120
  <style lang="scss" scoped>
121
121
 
122
+ .selections-container {
123
+ padding-top: 5px;
124
+ }
125
+
122
126
  .checkbox-container {
123
127
  display: flex;
124
128
  cursor: pointer;
@@ -163,6 +167,7 @@ export default {
163
167
  background: #ffffff;
164
168
  margin-top: 6px;
165
169
  scrollbar-width: thin;
170
+ overflow: hidden;
166
171
 
167
172
  :deep(.el-tree) {
168
173
  max-height: 240px;
@@ -3,5 +3,6 @@
3
3
  import FlatmapVuer from './FlatmapVuer.vue'
4
4
  import MultiFlatmapVuer from './MultiFlatmapVuer.vue'
5
5
  import Tooltip from './Tooltip.vue'
6
+ import HelpModeDialog from './HelpModeDialog.vue'
6
7
 
7
- export { FlatmapVuer, MultiFlatmapVuer, Tooltip}
8
+ export { FlatmapVuer, MultiFlatmapVuer, Tooltip, HelpModeDialog }
@@ -12,6 +12,7 @@ declare module 'vue' {
12
12
  DrawTool: typeof import('./components/DrawTool.vue')['default']
13
13
  DynamicLegends: typeof import('./components/legends/DynamicLegends.vue')['default']
14
14
  ElButton: typeof import('element-plus/es')['ElButton']
15
+ ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
15
16
  ElCard: typeof import('element-plus/es')['ElCard']
16
17
  ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
17
18
  ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
@@ -20,7 +21,6 @@ declare module 'vue' {
20
21
  ElIconArrowDown: typeof import('@element-plus/icons-vue')['ArrowDown']
21
22
  ElIconArrowLeft: typeof import('@element-plus/icons-vue')['ArrowLeft']
22
23
  ElIconArrowUp: typeof import('@element-plus/icons-vue')['ArrowUp']
23
- ElIconCircleClose: typeof import('@element-plus/icons-vue')['CircleClose']
24
24
  ElIconClose: typeof import('@element-plus/icons-vue')['Close']
25
25
  ElIconDelete: typeof import('@element-plus/icons-vue')['Delete']
26
26
  ElIconEdit: typeof import('@element-plus/icons-vue')['Edit']
@@ -38,6 +38,7 @@ declare module 'vue' {
38
38
  ElTree: typeof import('element-plus/es')['ElTree']
39
39
  ExternalResourceCard: typeof import('./components/ExternalResourceCard.vue')['default']
40
40
  FlatmapVuer: typeof import('./components/FlatmapVuer.vue')['default']
41
+ HelpModeDialog: typeof import('./components/HelpModeDialog.vue')['default']
41
42
  MultiFlatmapVuer: typeof import('./components/MultiFlatmapVuer.vue')['default']
42
43
  ProvenancePopup: typeof import('./components/ProvenancePopup.vue')['default']
43
44
  SelectionsGroup: typeof import('./components/SelectionsGroup.vue')['default']
@@ -231,7 +231,6 @@ let FlatmapQueries = function () {
231
231
 
232
232
  this.queryForConnectivity = function (keastIds, signal, processConnectivity=true) {
233
233
  const data = { sql: this.buildConnectivitySqlStatement(keastIds) }
234
-
235
234
  const headers = {
236
235
  method: 'POST',
237
236
  headers: {
@@ -276,11 +275,32 @@ let FlatmapQueries = function () {
276
275
  }
277
276
  }
278
277
 
279
- this.createLabelFromNeuralNode = function (node, lookUp, isSingle=true) {
280
- if (isSingle) {
281
- return lookUp[node]
278
+ // This function is used to determine if a node is a single node or a node with multiple children
279
+ // Returns the id of the node if it is a single node, otherwise returns false
280
+ this.findIfNodeIsSingle = function (node) {
281
+ if (node.length === 1) { // If the node is in the form [id]
282
+ console.error("Server returns a single node", node)
283
+ return node[0]
284
+ } else {
285
+ if (node.length === 2 && node[1].length === 0) { // If the node is in the form [id, []]
286
+ return node[0]
287
+ } else {
288
+ return false // If the node is in the form [id, [id1, id2]]
289
+ }
282
290
  }
291
+ }
292
+
293
+ this.createLabelFromNeuralNode = function (node, lookUp) {
283
294
 
295
+ // Check if the node is a single node or a node with multiple children
296
+ let nodeIsSingle = this.findIfNodeIsSingle(node)
297
+
298
+ // Case where node is in the form [id]
299
+ if (nodeIsSingle) {
300
+ return lookUp[nodeIsSingle]
301
+ }
302
+
303
+ // Case where node is in the form [id, [id1 (,id2)]]
284
304
  let label = lookUp[node[0]]
285
305
  if (node.length === 2 && node[1].length > 0) {
286
306
  node[1].forEach((n) => {
@@ -329,12 +349,11 @@ let FlatmapQueries = function () {
329
349
  this.destinations = axons.map((a) =>
330
350
  this.createLabelFromNeuralNode(a, lookUp)
331
351
  )
332
-
333
352
  this.origins = dendrites.map((d) =>
334
- this.createLabelFromNeuralNode(d, lookUp, true)
353
+ this.createLabelFromNeuralNode(d, lookUp)
335
354
  )
336
355
  this.components = components.map((c) =>
337
- this.createLabelFromNeuralNode(c, lookUp, false)
356
+ this.createLabelFromNeuralNode(c, lookUp)
338
357
  )
339
358
  this.flattenAndFindDatasets(components, axons, dendrites)
340
359
  resolve({