@abi-software/map-side-bar 2.11.2 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abi-software/map-side-bar",
3
- "version": "2.11.2",
3
+ "version": "2.11.4-acupoints.1",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@abi-software/gallery": "^1.2.0",
47
- "@abi-software/map-utilities": "1.7.6",
47
+ "@abi-software/map-utilities": "1.7.7",
48
48
  "@abi-software/svg-sprite": "^1.0.2",
49
49
  "@element-plus/icons-vue": "^2.3.1",
50
50
  "algoliasearch": "^4.10.5",
package/src/App.vue CHANGED
@@ -18,6 +18,7 @@
18
18
  <el-button @click="keywordSearch">keyword search</el-button>
19
19
  <el-button @click="getFacets">Get facets</el-button>
20
20
  <el-button @click="toggleCreateData">Create Data/Annotation</el-button>
21
+ <el-button @click="searchAcupoints">Search Acupoints</el-button>
21
22
  <el-button @click="openConnectivitySearch()">Connectivity Search</el-button>
22
23
  </div>
23
24
  <SideBar
@@ -26,14 +27,18 @@
26
27
  ref="sideBar"
27
28
  :visible="sideBarVisibility"
28
29
  :annotationEntry="annotationEntry"
30
+ :acupointsInfoList="acupoints"
29
31
  :createData="createData"
30
32
  :connectivityEntry="connectivityEntry"
31
33
  :connectivityKnowledge="connectivityKnowledge"
32
34
  :showVisibilityFilter="true"
35
+ :tabs="tabArray"
33
36
  @search-changed="searchChanged($event)"
34
37
  @hover-changed="hoverChanged($event)"
35
38
  @connectivity-hovered="onConnectivityHovered"
36
39
  @actionClick="action"
40
+ @acupoints-clicked="onAcupointsClicked"
41
+ @acupoints-hovered="onAcupointsHovered"
37
42
  @connectivity-collapse-change="onConnectivityCollapseChange"
38
43
  />
39
44
  </div>
@@ -42,6 +47,7 @@
42
47
  <script>
43
48
  /* eslint-disable no-alert, no-console */
44
49
  // optionally import default styles
50
+ import { acupointEntries } from './acupoints.js'
45
51
  import SideBar from './components/SideBar.vue'
46
52
  import EventBus from './components/EventBus.js'
47
53
  import exampleConnectivityInput from './exampleConnectivityInput.js'
@@ -121,6 +127,14 @@ export default {
121
127
  },
