@abi-software/map-side-bar 2.19.1-demo.0 → 2.20.0-acuppoints.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.
@@ -0,0 +1,620 @@
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
+ </div>
24
+ <SearchFilters
25
+ v-if="filterEntry.options"
26
+ class="filters"
27
+ ref="filtersRef"
28
+ :entry="filterEntry"
29
+ @filterResults="filterUpdate"
30
+ @numberPerPage="numberPerPageUpdate"
31
+ @cascaderReady="cascaderReady"
32
+ ></SearchFilters>
33
+ <div class="content scrollbar" ref="content">
34
+ <div v-if="paginatedResults.length > 0" v-for="result in paginatedResults" :key="result.Acupoint" class="step-item">
35
+ <AcupointsCard
36
+ class="dataset-card"
37
+ :entry="result"
38
+ @mouseenter="hoverChanged(result)"
39
+ @mouseleave="hoverChanged(undefined)"
40
+ />
41
+ </div>
42
+ <div v-else class="error-feedback">
43
+ No results found - Please change your search / filter criteria.
44
+ </div>
45
+ <el-pagination
46
+ class="pagination"
47
+ v-model:current-page="page"
48
+ hide-on-single-page
49
+ large
50
+ layout="prev, pager, next"
51
+ :page-size="numberPerPage"
52
+ :total="numberOfHits"
53
+ @current-change="pageChange"
54
+ ></el-pagination>
55
+ </div>
56
+ </div>
57
+ </template>
58
+
59
+ <script>
60
+ /* eslint-disable no-alert, no-console */
61
+ import { markRaw } from 'vue';
62
+ import {
63
+ ElButton as Button,
64
+ ElCard as Card,
65
+ ElCol as Col,
66
+ ElRadioButton as RadioButton,
67
+ ElRadioGroup as RadioGroup,
68
+ ElDrawer as Drawer,
69
+ ElIcon as Icon,
70
+ ElInput as Input,
71
+ ElPagination as Pagination,
72
+ ElRow as Row,
73
+ } from 'element-plus'
74
+ import AcupointsCard from './AcupointsCard.vue'
75
+ import SearchFilters from "./SearchFilters.vue"
76
+
77
+ export default {
78
+ components: {
79
+ AcupointsCard,
80
+ Button,
81
+ Card,
82
+ Col,
83
+ RadioButton,
84
+ RadioGroup,
85
+ SearchFilters,
86
+ Drawer,
87
+ Icon,
88
+ Input,
89
+ Pagination,
90
+ Row
91
+ },
92
+ name: 'AcupointsInfoSearch',
93
+ props: {
94
+ entry: {
95
+ type: Object,
96
+ default: () => {},
97
+ },
98
+ },
99
+ created: function() {
100
+ if (this.entry) {
101
+ let entries = Object.values(this.entry)
102
+ const meridians = []
103
+ let others = false
104
+ entries.forEach((acupoint) => {
105
+ let meridian = acupoint.Meridian
106
+ if (!meridian) {
107
+ others = true
108
+ } else if (!(meridians.includes(meridian))) {
109
+ meridians.push(meridian)
110
+ }
111
+ });
112
+ meridians.sort();
113
+ if (others) {
114
+ meridians.push("Others")
115
+ }
116
+ if (meridians.length > 1) {
117
+ const filterOption = {
118
+ "key": "acupoints.meridian",
119
+ "label": "Meridian",
120
+ "children": [
121
+ ],
122
+ }
123
+ meridians.forEach(meridian => {
124
+ const entry = {
125
+ "key": `acupoints.meridian.${meridian}`,
126
+ "label": meridian,
127
+ "value": meridian,
128
+ }
129
+ filterOption.children.push(entry)
130
+ })
131
+ this.filterOptions.unshift(filterOption)
132
+ this.filters.unshift({
133
+ "facetPropPath": "acupoints.meridian",
134
+ "facet": "Show all",
135
+ "term": "Meridian"
136
+ })
137
+ }
138
+ }
139
+ },
140
+ computed: {
141
+ // This computed property populates filter data's entry object with $data from this sidebar
142
+ filterEntry: function () {
143
+ return {
144
+ numberOfHits: this.numberOfHits,
145
+ filterFacets: this.filters,
146
+ options: this.filterOptions,
147
+ showFilters: true,
148
+ };
149
+ },
150
+ },
151
+ data: function () {
152
+ return {
153
+ filters: [
154
+ {
155
+ "facetPropPath": "acupoints.WHO",
156
+ "facet": "Show all",
157
+ "term": "WHO",
158
+ },
159
+ {
160
+ "facetPropPath": "acupoints.visualized",
161
+ "facet": "Show all",
162
+ "term": "Visualized"
163
+ },
164
+ {
165
+ "facetPropPath": "acupoints.onMRI",
166
+ "facet": "Show all",
167
+ "term": "onMRI"
168
+ },
169
+ {
170
+ "facetPropPath": "acupoints.userDefined",
171
+ "facet": "Show all",
172
+ "term": "User defined"
173
+ }
174
+ ],
175
+ filterOptions: [
176
+ {
177
+ "key": "acupoints.visualized",
178
+ "label": "Visualized",
179
+ "children": [
180
+ {
181
+ "key": "acupoints.visualized.yes",
182
+ "label": "Visualized",
183
+ "value": "Visualized>Yes"
184
+ },
185
+ {
186
+ "key": "acupoints.visualized.no",
187
+ "label": "Non visualized",
188
+ "value": "Visualized>No"
189
+ },
190
+ ],
191
+ },
192
+ {
193
+ "key": "acupoints.onMRI",
194
+ "label": "Markers",
195
+ "children": [
196
+ {
197
+ "key": "acupoints.onMRI.yes",
198
+ "label": "Marked",
199
+ "value": "onMRI>Yes"
200
+ },
201
+ {
202
+ "key": "acupoints.onMRI.no",
203
+ "label": "Unmarked",
204
+ "value": "onMRI>No"
205
+ },
206
+ ],
207
+ },
208
+ {
209
+ "key": "acupoints.WHO",
210
+ "label": "WHO",
211
+ "children": [
212
+ {
213
+ "key": "acupoints.WHO.yes",
214
+ "label": "WHO",
215
+ "value": "WHO>Yes"
216
+ },
217
+ {
218
+ "key": "acupoints.WHO.no",
219
+ "label": "Non WHO",
220
+ "value": "WHO>No"
221
+ },
222
+ ],
223
+ },
224
+ {
225
+ "key": "acupoints.userDefined",
226
+ "label": "User Defined",
227
+ "children": [
228
+ {
229
+ "key": "acupoints.userDefined.yes",
230
+ "label": "User data",
231
+ "value": "userDefined>Yes"
232
+ },
233
+ {
234
+ "key": "acupoints.userDefined.no",
235
+ "label": "Official data",
236
+ "value": "userDefined>No"
237
+ },
238
+ ],
239
+ },
240
+ ],
241
+ currentFilters: markRaw({
242
+ curated: "Both",
243
+ mri: "Both",
244
+ who: "Both",
245
+ userDefined: "Both",
246
+ meridian: {
247
+ showAll: true,
248
+ list: []
249
+ },
250
+ }),
251
+ results: [],
252
+ paginatedResults: [],
253
+ searchInput: "",
254
+ lastSearch: "",
255
+ numberOfHits: 0,
256
+ numberPerPage: 10,
257
+ page: 1,
258
+ }
259
+ },
260
+ watch: {
261
+ entry: {
262
+ handler: function () {
263
+ this.filterUpdate(this.filters)
264
+ },
265
+ immediate: true,
266
+ deep: true,
267
+ },
268
+ },
269
+ methods: {
270
+ cascaderReady: function () {
271
+ this.cascaderIsReady = true;
272
+ this.search(this.searchInput);
273
+ },
274
+ filterUpdate: function (filters) {
275
+ this.filters = [...filters]
276
+ this.currentFilters['meridian']['list'] = []
277
+ this.filters.forEach((filter) => {
278
+ if (filter.facetPropPath === "acupoints.visualized") {
279
+ if (filter.facet === "Visualized") {
280
+ this.currentFilters['curated'] = 'Curated'
281
+ } else if (filter.facet === 'Non visualized') {
282
+ this.currentFilters['curated'] = 'Uncurated'
283
+ } else {
284
+ this.currentFilters['curated'] = 'Both'
285
+ }
286
+ }
287
+ if (filter.facetPropPath === "acupoints.onMRI") {
288
+ if (filter.facet === "Marked") {
289
+ this.currentFilters['mri'] = 'On'
290
+ } else if (filter.facet === 'Unmarked') {
291
+ this.currentFilters['mri'] = 'Off'
292
+ } else {
293
+ this.currentFilters['mri'] = 'Both'
294
+ }
295
+ }
296
+ if (filter.facetPropPath === "acupoints.WHO") {
297
+ if (filter.facet === "WHO") {
298
+ this.currentFilters['who'] = 'Yes'
299
+ } else if (filter.facet === 'Non who') {
300
+ this.currentFilters['who'] = 'No'
301
+ } else {
302
+ this.currentFilters['who'] = 'Both'
303
+ }
304
+ }
305
+ if (filter.facetPropPath === "acupoints.userDefined") {
306
+ if (filter.facet === "User data") {
307
+ this.currentFilters['userDefined'] = 'Yes'
308
+ } else if (filter.facet === 'Official data') {
309
+ this.currentFilters['userDefined'] = 'No'
310
+ } else {
311
+ this.currentFilters['userDefined'] = 'Both'
312
+ }
313
+ }
314
+ if (filter.facetPropPath === "acupoints.meridian") {
315
+ if (filter.facet === "Show all") {
316
+ this.currentFilters['meridian']['showAll'] = true
317
+ } else {
318
+ this.currentFilters['meridian']['showAll'] = false
319
+ this.currentFilters['meridian']['list'].push(filter.facet.toLowerCase())
320
+ }
321
+ }
322
+ });
323
+
324
+ this.search( this.searchInput)
325
+ },
326
+ numberPerPageUpdate: function (val) {
327
+ this.numberPerPage = val;
328
+ this.pageChange(1);
329
+ },
330
+ hoverChanged: function (data) {
331
+ this.$emit('hover-changed', data)
332
+ },
333
+ resetSearch: function () {
334
+ this.numberOfHits = 0
335
+ this.search(this.searchInput)
336
+ },
337
+ clearSearchClicked: function () {
338
+ this.searchInput = '';
339
+ this.search("");
340
+ },
341
+ getFilteredList: function() {
342
+ let filteredList = Object.values(this.entry)
343
+ if (this.currentFilters['curated'] !== "Both") {
344
+ const curated = this.currentFilters['curated'] === "Curated" ? true : false
345
+ filteredList = filteredList.filter(
346
+ item => (item.Curated ? true : false) === curated)
347
+ }
348
+ if (this.currentFilters['mri'] !== "Both") {
349
+ const mri = this.currentFilters['mri'] === "On" ? true : false
350
+ filteredList = filteredList.filter(item => (item.onMRI ? true : false) === mri)
351
+ }
352
+ if (this.currentFilters['who'] !== "Both") {
353
+ const who = this.currentFilters['who'] === "Yes" ? true : false
354
+ filteredList = filteredList.filter(
355
+ item => (item["Meridian Point"] ? true : false) === who)
356
+ }
357
+ if (this.currentFilters['userDefined'] !== "Both") {
358
+ const user = this.currentFilters['userDefined'] === "Yes" ? true : false
359
+ filteredList = filteredList.filter(
360
+ item => (item["userDefined"] ? true : false) === user)
361
+ }
362
+ if (!this.currentFilters['meridian']['showAll']) {
363
+ filteredList = filteredList.filter((item) => {
364
+ let meridian = "others"
365
+ if (item.Meridian) {
366
+ meridian = item.Meridian.toLowerCase()
367
+ }
368
+ return this.currentFilters['meridian']['list'].includes(meridian)
369
+ })
370
+ }
371
+ return filteredList;
372
+ },
373
+ search: function(input) {
374
+ this.results = []
375
+ let filteredList = this.getFilteredList()
376
+ if (input === "") {
377
+ this.results = filteredList
378
+ } else {
379
+ const lowerCase = input.toLowerCase()
380
+ for (const value of filteredList) {
381
+ const searchFields = [
382
+ value["Acupoint"],
383
+ value["Synonym"],
384
+ value["Chinese Name"],
385
+ value["English Name"],
386
+ value["Reference"],
387
+ value["Indications"],
388
+ value["Acupuncture Method"],
389
+ value["Vasculature"],
390
+ value["Innervation"],
391
+ value["Note"],
392
+ ];
393
+ const allstrings = searchFields.join();
394
+ const myJSON = allstrings.toLowerCase();
395
+ if (myJSON.includes(lowerCase)) {
396
+ this.results.push(value)
397
+ }
398
+ }
399
+ }
400
+ this.results.sort((a, b) => {
401
+ if (a["Meridian Point"] == b["Meridian Point"]) {
402
+ return a['Acupoint'].localeCompare(b['Acupoint'], undefined, { numeric: true })
403
+ } else if (a["Meridian Point"]) {
404
+ return -1
405
+ } else {
406
+ return 1
407
+ }
408
+ })
409
+
410
+ const start = this.numberPerPage * (this.page - 1)
411
+ this.paginatedResults = this.results.slice(start, start + this.numberPerPage)
412
+ this.numberOfHits = this.results.length
413
+ this.searchInput = input
414
+ this.lastSearch = input
415
+ this.$emit("acupoints-result", {
416
+ list: this.results
417
+ });
418
+ },
419
+ openSearch: function (filters, searchTerm = '') {
420
+ this.searchInput = searchTerm
421
+ this.filterUpdate(filters)
422
+ this.$refs.filtersRef.setCascader(this.filters)
423
+ },
424
+ numberPerPageUpdate: function (val) {
425
+ this.numberPerPage = val
426
+ this.pageChange(1)
427
+ },
428
+ pageChange: function (page) {
429
+ this.page = page
430
+ this.search( this.searchInput)
431
+ },
432
+ scrollToTop: function () {
433
+ if (this.$refs.content) {
434
+ this.$refs.content.scroll({ top: 0, behavior: 'smooth' })
435
+ }
436
+ },
437
+ resetPageNavigation: function () {
438
+ this.page = 1
439
+ },
440
+ },
441
+ }
442
+ </script>
443
+
444
+ <style lang="scss" scoped>
445
+
446
+ .acuRadioGroup {
447
+ padding-top: 8px;
448
+ }
449
+
450
+ .dataset-card {
451
+ position: relative;
452
+
453
+ &::before {
454
+ content: "";
455
+ display: block;
456
+ width: calc(100% - 15px);
457
+ height: 100%;
458
+ position: absolute;
459
+ top: 7px;
460
+ left: 7px;
461
+ border-style: solid;
462
+ border-radius: 5px;
463
+ border-color: transparent;
464
+ }
465
+
466
+ &:hover {
467
+ &::before {
468
+ border-color: var(--el-color-primary);
469
+ }
470
+ }
471
+ }
472
+
473
+ .main {
474
+ font-size: 14px;
475
+ text-align: left;
476
+ line-height: 1.5em;
477
+ font-family: Asap, sans-serif, Helvetica;
478
+ font-weight: 400;
479
+ /* outline: thin red solid; */
480
+ overflow-y: auto;
481
+ scrollbar-width: thin;
482
+ min-width: 16rem;
483
+ background-color: #f7faff;
484
+ height: 100%;
485
+ border-left: 1px solid var(--el-border-color);
486
+ border-top: 1px solid var(--el-border-color);
487
+ display: flex;
488
+ flex-direction: column;
489
+ gap: 1rem;
490
+ padding: 1rem;
491
+ }
492
+
493
+ .step-item {
494
+ font-size: 14px;
495
+ margin-bottom: 8px;
496
+ text-align: left;
497
+ }
498
+
499
+ .search-input {
500
+ width: 298px !important;
501
+ height: 40px;
502
+ padding-right: 14px;
503
+
504
+ :deep(.el-input__inner) {
505
+ font-family: inherit;
506
+ }
507
+ }
508
+
509
+ .header {
510
+ .el-button {
511
+ font-family: inherit;
512
+
513
+ &:hover,
514
+ &:focus {
515
+ background: $app-primary-color;
516
+ box-shadow: -3px 2px 4px #00000040;
517
+ color: #fff;
518
+ }
519
+ }
520
+ }
521
+
522
+ .pagination {
523
+ padding-bottom: 16px;
524
+ background-color: white;
525
+ padding-left: 95px;
526
+ font-weight: bold;
527
+ }
528
+
529
+ .pagination :deep(button) {
530
+ background-color: white !important;
531
+ }
532
+ .pagination :deep(li) {
533
+ background-color: white !important;
534
+ }
535
+ .pagination :deep(li.is-active) {
536
+ color: $app-primary-color;
537
+ }
538
+
539
+ .error-feedback {
540
+ font-family: Asap;
541
+ font-size: 14px;
542
+ font-style: italic;
543
+ padding-top: 15px;
544
+ }
545
+
546
+ .content-card :deep(.el-card__header) {
547
+ background-color: #292b66;
548
+ padding: 1rem;
549
+ }
550
+
551
+ .content-card :deep(.el-card__body) {
552
+ background-color: #f7faff;
553
+ overflow-y: hidden;
554
+ padding: 1rem;
555
+ }
556
+
557
+ .content {
558
+ // width: 515px;
559
+ flex: 1 1 auto;
560
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
561
+ border: solid 1px #e4e7ed;
562
+ background-color: #ffffff;
563
+ overflow-y: scroll;
564
+ scrollbar-width: thin;
565
+ border-radius: var(--el-border-radius-base);
566
+ }
567
+
568
+ .content :deep(.el-loading-spinner .path) {
569
+ stroke: $app-primary-color;
570
+ }
571
+
572
+ .content :deep(.step-item:first-child .seperator-path) {
573
+ display: none;
574
+ }
575
+
576
+ .content :deep(.step-item:not(:first-child) .seperator-path) {
577
+ width: 455px;
578
+ height: 0px;
579
+ border: solid 1px #e4e7ed;
580
+ background-color: #e4e7ed;
581
+ }
582
+
583
+ .filter-text {
584
+ top:4px;
585
+ position:relative;
586
+ }
587
+
588
+ .scrollbar::-webkit-scrollbar-track {
589
+ border-radius: 10px;
590
+ background-color: #f5f5f5;
591
+ }
592
+
593
+ .scrollbar::-webkit-scrollbar {
594
+ width: 12px;
595
+ right: -12px;
596
+ background-color: #f5f5f5;
597
+ }
598
+
599
+ .scrollbar::-webkit-scrollbar-thumb {
600
+ border-radius: 4px;
601
+ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.06);
602
+ background-color: #979797;
603
+ }
604
+
605
+ :deep(.el-input__suffix) {
606
+ padding-right: 0px;
607
+ }
608
+
609
+ :deep(.my-drawer) {
610
+ background: rgba(0, 0, 0, 0);
611
+ box-shadow: none;
612
+ }
613
+
614
+ .error-feedback {
615
+ font-family: Asap;
616
+ font-size: 14px;
617
+ font-style: italic;
618
+ padding-top: 15px;
619
+ }
620
+ </style>
@@ -33,7 +33,6 @@
33
33
 
