@abi-software/flatmapvuer 1.4.1 → 1.4.3-image.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/README.md +4 -3
- package/dist/flatmapvuer.js +21945 -21450
- package/dist/flatmapvuer.umd.cjs +108 -102
- package/dist/style.css +1 -1
- package/package.json +8 -5
- package/src/App.vue +13 -1
- package/src/components/FlatmapVuer.vue +237 -58
- package/src/components/MultiFlatmapVuer.vue +17 -1
- package/src/main.js +1 -1
- package/src/mixins/imageMixin.js +116 -0
- package/src/services/flatmapQueries.js +5 -1
- package/src/services/scicrunchQueries.js +280 -0
- package/src/stores/settings.js +46 -0
- /package/src/{store → stores}/index.js +0 -0
|
@@ -81,6 +81,9 @@
|
|
|
81
81
|
style="height: 100%"
|
|
82
82
|
:flatmapAPI="flatmapAPI"
|
|
83
83
|
:sparcAPI="sparcAPI"
|
|
84
|
+
@imageThumbnailDisplay="onImageThumbnailDisplay"
|
|
85
|
+
:imageThumbnailSidebar="imageThumbnailSidebar"
|
|
86
|
+
@image-thumbnail-open="onImageThumbnailOpen"
|
|
84
87
|
/>
|
|
85
88
|
</div>
|
|
86
89
|
</template>
|
|
@@ -264,6 +267,12 @@ export default {
|
|
|
264
267
|
onConnectivityInfoOpen: function (entryData) {
|
|
265
268
|
this.$emit('connectivity-info-open', entryData);
|
|
266
269
|
},
|
|
270
|
+
onImageThumbnailDisplay: function (payload) {
|
|
271
|
+
this.$emit('imageThumbnailDisplay', payload);
|
|
272
|
+
},
|
|
273
|
+
onImageThumbnailOpen: function (payload) {
|
|
274
|
+
this.$emit('image-thumbnail-open', payload);
|
|
275
|
+
},
|
|
267
276
|
onSelectionsDataChanged: function (data) {
|
|
268
277
|
this.$emit('pathway-selection-changed', data);
|
|
269
278
|
},
|
|
@@ -700,7 +709,7 @@ export default {
|
|
|
700
709
|
*/
|
|
701
710
|
sparcAPI: {
|
|
702
711
|
type: String,
|
|
703
|
-
default: '
|
|
712
|
+
default: '',
|
|
704
713
|
},
|
|
705
714
|
/**
|
|
706
715
|
* Flag to disable UIs on Map
|
|
@@ -716,6 +725,13 @@ export default {
|
|
|
716
725
|
type: Boolean,
|
|
717
726
|
default: false,
|
|
718
727
|
},
|
|
728
|
+
/**
|
|
729
|
+
* The option to show image thumbnail in sidebar
|
|
730
|
+
*/
|
|
731
|
+
imageThumbnailSidebar: {
|
|
732
|
+
type: Boolean,
|
|
733
|
+
default: false,
|
|
734
|
+
},
|
|
719
735
|
},
|
|
720
736
|
data: function () {
|
|
721
737
|
return {
|
package/src/main.js
CHANGED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
methods: {
|
|
3
|
+
populateMapWithImages: function (mapImp, images, type) {
|
|
4
|
+
this.downloadImageKeys = []; // to count downloaded images
|
|
5
|
+
for (const [key, list] of Object.entries(images)) {
|
|
6
|
+
// Exclude empty list
|
|
7
|
+
if (list.length) {
|
|
8
|
+
this.downloadImageThumbnail(mapImp, key, list, type);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
downloadImageThumbnail: function (mapImp, key, list, type) {
|
|
13
|
+
// This inner list count will be updated by failed download
|
|
14
|
+
const count = list.length;
|
|
15
|
+
|
|
16
|
+
if (!this.downloadImageKeys.includes(key)) {
|
|
17
|
+
this.downloadImageKeys.push(key);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (count > 0) {
|
|
21
|
+
//Pick a random image
|
|
22
|
+
const index = Math.floor(Math.random() * count);
|
|
23
|
+
const thumbnail = list[index].thumbnail;
|
|
24
|
+
this.getThumbnail(thumbnail, type)
|
|
25
|
+
.then((wrapperElement) => {
|
|
26
|
+
this.downloadImageKeys = this.downloadImageKeys.filter((item) => item !== key);
|
|
27
|
+
this.addImageThumbnailMarker(mapImp, key, wrapperElement);
|
|
28
|
+
|
|
29
|
+
// All images are downloaded
|
|
30
|
+
if (!this.downloadImageKeys.length) {
|
|
31
|
+
this.imagesDownloading = false;
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
.catch(() => {
|
|
35
|
+
//Failed to download, pick another one
|
|
36
|
+
list.splice(index);
|
|
37
|
+
this.downloadImageThumbnail(mapImp, key, list, type);
|
|
38
|
+
});
|
|
39
|
+
} else {
|
|
40
|
+
if (this.downloadImageKeys.includes(key)) {
|
|
41
|
+
this.downloadImageKeys = this.downloadImageKeys.filter((item) => item !== key);
|
|
42
|
+
|
|
43
|
+
// Failed to download, the last item
|
|
44
|
+
if (!this.downloadImageKeys.length) {
|
|
45
|
+
this.imagesDownloading = false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
getThumbnail: async function (url, type) {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
if (type === "Image" || type === "Segmentation") {
|
|
53
|
+
this.getBinaryThumbnail(url)
|
|
54
|
+
.then((response) => resolve(response))
|
|
55
|
+
.catch((response) => reject(response));
|
|
56
|
+
} else {
|
|
57
|
+
this.getGenericThumbnail(url)
|
|
58
|
+
.then((response) => resolve(response))
|
|
59
|
+
.catch((response) => reject(response));
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
getBinaryThumbnail: async function (url) {
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
fetch(url)
|
|
66
|
+
.then((response) => {
|
|
67
|
+
if (response.status >= 200 && response.status < 300) {
|
|
68
|
+
return response.text();
|
|
69
|
+
} else {
|
|
70
|
+
reject();
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
.then((data) => {
|
|
74
|
+
if (data) {
|
|
75
|
+
let img = new Image();
|
|
76
|
+
let wrapperElement = document.createElement("div");
|
|
77
|
+
img.style = "height: auto;width: 50px;margin-right: 80px;";
|
|
78
|
+
img.onload = function () {
|
|
79
|
+
wrapperElement.appendChild(img);
|
|
80
|
+
resolve(wrapperElement);
|
|
81
|
+
};
|
|
82
|
+
img.onerror = function () {
|
|
83
|
+
reject(new Error("Failed to load image at " + url));
|
|
84
|
+
};
|
|
85
|
+
img.src = `data:'image/png';base64,${data}`;
|
|
86
|
+
} else {
|
|
87
|
+
reject(new Error("Failed to load image at " + url));
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
getGenericThumbnail: async function (url) {
|
|
93
|
+
return new Promise((resolve, reject) => {
|
|
94
|
+
let img = new Image();
|
|
95
|
+
let wrapperElement = document.createElement("div");
|
|
96
|
+
img.style = "height: auto;width: 50px;margin-right: 80px;";
|
|
97
|
+
img.onload = function () {
|
|
98
|
+
wrapperElement.appendChild(img);
|
|
99
|
+
resolve(wrapperElement);
|
|
100
|
+
};
|
|
101
|
+
img.onerror = function () {
|
|
102
|
+
reject(new Error("Failed to load image at " + url));
|
|
103
|
+
};
|
|
104
|
+
img.src = url;
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
addImageThumbnailMarker: function (mapImp, id, wrapperElement) {
|
|
108
|
+
const markerIdentifier = mapImp.addMarker(id, {
|
|
109
|
+
element: wrapperElement,
|
|
110
|
+
className: "highlight-marker",
|
|
111
|
+
cluster: false,
|
|
112
|
+
type: "image",
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
};
|
|
@@ -256,7 +256,11 @@ let FlatmapQueries = function () {
|
|
|
256
256
|
}
|
|
257
257
|
})
|
|
258
258
|
.catch((error) => {
|
|
259
|
-
|
|
259
|
+
if (error.name === 'AbortError') {
|
|
260
|
+
// This error is from AbortController's abort method.
|
|
261
|
+
} else {
|
|
262
|
+
console.error('Error:', error)
|
|
263
|
+
}
|
|
260
264
|
resolve(false)
|
|
261
265
|
})
|
|
262
266
|
})
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
const getOrganCuries = async (sparcApi, filetypes = [], species = []) => {
|
|
2
|
+
let params = new URLSearchParams();
|
|
3
|
+
filetypes.forEach((type) => {
|
|
4
|
+
params.append("filetypes", type);
|
|
5
|
+
});
|
|
6
|
+
species.forEach((name) => {
|
|
7
|
+
params.append("species", name);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const response = await fetch(`${sparcApi}/get-organ-curies/?${params}`);
|
|
11
|
+
const data = await response.json();
|
|
12
|
+
|
|
13
|
+
let organCuries = [];
|
|
14
|
+
data.uberon.array.forEach((pair) => {
|
|
15
|
+
const organCurie = {
|
|
16
|
+
id: pair.id.toUpperCase(),
|
|
17
|
+
name: pair.name,
|
|
18
|
+
};
|
|
19
|
+
organCuries.push(organCurie);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return organCuries;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const getFilesInfoForCuries = async (sparcApi, organCuries, filetypes) => {
|
|
26
|
+
const response = await fetch(`${sparcApi}/get-files-info-for-curies`, {
|
|
27
|
+
method: "POST",
|
|
28
|
+
body: JSON.stringify({
|
|
29
|
+
curies: organCuries.map((item) => item.id),
|
|
30
|
+
filetypes: filetypes,
|
|
31
|
+
}),
|
|
32
|
+
headers: {
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
const data = await response.json();
|
|
37
|
+
|
|
38
|
+
return data;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const getFileNameFromPath = (filePath) => {
|
|
42
|
+
return filePath.substring(filePath.lastIndexOf("/") + 1);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const getBiolucidaThumbnailURL = (sparcApi, biolucidaId) => {
|
|
46
|
+
return `${sparcApi}/thumbnail/${biolucidaId}`;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const getBiolucidaThumbnails = async (sparcApi, organCuries, type) => {
|
|
50
|
+
try {
|
|
51
|
+
const data = await getFilesInfoForCuries(sparcApi, organCuries, [
|
|
52
|
+
"biolucida-2d",
|
|
53
|
+
"biolucida-3d",
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
if (data["files_info"]) {
|
|
57
|
+
let images = {};
|
|
58
|
+
for (const [key, value] of Object.entries(data["files_info"])) {
|
|
59
|
+
if (value.length > 0) {
|
|
60
|
+
let list = [];
|
|
61
|
+
value.forEach((entry) => {
|
|
62
|
+
const thumbnailURL = getBiolucidaThumbnailURL(
|
|
63
|
+
sparcApi,
|
|
64
|
+
entry.biolucida_id
|
|
65
|
+
);
|
|
66
|
+
if (entry.biolucida_id) {
|
|
67
|
+
let image = {
|
|
68
|
+
thumbnail: thumbnailURL,
|
|
69
|
+
resource: entry.file_path,
|
|
70
|
+
id: entry.id,
|
|
71
|
+
title: getFileNameFromPath(entry.file_path),
|
|
72
|
+
type: type,
|
|
73
|
+
link: thumbnailURL,
|
|
74
|
+
mimetype: entry.mimetype,
|
|
75
|
+
species: entry.species,
|
|
76
|
+
version: entry.version,
|
|
77
|
+
};
|
|
78
|
+
list.push(image);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
images[key] = list;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return images;
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error("Error:", error);
|
|
88
|
+
}
|
|
89
|
+
return {};
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const getSegmentationThumbnailURL = (
|
|
93
|
+
sparcApi,
|
|
94
|
+
datasetId,
|
|
95
|
+
datasetVersion,
|
|
96
|
+
filePath
|
|
97
|
+
) => {
|
|
98
|
+
return `${sparcApi}/thumbnail/neurolucida?datasetId=${datasetId}&version=${datasetVersion}&path=files/${filePath}`;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const getSegmentationThumbnails = async (sparcApi, organCuries, type) => {
|
|
102
|
+
try {
|
|
103
|
+
const data = await getFilesInfoForCuries(sparcApi, organCuries, [
|
|
104
|
+
"mbf-segmentation",
|
|
105
|
+
]);
|
|
106
|
+
|
|
107
|
+
if (data["files_info"]) {
|
|
108
|
+
let images = {};
|
|
109
|
+
for (const [key, value] of Object.entries(data["files_info"])) {
|
|
110
|
+
if (value.length > 0) {
|
|
111
|
+
let list = [];
|
|
112
|
+
value.forEach((entry) => {
|
|
113
|
+
const thumbnailURL = getSegmentationThumbnailURL(
|
|
114
|
+
sparcApi,
|
|
115
|
+
entry.id,
|
|
116
|
+
entry.version,
|
|
117
|
+
entry.file_path
|
|
118
|
+
);
|
|
119
|
+
let image = {
|
|
120
|
+
thumbnail: thumbnailURL,
|
|
121
|
+
resource: entry.file_path,
|
|
122
|
+
id: entry.id,
|
|
123
|
+
title: getFileNameFromPath(entry.file_path),
|
|
124
|
+
type: type,
|
|
125
|
+
link: thumbnailURL,
|
|
126
|
+
mimetype: entry.mimetype,
|
|
127
|
+
species: entry.species,
|
|
128
|
+
version: entry.version,
|
|
129
|
+
};
|
|
130
|
+
list.push(image);
|
|
131
|
+
});
|
|
132
|
+
images[key] = list;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return images;
|
|
136
|
+
}
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error("Error:", error);
|
|
139
|
+
}
|
|
140
|
+
return {};
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const findEntryWithPathInArray = (entry, list, type) => {
|
|
144
|
+
if (entry && list) {
|
|
145
|
+
for (let i = 0; i < entry.isSourceOf.length; i++) {
|
|
146
|
+
for (let l = 0; l < list.length; l++) {
|
|
147
|
+
const item = list[l];
|
|
148
|
+
if (
|
|
149
|
+
entry.id === item.id &&
|
|
150
|
+
(!type || item.type === type) &&
|
|
151
|
+
entry.isSourceOf[i] === item.file_path
|
|
152
|
+
) {
|
|
153
|
+
return item;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return undefined;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const getScaffoldThumbnailURL = (sparcApi, entry, list) => {
|
|
162
|
+
const viewEntry = findEntryWithPathInArray(
|
|
163
|
+
entry,
|
|
164
|
+
list,
|
|
165
|
+
"abi-scaffold-view-file"
|
|
166
|
+
);
|
|
167
|
+
const thumbnailEntry = findEntryWithPathInArray(
|
|
168
|
+
viewEntry,
|
|
169
|
+
list,
|
|
170
|
+
"abi-thumbnail"
|
|
171
|
+
);
|
|
172
|
+
if (thumbnailEntry) {
|
|
173
|
+
return `${sparcApi}/s3-resource/${thumbnailEntry.id}/files/${thumbnailEntry.file_path}`;
|
|
174
|
+
}
|
|
175
|
+
return undefined;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const getScaffoldThumbnails = async (sparcApi, organCuries, type) => {
|
|
179
|
+
try {
|
|
180
|
+
const data = await getFilesInfoForCuries(sparcApi, organCuries, [
|
|
181
|
+
"abi-thumbnail",
|
|
182
|
+
"abi-scaffold-metadata-file",
|
|
183
|
+
"abi-scaffold-view-file",
|
|
184
|
+
]);
|
|
185
|
+
|
|
186
|
+
if (data["files_info"]) {
|
|
187
|
+
let images = {};
|
|
188
|
+
for (const [key, value] of Object.entries(data["files_info"])) {
|
|
189
|
+
if (value.length > 0) {
|
|
190
|
+
let list = [];
|
|
191
|
+
value.forEach((entry) => {
|
|
192
|
+
if (entry.type === "abi-scaffold-metadata-file") {
|
|
193
|
+
const thumbnailURL = getScaffoldThumbnailURL(
|
|
194
|
+
sparcApi,
|
|
195
|
+
entry,
|
|
196
|
+
value
|
|
197
|
+
);
|
|
198
|
+
if (thumbnailURL) {
|
|
199
|
+
let image = {
|
|
200
|
+
thumbnail: thumbnailURL,
|
|
201
|
+
resource: entry.file_path,
|
|
202
|
+
id: entry.id,
|
|
203
|
+
title: getFileNameFromPath(entry.file_path),
|
|
204
|
+
type: type,
|
|
205
|
+
link: thumbnailURL,
|
|
206
|
+
species: entry.species,
|
|
207
|
+
version: entry.version,
|
|
208
|
+
};
|
|
209
|
+
list.push(image);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
images[key] = list;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return images;
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error("Error:", error);
|
|
220
|
+
}
|
|
221
|
+
return {};
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const getPlotThumbnailURL = (sparcApi, entry) => {
|
|
225
|
+
//None of the thumbnail for plot is properly annotated.
|
|
226
|
+
//We will use the first in is source of for testing.
|
|
227
|
+
if (entry.isSourceOf.length > 0) {
|
|
228
|
+
return `${sparcApi}/s3-resource/${entry.id}/files/${entry.isSourceOf[0]}`;
|
|
229
|
+
}
|
|
230
|
+
return undefined;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const getPlotThumbnails = async (sparcApi, organCuries, type) => {
|
|
234
|
+
try {
|
|
235
|
+
const data = await getFilesInfoForCuries(sparcApi, organCuries, [
|
|
236
|
+
"abi-plot",
|
|
237
|
+
"abi-thumbnail",
|
|
238
|
+
]);
|
|
239
|
+
|
|
240
|
+
if (data["files_info"]) {
|
|
241
|
+
let images = {};
|
|
242
|
+
for (const [key, value] of Object.entries(data["files_info"])) {
|
|
243
|
+
if (value.length > 0) {
|
|
244
|
+
let list = [];
|
|
245
|
+
value.forEach((entry) => {
|
|
246
|
+
if (entry.type === "abi-plot") {
|
|
247
|
+
const thumbnailURL = getPlotThumbnailURL(sparcApi, entry);
|
|
248
|
+
if (thumbnailURL) {
|
|
249
|
+
let image = {
|
|
250
|
+
thumbnail: thumbnailURL,
|
|
251
|
+
resource: entry.file_path,
|
|
252
|
+
id: entry.id,
|
|
253
|
+
title: getFileNameFromPath(entry.file_path),
|
|
254
|
+
type: type,
|
|
255
|
+
link: thumbnailURL,
|
|
256
|
+
species: entry.species,
|
|
257
|
+
version: entry.version,
|
|
258
|
+
};
|
|
259
|
+
list.push(image);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
images[key] = list;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return images;
|
|
267
|
+
}
|
|
268
|
+
} catch (error) {
|
|
269
|
+
console.error("Error:", error);
|
|
270
|
+
}
|
|
271
|
+
return {};
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
export {
|
|
275
|
+
getOrganCuries,
|
|
276
|
+
getBiolucidaThumbnails,
|
|
277
|
+
getSegmentationThumbnails,
|
|
278
|
+
getScaffoldThumbnails,
|
|
279
|
+
getPlotThumbnails,
|
|
280
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { defineStore } from "pinia";
|
|
2
|
+
|
|
3
|
+
/* eslint-disable no-alert, no-console */
|
|
4
|
+
|
|
5
|
+
export const useSettingsStore = defineStore("settings", {
|
|
6
|
+
state: () => {
|
|
7
|
+
return {
|
|
8
|
+
organCuries: [],
|
|
9
|
+
imageTypes: [],
|
|
10
|
+
imageThumbnails: {},
|
|
11
|
+
};
|
|
12
|
+
},
|
|
13
|
+
getters: {
|
|
14
|
+
imageTypeCached: (state) => (imageType) => {
|
|
15
|
+
return state.imageTypes.includes(imageType);
|
|
16
|
+
},
|
|
17
|
+
getImageThumbnails:
|
|
18
|
+
(state) =>
|
|
19
|
+
(imageType, uberonIds = undefined) => {
|
|
20
|
+
if (uberonIds) {
|
|
21
|
+
let thumbnails = {};
|
|
22
|
+
Object.entries(state.imageThumbnails).forEach(([key, value]) => {
|
|
23
|
+
if (uberonIds.includes(key)) {
|
|
24
|
+
thumbnails[key] = value.filter((item) => item.type === imageType);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return thumbnails;
|
|
28
|
+
}
|
|
29
|
+
return state.imageThumbnails;
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
actions: {
|
|
33
|
+
updateOrganCuries(organCuries) {
|
|
34
|
+
this.organCuries = organCuries;
|
|
35
|
+
},
|
|
36
|
+
updateImageThumbnails(imageType, imageThumbnails) {
|
|
37
|
+
this.imageTypes.push(imageType);
|
|
38
|
+
Object.keys(imageThumbnails).forEach((key) => {
|
|
39
|
+
if (!(key in this.imageThumbnails)) {
|
|
40
|
+
this.imageThumbnails[key] = [];
|
|
41
|
+
}
|
|
42
|
+
this.imageThumbnails[key].push(...imageThumbnails[key]);
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
File without changes
|