@abi-software/gallery 0.2.1 → 0.3.0-beta.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/gallery",
3
- "version": "0.2.1",
3
+ "version": "0.3.0-beta.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/hsorby/mapcore-gallery.git"
@@ -17,6 +17,8 @@
17
17
  "main": "dist/gallery.common.js",
18
18
  "dependencies": {
19
19
  "@babel/code-frame": "^7.12.11",
20
+ "axios": "^0.26.1",
21
+ "babel-plugin-component": "^1.1.1",
20
22
  "core-js": "^3.8.3",
21
23
  "element-ui": "^2.15.0",
22
24
  "vue": "^2.6.11"
@@ -34,6 +36,7 @@
34
36
  "eslint-plugin-prettier": "^3.3.1",
35
37
  "eslint-plugin-vue": "^6.2.2",
36
38
  "prettier": "^2.2.1",
39
+ "vue-svg-inline-loader": "^2.1.3",
37
40
  "vue-template-compiler": "^2.6.11"
38
41
  }
39
42
  }
Binary file
Binary file
@@ -0,0 +1 @@
1
+ <svg id="logo-sparc-wave-primary" data-name="logo-sparc-wave-primary" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 400 190.5854"><defs><linearGradient id="linear-gradient" y1="96.3505" x2="400" y2="96.3505" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0b00bf"/><stop offset="1" stop-color="#bc00fc"/></linearGradient></defs><title>SPARC Logo</title><path d="M396.6976,128.6248l-146.2735-3.38a6.5358,6.5358,0,0,0-6.1378,3.9236l-.0612.1407-13.673,31.4482L202.9416,9.5333l-.01-.0685a8.9823,8.9823,0,0,0-17.6624-.0114L163.673,125.3819,3.298,129.2886a3.38,3.38,0,0,0,.0269,6.7592L169.34,138.7635a6.5939,6.5939,0,0,0,6.5373-5.1457l.0571-.2546,17.8534-79.5649L221.26,185.1237l.0256.1243A6.7593,6.7593,0,0,0,234.092,186.52L254.9005,138.66l141.7971-3.2762a3.3805,3.3805,0,0,0,0-6.7592Z" style="fill:url(#linear-gradient)"/><path d="M25.6793,0C41.3957,0,51.057,10.0926,51.057,26.0972v9.5162H36.2053V26.0972c0-7.3532-3.8945-11.8219-10.526-11.8219-6.7768,0-10.6691,4.4687-10.6691,11.8219,0,3.8945,1.2982,7.3533,5.4786,11.3908l19.754,18.601C46.5861,62.1441,52.21,68.7778,52.21,79.88c0,16.0047-9.95,26.0973-25.9543,26.0973C10.108,105.9776.1585,95.885.1585,79.88V70.362H15.01V79.88c0,7.3533,4.0375,11.822,11.2455,11.822,7.065,0,11.1025-4.4687,11.1025-11.822,0-4.4709-2.1628-8.6514-6.0551-12.2576L11.1158,48.7357C3.043,41.2372.1585,34.8939.1585,25.666.1585,10.0926,9.82,0,25.6793,0Z" style="fill:#0e0e19"/><path d="M99.7971,69.7856v35.1821H84.9454V1.01h25.3755c16.0047,0,26.0973,10.0926,26.0973,26.0973V43.6883c0,16.15-10.0926,26.0973-26.0973,26.0973Zm10.3808-13.9849c7.2081,0,11.3908-4.3257,11.3908-11.6789V26.6759c0-7.21-4.1827-11.6811-11.3908-11.6811H99.7971V55.8007Z" style="fill:#0e0e19"/><path d="M278.5812,108.147H263.73V4.1893h26.0972c16.0047,0,26.0973,10.0926,26.0973,26.0972v14.13c0,9.95-4.3257,17.7363-11.3908,22.205,4.1827,12.6889,10.0926,29.9917,13.8418,41.5254h-15.14L290.6914,70.3708h-12.11Zm11.1-51.7632c7.21,0,11.3907-4.3257,11.3907-11.5338V29.8553c0-7.21-4.18-11.6812-11.3907-11.6812h-11.1v38.21Z" style="fill:#0e0e19"/><path d="M399.844,73.5414V83.06c0,16.0047-9.9474,26.0973-25.9521,26.0973-16.15,0-26.0972-10.0926-26.0972-26.0973V29.2766c0-16.0046,9.9473-26.0972,26.0972-26.0972,16.0047,0,25.9521,10.0926,25.9521,26.0972v9.5161h-14.85V29.2766c0-7.3532-4.0374-11.822-11.1025-11.822-7.21,0-11.2477,4.4688-11.2477,11.822V83.06c0,7.3533,4.0375,11.822,11.2477,11.822,7.0651,0,11.1025-4.4687,11.1025-11.822V73.5414Z" style="fill:#0e0e19"/></svg>
Binary file
Binary file
@@ -1,7 +1,10 @@
1
1
  <template>