34
34
  <script>
35
35
  import EventBus from './EventBus';
36
- import { capitalise as capitaliseText } from '../utils/common.js';
37
36
 
38
37
  export default {
39
38
  name: 'ConnectivityCard',
@@ -82,7 +81,8 @@ export default {
82
81
  },
83
82
  methods: {
84
83
  capitalise: function (text) {
85
- return capitaliseText(text);
84
+ if (text) return text.charAt(0).toUpperCase() + text.slice(1);
85
+ return '';
86
86
  },
87
87
  cardClicked: function (data) {
88
88
  if (!this.loading) {
@@ -343,11 +343,6 @@ import {
343
343
  ExternalResourceCard,
344
344
  } from '@abi-software/map-utilities';
345
345
  import '@abi-software/map-utilities/dist/style.css';
346
- import {
347
- capitalise,
348
- formatAlertText as formatAlertTextUtil,
349
- scrollToRef,
350
- } from '../utils/common.js';
351
346
 
352
347
  const titleCase = (str) => {
353
348
  return str.replace(/\w\S*/g, (t) => {
@@ -355,6 +350,11 @@ const titleCase = (str) => {
355
350
  });
356
351
  };
357
352
 
353
+ const capitalise = function (str) {
354
+ if (str) return str.charAt(0).toUpperCase() + str.slice(1);
355
+ return '';
356
+ };
357
+
358
358
  export default {
359
359
  name: 'ConnectivityInfo',
360
360
  components: {
@@ -1162,10 +1162,40 @@ export default {
1162
1162
  EventBus.emit('trackEvent', data);
1163
1163
  },
1164
1164
  showAlertMessage: function () {
1165
- scrollToRef(this, 'alertElement');
1165
+ // scroll to alert message
1166
+ this.$nextTick(() => {
1167
+ const alertElement = this.$refs.alertElement;
1168
+ if (alertElement) {
1169
+ alertElement.scrollIntoView({
1170
+ behavior: 'smooth',
1171
+ block: 'start',
1172
+ inline: 'nearest',
1173
+ });
1174
+ }
1175
+ });
1166
1176
  },
1167
1177
  formatAlertText: function (text) {
1168
- return formatAlertTextUtil(text, { formatLines: true });
1178
+ if (!text) return '';
1179
+ const escaped = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1180
+ const linkified = escaped.replace(/(https?:\/\/[^\s"<>[]+)/g, (url) => {
1181
+ const parts = url.match(/^(.*?)([\].,;:!?]*)$/);
1182
+ const cleanUrl = parts ? parts[1] : url;
1183
+ const suffix = parts ? parts[2] : '';
1184
+ return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer">${cleanUrl}</a>${suffix}`;
1185
+ });
1186
+
1187
+ const normalised = linkified
1188
+ .replace(/\\n/g, '\n')
1189
+ .replace(/\r\n/g, '\n')
1190
+ .replace(/\r/g, '\n');
1191
+
1192
+ return normalised
1193
+ .split('\n')
1194
+ .map((line) => {
1195
+ const withBoldLabel = line.replace(/^\s*([A-Za-z][^:<]{0,120}:)/, '<strong>$1</strong>');
1196
+ return `<div class="alert-line">${withBoldLabel}</div>`;
1197
+ })
1198
+ .join('\n');
1169
1199
  },
1170
1200
  },
1171
1201
  mounted: function () {