@abi-software/map-side-bar 2.11.3 → 2.11.4-acupoints.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,389 @@
1
+ <template>
2
+ <div v-if="entry" class="main">
3
+ <div class="header">
4
+ <el-row>
5
+ <el-input
6
+ class="search-input"
7
+ placeholder="Search"
8
+ v-model="searchInput"
9
+ @keyup="search(searchInput)"
10
+ clearable
11
+ @clear="clearSearchClicked"
12
+ ></el-input>
13
+ <el-button
14
+ v-show="false"
15
+ type="primary"
16
+ class="button"
17
+ @click="search(searchInput)"
18
+ size="large"
19
+ >
20
+ Search
21
+ </el-button>
22
+ </el-row>
23
+ <el-row>
24
+ <span class="filterText">
25
+ Display:&nbsp;&nbsp;
26
+ </span>
27
+ <el-radio-group v-model="currentFilter" size="small" class="acuRadioGroup">
28
+ <el-radio-button
29
+ v-for="(value, key) in filters"
30
+ :key="key"
31
+ :label="key"
32
+ :value="value"
33
+ />
34
+ </el-radio-group>
35
+ </el-row>
36
+ </div>
37
+ <div class="content scrollbar" ref="content">
38
+ <div v-if="paginatedResults.length > 0" v-for="result in paginatedResults" :key="result.Acupoint" class="step-item">
39
+ <AcupointsCard
40
+ class="dataset-card"
41
+ :entry="result"
42
+ @mouseenter="hoverChanged(result)"
43
+ @mouseleave="hoverChanged(undefined)"
44
+ />
45
+ </div>
46
+ <div v-else class="error-feedback">
47
+ No results found - Please change your search / filter criteria.
48
+ </div>
49
+ <el-pagination
50
+ class="pagination"
51
+ v-model:current-page="page"
52
+ hide-on-single-page
53
+ large
54
+ layout="prev, pager, next"
55
+ :page-size="numberPerPage"
56
+ :total="numberOfHits"
57
+ @current-change="pageChange"
58
+ ></el-pagination>
59
+ </div>
60
+ </div>
61
+ </template>
62
+
63
+ <script>
64
+ /* eslint-disable no-alert, no-console */
65
+ import {
66
+ ElButton as Button,
67
+ ElCard as Card,
68
+ ElRadioButton as RadioButton,
69
+ ElRadioGroup as RadioGroup,
70
+ ElDrawer as Drawer,
71
+ ElIcon as Icon,
72
+ ElInput as Input,
73
+ ElPagination as Pagination,
74
+ ElRow as Row,
75
+ } from 'element-plus'
76
+ import AcupointsCard from './AcupointsCard.vue'
77
+
78
+ export default {
79
+ components: {
80
+ AcupointsCard,
81
+ Button,
82
+ Card,
83
+ RadioButton,
84
+ RadioGroup,
85
+ Drawer,
86
+ Icon,
87
+ Input,
88
+ Pagination,
89
+ Row
90
+ },
91
+ name: 'AcupointsInfoSearch',
92
+ props: {
93
+ entry: {
94
+ type: Object,
95
+ default: () => {},
96
+ },
97
+ },
98
+ data: function () {
99
+ return {
100
+ filters: {
101
+ "Curated Only": "Curated",
102
+ "Uncurated Only": "Uncurated",
103
+ "Both": "Both"
104
+ },
105
+ currentFilter: "Both",
106
+ previousFilter: undefined,
107
+ previousInput: undefined,
108
+ results: [],
109
+ paginatedResults: [],
110
+ searchInput: "",
111
+ lastSearch: "",
112
+ numberOfHits: 0,
113
+ numberPerPage: 10,
114
+ page: 1,
115
+ }
116
+ },
117
+ watch: {
118
+ currentFilter: {
119
+ handler: function () {
120
+ this.search(
121
+ this.searchInput,
122
+ this.numberPerPage,
123
+ this.page
124
+ )
125
+ },
126
+ },
127
+ entry: {
128
+ handler: function () {
129
+ this.search(
130
+ this.searchInput,
131
+ this.numberPerPage,
132
+ this.page
133
+ )
134
+ },
135
+ immediate: true,
136
+ deep: true,
137
+ },
138
+ },
139
+ methods: {
140
+ hoverChanged: function (data) {
141
+ this.$emit('hover-changed', data)
142
+ },
143
+ resetSearch: function () {
144
+ this.numberOfHits = 0
145
+ this.search(this.searchInput)
146
+ },
147
+ clearSearchClicked: function () {
148
+ this.searchInput = '';
149
+ this.search("");
150
+ },
151
+ search: function(input) {
152
+ this.results = []
153
+ if ((input !== this.previousInput) ||
154
+ (this.currentFilter !== this.previousFilter)) {
155
+ this.previousFilter = this.currentFilter
156
+ this.previousInput = this.input
157
+ let filteredList = Object.values(this.entry)
158
+ if (this.currentFilter !== "Both") {
159
+ const curated = this.currentFilter === "Curated" ? true : false
160
+ filteredList = filteredList.filter(
161
+ item => (item.Curated ? true : false) === curated)
162
+ }
163
+ if (input === "") {
164
+ this.results = filteredList
165
+ } else {
166
+ const lowerCase = input.toLowerCase()
167
+ for (const value of filteredList) {
168
+ const searchFields = [
169
+ value["Acupoint"],
170
+ value["Synonym"],
171
+ value["Chinese Name"],
172
+ value["English Name"],
173
+ value["Reference"],
174
+ value["Indications"],
175
+ value["Acupuncture Method"],
176
+ value["Vasculature"],
177
+ value["Innervation"],
178
+ value["Note"],
179
+ ];
180
+ const allstrings = searchFields.join();
181
+ const myJSON = allstrings.toLowerCase();
182
+ if (myJSON.includes(lowerCase)) {
183
+ this.results.push(value)
184
+ }
185
+ }
186
+ }
187
+ }
188
+ const start = this.numberPerPage * (this.page - 1)
189
+ this.paginatedResults = this.results.slice(start, start + this.numberPerPage)
190
+ this.numberOfHits = this.results.length
191
+ this.searchInput = input
192
+ this.lastSearch = input
193
+ },
194
+ numberPerPageUpdate: function (val) {
195
+ this.numberPerPage = val
196
+ this.pageChange(1)
197
+ },
198
+ pageChange: function (page) {
199
+ this.page = page
200
+ this.search( this.searchInput)
201
+ },
202
+ scrollToTop: function () {
203
+ if (this.$refs.content) {
204
+ this.$refs.content.scroll({ top: 0, behavior: 'smooth' })
205
+ }
206
+ },
207
+ resetPageNavigation: function () {
208
+ this.page = 1
209
+ },
210
+ },
211
+ }
212
+ </script>
213
+
214
+ <style lang="scss" scoped>
215
+
216
+ .acuRadioGroup {
217
+ padding-top: 8px;
218
+ }
219
+
220
+ .dataset-card {
221
+ position: relative;
222
+
223
+ &::before {
224
+ content: "";
225
+ display: block;
226
+ width: calc(100% - 15px);
227
+ height: 100%;
228
+ position: absolute;
229
+ top: 7px;
230
+ left: 7px;
231
+ border-style: solid;
232
+ border-radius: 5px;
233
+ border-color: transparent;
234
+ }
235
+
236
+ &:hover {
237
+ &::before {
238
+ border-color: var(--el-color-primary);
239
+ }
240
+ }
241
+ }
242
+
243
+ .main {
244
+ font-size: 14px;
245
+ text-align: left;
246
+ line-height: 1.5em;
247
+ font-family: Asap, sans-serif, Helvetica;
248
+ font-weight: 400;
249
+ /* outline: thin red solid; */
250
+ overflow-y: auto;
251
+ scrollbar-width: thin;
252
+ min-width: 16rem;
253
+ background-color: #f7faff;
254
+ height: 100%;
255
+ border-left: 1px solid var(--el-border-color);
256
+ border-top: 1px solid var(--el-border-color);
257
+ display: flex;
258
+ flex-direction: column;
259
+ gap: 1rem;
260
+ padding: 1rem;
261
+ }
262
+
263
+ .step-item {
264
+ font-size: 14px;
265
+ margin-bottom: 18px;
266
+ text-align: left;
267
+ }
268
+
269
+ .search-input {
270
+ width: 298px !important;
271
+ height: 40px;
272
+ padding-right: 14px;
273
+
274
+ :deep(.el-input__inner) {
275
+ font-family: inherit;
276
+ }
277
+ }
278
+
279
+ .header {
280
+ .el-button {
281
+ font-family: inherit;
282
+
283
+ &:hover,
284
+ &:focus {
285
+ background: $app-primary-color;
286
+ box-shadow: -3px 2px 4px #00000040;
287
+ color: #fff;
288
+ }
289
+ }
290
+ }
291
+
292
+ .pagination {
293
+ padding-bottom: 16px;
294
+ background-color: white;
295
+ padding-left: 95px;
296
+ font-weight: bold;
297
+ }
298
+
299
+ .pagination :deep(button) {
300
+ background-color: white !important;
301
+ }
302
+ .pagination :deep(li) {
303
+ background-color: white !important;
304
+ }
305
+ .pagination :deep(li.is-active) {
306
+ color: $app-primary-color;
307
+ }
308
+
309
+ .error-feedback {
310
+ font-family: Asap;
311
+ font-size: 14px;
312
+ font-style: italic;
313
+ padding-top: 15px;
314
+ }
315
+
316
+ .content-card :deep(.el-card__header) {
317
+ background-color: #292b66;
318
+ padding: 1rem;
319
+ }
320
+
321
+ .content-card :deep(.el-card__body) {
322
+ background-color: #f7faff;
323
+ overflow-y: hidden;
324
+ padding: 1rem;
325
+ }
326
+
327
+ .content {
328
+ // width: 515px;
329
+ flex: 1 1 auto;
330
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
331
+ border: solid 1px #e4e7ed;
332
+ background-color: #ffffff;
333
+ overflow-y: scroll;
334
+ scrollbar-width: thin;
335
+ border-radius: var(--el-border-radius-base);
336
+ }
337
+
338
+ .content :deep(.el-loading-spinner .path) {
339
+ stroke: $app-primary-color;
340
+ }
341
+
342
+ .content :deep(.step-item:first-child .seperator-path) {
343
+ display: none;
344
+ }
345
+
346
+ .content :deep(.step-item:not(:first-child) .seperator-path) {
347
+ width: 455px;
348
+ height: 0px;
349
+ border: solid 1px #e4e7ed;
350
+ background-color: #e4e7ed;
351
+ }
352
+
353
+ .filterText {
354
+ margin-top:8px;
355
+ }
356
+
357
+ .scrollbar::-webkit-scrollbar-track {
358
+ border-radius: 10px;
359
+ background-color: #f5f5f5;
360
+ }
361
+
362
+ .scrollbar::-webkit-scrollbar {
363
+ width: 12px;
364
+ right: -12px;
365
+ background-color: #f5f5f5;
366
+ }
367
+
368
+ .scrollbar::-webkit-scrollbar-thumb {
369
+ border-radius: 4px;
370
+ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.06);
371
+ background-color: #979797;
372
+ }
373
+
374
+ :deep(.el-input__suffix) {
375
+ padding-right: 0px;
376
+ }
377
+
378
+ :deep(.my-drawer) {
379
+ background: rgba(0, 0, 0, 0);
380
+ box-shadow: none;
381
+ }
382
+
383
+ .error-feedback {
384
+ font-family: Asap;
385
+ font-size: 14px;
386
+ font-style: italic;
387
+ padding-top: 15px;
388
+ }
389
+ </style>
@@ -3,14 +3,56 @@
3
3
  <MapSvgSpriteColor />