2
- <el-card :style="{ padding: '0px', maxWidth: width + 'rem' }" class="card">
2
+ <el-card :shadow="shadow" :body-style="bodyStyle" :style="{ padding: '0px', maxWidth: width + 'rem' }" class="card">
3
3
  <div v-loading="!isReady">
4
- <img :src="data.thumbnail" alt="thumbnail loading ..." />
4
+ <div :style="imageContainerStyle">
5
+ <img v-if="useDefaultImg" src="../assets/logo-sparc-wave-primary.svg" svg-inline :style="imageStyle" />
6
+ <img v-else :src="thumbnail" alt="thumbnail loading ..." :style="imageStyle" />
7
+ </div>
5
8
  <div v-if="false" class="image-overlay">
6
9
  <div
7
10
  class="triangle-right-corner"
@@ -21,18 +24,45 @@
21
24
  <p>
22
25
  <b>{{ data.type }}</b>
23
26
  </p>
24
- <el-tooltip :content="data.title" placement="top">
25
- <p class="title">
26
- {{ data.title }}
27
- </p>
28
- </el-tooltip>
29
- <el-button @click.prevent="openLinkInNewTab"> View {{ data.type }}</el-button>
27
+ <el-popover ref="galleryPopover" :content="data.title" placement="top" trigger="hover" popper-class="gallery-popper" />
28
+ <p class="title" v-popover:galleryPopover>
29
+ {{ data.title }}
30
+ </p>
31
+ <el-button class="button" @click.prevent="cardClicked"> View {{ data.type }}</el-button>
30
32
  </div>
31
33
  </div>
32
34
  </el-card>
33
35
  </template>
34
36
 
35
37
  <script>