122
128
  data: function () {
123
129
  return {
130
+ acupoints: acupointEntries,
131
+ contextArray: [null, null],
132
+ tabArray: [
133
+ { title: 'Dataset Explorer', id: 1, type: 'datasetExplorer', closable: false },
134
+ { title: 'Connectivity Explorer', id: 2, type: 'connectivityExplorer', closable: false },
135
+ { title: 'Annotation', id: 3, type: 'annotation', closable: true },
136
+ {title: 'Acupoints', id: 4, type: 'acupoints' },
137
+ ],
124
138
  annotationEntry: [{
125
139
  featureId: "epicardium",
126
140
  resourceId: "https://mapcore-bucket1.s3-us-west-2.amazonaws.com/others/29_Jan_2020/heartICN_metadata.json",
@@ -156,6 +170,12 @@ export default {
156
170
  }
157
171
  },
158
172
  methods: {
173
+ onAcupointsClicked: function (data) {
174
+ console.log("acupoints-clicked", data)
175
+ },
176
+ onAcupointsHovered: function (data) {
177
+ console.log("acupoints-hovered", data)
178
+ },
159
179
  loadConnectivityKnowledge: async function () {
160
180
  const sql = `select knowledge from knowledge
161
181
  where source="${this.sckanVersion}"
@@ -232,6 +252,9 @@ export default {
232
252
  'http://purl.obolibrary.org/obo/UBERON_0001103'
233
253
  )
234
254
  },
255
+ searchAcupoints: function() {
256
+ this.$refs.sideBar.openAcupointsSearch("LU 1")
257
+ },
235
258
  singleFacets: function () {
236
259
  this.$refs.sideBar.addFilter({
237
260
  facet: 'Cardiovascular system',
@@ -406,4 +429,4 @@ body {
406
429
  align-items: center;
407
430
  gap: 0.5rem;
408
431
  }
409
- </style>
432
+ </style>./acupoints.js
package/src/acupoints.js CHANGED
@@ -12,7 +12,8 @@ export const acupointEntries = {
12
12
  "Indications": "Test data",
13
13
  "Acupuncture Method": "Test data",
14
14
  "Vasculature": "Test data",
15
- "Innervation": "Test data"
15
+ "Innervation": "Test data",
16
+ "Curated": false,
16
17
  },
17
18
  "LU 2": {
18
19
  "Acupoint": "LU 2",
@@ -27,6 +28,22 @@ export const acupointEntries = {
27
28
  "Indications": "Test data",
28
29
  "Acupuncture Method": "Test data",
29
30
  "Vasculature": "Test data",
30
- "Innervation": "Test data"
31
+ "Innervation": "Test data",
32
+ "Curated": true,
33
+ },
34
+ "ST 3": {
35
+ "Acupoint": "ST 3",
36
+ "Label": "ST 3",
37
+ "Synonym": "Test data",
38
+ "UMLS CUI": "",
39
+ "Meridian": "STest data",
40
+ "Chinese Name": "Cheng Qi",
41
+ "English Name": "Not Available",
42
+ "Location": " z zxcxadadadzc.",
43
+ "Reference": "Test data",
44
+ "Indications": "Test data",
45
+ "Acupuncture Method": "Test data",
46
+ "Vasculature": "Test data",
47
+ "Innervation": "Test data"
31
48
  }
32
49
  }
@@ -8,12 +8,30 @@
8
8
  @mouseleave="cardHovered(undefined)"
9
9
  >
10
10
  <div class="card-right">
11
- <div class="title">Acupoint: {{ entry.Acupoint }}</div>
12
- <template v-for="field in displayFields" :key="field">
13
- <div class="details" v-if="entry[field]">
14
- <strong>{{ field }}:</strong> {{ entry[field] }}
15
- </div>
16
- </template>
11
+ <div class="title">{{ entry.Acupoint }}</div>
12
+ <el-collapse class="collapse-card" v-model="expanded" @change="expandedChanged">
13
+ <el-collapse-item :title="showDetailsText" name="1" class="collapse-card">
14
+ <template v-for="field in displayFields" :key="field['name']">
15
+ <div class="details" >
16
+ <strong>{{ field['name'] }}: </strong>
17
+ <span
18
+ v-if="!field['isEditing']"
19
+ @click="field['isEditing'] = true"
20
+ >
21
+ {{ entry[field['name']] || 'Not Available' }}
22
+ </span>
23
+ <el-input
24
+ v-else
25
+ v-model="entry[field['name']]"
26
+ @blur="field['isEditing'] = false"
27
+ @keyup.enter="field['isEditing'] = false"
28
+ @vue:mounted="inputMounted"
29
+ type="textarea"
30
+ />
31
+ </div>
32
+ </template>
33
+ </el-collapse-item>
34
+ </el-collapse>
17
35
  </div>
18
36
  </div>
19
37
  </div>
@@ -27,19 +45,30 @@ import EventBus from './EventBus.js'
27
45
  export default {
28
46
  data() {
29
47
  return {
48
+ expanded: [],
30
49
  displayFields: [
31
- "Synonym",
32
- "Chinese Name",
33
- "English Name",
34
- "Reference",
35
- "Indications",
36
- "Acupuncture Method",
37
- "Vasculature",
38
- "Innervation"
50
+ {name: "Synonym", isEditing: false},
51
+ {name: "Chinese Name", isEditing: false},
52
+ {name: "English Name", isEditing: false},
53
+ {name: "Reference", isEditing: false},
54
+ {name: "Indications", isEditing: false},
55
+ {name: "Acupuncture Method", isEditing: false},
56
+ {name: "Vasculature", isEditing: false},
57
+ {name: "Innervation", isEditing: false},
58
+ {name: "Notes", isEditing: false},
39
59
  ]
40
60
  }
41
61
  },
42
62
  name: 'AcupointsCard',
63
+ computed: {
64
+ showDetailsText: function() {
65
+ if (this.expanded.length > 0) {
66
+ return "Click here to hide information"
67
+ } else {
68
+ return "Click here to show more information"
69
+ }
70
+ }
71
+ },
43
72
  props: {
44
73
  /**
45
74
  * Object containing information for
@@ -51,22 +80,36 @@ export default {
51
80
  },
52
81
  },
53
82
  methods: {
83
+ expandedChanged: function() {
84
+ if (this.expanded.length > 0) {
85
+ EventBus.emit('acupoints-clicked', this.entry);
86
+ }
87
+ },
54
88
  cardClicked: function (data) {
55
89
  EventBus.emit('acupoints-clicked', data);
56
90
  },
57
91
  cardHovered: function (data) {
58
92
  EventBus.emit('acupoints-hovered', data);
59
93
  },
94
+ inputMounted: function(event) {
95
+ event.el?.querySelector('textarea')?.focus();
96
+ }
60
97
  }
61
98
  }
62
99
  </script>
63
100
 
64
101
  <style lang="scss" scoped>
102
+ .collapse-card {
103
+ border-top: none;
104
+ :deep(.el-collapse-item__header) {
105
+ border-bottom: none;
106
+ }
107
+ }
108
+
65
109
  .dataset-card {
66
110
  padding-left: 5px;
67
111
  padding-right: 5px;
68
112
  position: relative;
69
- min-height: 17rem;
70
113
  }
71
114
 
72
115
  .title {
@@ -1,26 +1,41 @@
1
1
  <template>
2
2
  <div v-if="entry" class="main">
3
3
  <div class="header">
4
- <el-input
5
- class="search-input"
6
- placeholder="Search"
7
- v-model="searchInput"
8
- @keyup="search(searchInput)"
9
- clearable
10
- @clear="clearSearchClicked"
11
- ></el-input>
12
- <el-button
13
- v-show="false"
14
- type="primary"
15
- class="button"
16
- @click="search(searchInput)"
17
- size="large"
18
- >
19
- Search
20
- </el-button>
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>
21
36
  </div>
22
37
  <div class="content scrollbar" ref="content">
23
- <div v-for="result in paginatedResults" :key="result.Acupoint" class="step-item">
38
+ <div v-if="paginatedResults.length > 0" v-for="result in paginatedResults" :key="result.Acupoint" class="step-item">
24
39
  <AcupointsCard
25
40
  class="dataset-card"
26
41
  :entry="result"
@@ -28,6 +43,9 @@
28
43
  @mouseleave="hoverChanged(undefined)"
29
44
  />
30
45
  </div>
46
+ <div v-else class="error-feedback">
47
+ No results found - Please change your search / filter criteria.
48
+ </div>
31
49
  <el-pagination
32
50
  class="pagination"
33
51
  v-model:current-page="page"
@@ -47,10 +65,13 @@
47
65
  import {
48
66
  ElButton as Button,
49
67
  ElCard as Card,
68
+ ElRadioButton as RadioButton,
69
+ ElRadioGroup as RadioGroup,
50
70
  ElDrawer as Drawer,
51
71
  ElIcon as Icon,
52
72
  ElInput as Input,
53
73
  ElPagination as Pagination,
74
+ ElRow as Row,
54
75
  } from 'element-plus'
55
76
  import AcupointsCard from './AcupointsCard.vue'
56
77
 
@@ -59,20 +80,31 @@ export default {
59
80
  AcupointsCard,
60
81
  Button,
61
82
  Card,
83
+ RadioButton,
84
+ RadioGroup,
62
85
  Drawer,
63
86
  Icon,
64
87
  Input,
65
- Pagination
88
+ Pagination,
89
+ Row
66
90
  },
67
91
  name: 'AcupointsInfoSearch',
68
92
  props: {
69
93
  entry: {
70
94
  type: Object,
71
- default: () => initial_state,
95
+ default: () => {},
72
96
  },
73
97
  },
74
98
  data: function () {
75
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,
76
108
  results: [],
77
109
  paginatedResults: [],
78
110
  searchInput: "",
@@ -83,6 +115,15 @@ export default {
83
115
  }
84
116
  },
85
117
  watch: {
118
+ currentFilter: {
119
+ handler: function () {
120
+ this.search(
121
+ this.searchInput,
122
+ this.numberPerPage,
123
+ this.page
124
+ )
125
+ },
126
+ },
86
127
  entry: {
87
128
  handler: function () {
88
129
  this.search(
@@ -109,12 +150,21 @@ export default {
109
150
  },
110
151
  search: function(input) {
111
152
  this.results = []
112
- if (input !== this.previousSearch) {
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
+ }
113
163
  if (input === "") {
114
- this.results = Object.values(this.entry)
164
+ this.results = filteredList
115
165
  } else {
116
166
  const lowerCase = input.toLowerCase()
117
- for (const value of Object.values(this.entry)) {
167
+ for (const value of filteredList) {
118
168
  const searchFields = [
119
169
  value["Acupoint"],
120
170
  value["Synonym"],
@@ -124,7 +174,8 @@ export default {
124
174
  value["Indications"],
125
175
  value["Acupuncture Method"],
126
176
  value["Vasculature"],
127
- value["Innervation"]
177
+ value["Innervation"],
178
+ value["Note"],
128
179
  ];
129
180
  const allstrings = searchFields.join();
130
181
  const myJSON = allstrings.toLowerCase();
@@ -139,7 +190,6 @@ export default {
139
190
  this.numberOfHits = this.results.length
140
191
  this.searchInput = input
141
192
  this.lastSearch = input
142
- console.log(this.numberOfHits)
143
193
  },
144
194
  numberPerPageUpdate: function (val) {
145
195
  this.numberPerPage = val
@@ -162,6 +212,11 @@ export default {
162
212
  </script>
163
213
 
164
214
  <style lang="scss" scoped>
215
+
216
+ .acuRadioGroup {
217
+ padding-top: 8px;
218
+ }
219
+
165
220
  .dataset-card {
166
221
  position: relative;
167
222
 
@@ -201,7 +256,7 @@ export default {
201
256
  border-top: 1px solid var(--el-border-color);
202
257
  display: flex;
203
258
  flex-direction: column;
204
- gap: 1.75rem;
259
+ gap: 1rem;
205
260
  padding: 1rem;
206
261
  }
207
262
 
@@ -295,6 +350,10 @@ export default {
295
350
  background-color: #e4e7ed;
296
351
  }
297
352
 
353
+ .filterText {
354
+ margin-top:8px;
355
+ }
356
+
298
357
  .scrollbar::-webkit-scrollbar-track {
299
358
  border-radius: 10px;
300
359
  background-color: #f5f5f5;
@@ -320,4 +379,11 @@ export default {
320
379
  background: rgba(0, 0, 0, 0);
321
380
  box-shadow: none;
322
381
  }
382
+
383
+ .error-feedback {
384
+ font-family: Asap;
385
+ font-size: 14px;
386
+ font-style: italic;
387
+ padding-top: 15px;
388
+ }
323
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>
@@ -103,9 +103,9 @@
103
103
  <el-icon class="info"><el-icon-warning /></el-icon>
104
104
  </template>
105
105
  <span style="word-break: keep-all">
106
- <strong>Map</strong> - connectivity as defined in displayed map
106
+ <strong>Map</strong> - connectivity as defined in active map.
107
107
  <br>
108
- <strong>SCKAN</strong> - connectivity as defined in SCKAN
108
+ <strong>SCKAN</strong> - connectivity as defined in SCKAN.
109
109
  </span>
110
110
  </el-popover>
111
111
  </span>