@abi-software/map-side-bar 2.4.0 → 2.4.2-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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abi-software/map-side-bar",
3
- "version": "2.4.0",
3
+ "version": "2.4.2-beta.0",
4
4
  "files": [
5
5
  "dist/*",
6
6
  "src/*",
package/src/App.vue CHANGED
@@ -14,6 +14,7 @@
14
14
  <el-button @click="neuronSearch">open neuron search</el-button>
15
15
  <el-button @click="keywordSearch">keyword search</el-button>
16
16
  <el-button @click="getFacets">Get facets</el-button>
17
+ <el-button @click="showImages">Show Images</el-button>
17
18
  </div>
18
19
  <SideBar
19
20
  :envVars="envVars"
@@ -23,10 +24,13 @@
23
24
  :tabs="tabs"
24
25
  :activeTabId="activeId"
25
26
  :connectivityInfo="connectivityInput"
27
+ :imageThumbnails="imageThumbnails"
26
28
  @tabClicked="tabClicked"
27
29
  @search-changed="searchChanged($event)"
28
30
  @hover-changed="hoverChanged($event)"
29
31
  @actionClick="action"
32
+ @connectivity-info-close="onConnectivityInfoClose"
33
+ @image-thumbnail-close="onImageThumbnailClose"
30
34
  />
31
35
  </div>
32
36
  </template>
@@ -37,6 +41,7 @@
37
41
  import SideBar from './components/SideBar.vue'
38
42
  import EventBus from './components/EventBus.js'
39
43
  import exampleConnectivityInput from './exampleConnectivityInput.js'
44
+ import imageThumbnails from './data/images.json';
40
45
 
41
46
  const capitalise = (str) => str.charAt(0).toUpperCase() + str.slice(1);
42
47
 
@@ -101,7 +106,11 @@ export default {
101
106
  data: function () {
102
107
  return {
103
108
  contextArray: [null, null],
104
- tabArray: [{ title: 'Flatmap', id: 1, type: 'search'}, { title: 'Connectivity', id: 2, type: 'connectivity' }],
109
+ tabArray: [
110
+ { title: 'Flatmap', id: 1, type: 'search'},
111
+ { title: 'Connectivity', id: 2, type: 'connectivity' },
112
+ { title: 'Images', id: 3, type: 'images' },
113
+ ],
105
114
  sideBarVisibility: true,
106
115
  envVars: {
107
116
  API_LOCATION: import.meta.env.VITE_APP_API_LOCATION,
@@ -114,6 +123,7 @@ export default {
114
123
  ROOT_URL: import.meta.env.VITE_APP_ROOT_URL,
115
124
  },
116
125
  connectivityInput: exampleConnectivityInput,
126
+ imageThumbnails: imageThumbnails,
117
127
  activeId: 1,
118
128
  }
119
129
  },
@@ -224,6 +234,18 @@ export default {
224
234
  let facets = await this.$refs.sideBar.getAlgoliaFacets()
225
235
  console.log('Algolia facets:', facets)
226
236
  },
237
+ showImages: function () {
238
+ if (this.$refs.sideBar) {
239
+ this.tabClicked({id: 3, type: 'images'});
240
+ this.$refs.sideBar.setDrawerOpen(true);
241
+ }
242
+ },
243
+ onConnectivityInfoClose: function () {
244
+ console.log('connectivity-info-close');
245
+ },
246
+ onImageThumbnailClose: function () {
247
+ console.log('image-thumbnail-close');
248
+ },
227
249
  },
228
250
  mounted: function () {
229
251
  console.log('mounted app')
@@ -4,7 +4,24 @@
4
4
  <div class="connectivity-info-title">
5
5
  <div>
6
6
  <div class="block" v-if="entry.title">
7
- <div class="title">{{ capitalise(entry.title) }}</div>
7
+ <div class="title">
8
+ {{ capitalise(entry.title) }}
9
+ <template v-if="entry.featuresAlert">
10
+ <el-popover
11
+ width="250"
12
+ trigger="hover"
13
+ :teleported="false"
14
+ popper-class="popover-origin-help"
15
+ >
16
+ <template #reference>
17
+ <el-icon class="alert"><el-icon-warn-triangle-filled /></el-icon>
18
+ </template>
19
+ <span style="word-break: keep-all">
20
+ {{ entry.featuresAlert }}
21
+ </span>
22
+ </el-popover>
23
+ </template>
24
+ </div>
8
25
  <div
9
26
  v-if="
10
27
  entry.provenanceTaxonomyLabel &&
@@ -41,22 +58,6 @@
41
58
  <CopyToClipboard :content="updatedCopyContent" />
42
59
  </div>
43
60
  </div>
44
- <div v-if="featuresAlert" class="attribute-title-container">
45
- <span class="attribute-title">Alert</span>
46
- <el-popover
47
- width="250"
48
- trigger="hover"
49
- :teleported="false"
50
- popper-class="popover-origin-help"
51
- >
52
- <template #reference>
53
- <el-icon class="info"><el-icon-warning /></el-icon>
54
- </template>
55
- <span style="word-break: keep-all">
56
- {{ featuresAlert }}
57
- </span>
58
- </el-popover>
59
- </div>
60
61
  <div class="content-container scrollbar">
61
62
  {{ entry.paths }}
62
63
  <div v-if="entry.origins && entry.origins.length > 0" class="block">
@@ -229,6 +230,7 @@ export default {
229
230
  originsWithDatasets: [],
230
231
  componentsWithDatasets: [],
231
232
  resource: undefined,
233
+ featuresAlert: undefined,
232
234
  }),
233
235
  },
234
236
  availableAnatomyFacets: {
@@ -236,7 +238,6 @@ export default {
236
238
  default: () => [],
237
239
  },
238
240
  },
239
- // inject: ['getFeaturesAlert'],
240
241
  data: function () {
241
242
  return {
242
243
  controller: undefined,
@@ -274,9 +275,6 @@ export default {
274
275
  }
275
276
  return resources;
276
277
  },
277
- featuresAlert() {
278
- // return this.getFeaturesAlert()
279
- },
280
278
  originDescription: function () {
281
279
  if (
282
280
  this.entry &&
@@ -517,6 +515,9 @@ export default {
517
515
  :deep(.popover-origin-help.el-popover) {
518
516
  text-transform: none !important; // need to overide the tooltip text transform
519
517
  border: 1px solid $app-primary-color;
518
+ font-weight: 400;
519
+ font-family: Asap, sans-serif, Helvetica;
520
+
520
521
  .el-popper__arrow {
521
522
  &:before {
522
523
  border-color: $app-primary-color;
@@ -525,12 +526,27 @@ export default {
525
526
  }
526
527
  }
527
528
 
529
+ .info,
530
+ .alert {
531
+ color: #8300bf;
532
+ }
533
+
528
534
  .info {
529
535
  transform: rotate(180deg);
530
- color: #8300bf;
531
536
  margin-left: 8px;
532
537
  }
533
538
 
539
+ .alert {
540
+ margin-left: 5px;
541
+ vertical-align: text-bottom;
542
+
543
+ &,
544
+ > svg {
545
+ width: 1.25rem;
546
+ height: 1.25rem;
547
+ }
548
+ }
549
+
534
550
  .seperator {
535
551
  width: 90%;
536
552
  height: 1px;
@@ -0,0 +1,497 @@
1
+ <template>
2
+ <div class="image-thumbnails-container">
3
+ <div class="toolbar">
4
+ <div class="filters">
5
+ <el-tag
6
+ v-for="(species, index) in speciesFilterTags"
7
+ :key="index"
8
+ type="info"
9
+ class="tag"
10
+ :class="{ 'active-tag': species.name === activeSpecies.name }"
11
+ :closable="species.name === activeSpecies.name"
12
+ @close="removeSpeciesFilterTag"
13
+ @click="filterBySpecies(species)"
14
+ >
15
+ {{ species.name }} ({{ species.count }})
16
+ </el-tag>
17
+ </div>
18
+ <div class="view-selector">
19
+ <el-select
20
+ v-model="viewOption"
21
+ placeholder="Select view"
22
+ popper-class="view-selector-select-popper"
23
+ >
24
+ <el-option
25
+ v-for="item in viewOptions"
26
+ :key="item.value"
27
+ :label="item.label"
28
+ :value="item.value"
29
+ />
30
+ </el-select>
31
+ </div>
32
+ </div>
33
+ <div class="gallery-container" v-if="viewOption === 'gallery'">
34
+ <Gallery :items="imageItems" :imageStyle="imageStyle" />
35
+ </div>
36
+ <div class="dataset-card-container" v-if="viewOption === 'list'">
37
+ <div class="dataset-card" v-for="imageThumbnail in imageItems">
38
+ <div class="card" :key="imageThumbnail.link">
39
+ <div class="card-left">
40
+ <a :href="imageThumbnail.link" class="card-image card-button-link" target="_blank">
41
+ <el-image :src="imageThumbnail.imgSrc" loading="lazy">
42
+ <template #error>
43
+ <div class="image-slot">Loading...</div>
44
+ </template>
45
+ </el-image>
46
+ </a>
47
+ </div>
48
+ <div class="card-right">
49
+ <div class="details">
50
+ <div class="title">
51
+ <strong>{{ imageThumbnail.type }}</strong>
52
+ </div>
53
+ </div>
54
+ <div class="details">
55
+ <div>
56
+ {{ formattedTitle(imageThumbnail) }}
57
+ </div>
58
+ </div>
59
+ <div class="details">
60
+ <a class="button el-button el-button--large card-button-link" :href="imageThumbnail.link" target="_blank">
61
+ View {{ imageThumbnail.type }}
62
+ </a>
63
+ </div>
64
+ <!-- Copy to clipboard button container -->
65
+ <div class="float-button-container">
66
+ <CopyToClipboard :content="getCopyContent(imageThumbnail)" />
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ </template>
74
+
75
+ <script>
76
+ import {
77
+ ElImage,
78
+ ElTag as Tag,
79
+ ElOption as Option,
80
+ ElSelect as Select,
81
+ } from 'element-plus';
82
+ import { CopyToClipboard } from '@abi-software/map-utilities';
83
+ import '@abi-software/map-utilities/dist/style.css';
84
+ import Gallery from "@abi-software/gallery";
85
+ import "@abi-software/gallery/dist/style.css";
86
+
87
+ const BASE64PREFIX = 'data:image/png;base64,';
88
+ const VIEW_OPTIONS = [
89
+ {
90
+ value: 'list',
91
+ label: 'List'
92
+ },
93
+ {
94
+ value: 'gallery',
95
+ label: 'Gallery'
96
+ }
97
+ ];
98
+
99
+ export default {
100
+ name: 'ImageThumbnails',
101
+ components: {
102
+ Tag,
103
+ Option,
104
+ Select,
105
+ ElImage,
106
+ Gallery,
107
+ CopyToClipboard,
108
+ },
109
+ props: {
110
+ /**
111
+ * The image thumbnails data to show in sidebar.
112
+ */
113
+ imageThumbnails: {
114
+ type: Array,
115
+ default: [],
116
+ },
117
+ },
118
+ data: function () {
119
+ return {
120
+ activeSpecies: { name: "" },
121
+ speciesFilterTags: [],
122
+ imageItems: [],
123
+ showImageGallery: false,
124
+ viewOption: 'list',
125
+ viewOptions: VIEW_OPTIONS,
126
+ };
127
+ },
128
+ computed: {
129
+ imageStyle() {
130
+ return {
131
+ width: "180px",
132
+ height: "135px",
133
+ };
134
+ },
135
+ },
136
+ mounted: function () {
137
+ this.injectImgSrc();
138
+ this.populateFilterTags();
139
+ this.imageItems = this.imageThumbnails;
140
+ },
141
+ watch: {
142
+ imageThumbnails: {
143
+ handler: function (value) {
144
+ if (value) {
145
+ this.injectImgSrc();
146
+ this.populateFilterTags();
147
+ this.imageItems = this.imageThumbnails;
148
+ }
149
+ },
150
+ deep: true,
151
+ },
152
+ },
153
+ methods: {
154
+ injectImgSrc: function() {
155
+ this.imageThumbnails.forEach((imageThumbnail) => {
156
+ return this.mapImage(imageThumbnail);
157
+ });
158
+ },
159
+ formattedTitle: function(imageThumbnail) {
160
+ const type = imageThumbnail.mimetype?.split('/')[1];
161
+ const title = imageThumbnail.title;
162
+ let formattedTitle = '';
163
+ if (type !== 'jpg') {
164
+ formattedTitle = title.replace('.' + type, '');
165
+ }
166
+ formattedTitle = formattedTitle.replaceAll('_', ' ');
167
+ return formattedTitle;
168
+ },
169
+ datasetURL: function(id) {
170
+ return `https://sparc.science/datasets/${id}?type=dataset`;
171
+ },
172
+ mapImage: async function (image) {
173
+ const imgSrc = await this.transformedImage(image.thumbnail);
174
+ if (imgSrc) {
175
+ image.imgSrc = imgSrc;
176
+ }
177
+ },
178
+ transformedImage: async function(imgUrl) {
179
+ try {
180
+ const response = await fetch(imgUrl);
181
+ if (!response.ok) {
182
+ return null;
183
+ }
184
+ const data = await response.text();
185
+ return BASE64PREFIX + data;
186
+ } catch (error) {
187
+ return null;
188
+ }
189
+ },
190
+ removeSpeciesFilterTag: function () {
191
+ this.activeSpecies = { name: "" };
192
+ this.imageItems = this.imageThumbnails;
193
+ },
194
+ filterBySpecies: function (tagInfo) {
195
+ this.activeSpecies = tagInfo;
196
+ let filteredImageItems = [];
197
+ this.imageThumbnails.forEach((image) => {
198
+ if (image.species && image.species.length) {
199
+ image.species.forEach((species) => {
200
+ if (species === tagInfo.name) {
201
+ filteredImageItems.push(image);
202
+ }
203
+ });
204
+ }
205
+ });
206
+ this.imageItems = filteredImageItems;
207
+ },
208
+ populateFilterTags: function () {
209
+ let imageObjects = {};
210
+ this.imageThumbnails.forEach((image) => {
211
+ if (image.species && image.species.length) {
212
+ image.species.forEach((species) => {
213
+ if (!(species in imageObjects)) {
214
+ imageObjects[species] = {
215
+ name: species,
216
+ count: 0,
217
+ };
218
+ }
219
+ imageObjects[species].count++;
220
+ });
221
+ }
222
+ });
223
+ this.speciesFilterTags = Object.values(imageObjects);
224
+ },
225
+ getCopyContent: function (imageThumbnail) {
226
+ const contentArray = [];
227
+
228
+ // Use <div> instead of <h1>..<h6> or <p>
229
+ // to avoid default formatting on font size and margin
230
+
231
+ // Title
232
+ if (imageThumbnail.title) {
233
+ contentArray.push(`<div><strong>${this.formattedTitle(imageThumbnail)}</strong></div>`);
234
+ }
235
+
236
+ // Type
237
+ if (imageThumbnail.type) {
238
+ let imageType = `<div><strong>Type:</strong></div>`;
239
+ imageType += `\n`;
240
+ imageType += `${imageThumbnail.type}`;
241
+ contentArray.push(`<div>${imageType}</div>`);
242
+ }
243
+
244
+ // Species
245
+ if (imageThumbnail.species?.length) {
246
+ let species = `<div><strong>Species:</strong></div>`;
247
+ species += `\n`;
248
+ species += imageThumbnail.species.join(', ');
249
+ contentArray.push(`<div>${species}</div>`);
250
+ }
251
+
252
+ // Image URL
253
+ if (imageThumbnail.link) {
254
+ let thumbnailLink = `<div><strong>Link:</strong></div>`;
255
+ thumbnailLink += `\n`;
256
+ thumbnailLink += `<a href="${imageThumbnail.link}">${imageThumbnail.link}</a>`;
257
+ contentArray.push(`<div>${thumbnailLink}</div>`);
258
+ }
259
+
260
+ // Dataset ID
261
+ if (imageThumbnail.id) {
262
+ let datasetIdContent = `<div><strong>Dataset ID:</strong></div>`;
263
+ datasetIdContent += `\n`;
264
+ datasetIdContent += `${imageThumbnail.id}`;
265
+ contentArray.push(`<div>${datasetIdContent}</div>`);
266
+ }
267
+
268
+ // Dataset URL
269
+ if (imageThumbnail.id) {
270
+ const datasetURL = this.datasetURL(imageThumbnail.id);
271
+ let dataLocationContent = `<div><strong>Dataset URL:</strong></div>`;
272
+ dataLocationContent += `\n`;
273
+ dataLocationContent += `<a href="${datasetURL}">${datasetURL}</a>`;
274
+ contentArray.push(`<div>${dataLocationContent}</div>`);
275
+ }
276
+
277
+ // Dataset version
278
+ if (imageThumbnail.version) {
279
+ let versionContent = `<div><strong>Dataset version:</strong></div>`;
280
+ versionContent += `\n`;
281
+ versionContent += `${imageThumbnail.version}`;
282
+ contentArray.push(`<div>${versionContent}</div>`);
283
+ }
284
+
285
+ const copyContent = contentArray.join('\n\n<br>');
286
+ return copyContent;
287
+ },
288
+ },
289
+ }
290
+ </script>
291
+
292
+ <style lang="scss" scoped>
293
+ .image-thumbnails-container {
294
+ font-size: 14px;
295
+ text-align: left;
296
+ line-height: 1.5em;
297
+ font-family: $font-family;
298
+ font-weight: 400;
299
+ background-color: #f7faff;
300
+ border-left: 1px solid var(--el-border-color);
301
+ border-top: 1px solid var(--el-border-color);
302
+ display: flex;
303
+ flex-direction: column;
304
+ height: calc(100% - 30px); // minus tabs height
305
+ z-index: 1;
306
+ }
307
+
308
+ .toolbar {
309
+ display: flex;
310
+ flex-direction: row;
311
+ align-items: center;
312
+ justify-content: space-between;
313
+ padding: 1rem;
314
+ gap: 1rem;
315
+ background-color: #f7faff;
316
+ position: sticky;
317
+ top: 0;
318
+ z-index: 1;
319
+ }
320
+
321
+ .filters {
322
+ display: flex;
323
+ flex-direction: row;
324
+ flex-wrap: wrap;
325
+ gap: 5px;
326
+
327
+ .tag {
328
+ cursor: pointer;
329
+ }
330
+ }
331
+
332
+ .view-selector .el-select {
333
+ width: 100px;
334
+ font-family: $font-family;
335
+ }
336
+
337
+ .gallery-container,
338
+ .dataset-card-container {
339
+ margin: 1rem;
340
+ margin-top: 0;
341
+ padding: 1rem;
342
+ flex-grow: 1;
343
+ overflow-y: auto;
344
+ scrollbar-width: thin;
345
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
346
+ border: solid 1px #e4e7ed;
347
+ background-color: #ffffff;
348
+ border-radius: var(--el-border-radius-base);
349
+ }
350
+
351
+ .gallery-container {
352
+ :deep(.gallery) {
353
+ .gallery-strip {
354
+ padding: 1rem 0;
355
+ }
356
+
357
+ > div {
358
+ min-height: max-content !important;
359
+ }
360
+ }
361
+ }
362
+
363
+ .dataset-card-container {
364
+ display: flex;
365
+ flex-direction: column;
366
+ gap: 1.5rem;
367
+ }
368
+
369
+ .dataset-card {
370
+ position: relative;
371
+
372
+ + .dataset-card {
373
+ padding-top: 1.5rem;
374
+ border-top: 1px solid var(--el-border-color);
375
+ }
376
+ }
377
+
378
+ .card {
379
+ position: relative;
380
+ display: flex;
381
+ gap: 1.5rem;
382
+ }
383
+
384
+ .card-left {
385
+ flex: 1;
386
+ }
387
+
388
+ .card-right {
389
+ flex: 1.3;
390
+ padding-left: 6px;
391
+ display: flex;
392
+ flex-direction: column;
393
+ gap: 1rem;
394
+ }
395
+
396
+ .card-image {
397
+ display: block;
398
+ text-decoration: none;
399
+ outline: none;
400
+
401
+ .el-image {
402
+ display: block;
403
+ width: 200px;
404
+ height: 150px;
405
+
406
+ :deep(img) {
407
+ aspect-ratio: 4/3;
408
+ object-fit: cover;
409
+ }
410
+ }
411
+
412
+ .image-slot {
413
+ display: flex;
414
+ justify-content: center;
415
+ align-items: center;
416
+ width: 100%;
417
+ height: 100%;
418
+ background: var(--el-fill-color-light);
419
+ color: var(--el-text-color-secondary);
420
+ font-size: 14px;
421
+ }
422
+ }
423
+
424
+ .title {
425
+ font-family: $font-family;
426
+ font-size: 14px;
427
+ font-weight: bold;
428
+ font-stretch: normal;
429
+ font-style: normal;
430
+ line-height: 1.5;
431
+ letter-spacing: 1.05px;
432
+ color: #484848;
433
+ }
434
+
435
+ .details {
436
+ font-family: $font-family;
437
+ font-size: 14px;
438
+ font-weight: normal;
439
+ font-stretch: normal;
440
+ font-style: normal;
441
+ line-height: 1.5;
442
+ letter-spacing: 1.05px;
443
+ color: #484848;
444
+ }
445
+
446
+ .active-tag {
447
+ background-color: $app-primary-color;
448
+ color: #fff;
449
+ }
450
+
451
+ .button {
452
+ margin-left: 0px !important;
453
+ margin-top: 0px !important;
454
+ font-size: 14px !important;
455
+ letter-spacing: initial;
456
+ background-color: $app-primary-color;
457
+ color: #fff;
458
+
459
+ & + .button {
460
+ margin-top: 10px !important;
461
+ }
462
+ &:hover {
463
+ color: #fff !important;
464
+ background: #ac76c5 !important;
465
+ border: 1px solid #ac76c5 !important;
466
+ }
467
+ }
468
+
469
+ .card-button-link {
470
+ text-decoration: none;
471
+ }
472
+
473
+ .float-button-container {
474
+ position: absolute;
475
+ bottom: 0;
476
+ right: 0;
477
+ opacity: 0;
478
+ visibility: hidden;
479
+
480
+ .dataset-card:hover & {
481
+ opacity: 1;
482
+ visibility: visible;
483
+ }
484
+ }
485
+ </style>
486
+
487
+ <style lang="scss">
488
+ .view-selector-select-popper {
489
+ font-family: $font-family;
490
+ font-size: 14px;
491
+ color: #292b66;
492
+
493
+ .el-select-dropdown__item.is-selected {
494
+ color: $app-primary-color;
495
+ }
496
+ }
497
+ </style>