4
4
  <template #header>
5
5
  <div class="header">
6
- <el-input
7
- class="search-input"
8
- placeholder="Search"
9
- v-model="searchInput"
10
- @keyup="searchEvent"
11
- clearable
12
- @clear="clearSearchClicked"
13
- ></el-input>
6
+ <div class="search-input-container" :class="{'is-focus': inputFocus}">
7
+ <el-input
8
+ class="search-input"
9
+ placeholder="Search"
10
+ v-model="searchInput"
11
+ @focus="onInputFocus"
12
+ @blur="onInputBlur"
13
+ @input="onInputChange"
14
+ @keyup="searchEvent"
15
+ clearable
16
+ @clear="clearSearchClicked"
17
+ ></el-input>
18
+ <el-popover
19
+ width="350"
20
+ trigger="hover"
21
+ popper-class="filter-help-popover"
22
+ >
23
+ <template #reference>
24
+ <MapSvgIcon icon="help" class="help" />
25
+ </template>
26
+ <div>
27
+ <strong>Search rules:</strong>
28
+ <ul>
29
+ <li>
30
+ <strong>Partial Matching:</strong> You don't need to type the full word or ID.
31
+ The search will find items that contain your search term.
32
+ </li>
33
+ <li>
34
+ <strong>Multiple Terms:</strong> Separate terms with a comma (<code>,</code>).
35
+ This will find pathways that match any of the terms (an "OR" search).
36
+ </li>
37
+ </ul>
38
+ <br/>
39
+ <strong>Examples:</strong>
40
+ <ul>
41
+ <li>
42
+ <strong>To find by partial ID:</strong>
43
+ Searching for <code>kidney/132</code> will match the full <strong>Pathway ID</strong> <code>ilxtr:sparc-nlp/kidney/132</code>
44
+ </li>
45
+ <li>
46
+ <strong>To find by keyword:</strong>
47
+ Searching for (<code>vagus nerve</code>) will match <strong>pathways</strong> that have <code>vagus nerve</code> in their title OR are linked to a related component (like UBERON:0001759).
48
+ </li>
49
+ <li>
50
+ <strong>To find by multiple terms:</strong>
51
+ Searching for <code>kidney</code>, <code>vagus nerve</code> will find pathways that are related to either <code>kidney</code> OR <code>vagus nerve</code>.</li>
52
+ </ul>
53
+ </div>
54
+ </el-popover>
55
+ </div>
14
56
  <el-button
