@abi-software/map-utilities 0.0.0-beta.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,467 @@
1
+ <template>
2
+ <el-main class="main">
3
+ <div class="block">
4
+ <el-row class="info-field">
5
+ <div class="title">Feature Annotations</div>
6
+ </el-row>
7
+ <template v-if="annotationEntry">
8
+ <el-row
9
+ v-for="(key, label) in displayPair"
10
+ v-show="annotationEntry[key]"
11
+ class="dialog-text"
12
+ :key="key"
13
+ >
14
+ <strong>{{ label }}: </strong>&nbsp;{{ annotationEntry[key] }}
15
+ </el-row>
16
+ <template v-if="prevSubs.length > 0">
17
+ <div
18
+ v-show="showSubmissions"
19
+ class="hide"
20
+ @click="showSubmissions = false"
21
+ >
22
+ Hide previous submissions
23
+ <el-icon><el-icon-arrow-up /></el-icon>
24
+ </div>
25
+ <div
26
+ v-show="!showSubmissions"
27
+ class="hide"
28
+ @click="showSubmissions = true"
29
+ >
30
+ Show previous {{ prevSubs.length }} submission(s)
31
+ <el-icon><el-icon-arrow-down /></el-icon>
32
+ </div>
33
+ <template v-if="showSubmissions">
34
+ <el-row class="dialog-spacer"></el-row>
35
+ <el-row class="dialog-text">
36
+ <strong class="sub-title">Previous submissions:</strong>
37
+ </el-row>
38
+ <div class="entry" v-for="(sub, index) in prevSubs" :key="index">
39
+ <el-row class="dialog-text">
40
+ <strong>{{ formatTime(sub.created) }}</strong>
41
+ {{ sub.creator.name }}
42
+ </el-row>
43
+ <el-row class="dialog-text">
44
+ <strong>Evidence: </strong>
45
+ <el-row
46
+ v-for="evidence in sub.body.evidence"
47
+ :key="evidence"
48
+ class="dialog-text"
49
+ >
50
+ <a :href="evidence" target="_blank"> {{ evidence }}</a>
51
+ </el-row>
52
+ </el-row>
53
+ <el-row class="dialog-text">
54
+ <strong>Comment: </strong> {{ sub.body.comment }}
55
+ </el-row>
56
+ </div>
57
+ </template>
58
+ </template>
59
+ <template v-if="authenticated">
60
+ <template v-if="isEditable">
61
+ <el-row class="dialog-spacer"></el-row>
62
+ <el-row v-if="!editing">
63
+ <el-icon class="standard-icon">
64
+ <el-icon-edit @click="editing = true" />
65
+ </el-icon>
66
+ <el-icon class="standard-icon" v-if="isDeleted">
67
+ <el-icon-delete @click="submit" />
68
+ </el-icon>
69
+ <el-icon class="standard-icon" v-else-if="isPositionUpdated">
70
+ <el-icon-finished @click="submit" />
71
+ </el-icon>
72
+ </el-row>
73
+ <template v-else>
74
+ <el-row class="dialog-text">
75
+ <strong class="sub-title">Suggest changes:</strong>
76
+ </el-row>
77
+ <template v-if="!isDeleted">
78
+ <el-row class="dialog-text">
79
+ <strong>Evidence:</strong>
80
+ </el-row>
81
+ <el-row v-for="(value, index) in evidence" :key="value">
82
+ <el-col :span="20">
83
+ {{ evidence[index] }}
84
+ </el-col>
85
+ <el-col :span="4">
86
+ <el-icon class="standard-icon">
87
+ <el-icon-close @click="removeEvidence(index)" />
88
+ </el-icon>
89
+ </el-col>
90
+ </el-row>
91
+ <el-row>
92
+ <el-input
93
+ size="small"
94
+ placeholder="Enter"
95
+ v-model="newEvidence"
96
+ @change="evidenceEntered($event)"
97
+ >
98
+ <template #prepend>
99
+ <el-select
100
+ :teleported="false"
101
+ v-model="evidencePrefix"
102
+ placeholder="Select"
103
+ class="select-box"
104
+ popper-class="flatmap_dropdown"
105
+ >
106
+ <el-option
107
+ v-for="item in evidencePrefixes"
108
+ :key="item"
109
+ :label="item"
110
+ :value="item"
111
+ >
112
+ <el-row>
113
+ <el-col :span="12">{{ item }}</el-col>
114
+ </el-row>
115
+ </el-option>
116
+ </el-select>
117
+ </template>
118
+ </el-input>
119
+ </el-row>
120
+ </template>
121
+ <el-row>
122
+ <strong>Comment:</strong>
123
+ </el-row>
124
+ <el-row class="dialog-text">
125
+ <el-input
126
+ type="textarea"
127
+ :autosize="{ minRows: 2, maxRows: 4 }"
128
+ placeholder="Enter"
129
+ v-model="comment"
130
+ />
131
+ </el-row>
132
+ <el-row class="dialog-text">
133
+ <el-button class="button" type="primary" plain @click="submit">
134
+ Submit
135
+ </el-button>
136
+ </el-row>
137
+ </template>
138
+ <el-row class="dialog-text" v-if="errorMessage">
139
+ <strong class="sub-title"> {{ errorMessage }} </strong>
140
+ </el-row>
141
+ </template>
142
+ </template>
143
+ </template>
144
+ </div>
145
+ </el-main>
146
+ </template>
147
+
148
+ <script>
149
+ export default {
150
+ name: "AnnotationTool",
151
+ props: {
152
+ annotationEntry: {
153
+ type: Object,
154
+ },
155
+ },
156
+ inject: ["$annotator", "userApiKey"],
157
+ data: function () {
158
+ return {
159
+ displayPair: {
160
+ "Feature ID": "featureId",
161
+ Tooltip: "label",
162
+ Models: "models",
163
+ Name: "name",
164
+ Resource: "resourceId",
165
+ },
166
+ editing: false,
167
+ evidencePrefixes: ["DOI:", "PMID:"],
168
+ evidencePrefix: "DOI:",
169
+ evidence: [],
170
+ authenticated: false,
171
+ newEvidence: "",
172
+ comment: "",
173
+ prevSubs: [],
174
+ showSubmissions: true,
175
+ errorMessage: "",
176
+ creator: undefined,
177
+ };
178
+ },
179
+ computed: {
180
+ isEditable: function () {
181
+ return (
182
+ this.annotationEntry["resourceId"] && this.annotationEntry["featureId"]
183
+ );
184
+ },
185
+ isPositionUpdated: function () {
186
+ return (
187
+ this.annotationEntry["resourceId"] &&
188
+ this.annotationEntry["type"] === "updated" &&
189
+ this.annotationEntry["positionUpdated"]
190
+ );
191
+ },
192
+ isDeleted: function () {
193
+ return (
194
+ this.annotationEntry["resourceId"] &&
195
+ this.annotationEntry["type"] === "deleted"
196
+ );
197
+ },
198
+ },
199
+ methods: {
200
+ evidenceEntered: function (value) {
201
+ if (value) {
202
+ this.evidence.push(this.evidencePrefix + value);
203
+ this.newEvidence = "";
204
+ }
205
+ },
206
+ formatTime: function (dateString) {
207
+ const options = {
208
+ year: "numeric",
209
+ month: "long",
210
+ day: "numeric",
211
+ hour: "numeric",
212
+ minute: "numeric",
213
+ second: "numeric",
214
+ };
215
+ return new Date(dateString).toLocaleDateString(undefined, options);
216
+ },
217
+ updatePrevSubmissions: function () {
218
+ if (this.$annotator && this.authenticated) {
219
+ if (
220
+ this.annotationEntry["resourceId"] &&
221
+ this.annotationEntry["featureId"]
222
+ ) {
223
+ this.$annotator
224
+ ?.itemAnnotations(
225
+ this.userApiKey,
226
+ this.annotationEntry["resourceId"],
227
+ this.annotationEntry["featureId"]
228
+ )
229
+ .then((value) => {
230
+ this.prevSubs = value;
231
+ })
232
+ .catch((reason) => {
233
+ console.log(reason); // Error!
234
+ });
235
+ }
236
+ }
237
+ },
238
+ submit: function () {
239
+ // User can either update/delete annotation directly
240
+ // or provide extra comments for update/delete action
241
+ if (
242
+ this.annotationEntry["type"] === "updated" &&
243
+ this.annotationEntry["positionUpdated"]
244
+ ) {
245
+ this.comment = this.comment
246
+ ? `Position Updated: ${this.comment}`
247
+ : "Position Updated";
248
+ } else if (this.annotationEntry["type"] === "deleted") {
249
+ this.comment = this.comment
250
+ ? `Feature Deleted: ${this.comment}`
251
+ : "Feature Deleted";
252
+ }
253
+
254
+ if (this.evidence.length > 0 || this.comment) {
255
+ if (
256
+ this.annotationEntry["resourceId"] &&
257
+ this.annotationEntry["featureId"]
258
+ ) {
259
+ const evidenceURLs = [];
260
+ this.evidence.forEach((evidence) => {
261
+ if (evidence.includes("DOI:")) {
262
+ const link = evidence.replace("DOI:", "https://doi.org/");
263
+ evidenceURLs.push(new URL(link));
264
+ } else if (evidence.includes("PMID:")) {
265
+ const link = evidence.replace(
266
+ "PMID:",
267
+ "https://pubmed.ncbi.nlm.nih.gov/"
268
+ );
269
+ evidenceURLs.push(new URL(link));
270
+ }
271
+ });
272
+ const userAnnotation = {
273
+ resource: this.annotationEntry["resourceId"],
274
+ item: Object.assign(
275
+ { id: this.annotationEntry["featureId"] },
276
+ Object.fromEntries(
277
+ Object.entries(this.annotationEntry).filter(([key]) =>
278
+ ["label", "models"].includes(key)
279
+ )
280
+ )
281
+ ),
282
+ body: {
283
+ evidence: evidenceURLs,
284
+ comment: this.comment,
285
+ },
286
+ feature: this.annotationEntry["feature"],
287
+ };
288
+ Object.assign(userAnnotation.body, this.annotationEntry["body"]);
289
+ if (this.annotationEntry["type"] === "deleted") {
290
+ userAnnotation.feature = undefined;
291
+ }
292
+ if (this.creator) userAnnotation.creator = this.creator;
293
+ this.$annotator
294
+ ?.addAnnotation(this.userApiKey, userAnnotation)
295
+ .then(() => {
296
+ this.$emit("annotation", userAnnotation);
297
+ this.errorMessage = "";
298
+ this.resetSubmission();
299
+ this.updatePrevSubmissions();
300
+ })
301
+ .catch(() => {
302
+ this.errorMessage =
303
+ "There is a problem with the submission, please try again later";
304
+ });
305
+ }
306
+ }
307
+ },
308
+ removeEvidence: function (index) {
309
+ this.evidence.splice(index, 1);
310
+ },
311
+ resetSubmission: function () {
312
+ this.editing = false;
313
+ this.evidence = [];
314
+ this.newFeature = "";
315
+ this.comment = "";
316
+ },
317
+ },
318
+ watch: {
319
+ annotationEntry: {
320
+ handler: function (newVal, oldVal) {
321
+ if (newVal !== oldVal) {
322
+ this.resetSubmission();
323
+ this.updatePrevSubmissions();
324
+ }
325
+ },
326
+ immediate: false,
327
+ deep: false,
328
+ },
329
+ },
330
+ mounted: function () {
331
+ this.$annotator?.authenticate(this.userApiKey).then((userData) => {
332
+ if (userData.name && userData.email) {
333
+ this.creator = userData;
334
+ if (!userData.orcid) this.creator.orcid = "0000-0000-0000-0000";
335
+ this.authenticated = true;
336
+ this.updatePrevSubmissions();
337
+ } else {
338
+ this.errorMessage = "";
339
+ }
340
+ });
341
+ },
342
+ };
343
+ </script>
344
+
345
+ <style lang="scss" scoped>
346
+ .info-field {
347
+ display: flex;
348
+ }
349
+
350
+ .block {
351
+ margin-bottom: 0.5em;
352
+
353
+ .main > &:first-of-type {
354
+ margin-right: 0.5em;
355
+ }
356
+ }
357
+
358
+ .button {
359
+ padding-top: 5px;
360
+ padding-bottom: 5px;
361
+ }
362
+
363
+ .standard-icon {
364
+ color: $app-primary-color;
365
+ &:hover {
366
+ cursor: pointer;
367
+ }
368
+ }
369
+
370
+ .dialog-text {
371
+ color: rgb(48, 49, 51);
372
+ font-size: 14px;
373
+ font-weight: normal;
374
+ line-height: 20px;
375
+ }
376
+
377
+ .main {
378
+ font-size: 14px;
379
+ text-align: left;
380
+ line-height: 1.5em;
381
+ font-family: Asap, sans-serif, Helvetica;
382
+ font-weight: 400;
383
+ /* outline: thin red solid; */
384
+ padding: 1em !important;
385
+ overflow-x: hidden;
386
+ overflow-y: auto;
387
+ min-width: 300px; // .maplibregl-popup max-width
388
+ max-height: 400px;
389
+ scrollbar-width: thin;
390
+
391
+ &::-webkit-scrollbar {
392
+ width: 4px;
393
+ }
394
+
395
+ &::-webkit-scrollbar-thumb {
396
+ border-radius: 10px;
397
+ box-shadow: inset 0 0 6px #c0c4cc;
398
+ }
399
+ }
400
+
401
+ .title {
402
+ font-size: 18px;
403
+ font-weight: 500;
404
+ font-weight: bold;
405
+ padding-bottom: 8px;
406
+ color: rgb(131, 0, 191);
407
+ }
408
+
409
+ .sub-title {
410
+ font-size: 16px;
411
+ }
412
+
413
+ .dialog-spacer {
414
+ border-bottom: 1px solid #e4e7ed;
415
+ margin-bottom: 10px;
416
+ }
417
+
418
+ .submit {
419
+ color: $app-primary-color;
420
+ &:hover {
421
+ cursor: pointer;
422
+ }
423
+ }
424
+
425
+ .entry ~ .entry {
426
+ border-top: 1px solid #e4e7ed;
427
+ margin-top: 10px;
428
+ }
429
+
430
+ .hide {
431
+ color: $app-primary-color;
432
+ cursor: pointer;
433
+ margin-right: 6px;
434
+ margin-top: 3px;
435
+ }
436
+
437
+ :deep(.el-input__inner),
438
+ :deep(.el-textarea__inner) {
439
+ font-family: Asap, sans-serif;
440
+ }
441
+
442
+ .select-box {
443
+ width: 80px;
444
+ background-color: var(--white);
445
+ font-weight: 500;
446
+ color: rgb(48, 49, 51);
447
+ :deep(.el-input__inner) {
448
+ height: 30px;
449
+ color: rgb(48, 49, 51);
450
+ }
451
+ :deep(.el-input__icon) {
452
+ line-height: 30px;
453
+ }
454
+ }
455
+
456
+ :deep(.flatmap_dropdown) {
457
+ min-width: 80px !important;
458
+ .el-select-dropdown__item {
459
+ white-space: nowrap;
460
+ text-align: left;
461
+ &.selected {
462
+ color: $app-primary-color;
463
+ font-weight: normal;
464
+ }
465
+ }
466
+ }
467
+ </style>
@@ -0,0 +1,104 @@
1
+ <template>
2
+ <div class="resource-container">
3
+ <template v-for="resource in resources" :key="resource.id">
4
+ <div class="resource">
5
+ <el-button
6
+ v-if="resource.id === 'pubmed'"
7
+ class="button"
8
+ id="open-pubmed-button"
9
+ :icon="ElIconNotebook"
10
+ @click="openUrl(resource.url)"
11
+ >
12
+ Open publications in pubmed
13
+ </el-button>
14
+ </div>
15
+ </template>
16
+ </div>
17
+ </template>
18
+
19
+ <script>
20
+ /* eslint-disable no-alert, no-console */
21
+ import { shallowRef } from "vue";
22
+ import { Notebook as ElIconNotebook } from "@element-plus/icons-vue";
23
+
24
+ import EventBus from "../EventBus";
25
+
26
+ export default {
27
+ name: "ExternalResourceCard",
28
+ props: {
29
+ resources: {
30
+ type: Array,
31
+ default: () => [],
32
+ },
33
+ },
34
+ data: function () {
35
+ return {
36
+ pubmeds: [],
37
+ pubmedIds: [],
38
+ ElIconNotebook: shallowRef(ElIconNotebook),
39
+ };
40
+ },
41
+ methods: {
42
+ capitalise: function (string) {
43
+ return string.charAt(0).toUpperCase() + string.slice(1);
44
+ },
45
+ openUrl: function (url) {
46
+ EventBus.emit("open-pubmed-url", url);
47
+ window.open(url, "_blank");
48
+ },
49
+ },
50
+ };
51
+ </script>
52
+
53
+ <style lang="scss" scoped>
54
+ .attribute-title {
55
+ font-size: 16px;
56
+ font-weight: 600;
57
+ /* font-weight: bold; */
58
+ text-transform: uppercase;
59
+ }
60
+
61
+ .attribute-content {
62
+ font-size: 14px;
63
+ font-weight: 400;
64
+ }
65
+
66
+ .el-link {
67
+ color: $app-primary-color;
68
+ text-decoration: none;
69
+ word-wrap: break-word;
70
+ &:hover,
71
+ &:focus {
72
+ color: $app-primary-color;
73
+ }
74
+ }
75
+
76
+ :deep(.el-carousel__button) {
77
+ background-color: $app-primary-color;
78
+ }
79
+
80
+ .attribute-title {
81
+ font-size: 16px;
82
+ font-weight: 600;
83
+ /* font-weight: bold; */
84
+ text-transform: uppercase;
85
+ }
86
+
87
+ .button {
88
+ margin-left: 0px !important;
89
+ margin-top: 0px !important;
90
+ font-size: 14px !important;
91
+ background-color: $app-primary-color;
92
+ color: #fff;
93
+ &:hover {
94
+ color: #fff !important;
95
+ background: #ac76c5 !important;
96
+ border: 1px solid #ac76c5 !important;
97
+ }
98
+ & + .button {
99
+ margin-top: 10px !important;
100
+ background-color: $app-primary-color;
101
+ color: #fff;
102
+ }
103
+ }
104
+ </style>