@abi-software/map-side-bar 2.5.3-beta.5 → 2.5.3-beta.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abi-software/map-side-bar",
3
- "version": "2.5.3-beta.5",
3
+ "version": "2.5.3-beta.6",
4
4
  "files": [
5
5
  "dist/*",
6
6
  "src/*",
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@abi-software/gallery": "^1.1.2",
42
- "@abi-software/map-utilities": "^1.2.2-beta.1",
42
+ "@abi-software/map-utilities": "^1.2.1",
43
43
  "@abi-software/svg-sprite": "^1.0.1",
44
44
  "@element-plus/icons-vue": "^2.3.1",
45
45
  "algoliasearch": "^4.10.5",
@@ -469,7 +469,14 @@ export default {
469
469
  facetSubPropPath: facetSubPropPath, // will be used for filters if we are at the third level of the cascader
470
470
  }
471
471
  })
472
-
472
+
473
+ // if all checkboxes are checked
474
+ // there has no filter values
475
+ const filtersLength = filters.filter((item) => item.facet !== 'Show all');
476
+ if (!filtersLength.length) {
477
+ filters = [];
478
+ }
479
+
473
480
  this.$emit('loading', true) // let sidebarcontent wait for the requests
474
481
  this.$emit('filterResults', filters) // emit filters for apps above sidebar
475
482
  this.setCascader(filterKeys) //update our cascader v-model if we modified the event
@@ -631,7 +638,7 @@ export default {
631
638
  let filters = createFilter(e)
632
639
  return filters
633
640
  })
634
-
641
+
635
642
  // Unforttunately the cascader is very particular about it's v-model
636
643
  // to get around this we create a clone of it and use this clone for adding our boolean information