38
+ // import { SvgIcon } from '@abi-software/svg-sprite'
39
+ import axios from 'axios'
40
+ import Vue from 'vue'
41
+ import { Button, Card, Popover } from 'element-ui'
42
+ Vue.use(Button)
43
+ Vue.use(Card)
44
+ Vue.use(Popover)
45
+
46
+ function isValidHttpUrl(string) {
47
+ let url = undefined
48
+
49
+ try {
50
+ url = new URL(string)
51
+ } catch (_) {
52
+ return false
53
+ }
54
+ return url.protocol === 'http:' || url.protocol === 'https:'
55
+ }
56
+
57
+ const getRequest = async (url, params, timeout) => {
58
+ return await axios({
59
+ method: 'get',
60
+ url,
61
+ params,
62
+ timeout,
63
+ })
64
+ }
65
+
36
66
  export default {
37
67
  name: 'GalleryCard',
38
68
  props: {
@@ -51,16 +81,40 @@ export default {
51
81
  showCardDetails: {
52
82
  type: Boolean,
53
83
  },
84
+ bodyStyle: {
85
+ type: Object,
86
+ default: () => {
87
+ return { padding: '20px', background: '#ffffff' }
88
+ },
89
+ },
90
+ imageStyle: {
91
+ type: Object,
92
+ default: () => {
93
+ return {}
94
+ },
95
+ },
96
+ imageContainerStyle: {
97
+ type: Object,
98
+ default: () => {
99
+ return {}
100
+ },
101
+ },
102
+ shadow: {
103
+ type: String,
104
+ default: 'always',
105
+ },
54
106
  },
55
107
  data() {
56
108
  return {
57
109
  ro: null,
58
110
  triangleSize: 4,
111
+ thumbnail: undefined,
112
+ useDefaultImg: false,
59
113
  }
60
114
  },
61
115
  computed: {
62
116
  isReady() {
63
- return this.data.title && this.data.thumbnail && this.data.link
117
+ return this.data.title && (this.thumbnail || this.useDefaultImg) && (this.data.link || this.data.userData)
64
118
  },
65
119
  imageHeight() {
66
120
  return this.showCardDetails ? this.height * 0.525 : this.height
@@ -79,19 +133,89 @@ export default {
79
133
  },
80
134
  },
81
135
  methods: {
82
- openLinkInNewTab() {
83
- const link = document.createElement('a')
84
- link.href = this.data.link
85
- link.target = '_blank'
86
- document.body.appendChild(link)
87
- link.click()
88
- link.remove()
136
+ cardClicked: function () {
137
+ if (this.data.link) {
138
+ const link = document.createElement('a')
139
+ link.href = this.data.link
140
+ link.target = '_blank'
141
+ document.body.appendChild(link)
142
+ link.click()
143
+ link.remove()
144
+ }
145
+ if (this.data.userData) {
146
+ this.$emit('card-clicked', this.data.userData)
147
+ }
148
+ },
149
+ downloadThumbnail: function (url, info) {
150
+ getRequest(url, {}, 11000).then(
151
+ (response) => {
152
+ let data = response.data
153
+ if (data.startsWith('data:')) {
154
+ this.thumbnail = response.data
155
+ } else {
156
+ if (this.data.mimetype) {
157
+ this.thumbnail = `data:${this.data.mimetype};base64,${response.data}`
158
+ } else {
159
+ this.thumbnail = response.data
160
+ }
161
+ }
162
+ },
163
+ (reason) => {
164
+ if (reason.message.includes('timeout') && reason.message.includes('exceeded') && info.fetchAttempts < 3) {
165
+ info.fetchAttempts += 1
166
+ this.downloadThumbnail(url, info)
167
+ } else {
168
+ this.useDefaultImg = true
169
+ }
170
+ }
171
+ )
172
+ },
173
+ },
174
+ watch: {
175
+ data: {
176
+ deep: true,
177
+ immediate: true,
178
+ handler: function () {
179
+ this.thumbnail = undefined
180
+ this.useDefaultImg = false
181
+ if (this.data.thumbnail) {
182
+ if (isValidHttpUrl(this.data.thumbnail) && this.data.mimetype) {
183
+ this.downloadThumbnail(this.data.thumbnail, { fetchAttempts: 0 })
184
+ } else {
185
+ this.thumbnail = this.data.thumbnail
186
+ }
187
+ } else {
188
+ this.useDefaultImg = true
189
+ }
190
+ },
89
191
  },
90
192
  },
91
193
  }
92
194
  </script>
93
195
 
94
196
  <style scoped>
197
+ .button {
198
+ z-index: 10;
199
+ font-family: Asap;
200
+ font-size: 14px;
201
+ font-weight: normal;
202
+ font-stretch: normal;
203
+ font-style: normal;
204
+ line-height: normal;
205
+ letter-spacing: normal;
206
+ background-color: #8300bf;
207
+ border: #8300bf;
208
+ color: white;
209
+ cursor: pointer;
210
+ margin-top: 8px;
211
+ }
212
+
213
+ .button:hover {
214
+ background: #8300bf;
215
+ box-shadow: -3px 2px 4px 0 rgba(0, 0, 0, 0.25);
216
+ color: #fff;
217
+ }
218
+
95
219
  .card {
96
220
  position: relative;
97
221
  }
@@ -126,3 +250,22 @@ p.bold {
126
250
  border-top: solid #8300bf;
127
251
  }
128
252
  </style>
253
+
254
+ <style>
255
+ .gallery-popper {
256
+ background: #f3ecf6 !important;
257
+ border: 1px solid #8300bf;
258
+ border-radius: 4px;
259
+ color: #303133 !important;
260
+ font-size: 12px;
261
+ line-height: 1rem;
262
+ height: 1rem;
263
+ padding: 10px;
264
+ }
265
+ .gallery-popper.el-popper[x-placement^='top'] .popper__arrow {
266
+ border-top-color: #8300bf !important;
267
+ }
268
+ .gallery-popper.el-popper[x-placement^='top'] .popper__arrow:after {
269
+ border-top-color: #f3ecf6 !important;
270
+ }
271
+ </style>
@@ -1,21 +1,33 @@
1
1
  <template>
2
2
  <div ref="myButton" class="gallery">
3
3
  <div class="gallery-strip">
4
- <a href="#" :class="['oval', 'prev', { disabled: !isPrevPossible }]" @click.prevent="goPrev">
4
+ <a v-if="items.length > 1" href="#" :class="['oval', 'prev', { disabled: !isPrevPossible }]" @click.prevent="goPrev">
5
5
  <span class="progress-button">&lsaquo;</span>
6
6
  </a>
7
+ <div v-else style="width: 2rem" />
7
8
  <div class="filler" />
8
9
  <div class="card-line">
9
10
  <span v-for="(item, index) in windowedItems" :key="'card_' + index" :class="['key-image-span', { active: isActive(index) }]">
10
- <card :data="item" :width="cardWidth" :height="cardHeight" :show-card-details="showCardDetails" />
11
+ <card
12
+ :data="item"
13
+ :body-style="bodyStyle"
14
+ :image-container-style="imageContainerStyle"
15
+ :image-style="imageStyle"
16
+ :width="cardWidth"
17
+ :height="cardHeight"
18
+ :shadow="shadow"
19
+ :show-card-details="showCardDetails"
20
+ @card-clicked="cardClicked"
21
+ />
11
22
  </span>
12
23
  </div>
13
24
  <div class="filler" />
14
- <a href="#" :class="['oval', 'next', { disabled: !isNextPossible }]" @click.prevent="goNext">
25
+ <a v-if="items.length > 1" href="#" :class="['oval', 'next', { disabled: !isNextPossible }]" @click.prevent="goNext">
15
26
  <span class="progress-button">&rsaquo;</span>
16
27
  </a>
28
+ <div v-else style="width: 2rem" />
17
29
  </div>
18
- <div v-if="canShowIndicatorBar" class="bottom-spacer" />
30
+ <div v-if="canShowIndicatorBar" :style="bottomSpacer" />
19
31
  <index-indicator v-if="canShowIndicatorBar" :count="itemCount" :current="currentIndex" @clicked="indicatorClicked" />
20
32
  </div>
21
33
  </template>
@@ -61,6 +73,30 @@ export default {
61
73
  type: Boolean,
62
74
  default: true,
63
75
  },
76
+ bodyStyle: {
77
+ type: Object,
78
+ default: () => {
79
+ return { padding: '20px', background: '#ffffff' }
80
+ },
81
+ },
82
+ bottomSpacer: {
83
+ type: Object,
84
+ default: () => {
85
+ return { minHeight: '4rem' }
86
+ },
87
+ },
88
+ imageContainerStyle: {
89
+ type: Object,
90
+ default: () => {
91
+ return {}
92
+ },
93
+ },
94
+ imageStyle: {
95
+ type: Object,
96
+ default: () => {
97
+ return {}
98
+ },
99
+ },
64
100
  metaData: {
65
101
  type: Object,
66
102
  default: () => {
@@ -74,6 +110,10 @@ export default {
74
110
  type: String,
75
111
  default: '',
76
112
  },
113
+ shadow: {
114
+ type: String,
115
+ default: 'always',
116
+ },
77
117
  },
78
118
  data() {
79
119
  return {
@@ -81,6 +121,7 @@ export default {
81
121
  currentIndex: 0,
82
122
  controlHeight: 2,
83
123
  controlWidth: 2,
124
+ visibleIndecies: [],
84
125
  }
85
126
  },
86
127
  computed: {
@@ -108,7 +149,7 @@ export default {
108
149
  const buttonPx = convertRemToPixels(2)
109
150
  const cardWidthPx = convertRemToPixels(this.cardWidth)
110
151
  const cardItems = (this.maxWidth - 2 * buttonPx - 2 * cardSpacingPx) / (1.1 * cardWidthPx)
111
- return Math.floor(cardItems)
152
+ return Math.max(1, Math.floor(cardItems))
112
153
  },
113
154
  canShowIndicatorBar() {
114
155
  const indicatorWidth = convertRemToPixels(1)
@@ -135,9 +176,15 @@ export default {
135
176
  },
136
177
  },
137
178
  methods: {
179
+ cardClicked(payload) {
180
+ this.$emit('card-clicked', payload)
181
+ },
138
182
  isActive(index) {
139
183
  return this.currentIndex - this.valueAdjustment === index && this.highlightActive
140
184
  },
185
+ isVisible(index) {
186
+ return this.visibleIndecies.includes(index)
187
+ },
141
188
  goNext() {
142
189
  this.currentIndex += 1
143
190
  },
@@ -150,6 +197,40 @@ export default {
150
197
  }
151
198
  },
152
199
  },
200
+ created() {
201
+ this._visibleIndecies = []
202
+ },
203
+ watch: {
204
+ currentIndex: {
205
+ handler: function () {
206
+ const oddImagesVisible = this.numberOfItemsVisible % 2 === 1
207
+ let halfVisible = this.numberOfItemsVisible / 2
208
+ if (oddImagesVisible) {
209
+ halfVisible = (this.numberOfItemsVisible - 1) / 2
210
+ }
211
+ let rawIndicies = [this.currentIndex]
212
+ for (let i = 1; i <= halfVisible; i++) {
213
+ rawIndicies.push(this.currentIndex + i)
214
+ rawIndicies.push(this.currentIndex - i)
215
+ }
216
+
217
+ if (!oddImagesVisible) {
218
+ rawIndicies.pop()
219
+ }
220
+ this.visibleIndecies = []
221
+ for (let v of rawIndicies) {
222
+ if (v < 0) {
223
+ this.visibleIndecies.push(v + this.numberOfItemsVisible)
224
+ } else if (v >= this.itemCount) {
225
+ this.visibleIndecies.push(v - this.numberOfItemsVisible)
226
+ } else {
227
+ this.visibleIndecies.push(v)
228
+ }
229
+ }
230
+ },
231
+ immediate: true,
232
+ },
233
+ },
153
234
  }
154
235
  </script>
155
236
 
@@ -182,10 +263,7 @@ export default {
182
263
  .progress-button {
183
264
  font-size: 1.5rem;
184
265
  font-weight: bold;
185
- }
186
-
187
- .bottom-spacer {
188
- min-height: 4rem;
266
+ color: #8300bf;
189
267
  }
190
268
 
191
269
  .filler {
@@ -0,0 +1,105 @@
1
+ import axios from 'axios'
2
+
3
+ const getRequest = async (url, params, timeout) => {
4
+ return await axios({
5
+ method: "get",
6
+ url,
7
+ params,
8
+ timeout
9
+ })
10
+ }
11
+
12
+ export default {
13
+ data() {
14
+ return {
15
+ defaultImg: require('../assets/logo-sparc-wave-primary.svg'),
16
+ }
17
+ },
18
+ methods: {
19
+ /**
20
+ * Returns a file path for S3.
21
+ * @param {String} dataset_id dataset id.
22
+ * @param {String} dataset_version dataset version.
23
+ * @param {String} file_path file path.
24
+ * @returns {String} full path to S3 file.
25
+ */
26
+ getS3FilePath(dataset_id, dataset_version, file_path) {
27
+ const encoded_file_path = encodeURIComponent(file_path)
28
+ return `${dataset_id}/${dataset_version}/files/${encoded_file_path}`
29
+ },
30
+ findEntryWithPathInArray(array, path) {
31
+ if (path && array) {
32
+ for (let i = 0; i < array.length; i++) {
33
+ if (path === array[i].dataset.path)
34
+ return array[i];
35
+ }
36
+ }
37
+ return undefined;
38
+ },
39
+ getThumbnailForPlot(plot, thumbnails) {
40
+ if (thumbnails && plot) {
41
+ return this.findEntryWithPathInArray(thumbnails, plot.datacite.isSourceOf.path[0]);
42
+ }
43
+ return undefined;
44
+ },
45
+ getThumbnailForScaffold(scaffold, scaffoldViews, thumbnails, index) {
46
+ if (thumbnails && (thumbnails.length > 0)) {
47
+ let thumbnail = undefined;
48
+ if (scaffold && scaffoldViews) {
49
+ const view = this.findEntryWithPathInArray(scaffoldViews, scaffold.datacite.isSourceOf.path[0])
50
+ if (view) {
51
+ thumbnail = this.findEntryWithPathInArray(thumbnails, view.datacite.isSourceOf.path[0]);
52
+ }
53
+ }
54
+ if (thumbnail) {
55
+ return thumbnail
56
+ } else if (index < thumbnails.length) {
57
+ return thumbnails[index]
58
+ }
59
+ }
60
+ return undefined;
61
+ },
62
+ getImageURLFromS3(apiEndpoint, info) {
63
+ return `${apiEndpoint}/s3-resource/${info.datasetId}/${info.datasetVersion}/files/${info.file_path}?encodeBase64=true`
64
+ },
65
+ getSegmentationThumbnailURL(apiEndpoint, info) {
66
+ let endpoint = `${apiEndpoint}/thumbnail/neurolucida`
67
+ endpoint = endpoint + `?datasetId=${info.datasetId}`
68
+ endpoint = endpoint + `&version=${info.datasetVersion}`
69
+ endpoint = endpoint + `&path=files/${info.segmentationFilePath}`
70
+ return endpoint
71
+ },
72
+ getThumbnailURLFromBiolucida(apiEndpoint, info) {
73
+ return`${apiEndpoint}/thumbnail/${info.id}`
74
+ },
75
+ getImageInfoFromBiolucida(apiEndpoint, items, info) {
76
+ const endpoint = `${apiEndpoint}/image/${info.id}`
77
+ const params = { }
78
+ getRequest(endpoint, params, 20000)
79
+ .then(
80
+ response => {
81
+ let item = items.find(x => x.id === info.id)
82
+ const name = response.name
83
+ if (name) {
84
+ this.$set(item, 'title', name.substring(0, name.lastIndexOf('.')))
85
+ }
86
+ },
87
+ reason => {
88
+ if (
89
+ reason.message.includes('timeout') &&
90
+ reason.message.includes('exceeded') &&
91
+ info.fetchAttempts < 3
92
+ ) {
93
+ info.fetchAttempts += 1
94
+ this.getImageInfoFromBiolucida(apiEndpoint, items, info)
95
+ } else {
96
+ let item = items.find(x => x.id === info.id)
97
+ this.$set(item, 'thumbnail', this.defaultImg)
98
+ }
99
+
100
+ return Promise.reject('Maximum iterations reached.')
101
+ }
102
+ )
103
+ }
104
+ }
105
+ };
package/vue.config.js CHANGED
@@ -1,4 +1,12 @@
1
1
  // vue.config.js
2
2
  module.exports = {
3
+ chainWebpack: config => {
4
+ // GraphQL Loader
5
+ config.module
6
+ .rule('vue')
7
+ .use('vue-svg-inline-loader')
8
+ .loader('vue-svg-inline-loader')
9
+ .end()
10
+ },
3
11
  css: { extract: false },
4
12
  }