15
57
  type="primary"
16
58
  class="button"
@@ -216,6 +258,7 @@ export default {
216
258
  expanded: "",
217
259
  filterVisibility: true,
218
260
  expandedData: null,
261
+ inputFocus: false,
219
262
  };
220
263
  },
221
264
  computed: {
@@ -250,6 +293,7 @@ export default {
250
293
  this.$refs.filtersRef.checkShowAllBoxes();
251
294
  this.searchInput = '';
252
295
  this.filter = [];
296
+ this.updateInputFocus();
253
297
  }
254
298
  }
255
299
  },
@@ -306,6 +350,7 @@ export default {
306
350
  onConnectivityClicked: function (data) {
307
351
  this.searchInput = data.query;
308
352
  this.searchAndFilterUpdate();
353
+ this.updateInputFocus();
309
354
  },
310
355
  collapseChange:function (data) {
311
356
  this.expanded = this.expanded === data.id ? "" : data.id;
@@ -380,6 +425,7 @@ export default {
380
425
  },
381
426
  openSearch: function (filter, search = "") {
382
427
  this.searchInput = search;
428
+ this.updateInputFocus();
383
429
  this.resetPageNavigation();
384
430
  //Proceed normally if cascader is ready
385
431
  if (this.cascaderIsReady) {
@@ -468,13 +514,27 @@ export default {
468
514
  clearSearchClicked: function () {
469
515
  this.searchInput = "";
470
516
  this.searchAndFilterUpdate();
517
+ this.updateInputFocus();
471
518
  },
472
519
  searchEvent: function (event = false) {
473
520
  if (event.keyCode === 13 || event instanceof MouseEvent) {
474
521
  this.searchInput = this.searchInput.trim();
475
522
  this.searchAndFilterUpdate();
523
+ this.updateInputFocus();
476
524
  }
477
525
  },
526
+ updateInputFocus: function () {
527
+ this.inputFocus = this.searchInput ? true : false;
528
+ },
529
+ onInputFocus: function () {
530
+ this.updateInputFocus();
531
+ },
532
+ onInputBlur: function () {
533
+ this.updateInputFocus();
534
+ },
535
+ onInputChange: function () {
536
+ this.updateInputFocus();
537
+ },
478
538
  filterUpdate: function (filters) {
479
539
  this.filter = [...filters];
480
540
  this.searchAndFilterUpdate();
@@ -554,6 +614,7 @@ export default {
554
614
  this.searchInput = item.search;
555
615
  this.filter = item.filters;
556
616
  this.openSearch([...item.filters], item.search);
617
+ this.updateInputFocus();
557
618
  },
558
619
  onConnectivityInfoLoaded: function (result) {
559
620
  const stepItemRef = this.$refs['stepItem-' + result.id];
@@ -650,6 +711,24 @@ export default {
650
711
  }
651
712
  }
652
713
 
714
+ .search-input-container {
715
+ position: relative;
716
+ display: flex;
717
+ align-items: center;
718
+
719
+ .map-icon {
720
+ position: absolute;
721
+ right: 18px;
722
+ color: $app-primary-color !important;
723
+ }
724
+
725
+ &.is-focus {
726
+ .map-icon {
727
+ display: none;
728
+ }
729
+ }
730
+ }
731
+
653
732
  .header {
654
733
  display: flex;
655
734
  align-items: center;
@@ -802,6 +881,19 @@ export default {
802
881
  font-size: 12px !important;
803
882
  line-height: 18px !important;
804
883
 
884
+ > div {
885
+ word-break: break-word;
886
+ text-align: initial;
887
+ }
888
+
889
+ ul {
890
+ padding-left: 1.5rem;
891
+
892
+ > li + li {
893
+ margin-top: 0.5rem;
894
+ }
895
+ }
896
+
805
897
  .el-popper__arrow::before {
806
898
  background: #f3ecf6 !important;
807
899
  border-color: $app-primary-color !important;
@@ -811,5 +903,9 @@ export default {
811
903
  border-bottom-color: transparent !important;
812
904
  border-right-color: transparent !important;
813
905
  }
906
+
907
+ code {
908
+ font-size: 90%;
909
+ }
814
910
  }
815
911
  </style>
@@ -64,6 +64,14 @@
64
64
  @connectivity-item-close="onConnectivityItemClose"
65
65
  />
66
66
  </template>
67
+ <template v-else-if="tab.type === 'acupoints' && acupointsInfoList">
68
+ <acupoints-info-search
69
+ :ref="'acupointsTab_' + tab.id"
70
+ :entry="acupointsInfoList"
71
+ @on-acupoints-click="acupointClicked"
72
+ v-show="tab.id === activeTabId"
73
+ />
74
+ </template>
67
75
  <template v-else>
68
76
  <DatasetExplorer
69
77
  class="sidebar-content-container"
@@ -92,6 +100,7 @@ import { ElDrawer as Drawer, ElIcon as Icon } from 'element-plus'
92
100
  import DatasetExplorer from './DatasetExplorer.vue'
93
101
  import EventBus from './EventBus.js'
94
102
  import Tabs from './Tabs.vue'
103
+ import AcupointsInfoSearch from './AcupointsInfoSearch.vue'
95
104
  import AnnotationTool from './AnnotationTool.vue'
96
105
  import ConnectivityExplorer from './ConnectivityExplorer.vue'
97
106
  import { removeShowAllFacets } from '../algolia/utils.js'
@@ -151,6 +160,13 @@ export default {
151
160
  type: Array,
152
161
  default: [],
153
162
  },
163
+ /**
164
+ * The acupoints info to show in sidebar.
165
+ */
166
+ acupointsInfoList: {
167
+ type: Object,
168
+ default: null,
169
+ },
154
170
  /**
155
171
  * The annotation data to show in sidebar.
156
172
  */
@@ -204,6 +220,13 @@ export default {
204
220
  }
205
221
  },
206
222
  methods: {
223
+ /**
224
+ * This event is emitted with a mouse click on acupoint card
225
+ * @arg data
226
+ */
227
+ acupointClicked: function (data) {
228
+ this.$emit('acupoints-clicked', data)
229
+ },
207
230
  onConnectivityCollapseChange: function (data) {
208
231
  this.$emit('connectivity-collapse-change', data)
209
232
  },
@@ -295,9 +318,6 @@ export default {
295
318
  datasetExplorerTabRef.openSearch(facets, query);
296
319
  })
297
320
  },
298
- /**
299
- * Get the ref id of the tab by id and type.
300
- */
301
321
  getTabRef: function (id, type, switchTab = false) {
302
322
  const matchedTab = this.tabEntries.filter((tabEntry) => {
303
323
  return (id === undefined || tabEntry.id === id) &&
@@ -407,6 +427,14 @@ export default {
407
427
  updateConnectivityError: function (errorInfo) {
408
428
  EventBus.emit('connectivity-error', errorInfo);
409
429
  },
430
+ openAcupointsSearch: function (term) {
431
+ this.drawerOpen = true
432
+ // Because refs are in v-for, nextTick is needed here
433
+ this.$nextTick(() => {
434
+ const tabRef = this.getTabRef(undefined, 'acupoints', true);
435
+ tabRef.search(term);
436
+ })
437
+ },
410
438
  /**
411
439
  * Store available anatomy facets data for connectivity list component
412
440
  */
@@ -503,6 +531,11 @@ export default {
503
531
  // This should respect the information provided by the property
504
532
  tabEntries: function () {
505
533
  return this.tabs.filter((tab) =>
534
+ (tab.type === "acupoints" &&
535
+ (
536
+ this.acupointsInfoList &&
537
+ Object.keys(this.acupointsInfoList).length > 0
538
+ )) ||
506
539
  tab.type === "datasetExplorer" ||
507
540
  tab.type === "connectivityExplorer" ||
508
541
  (
@@ -565,6 +598,16 @@ export default {
565
598
  this.$emit('connectivity-source-change', payLoad);
566
599
  })
567
600
 
601
+ // Emit acupoints clicked event
602
+ EventBus.on('acupoints-clicked', (payLoad) => {
603
+ this.$emit('acupoints-clicked', payLoad);
604
+ })
605
+
606
+ // Emit acupoints hovered event
607
+ EventBus.on('acupoints-hovered', (payLoad) => {
608
+ this.$emit('acupoints-hovered', payLoad);
609
+ })
610
+
568
611
  // Get available anatomy facets for the connectivity info
569
612
  EventBus.on('available-facets', (payLoad) => {
570
613
  this.availableAnatomyFacets = payLoad.find((facet) => facet.label === 'Anatomical Structure').children
@@ -7,6 +7,8 @@ export {}
7
7
 
8
8
  declare module 'vue' {
9
9
  export interface GlobalComponents {
10
+ AcupointsCard: typeof import('./components/AcupointsCard.vue')['default']
11
+ AcupointsInfoSearch: typeof import('./components/AcupointsInfoSearch.vue')['default']
10
12
  AnnotationTool: typeof import('./components/AnnotationTool.vue')['default']
11
13
  BadgesGroup: typeof import('./components/BadgesGroup.vue')['default']
12
14
  ConnectivityCard: typeof import('./components/ConnectivityCard.vue')['default']
@@ -19,6 +21,8 @@ declare module 'vue' {
19
21
  ElCascader: typeof import('element-plus/es')['ElCascader']
20
22
  ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
21
23
  ElCol: typeof import('element-plus/es')['ElCol']
24
+ ElCollapse: typeof import('element-plus/es')['ElCollapse']
25
+ ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
22
26
  ElDrawer: typeof import('element-plus/es')['ElDrawer']
23
27
  ElDropdown: typeof import('element-plus/es')['ElDropdown']
24
28
  ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
@@ -38,6 +42,7 @@ declare module 'vue' {
38
42
  ElPagination: typeof import('element-plus/es')['ElPagination']
39
43
  ElPopover: typeof import('element-plus/es')['ElPopover']
40
44
  ElRadio: typeof import('element-plus/es')['ElRadio']
45
+ ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
41
46
  ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
42
47
  ElRow: typeof import('element-plus/es')['ElRow']
43
48
  ElSelect: typeof import('element-plus/es')['ElSelect']