637
644
  this.cascadeSelectedWithBoolean = filterFacets.map((e) => {
@@ -1,33 +1,138 @@
1
1
  <template>
2
- <div class="history-container">
3
- <!-- <span v-if="reversedSearchHistory.length > 0" class="title"> Search History </span> -->
4
- <template v-for="(item, i) in reversedSearchHistory">
5
- <el-tag
6
- class="search-tag"
7
- v-if="i < 3"
8
- v-bind:key="i"
9
- @click="search(item)"
10
- size="large"
11
- >
12
- {{ item.search }}
13
- </el-tag>
14
- </template>
15
- <el-select
16
- v-if="reversedSearchHistory.length > 0"
17
- :model-value="selectValue"
18
- class="m-2 search-select"
19
- placeholder="Full search History"
20
- popper-class="sidebar-search-select-popper"
21
- @change="selectChange"
22
- :teleported="false"
23
- >
24
- <el-option
25
- v-for="(item, i) in cascaderOptions"
26
- :key="i"
27
- :label="item.label"
28
- :value="item.value"
29
- />
30
- </el-select>
2
+ <div class="history-container" v-if="searchHistory.length">
3
+ <div class="saved-search-history" v-if="savedSearchHistory.length">
4
+ <template v-for="(item, i) in savedSearchHistory" :key="item.id">
5
+ <el-tag
6
+ class="search-tag"
7
+ v-if="i < 2"
8
+ v-bind:key="i"
9
+ @click="search(item)"
10
+ size="large"
11
+ >
12
+ <template v-if="item.longLabel">
13
+ <el-popover
14
+ width="auto"
15
+ trigger="hover"
16
+ :show-after="200"
17
+ :persistent="false"
18
+ popper-class="popover-dropdown"
19
+ >
20
+ <template #reference>
21
+ {{ item.label }}
22
+ </template>
23
+ {{ item.longLabel }}
24
+ </el-popover>
25
+ </template>
26
+ <template v-else>
27
+ {{ item.label }}
28
+ </template>
29
+ </el-tag>
30
+ </template>
31
+ </div>
32
+ <div v-else>
33
+ <span class="empty-saved-search">No Saved Searches</span>
34
+ </div>
35
+ <el-dropdown trigger="click" :hide-on-click="false">
36
+ <span class="el-dropdown-select">
37
+ Search history
38
+ <el-icon class="el-icon--right">
39
+ <el-icon-arrow-down />
40
+ </el-icon>
41
+ </span>
42
+ <template #dropdown>
43
+ <el-dropdown-menu>
44
+ <el-dropdown-item v-for="item in searchHistory" :key="item.id">
45
+ <div>
46
+ <template v-if="item.longLabel">
47
+ <el-popover
48
+ width="auto"
49
+ trigger="hover"
50
+ :show-after="200"
51
+ :persistent="false"
52
+ popper-class="popover-dropdown"
53
+ >
54
+ <template #reference>
55
+ <span class="dropdown-clickable-item" @click="search(item)">
56
+ {{ item.label }}
57
+ </span>
58
+ </template>
59
+ {{ item.longLabel }}
60
+ </el-popover>
61
+ </template>
62
+ <template v-else>
63
+ <span class="dropdown-clickable-item" @click="search(item)">
64
+ {{ item.label }}
65
+ </span>
66
+ </template>
67
+ </div>
68
+ <div>
69
+ <el-popover
70
+ width="auto"
71
+ trigger="hover"
72
+ :teleported="true"
73
+ :show-after="200"
74
+ :persistent="false"
75
+ popper-class="popover-dropdown"
76
+ >
77
+ <template #reference>
78
+ <el-button circle text size="small"
79
+ @click="toggleSavedSearch(item)"
80
+ :disabled="savedSearchHistory.length > 1 && !item.saved"
81
+ >
82
+ <el-icon color="#8300BF">
83
+ <template v-if="item.saved">
84
+ <svg
85
+ viewBox="0 0 24 24"
86
+ >
87
+ <path d="m12 21.35-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54z"></path>
88
+ </svg>
89
+ </template>
90
+ <template v-else>
91
+ <svg
92
+ viewBox="0 0 24 24"
93
+ >
94
+ <path d="M16.5 3c-1.74 0-3.41.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3 4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5 22 5.42 19.58 3 16.5 3m-4.4 15.55-.1.1-.1-.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05"></path>
95
+ </svg>
96
+ </template>
97
+ </el-icon>
98
+ </el-button>
99
+ </template>
100
+ <span v-if="savedSearchHistory.length > 1 && !item.saved">
101
+ Please unsave one before adding another.
102
+ </span>
103
+ <span v-else-if="item.saved">
104
+ Remove from saved searches.
105
+ </span>
106
+ <span v-else>
107
+ Add up to two saved searches.
108
+ </span>
109
+ </el-popover>
110
+ <el-popover
111
+ width="auto"
112
+ trigger="hover"
113
+ :teleported="true"
114
+ :show-after="200"
115
+ :persistent="false"
116
+ popper-class="popover-dropdown"
117
+ >
118
+ <template #reference>
119
+ <el-button circle text size="small"
120
+ @click="removeFromSavedSearch(item)"
121
+ >
122
+ <el-icon color="#8300BF">
123
+ <el-icon-delete />
124
+ </el-icon>
125
+ </el-button>
126
+ </template>
127
+ <span>
128
+ Remove from search history.
129
+ </span>
130
+ </el-popover>
131
+ </div>
132
+ </el-dropdown-item>
133
+ </el-dropdown-menu>
134
+ </template>
135
+ </el-dropdown>
31
136
  </div>
32
137
  </template>
33
138
 
@@ -35,16 +140,27 @@
35
140
  /* eslint-disable no-alert, no-console */
36
141
  import {
37
142
  ElTag as Tag,
38
- ElSelect as Select
143
+ ElSelect as Select,
144
+ ElDropdown,
145
+ ElIcon,
39
146
  } from 'element-plus'
40
147
 
41
148
  import EventBus from './EventBus.js'
42
149
 
43
- // remove duplicates by stringifying the objects
44
- const removeDuplicates = function (arrayOfAnything) {
45
- return [...new Set(arrayOfAnything.map((e) => JSON.stringify(e)))].map((e) =>
46
- JSON.parse(e)
47
- )
150
+ const MAX_SEARCH_HISTORY = 12;
151
+
152
+ function generateUUID() {
153
+ const arr = new Uint8Array(16);
154
+ window.crypto.getRandomValues(arr);
155
+
156
+ arr[6] = (arr[6] & 0x0f) | 0x40;
157
+ arr[8] = (arr[8] & 0x3f) | 0x80;
158
+
159
+ const hex = Array.from(arr)
160
+ .map(byte => byte.toString(16).padStart(2, '0'))
161
+ .join('');
162
+
163
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
48
164
  }
49
165
 
50
166
  export default {
@@ -56,26 +172,16 @@ export default {
56
172
  data() {
57
173
  return {
58
174
  searchHistory: [],
59
- selectValue: 'Full search history',
175
+ savedSearchHistory: [],
60
176
  }
61
177
  },
62
- computed: {
63
- reversedSearchHistory: function () {
64
- return removeDuplicates(
65
- this.searchHistory
66
- .slice()
67
- .reverse()
68
- .filter((item) => item.search !== '')
69
- )
70
- },
71
- cascaderOptions: function () {
72
- return this.reversedSearchHistory.map((item) => {
73
- return {
74
- value: item.search,
75
- label: item.search,
76
- }
77
- })
78
- },
178
+ mounted: function () {
179
+ this.getSearchHistory()
180
+ EventBus.on('search-changed', (data) => {
181
+ this.setSearchHistory(data)
182
+ });
183
+ this.updateSearchHistory();
184
+ this.savedSearchHistory = this.searchHistory.filter((item) => item.saved);
79
185
  },
80
186
  methods: {
81
187
  getSearchHistory() {
@@ -91,58 +197,256 @@ export default {
91
197
  localStorage.removeItem('sparc.science-sidebar-search-history')
92
198
  this.searchHistory = []
93
199
  },
94
- addSearchToHistory(filters, search) {
95
- filters = [] // disable filters for now
200
+ sortFilters(a, b) {
201
+ return a.facetPropPath.localeCompare(b.facetPropPath);
202
+ },
203
+ // Sort by saved and updated
204
+ sortSearchHistory(a, b) {
205
+ if (a.saved !== b.saved) {
206
+ return b.saved - a.saved;
207
+ }
208
+ if (a.updated !== b.updated) {
209
+ return a.updated - b.updated;
210
+ }
211
+ return 0;
212
+ },
213
+ formatFilters(filterItem) {
214
+ // because filters do not work correctly with facet2
215
+ if (filterItem.facet2) {
216
+ filterItem.facet = filterItem.facet2;
217
+ delete filterItem.facet2;
218
+ }
219
+ return filterItem;
220
+ },
221
+ addSearchToHistory(filters = [], search = '') {
96
222
  search = search.trim() // remove whitespace
97
- let searchHistory = JSON.parse(
98
- localStorage.getItem('sparc.science-sidebar-search-history')
99
- )
100
- if (searchHistory) {
101
- searchHistory.push({ filters: filters, search: search })
102
- this.searchHistory = removeDuplicates(searchHistory)
103
- localStorage.setItem(
104
- 'sparc.science-sidebar-search-history',
105
- JSON.stringify(searchHistory)
106
- )
107
- } else {
223
+
224
+ const isExistingItem = this.searchHistory.some((item) => {
225
+ let historyFilters = item.filters;
226
+ let newFilters = filters;
227
+
228
+ // make all filters same format
229
+ historyFilters.forEach((filter) => this.formatFilters(filter));
230
+ newFilters.forEach((filter) => this.formatFilters(filter));
231
+
232
+ // sort filters (to check duplicates in string format)
233
+ historyFilters = historyFilters.sort(this.sortFilters);
234
+ newFilters = newFilters.sort(this.sortFilters);
235
+
236
+ const historyFiltersString = JSON.stringify(historyFilters);
237
+ const newFiltersString = JSON.stringify(newFilters);
238
+
239
+ return (
240
+ item.search === search &&
241
+ historyFiltersString === newFiltersString
242
+ );
243
+ });
244
+
245
+ if (!isExistingItem) {
246
+ const {label, longLabel} = this.searchHistoryItemLabel(search, filters);
247
+ const newItem = {
248
+ filters: filters,
249
+ search: search,
250
+ saved: false,
251
+ label: label,
252
+ longLabel: longLabel,
253
+ id: generateUUID(),
254
+ updated: (new Date()).getTime(),
255
+ };
256
+
257
+ this.searchHistory.push(newItem);
258
+
259
+ // trim search history to 12 items
260
+ this.trimSearchHistory();
261
+
262
+ // Save new data
108
263
  localStorage.setItem(
109
264
  'sparc.science-sidebar-search-history',
110
- JSON.stringify([{ filters: filters, search: search }])
111
- )
265
+ JSON.stringify(this.searchHistory)
266
+ );
267
+ }
268
+ },
269
+ /**
270
+ * Remove the duplicate items in search history.
271
+ */
272
+ removeDuplicateSearchHistory: function () {
273
+ const keys = [];
274
+ const duplicateItemIDs = [];
275
+
276
+ this.searchHistory.forEach((item) => {
277
+ const key = `${item.search}-${JSON.stringify(item.filters)}`;
278
+ const existingItem = keys.find(k => k.key === key);
279
+ // duplicate item
280
+ if (existingItem) {
281
+ // if current item is saved item
282
+ // add the other item into duplicates
283
+ if (item.saved) {
284
+ duplicateItemIDs.push(existingItem.id);
285
+ } else {
286
+ duplicateItemIDs.push(item.id);
287
+ }
288
+ } else {
289
+ keys.push({
290
+ id: item.id,
291
+ key: key
292
+ });
293
+ }
294
+ });
295
+
296
+ if (duplicateItemIDs.length) {
297
+ this.searchHistory = this.searchHistory.filter((item) => !duplicateItemIDs.includes(item.id));
112
298
  }
113
299
  },
300
+ /**
301
+ * Function to trim search history to maximum items,
302
+ */
303
+ trimSearchHistory: function () {
304
+ // since saved has max 2
305
+ // remove extra from unsaved
306
+ if (this.searchHistory.length > MAX_SEARCH_HISTORY) {
307
+ const savedItems = this.searchHistory.filter((item) => item.saved);
308
+ const unsavedItems = this.searchHistory.filter((item) => !item.saved);
309
+ const extra = this.searchHistory.length - MAX_SEARCH_HISTORY;
310
+
311
+ this.searchHistory = [
312
+ ...savedItems,
313
+ ...unsavedItems.slice(extra),
314
+ ];
315
+ }
316
+ },
317
+ updateSearchHistory: function () {
318
+ // Update for missing attributes
319
+ this.searchHistory.forEach((item) => {
320
+ if (!item.id) {
321
+ item['id'] = generateUUID();
322
+ }
323
+
324
+ if (!item.label) {
325
+ const {label, longLabel} = this.searchHistoryItemLabel(item.search, item.filters);
326
+ item['label'] = label;
327
+ item['longLabel'] = longLabel;
328
+ }
329
+
330
+ // make all filters same format
331
+ item.filters.forEach((filter) =>
332
+ this.formatFilters(filter)
333
+ );
334
+
335
+ // sort filters (to check duplicates in string format)
336
+ item.filters = item.filters.sort(this.sortFilters);
337
+
338
+ if (!item.saved) {
339
+ item['saved'] = false;
340
+ }
341
+
342
+ if (!item.updated) {
343
+ item['updated'] = (new Date()).getTime();
344
+ }
345
+ });
346
+
347
+ this.searchHistory = this.searchHistory.sort(this.sortSearchHistory);
348
+
349
+ // check and remove duplicates
350
+ this.removeDuplicateSearchHistory();
351
+
352
+ // trim search history to 12 items
353
+ this.trimSearchHistory();
354
+
355
+ // Save updated data
356
+ localStorage.setItem(
357
+ 'sparc.science-sidebar-search-history',
358
+ JSON.stringify(this.searchHistory)
359
+ )
360
+ },
114
361
  search: function (item) {
115
362
  this.$emit('search', item)
116
363
  },
117
- selectChange: function (value) {
118
- this.selectValue = value
119
- this.search({ search: value })
364
+ searchHistoryItemLabel: function (search, filters) {
365
+ let label = search ? `"${search.trim()}"` : '';
366
+ let longLabel = '';
367
+ let filterItems = [];
368
+ let filterLabels = [];
369
+
370
+ if (filters) {
371
+ filterItems = filters.filter((filterItem) => filterItem.facet !== 'Show all');
372
+ filterLabels = filterItems.map((item) => item.facet2 || item.facet);
373
+ }
374
+
375
+ if (label && filterItems.length) {
376
+ longLabel += label;
377
+ longLabel += `, ${filterLabels.join(', ')}`;
378
+ label += ` (+${filterItems.length})`;
379
+ }
380
+
381
+ if (!label && filterItems.length) {
382
+ label = filterItems[0].facet;
383
+
384
+ if (filterItems.length > 1) {
385
+ longLabel += `${filterLabels.join(', ')}`;
386
+ label += ` (+${filterItems.length - 1})`;
387
+ }
388
+ }
389
+
390
+ if (!label) {
391
+ label = 'Unknown search';
392
+ } else if (label.length > 15 && !longLabel) {
393
+ longLabel = label;
394
+ }
395
+
396
+ return {label, longLabel};
397
+ },
398
+ toggleSavedSearch: function (item) {
399
+ this.searchHistory.forEach((_item) => {
400
+ if (_item.id === item.id) {
401
+ _item.saved = !_item.saved;
402
+ }
403
+ });
404
+ this.savedSearchHistory = this.searchHistory.filter((item) => item.saved);
405
+ this.updateSearchHistory();
406
+ },
407
+ removeFromSavedSearch: function (item) {
408
+ const itemIndex = this.searchHistory.findIndex((_item) => _item.id === item.id);
409
+ this.searchHistory.splice(itemIndex, 1);
410
+ this.savedSearchHistory = this.searchHistory.filter((item) => item.saved);
411
+ this.updateSearchHistory();
120
412
  },
121
- },
122
- mounted: function () {
123
- this.getSearchHistory()
124
- EventBus.on('search-changed', (data) => {
125
- this.setSearchHistory(data)
126
- })
127
413
  },
128
414
  }
129
415
  </script>
130
416
 
131
417
  <style lang="scss" scoped>
132
418
  .history-container {
133
- padding-bottom: 3px;
419
+ display: flex;
420
+ align-items: center;
421
+ justify-content: space-between;
422
+ padding-bottom: 6px;
423
+ }
424
+
425
+ .empty-saved-search {
426
+ font-style: italic;
427
+ font-size: 14px;
428
+ font-weight: 400;
429
+ color: var(--el-text-color-secondary);
430
+ }
431
+
432
+ .saved-search-history {
433
+ display: flex;
434
+ align-items: center;
435
+ gap: 6px;
134
436
  }
135
437
 
136
438
  .search-tag.el-tag {
137
- margin: 0 5px 5px 0;
439
+ margin: 0;
138
440
  cursor: pointer;
139
- max-width: 100px;
140
- overflow: hidden;
141
- text-overflow: ellipsis;
142
- float: left;
143
441
  background: #f9f2fc!important;
144
442
  border-color: $app-primary-color!important;
145
443
  color:$app-primary-color!important;
444
+
445
+ :deep(.el-tag__content) {
446
+ max-width: 15ch;
447
+ overflow: hidden;
448
+ text-overflow: ellipsis;
449
+ }
146
450
  }
147
451
 
148
452
  .title {
@@ -154,22 +458,125 @@ export default {
154
458
  align-items: center;
155
459
  }
156
460
 
157
- .search-select {
158
- float: right;
461
+ .el-dropdown {
159
462
  width: 180px;
463
+ position: relative;
464
+ box-sizing: border-box;
465
+ cursor: pointer;
466
+ text-align: left;
467
+ font-size: 14px;
468
+ padding: 4px 12px;
469
+ min-height: 32px;
470
+ line-height: 24px;
471
+ border-radius: var(--el-border-radius-base);
472
+ background-color: var(--el-fill-color-blank);
473
+ transition: var(--el-transition-duration);
474
+ transform: translateZ(0);
475
+ box-shadow: 0 0 0 1px var(--el-border-color) inset;
476
+ }
477
+
478
+ .el-dropdown-select {
479
+ width: 100%;
480
+ display: flex;
481
+ align-items: center;
482
+ justify-content: space-between;
483
+ gap: 1rem;
484
+
485
+ .el-icon {
486
+ transform: rotate(0deg);
487
+ transition: transform 0.25s linear;
488
+ }
489
+
490
+ &[aria-expanded="true"] {
491
+ .el-icon {
492
+ transform: rotate(180deg);
493
+ }
494
+ }
495
+ }
496
+
497
+ :deep(.el-dropdown-menu__item) {
498
+ justify-content: space-between;
499
+ gap: 0.5rem;
500
+ cursor: default;
501
+ position: relative;
502
+
503
+ > div:first-child {
504
+ max-width: 148px;
505
+ text-overflow: ellipsis;
506
+ overflow: hidden;
507
+ }
508
+
509
+ .dropdown-clickable-item {
510
+ cursor: pointer;
511
+
512
+ &:hover {
513
+ color: $app-primary-color;
514
+ }
515
+ }
516
+
517
+ + .el-dropdown-menu__item {
518
+ &::before {
519
+ content: "";
520
+ display: block;
521
+ width: calc(100% - 32px);
522
+ border-top: 1px solid var(--el-border-color);
523
+ position: absolute;
524
+ top: 0;
525
+ }
526
+ }
527
+
528
+ &:hover,
529
+ &:focus,
530
+ &:active {
531
+ color: inherit;
532
+ background-color: var(--el-bg-color-page);
533
+ }
534
+
535
+ i {
536
+ margin: 0;
537
+ }
160
538
  }
161
539
  </style>
162
540
 
163
541
  <style lang="scss">
164
- .sidebar-search-select-popper {
542
+ .el-popper.el-dropdown__popper {
165
543
  font-family: Asap;
166
544
  font-size: 14px;
167
- font-weight: 500;
545
+ font-weight: 400;
168
546
  font-stretch: normal;
169
547
  font-style: normal;
170
548
  line-height: normal;
171
549
  letter-spacing: normal;
172
550
  color: #292b66;
173
- max-width: 200px;
551
+ min-width: 180px;
552
+ width: fit-content;
553
+
554
+ .el-button + .el-button {
555
+ margin: 0;
556
+ }
557
+
558
+ // element plus's dropdown max-height has problem
559
+ .el-dropdown__list {
560
+ max-height: 350px;
561
+ overflow-y: auto;
562
+ scrollbar-width: thin;
563
+ }
564
+ }
565
+
566
+ .el-popover.el-popper.popover-dropdown {
567
+ padding: 4px 10px;
568
+ width: max-content;
569
+ max-width: 240px;
570
+ font-family: Asap;
571
+ font-size: 12px;
572
+ color: inherit;
573
+ background: #f3ecf6 !important;
574
+ border: 1px solid $app-primary-color;
575
+
576
+ & .el-popper__arrow::before {
577
+ border: 1px solid;
578
+ border-color: $app-primary-color;
579
+ background: #f3ecf6;
580
+ }
174
581
  }
175
582
  </style>