@abi-software/map-side-bar 2.14.1-simulation.8 → 2.14.2-demo.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.
@@ -20,36 +20,77 @@
20
20
  </template>
21
21
 
22
22
  <script>
23
+ /* eslint-disable no-alert, no-console */
24
+ const baseName = (str) => {
25
+ return str.split('\\').pop().split('/').pop()
26
+ }
27
+
28
+ const capitalise = function (string) {
29
+ return string.replace(/\b\w/g, (v) => v.toUpperCase())
30
+ }
31
+
32
+ import GalleryHelper from '@abi-software/gallery/src/mixins/GalleryHelpers.js'
23
33
  import Gallery from "@abi-software/gallery";
24
34
  import "@abi-software/gallery/dist/style.css";
25
35
  //provide the s3Bucket related methods and data.
36
+ import S3Bucket from '../mixins/S3Bucket.vue';
26
37
 
27
38
  export default {
28
39
  name: 'ImageGallery',
29
40
  components: { Gallery },
41
+ mixins: [GalleryHelper, S3Bucket],
30
42
  props: {
43
+ envVars: {
44
+ type: Object,
45
+ default: () => {},
46
+ },
47
+ label: {
48
+ type: String,
49
+ default: '',
50
+ },
51
+ plots: {
52
+ type: Array,
53
+ default: () => {
54
+ return []
55
+ },
56
+ },
57
+ datasetId: {
58
+ type: Number,
59
+ default: -1,
60
+ },
61
+ datasetVersion: {
62
+ type: Number,
63
+ default: -1,
64
+ },
65
+ datasetThumbnail: {
66
+ type: String,
67
+ default: '',
68
+ },
31
69
  category: {
32
70
  type: String,
33
71
  default: 'All',
34
72
  },
35
- items: {
73
+ entry: {
36
74
  type: Object,
37
- defualt: () => {
38
- return {
39
- Dataset: [],
40
- Flatmaps:[],
41
- Scaffolds: [],
42
- Simulations: [],
43
- Plots: [],
44
- }
45
- }
46
- }
75
+ default: () => {
76
+ return {}
77
+ },
78
+ },
47
79
  },
48
80
  data() {
49
81
  return {
50
82
  currentIndex: 0,
51
83
  ro: null,
52
84
  maxWidth: 3,
85
+ items: {
86
+ Dataset: [],
87
+ Flatmaps:[],
88
+ Images: [],
89
+ Scaffolds: [],
90
+ Simulations: [],
91
+ Videos: [],
92
+ Plots: [],
93
+ },
53
94
  bodyStyle: { padding: '0px', background: '#ffffff' },
54
95
  imageContainerStyle: {
55
96
  width: '160px',
@@ -71,10 +112,314 @@ export default {
71
112
  datalinkClicked: function (payload) {
72
113
  this.$emit('datalink-clicked', payload);
73
114
  },
115
+ createSciCurnchItems: function () {
116
+ this.updateS3Bucket(this.entry.s3uri)
117
+ this.createDatasetItem()
118
+ this.createFlatmapItems()
119
+ this.createScaffoldItems()
120
+ this.createSimulationItems()
121
+ this.createPlotItems()
122
+ /* Disable these two
123
+ this.createImageItems();
124
+ this.createVideoItems();
125
+ */
126
+ },
127
+ createDatasetItem: function () {
128
+ const link = `${this.envVars.ROOT_URL}/datasets/${this.datasetId}?type=dataset`
129
+ if (this.datasetThumbnail) {
130
+ this.items['Dataset'].push({
131
+ id: -1,
132
+ //Work around gallery requires a truthy string
133
+ title: ' ',
134
+ type: `Dataset ${this.datasetId}`,
135
+ thumbnail: this.datasetThumbnail,
136
+ link,
137
+ hideType: true,
138
+ hideTitle: true,
139
+ })
140
+ }
141
+ },
142
+ createFlatmapItems: function () {
143
+ if (this.entry.flatmaps) {
144
+ this.entry.flatmaps.forEach((flatmap) => {
145
+ if (flatmap.associated_flatmap?.identifier) {
146
+ const filePath = flatmap.dataset.path
147
+ const id = flatmap.identifier
148
+ const thumbnail = this.getThumbnailForPlot(
149
+ flatmap,
150
+ this.entry.thumbnails
151
+ )
152
+ let thumbnailURL = undefined
153
+ let mimetype = ''
154
+ if (thumbnail) {
155
+ thumbnailURL = this.getImageURL(this.envVars.API_LOCATION, {
156
+ id,
157
+ prefix: this.getS3Prefix(),
158
+ file_path: thumbnail.dataset.path,
159
+ s3Bucket: this.s3Bucket,
160
+ })
161
+ mimetype = thumbnail.mimetype.name
162
+ }
163
+ let action = {
164
+ label: capitalise(this.label),
165
+ resource: flatmap.associated_flatmap.identifier,
166
+ title: 'View Flatmap',
167
+ type: 'Flatmap',
168
+ discoverId: this.datasetId,
169
+ version: this.datasetVersion,
170
+ }
171
+ this.items['Flatmaps'].push({
172
+ id,
173
+ title: baseName(filePath),
174
+ type: 'Flatmap',
175
+ thumbnail: thumbnailURL,
176
+ userData: action,
177
+ hideType: true,
178
+ mimetype,
179
+ })
180
+ }
181
+ })
182
+ }
183
+ },
184
+ createImageItems: function () {
185
+ if (this.entry.images) {
186
+ this.entry.images.forEach((image) => {
187
+ const filePath = image.dataset.path
188
+ const id = image.identifier
189
+ const linkUrl = `${this.envVars.ROOT_URL}/datasets/imageviewer?dataset_id=${this.datasetId}&dataset_version=${this.datasetVersion}&file_path=${filePath}&mimetype=${image.mimetype.name}`
190
+ this.items['Images'].push({
191
+ id,
192
+ title: baseName(filePath),
193
+ type: 'Image',
194
+ link: linkUrl,
195
+ hideType: true,
196
+ })
197
+ })
198
+ }
199
+ },
200
+ createPlotItems: function () {
201
+ if (this.entry.plots) {
202
+ this.entry.plots.forEach((plot) => {
203
+ const filePath = plot.dataset.path
204
+ const id = plot.identifier
205
+ const thumbnail = this.getThumbnailForPlot(
206
+ plot,
207
+ this.entry.thumbnails
208
+ )
209
+ let thumbnailURL = undefined
210
+ let mimetype = ''
211
+ if (thumbnail) {
212
+
213
+ thumbnailURL = this.getImageURL(this.envVars.API_LOCATION, {
214
+ id,
215
+ prefix: this.getS3Prefix(),
216
+ file_path: thumbnail.dataset.path,
217
+ s3Bucket: this.s3Bucket,
218
+ })
219
+ mimetype = thumbnail.mimetype.name
220
+ }
221
+ const plotAnnotation = plot.datacite;
222
+ const filePathPrefix = `${this.envVars.API_LOCATION}/s3-resource/${this.getS3Prefix()}files/`;
223
+ const sourceUrl = filePathPrefix + plot.dataset.path + this.getS3Args();
224
+
225
+ //plotAnnotation.supplemental_json_metadata.description can be undefined or
226
+ //contain an empty string causing an error with JSON.parse
227
+ let metadata = {}
228
+ try {
229
+ metadata = JSON.parse(
230
+ plotAnnotation.supplemental_json_metadata.description
231
+ )
232
+ } catch (error) {
233
+ console.warn(error)
234
+ }
235
+
236
+ let supplementalData = []
237
+ if (plotAnnotation.isDescribedBy) {
238
+ supplementalData.push({
239
+ url: filePathPrefix + plotAnnotation.isDescribedBy.path,
240
+ })
241
+ }
242
+
243
+ const resource = {
244
+ dataSource: { url: sourceUrl },
245
+ metadata,
246
+ supplementalData,
247
+ }
248
+
249
+ let action = {
250
+ label: capitalise(this.label),
251
+ resource: resource,
252
+ s3uri: this.entry.s3uri,
253
+ title: 'View plot',
254
+ type: 'Plot',
255
+ discoverId: this.discoverId,
256
+ version: this.datasetVersion,
257
+ }
258
+ this.items['Plots'].push({
259
+ id,
260
+ title: baseName(filePath),
261
+ type: 'Plot',
262
+ thumbnail: thumbnailURL,
263
+ userData: action,
264
+ hideType: true,
265
+ mimetype,
266
+ })
267
+ })
268
+ }
269
+ },
270
+ createScaffoldItems: function () {
271
+ if (this.entry.scaffolds) {
272
+ let index = 0
273
+ this.entry.scaffolds.forEach((scaffold, i) => {
274
+ const filePath = scaffold.dataset.path
275
+ const id = scaffold.identifier
276
+ const thumbnail = this.getThumbnailForScaffold(
277
+ scaffold,
278
+ this.entry.scaffoldViews,
279
+ this.entry.thumbnails,
280
+ index
281
+ )
282
+ let mimetype = ''
283
+ let thumbnailURL = undefined
284
+ if (thumbnail) {
285
+ thumbnailURL = this.getImageURL(this.envVars.API_LOCATION, {
286
+ id,
287
+ prefix: this.getS3Prefix(),
288
+ file_path: thumbnail.dataset.path,
289
+ s3Bucket: this.s3Bucket,
290
+ })
291
+ mimetype = thumbnail.mimetype.name
292
+ }
293
+ let action = {
294
+ label: capitalise(this.label),
295
+ resource: `${this.envVars.API_LOCATION}s3-resource/${this.getS3Prefix()}files/${filePath}${this.getS3Args()}`,
296
+ title: "View 3D scaffold",
297
+ type: "Scaffold",
298
+ discoverId: this.datasetId,
299
+ apiLocation: this.envVars.API_LOCATION,
300
+ version: this.datasetVersion,
301
+ banner: this.datasetThumbnail,
302
+ s3uri: this.entry.s3uri,
303
+ contextCardUrl: this.getContextCardUrl(i),
304
+ }
305
+ this.items['Scaffolds'].push({
306
+ id,
307
+ title: baseName(filePath),
308
+ type: 'Scaffold',
309
+ thumbnail: thumbnailURL,
310
+ userData: action,
311
+ hideType: true,
312
+ mimetype,
313
+ })
314
+ })
315
+ }
316
+ },
317
+ createSimulationItems: function () {
318
+ if (this.entry.simulation) {
319
+ this.entry.simulation.forEach((simulation) => {
320
+ if (simulation.additional_mimetype.name === "application/x.vnd.abi.simulation+json") {
321
+ let action = {
322
+ label: undefined,
323
+ apiLocation: this.envVars.API_LOCATION,
324
+ s3uri: this.entry.s3uri,
325
+ version: this.datasetVersion,
326
+ title: 'View simulation',
327
+ type: 'Simulation',
328
+ name: this.entry.name,
329
+ description: this.entry.description,
330
+ discoverId: this.datasetId,
331
+ dataset: `${this.envVars.ROOT_URL}/datasets/${this.datasetId}?type=dataset`,
332
+ }
333
+ this.items['Simulations'].push({
334
+ id: 'simulation',
335
+ title: ' ',
336
+ type: 'Simulation',
337
+ hideType: true,
338
+ hideTitle: true,
339
+ userData: action,
340
+ })
341
+ } else {
342
+ const filePath = simulation.dataset.path
343
+ const id = simulation.identifier
344
+ //Despite of the name, this method can be used to retreive
345
+ //the thumbnail information for any none scaffold type thumbnail
346
+ const thumbnail = this.getThumbnailForPlot(
347
+ simulation,
348
+ this.entry.thumbnails
349
+ )
350
+ let thumbnailURL = undefined
351
+ let mimetype = ''
352
+ if (thumbnail) {
353
+ thumbnailURL = this.getImageURL(this.envVars.API_LOCATION, {
354
+ id,
355
+ prefix: this.getS3Prefix(),
356
+ file_path: thumbnail.dataset.path,
357
+ s3Bucket: this.s3Bucket,
358
+ })
359
+ mimetype = thumbnail.mimetype.name
360
+ }
361
+ const resource = `${this.envVars.API_LOCATION}s3-resource/${this.getS3Prefix()}files/${filePath}${this.getS3Args()}`
362
+ let action = {
363
+ label: capitalise(this.label),
364
+ resource: resource,
365
+ s3uri: this.entry.s3uri,
366
+ title: 'View simulation',
367
+ type: 'Simulation',
368
+ discoverId: this.discoverId,
369
+ version: this.datasetVersion,
370
+ }
371
+ this.items['Simulations'].push({
372
+ id,
373
+ title: baseName(filePath),
374
+ type: 'Simulation',
375
+ thumbnail: thumbnailURL,
376
+ userData: action,
377
+ hideType: true,
378
+ mimetype,
379
+ })
380
+ }
381
+ })
382
+ }
383
+ },
384
+ createVideoItems: function () {
385
+ if (this.entry.videos) {
386
+ this.entry.videos.forEach((video) => {
387
+ const filePath = this.getS3FilePath(
388
+ this.datasetId,
389
+ this.datasetVersion,
390
+ video.dataset.path
391
+ )
392
+ const linkUrl = `${this.envVars.ROOT_URL}/datasets/videoviewer?dataset_version=${this.datasetVersion}&dataset_id=${this.datasetId}&file_path=${filePath}&mimetype=${video.mimetype.name}`
393
+ this.items['Videos'].push({
394
+ title: video.name,
395
+ type: 'Video',
396
+ thumbnail: this.defaultVideoImg,
397
+ hideType: true,
398
+ link: linkUrl,
399
+ })
400
+ })
401
+ }
402
+ },
74
403
  onResize: function () {
75
404
  this.maxWidth = this.$el.clientWidth
76
405
  // this.$emit('resize', this.$el.clientWidth)
77
406
  },
407
+ getContextCardUrl: function(scaffoldIndex){
408
+ if(!this.entry.contextualInformation || this.entry.contextualInformation.length == 0){
409
+ return undefined
410
+ } else {
411
+ // The line below checks if there is a context file for each scaffold. If there is not, we use the first context card for each scaffold.
412
+ let contextIndex = this.entry['abi-contextual-information'].length == this.entry.scaffolds.length ? scaffoldIndex : 0
413
+ return `${this.envVars.API_LOCATION}s3-resource/${this.getS3Prefix()}files/${this.entry.contextualInformation[contextIndex]}${this.getS3Args()}`
414
+ }
415
+ },
416
+ getImageURL: function(apiEndpoint, info) {
417
+ let url = `${apiEndpoint}/s3-resource/${info.prefix}files/${info.file_path}?encodeBase64=true`
418
+ if (info.s3Bucket) {
419
+ url = url + `&s3BucketName=${info.s3Bucket}`
420
+ }
421
+ return url
422
+ },
78
423
  },
79
424
  computed: {
80
425
  galleryItems() {
@@ -90,6 +435,9 @@ export default {
90
435
  } else return [...this.items[this.category]]
91
436
  },
92
437
  },
438
+ created: function () {
439
+ this.createSciCurnchItems()
440
+ },
93
441
  watch: {
94
442
  category: function () {
95
443
  this.resetIndex = true
@@ -299,14 +299,6 @@ export default {
299
299
  datasetExplorerTabRef.openSearch(facets, query);
300
300
  })
301
301
  },
302
- displayFileInfo: function (datasetID, fileSearch, searchTerm = "") {
303
- this.drawerOpen = true
304
- // Because refs are in v-for, nextTick is needed here
305
- this.$nextTick(() => {
306
- const datasetExplorerTabRef = this.getTabRef(undefined, 'datasetExplorer', true);
307
- datasetExplorerTabRef.displayFileInfo(datasetID, fileSearch, searchTerm);
308
- })
309
- },
310
302
  /**
311
303
  * Get the ref id of the tab by id and type.
312
304
  */
@@ -19,7 +19,6 @@ declare module 'vue' {
19
19
  ElCascader: typeof import('element-plus/es')['ElCascader']
20
20
  ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
21
21
  ElCol: typeof import('element-plus/es')['ElCol']
22
- ElDialog: typeof import('element-plus/es')['ElDialog']
23
22
  ElDrawer: typeof import('element-plus/es')['ElDrawer']
24
23
  ElDropdown: typeof import('element-plus/es')['ElDropdown']
25
24
  ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
@@ -35,7 +34,6 @@ declare module 'vue' {
35
34
  ElIconSearch: typeof import('@element-plus/icons-vue')['Search']
36
35
  ElIconWarning: typeof import('@element-plus/icons-vue')['Warning']
37
36
  ElIconWarnTriangleFilled: typeof import('@element-plus/icons-vue')['WarnTriangleFilled']
38
- ElImage: typeof import('element-plus/es')['ElImage']
39
37
  ElInput: typeof import('element-plus/es')['ElInput']
40
38
  ElOption: typeof import('element-plus/es')['ElOption']
41
39
  ElPagination: typeof import('element-plus/es')['ElPagination']
@@ -44,10 +42,7 @@ declare module 'vue' {
44
42
  ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
45
43
  ElRow: typeof import('element-plus/es')['ElRow']
46
44
  ElSelect: typeof import('element-plus/es')['ElSelect']
47
- ElTable: typeof import('element-plus/es')['ElTable']
48
- ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
49
45
  ElTag: typeof import('element-plus/es')['ElTag']
50
- FileBrowser: typeof import('./components/FileBrowser.vue')['default']
51
46
  ImageGallery: typeof import('./components/ImageGallery.vue')['default']
52
47
  SearchFilters: typeof import('./components/SearchFilters.vue')['default']
53
48
  SearchHistory: typeof import('./components/SearchHistory.vue')['default']
@@ -1,252 +0,0 @@
1
- <template>
2
- <div class="file-browser-container">
3
- <div class="badges-container">
4
- <BadgesGroup
5
- :displayDataset="false"
6
- :displayText="false"
7
- :items="items"
8
- @categoryChanged="categoryChanged"
9
- />
10
- </div>
11
- <div class="table-container">
12
- <el-table
13
- v-if="fileLists"
14
- :data="fileLists"
15
- style="width: 100%;"
16
- height="100%"
17
- :stripe="true"
18
- >
19
- <el-table-column type="expand">
20
- <template #default="props">
21
- <div class="file-details" m="4">
22
- <p m="t-0 b-2" v-if="props.row.description">
23
- <b>Description:</b> {{ props.row.description }}
24
- </p>
25
- <p m="t-0 b-2" v-if="props.row.protocol">
26
- <b>Protocol</b>: {{ props.row.protocol }}
27
- </p>
28
- <div v-for="(val, key) in props.row.columns">
29
- <p :key="key" m="t-0 b-2"><b>Column {{ key + 1 }}</b>: {{ val }}</p>
30
- </div>
31
- <p m="t-0 b-2">
32
- <b>File path:</b> {{ props.row.filePath }}
33
- </p>
34
- </div>
35
- </template>
36
- </el-table-column>
37
- <el-table-column
38
- prop="thumbnail"
39
- label="Thumbnail"
40
- width="170"
41
- >
42
- <template #default="scope">
43
- <el-image
44
- v-if="scope.row.thumbnail"
45
- :src="scope.row.thumbnail"
46
- style="max-width: 150px; max-height: 150px"
47
- fit="contain"
48
- lazy
49
- />
50
- </template>
51
- </el-table-column>
52
- <el-table-column
53
- prop="fileName"
54
- label="File name"
55
- width="200"
56
- class-name="column-text"
57
- />
58
- <el-table-column
59
- prop="type"
60
- label="Type"
61
- width="100"
62
- class-name="column-text"
63
- />
64
- <el-table-column
65
- fixed="right"
66
- >
67
- <template #header>
68
- <el-input v-model="search" size="small" placeholder="Type to search" clearable/>
69
- </template>
70
- <template #default="scope">
71
- <el-button
72
- class="file-button"
73
- @click="handleView(scope.row)"
74
- :icon="ElIconView"
75
- type="primary"
76
- />
77
- </template>
78
- </el-table-column>
79
- </el-table>
80
- </div>
81
- </div>
82
- </template>
83
-
84
- <script>
85
- //provide the s3Bucket related methods and data.
86
- import { shallowRef } from 'vue';
87
- import BadgesGroup from './BadgesGroup.vue'
88
- import {
89
- ElButton as Button,
90
- ElImage as Image,
91
- ElIcon as Icon,
92
- ElInput as Input,
93
- ElTable as Table,
94
- ElTableColumn as TableColumn
95
- } from "element-plus";
96
- import { ref } from 'vue'
97
- import {
98
- View as ElIconView,
99
- } from '@element-plus/icons-vue'
100
-
101
-
102
- export default {
103
- name: 'FileBrowser',
104
- components: {
105
- BadgesGroup,
106
- Button,
107
- ElIconView,
108
- Icon,
109
- Image,
110
- Input,
111
- Table,
112
- TableColumn
113
- },
114
- data() {
115
- return {
116
- category: "All",
117
- ElIconView: shallowRef(ElIconView),
118
- items: {
119
- Dataset: [],
120
- Flatmaps:[],
121
- Scaffolds: [],
122
- Simulations: [],
123
- Plots: [],
124
- },
125
- search: "",
126
- tableData: undefined,
127
- }
128
- },
129
- methods: {
130
- categoryChanged: function(name) {
131
- this.category = name
132
- },
133
- setSearchTerm: function(searchTerm) {
134
- this.search = searchTerm
135
- },
136
- handleView: function(row) {
137
- this.$emit("fileActionTriggered", row.action)
138
- },
139
- downloadThumbnail: async function(url, entry) {
140
- const response = await fetch(url)
141
- if (response.ok) {
142
- let data = await response.text()
143
- if (typeof data === 'string' && data.startsWith('data:')) {
144
- entry.thumbnail = data
145
- } else {
146
- if (entry.mimetype) {
147
- entry.thumbnail = `data:${entry.mimetype};base64,${data}`
148
- } else {
149
- entry.thumbnail = data
150
- }
151
- let index = this.tableData.findIndex((item) => item.filePath === entry.filePath);
152
- if (index > -1) {
153
- this.tableData[index] = {...entry}
154
- }
155
- }
156
- }
157
- },
158
- setData: function(data) {
159
- Object.assign(this.items, data)
160
- this.tableData = ref([])
161
- this.category = "All"
162
- this.search = ""
163
- Object.keys(data).forEach((key) => {
164
- if (key !== "Dataset") {
165
- data[key].forEach((item) => {
166
- const entry = {
167
- action: item.userData,
168
- description: item.description ? item.description : "",
169
- protocol: item.protocol ? item.protocol : "",
170
- columns: [],
171
- fileName: item.title,
172
- filePath: item.filePath,
173
- mimetype: item.mimetype,
174
- type: key,
175
- }
176
- if (item.columns && item.columns.length > 0) {
177
- item.columns.forEach((column) => entry.columns.push(JSON.stringify(column)))
178
- }
179
-
180
- this.tableData.push(entry)
181
- if (item.thumbnail?.includes("encodeBase64")) {
182
- this.downloadThumbnail(item.thumbnail, entry)
183
- } else {
184
- entry.thumbnail = item.thumbnail
185
- }
186
- })
187
- }
188
- })
189
- }
190
- },
191
- computed: {
192
- fileLists() {
193
- if (!this.search && this.category === "All") return this.tableData
194
- const keys = ["fileName", "filePath", "description", "type", "protocol"]
195
- const lower = this.search.toLowerCase()
196
- const list = this.tableData.filter((data) => {
197
- if (this.category !== "All") {
198
- if (data.type !== this.category) {
199
- return false
200
- }
201
- }
202
- for (let key of keys) {
203
- if (data[key] && data[key].toLowerCase().includes(lower)) {
204
- return true
205
- }
206
- }
207
- if (data.columns) {
208
- for (let column of data.columns) {
209
- if (column.toLowerCase().includes(lower)) {
210
- return true
211
- }
212
- }
213
- }
214
- })
215
- return list
216
- }
217
- },
218
- }
219
- </script>
220
-
221
- <style lang="scss" scoped>
222
- .badges-container {
223
- padding-bottom: 8px;
224
- }
225
-
226
- :deep(.el-table__body-wrapper) {
227
- .column-text {
228
- font-size: 12px;
229
- }
230
- }
231
-
232
- .file-button {
233
- border-radius: 50%;
234
- width: 26px;
235
- height: 26px;
236
- padding:5px;
237
- :deep(svg) {
238
- scale: 1.2;
239
- }
240
- }
241
-
242
- .file-details {
243
- padding-left: 8px;
244
- font-size: 12px;
245
- }
246
-
247
-
248
- .file-browser-container {
249
- max-height: 75vh;
250
- overflow: auto;
251
- }
252
- </style>