@mongoosejs/studio 0.0.136 → 0.0.138

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.
@@ -594,6 +594,10 @@ video {
594
594
  pointer-events: none;
595
595
  }
596
596
 
597
+ .invisible {
598
+ visibility: hidden;
599
+ }
600
+
597
601
  .fixed {
598
602
  position: fixed;
599
603
  }
@@ -635,6 +639,10 @@ video {
635
639
  right: 0.25rem;
636
640
  }
637
641
 
642
+ .right-2 {
643
+ right: 0.5rem;
644
+ }
645
+
638
646
  .right-4 {
639
647
  right: 1rem;
640
648
  }
@@ -643,6 +651,10 @@ video {
643
651
  top: 0.25rem;
644
652
  }
645
653
 
654
+ .top-2 {
655
+ top: 0.5rem;
656
+ }
657
+
646
658
  .top-\[65px\] {
647
659
  top: 65px;
648
660
  }
@@ -701,6 +713,11 @@ video {
701
713
  margin-bottom: -0.5rem;
702
714
  }
703
715
 
716
+ .mx-1 {
717
+ margin-left: 0.25rem;
718
+ margin-right: 0.25rem;
719
+ }
720
+
704
721
  .mx-4 {
705
722
  margin-left: 1rem;
706
723
  margin-right: 1rem;
@@ -764,6 +781,10 @@ video {
764
781
  margin-left: 0.75rem;
765
782
  }
766
783
 
784
+ .ml-4 {
785
+ margin-left: 1rem;
786
+ }
787
+
767
788
  .ml-auto {
768
789
  margin-left: auto;
769
790
  }
@@ -1164,6 +1185,10 @@ video {
1164
1185
  align-items: center;
1165
1186
  }
1166
1187
 
1188
+ .items-baseline {
1189
+ align-items: baseline;
1190
+ }
1191
+
1167
1192
  .justify-end {
1168
1193
  justify-content: flex-end;
1169
1194
  }
@@ -1238,6 +1263,12 @@ video {
1238
1263
  margin-bottom: calc(1rem * var(--tw-space-y-reverse));
1239
1264
  }
1240
1265
 
1266
+ .space-y-6 > :not([hidden]) ~ :not([hidden]) {
1267
+ --tw-space-y-reverse: 0;
1268
+ margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
1269
+ margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
1270
+ }
1271
+
1241
1272
  .divide-y > :not([hidden]) ~ :not([hidden]) {
1242
1273
  --tw-divide-y-reverse: 0;
1243
1274
  border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
@@ -1856,14 +1887,27 @@ video {
1856
1887
  text-transform: capitalize;
1857
1888
  }
1858
1889
 
1890
+ .italic {
1891
+ font-style: italic;
1892
+ }
1893
+
1859
1894
  .leading-6 {
1860
1895
  line-height: 1.5rem;
1861
1896
  }
1862
1897
 
1898
+ .leading-none {
1899
+ line-height: 1;
1900
+ }
1901
+
1863
1902
  .leading-tight {
1864
1903
  line-height: 1.25;
1865
1904
  }
1866
1905
 
1906
+ .text-amber-600 {
1907
+ --tw-text-opacity: 1;
1908
+ color: rgb(217 119 6 / var(--tw-text-opacity));
1909
+ }
1910
+
1867
1911
  .text-black {
1868
1912
  --tw-text-opacity: 1;
1869
1913
  color: rgb(0 0 0 / var(--tw-text-opacity));
@@ -1879,6 +1923,11 @@ video {
1879
1923
  color: rgb(30 64 175 / var(--tw-text-opacity));
1880
1924
  }
1881
1925
 
1926
+ .text-emerald-600 {
1927
+ --tw-text-opacity: 1;
1928
+ color: rgb(5 150 105 / var(--tw-text-opacity));
1929
+ }
1930
+
1882
1931
  .text-forest-green-500 {
1883
1932
  --tw-text-opacity: 1;
1884
1933
  color: rgb(0 242 58 / var(--tw-text-opacity));
@@ -1959,6 +2008,21 @@ video {
1959
2008
  color: rgb(7 89 133 / var(--tw-text-opacity));
1960
2009
  }
1961
2010
 
2011
+ .text-slate-500 {
2012
+ --tw-text-opacity: 1;
2013
+ color: rgb(100 116 139 / var(--tw-text-opacity));
2014
+ }
2015
+
2016
+ .text-slate-700 {
2017
+ --tw-text-opacity: 1;
2018
+ color: rgb(51 65 85 / var(--tw-text-opacity));
2019
+ }
2020
+
2021
+ .text-slate-800 {
2022
+ --tw-text-opacity: 1;
2023
+ color: rgb(30 41 59 / var(--tw-text-opacity));
2024
+ }
2025
+
1962
2026
  .text-ultramarine-600 {
1963
2027
  --tw-text-opacity: 1;
1964
2028
  color: rgb(24 35 255 / var(--tw-text-opacity));
@@ -1974,6 +2038,11 @@ video {
1974
2038
  color: rgb(220 73 73 / var(--tw-text-opacity));
1975
2039
  }
1976
2040
 
2041
+ .text-violet-600 {
2042
+ --tw-text-opacity: 1;
2043
+ color: rgb(124 58 237 / var(--tw-text-opacity));
2044
+ }
2045
+
1977
2046
  .text-white {
1978
2047
  --tw-text-opacity: 1;
1979
2048
  color: rgb(255 255 255 / var(--tw-text-opacity));
@@ -1983,6 +2052,10 @@ video {
1983
2052
  accent-color: #0284c7;
1984
2053
  }
1985
2054
 
2055
+ .opacity-0 {
2056
+ opacity: 0;
2057
+ }
2058
+
1986
2059
  .opacity-25 {
1987
2060
  opacity: 0.25;
1988
2061
  }
@@ -2117,12 +2190,22 @@ video {
2117
2190
  transition-duration: 150ms;
2118
2191
  }
2119
2192
 
2193
+ .transition-opacity {
2194
+ transition-property: opacity;
2195
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
2196
+ transition-duration: 150ms;
2197
+ }
2198
+
2120
2199
  .transition-transform {
2121
2200
  transition-property: transform;
2122
2201
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
2123
2202
  transition-duration: 150ms;
2124
2203
  }
2125
2204
 
2205
+ .duration-150 {
2206
+ transition-duration: 150ms;
2207
+ }
2208
+
2126
2209
  .duration-200 {
2127
2210
  transition-duration: 200ms;
2128
2211
  }
@@ -2320,6 +2403,11 @@ video {
2320
2403
  color: rgb(55 65 81 / var(--tw-text-opacity));
2321
2404
  }
2322
2405
 
2406
+ .hover\:text-slate-700:hover {
2407
+ --tw-text-opacity: 1;
2408
+ color: rgb(51 65 85 / var(--tw-text-opacity));
2409
+ }
2410
+
2323
2411
  .hover\:text-ultramarine-900:hover {
2324
2412
  --tw-text-opacity: 1;
2325
2413
  color: rgb(6 14 172 / var(--tw-text-opacity));
@@ -2438,6 +2526,10 @@ video {
2438
2526
  --tw-ring-offset-color: #1f2937;
2439
2527
  }
2440
2528
 
2529
+ .focus-visible\:opacity-100:focus-visible {
2530
+ opacity: 1;
2531
+ }
2532
+
2441
2533
  .focus-visible\:outline:focus-visible {
2442
2534
  outline-style: solid;
2443
2535
  }
@@ -2494,6 +2586,21 @@ video {
2494
2586
  outline-color: #1823ff;
2495
2587
  }
2496
2588
 
2589
+ .focus-visible\:ring-2:focus-visible {
2590
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2591
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2592
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2593
+ }
2594
+
2595
+ .focus-visible\:ring-slate-400:focus-visible {
2596
+ --tw-ring-opacity: 1;
2597
+ --tw-ring-color: rgb(148 163 184 / var(--tw-ring-opacity));
2598
+ }
2599
+
2600
+ .focus-visible\:ring-offset-1:focus-visible {
2601
+ --tw-ring-offset-width: 1px;
2602
+ }
2603
+
2497
2604
  .disabled\:cursor-not-allowed:disabled {
2498
2605
  cursor: not-allowed;
2499
2606
  }
@@ -2522,6 +2629,10 @@ video {
2522
2629
  opacity: 0.5;
2523
2630
  }
2524
2631
 
2632
+ .group:hover .group-hover\:opacity-100 {
2633
+ opacity: 1;
2634
+ }
2635
+
2525
2636
  @media (min-width: 640px) {
2526
2637
  .sm\:-mx-6 {
2527
2638
  margin-left: -1.5rem;
@@ -1,4 +1,18 @@
1
- <div class="list-json tooltip">
2
- <pre><code ref="JSONCode" class="language-javascript">{{shortenValue}}</code></pre>
1
+ <div class="tooltip w-full font-mono text-sm py-3 text-slate-800">
2
+ <div class="w-full">
3
+ <json-node
4
+ :node-key="null"
5
+ :value="value"
6
+ :level="0"
7
+ :is-last="true"
8
+ path="root"
9
+ :toggle-collapse="toggleCollapse"
10
+ :is-collapsed="isPathCollapsed"
11
+ :create-child-path="createChildPath"
12
+ :indent-size="indentSize"
13
+ :max-top-level-fields="maxTopLevelFields"
14
+ :top-level-expanded="topLevelExpanded"
15
+ :expand-top-level="expandTopLevel"
16
+ ></json-node>
17
+ </div>
3
18
  </div>
4
-
@@ -1,40 +1,365 @@
1
1
  'use strict';
2
2
 
3
- const api = require('../api');
4
3
  const template = require('./list-json.html');
5
4
 
6
- const vanillatoast = require('vanillatoasts');
7
-
8
- require('../appendCSS')(require('./list-json.css'));
5
+ const JsonNodeTemplate = `
6
+ <div>
7
+ <div class="flex items-baseline whitespace-pre" :style="indentStyle">
8
+ <button
9
+ v-if="showToggle"
10
+ type="button"
11
+ class="w-4 h-4 mr-1 inline-flex items-center justify-center leading-none text-gray-500 hover:text-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400 cursor-pointer"
12
+ @click.stop="handleToggle"
13
+ >
14
+ {{ isCollapsedNode ? '+' : '-' }}
15
+ </button>
16
+ <span v-else class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible flex-shrink-0"></span>
17
+ <template v-if="hasKey">
18
+ <span class="text-blue-600">"{{ nodeKey }}"</span><span>: </span>
19
+ </template>
20
+ <template v-if="isComplex">
21
+ <template v-if="hasChildren">
22
+ <span>{{ openingBracket }}</span>
23
+ <span v-if="isCollapsedNode" class="mx-1">…</span>
24
+ <span v-if="isCollapsedNode">{{ closingBracket }}{{ comma }}</span>
25
+ </template>
26
+ <template v-else>
27
+ <span>{{ openingBracket }}{{ closingBracket }}{{ comma }}</span>
28
+ </template>
29
+ </template>
30
+ <template v-else>
31
+ <!--
32
+ If value is a string and overflows its container (i.e. goes over one line), show an ellipsis.
33
+ This is done via CSS ellipsis strategy.
34
+ -->
35
+ <span
36
+ :class="valueClasses"
37
+ :style="typeof value === 'string'
38
+ ? {
39
+ display: 'inline-block',
40
+ maxWidth: '100%',
41
+ overflow: 'hidden',
42
+ textOverflow: 'ellipsis',
43
+ whiteSpace: 'nowrap',
44
+ verticalAlign: 'bottom'
45
+ }
46
+ : {}"
47
+ :title="typeof value === 'string' && $el && $el.scrollWidth > $el.clientWidth ? value : undefined"
48
+ >
49
+ {{ formattedValue }}{{ comma }}
50
+ </span>
51
+ </template>
52
+ </div>
53
+ <template v-if="isComplex && hasChildren && !isCollapsedNode">
54
+ <json-node
55
+ v-for="child in children"
56
+ :key="child.path"
57
+ :node-key="child.displayKey"
58
+ :value="child.value"
59
+ :level="level + 1"
60
+ :is-last="child.isLast"
61
+ :path="child.path"
62
+ :toggle-collapse="toggleCollapse"
63
+ :is-collapsed="isCollapsed"
64
+ :create-child-path="createChildPath"
65
+ :indent-size="indentSize"
66
+ :max-top-level-fields="maxTopLevelFields"
67
+ :top-level-expanded="topLevelExpanded"
68
+ :expand-top-level="expandTopLevel"
69
+ ></json-node>
70
+ <div
71
+ v-if="hasHiddenRootChildren"
72
+ class="flex items-baseline whitespace-pre"
73
+ :style="indentStyle"
74
+ >
75
+ <span class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible"></span>
76
+ <button
77
+ type="button"
78
+ class="text-xs inline-flex items-center gap-1 ml-4 text-slate-500 hover:text-slate-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400"
79
+ :title="hiddenChildrenTooltip"
80
+ @click.stop="handleExpandTopLevel"
81
+ >
82
+ <span aria-hidden="true">{{hiddenChildrenLabel}}…</span>
83
+ </button>
84
+ </div>
85
+ <div class="flex items-baseline whitespace-pre" :style="indentStyle">
86
+ <span class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible"></span>
87
+ <span>{{ closingBracket }}{{ comma }}</span>
88
+ </div>
89
+ </template>
90
+ </div>
91
+ `;
9
92
 
10
93
  module.exports = app => app.component('list-json', {
11
94
  template: template,
12
95
  props: ['value'],
13
- computed: {
14
- shortenValue() {
15
- return JSON.stringify(this.value, null, 4);
96
+ data() {
97
+ return {
98
+ collapsedMap: {},
99
+ indentSize: 16,
100
+ maxTopLevelFields: 15,
101
+ topLevelExpanded: false
102
+ };
103
+ },
104
+ watch: {
105
+ value: {
106
+ handler() {
107
+ this.resetCollapse();
108
+ }
16
109
  }
17
110
  },
111
+ created() {
112
+ this.resetCollapse();
113
+ },
18
114
  methods: {
19
- copyText(value) {
20
- const storage = document.createElement('textarea');
21
- storage.value = JSON.stringify(value);
22
- const elem = this.$refs.JSONCode;
23
- elem.appendChild(storage);
24
- storage.select();
25
- storage.setSelectionRange(0, 99999);
26
- document.execCommand('copy');
27
- elem.removeChild(storage);
28
- vanillatoast.create({
29
- title: 'Text copied!',
30
- type: 'success',
31
- timeout: 3000,
32
- icon: 'images/success.png',
33
- positionClass: 'bottomRight'
34
- });
115
+ resetCollapse() {
116
+ this.collapsedMap = {};
117
+ this.topLevelExpanded = false;
118
+ },
119
+ toggleCollapse(path) {
120
+ const current = this.isPathCollapsed(path);
121
+ this.collapsedMap = Object.assign({}, this.collapsedMap, { [path]: !current });
122
+ },
123
+ isPathCollapsed(path) {
124
+ if (path === 'root') {
125
+ return false;
126
+ }
127
+ if (Object.prototype.hasOwnProperty.call(this.collapsedMap, path)) {
128
+ return this.collapsedMap[path];
129
+ }
130
+ return true;
131
+ },
132
+ createChildPath(parentPath, childKey, isArray) {
133
+ if (parentPath == null || parentPath === '') {
134
+ return isArray ? `[${childKey}]` : `${childKey}`;
135
+ }
136
+ if (parentPath === 'root') {
137
+ return isArray ? `root[${childKey}]` : `root.${childKey}`;
138
+ }
139
+ if (isArray) {
140
+ return `${parentPath}[${childKey}]`;
141
+ }
142
+ return `${parentPath}.${childKey}`;
143
+ },
144
+ expandTopLevel() {
145
+ this.topLevelExpanded = true;
35
146
  }
36
147
  },
37
- mounted: function() {
38
- Prism.highlightElement(this.$refs.JSONCode);
148
+ components: {
149
+ JsonNode: {
150
+ name: 'JsonNode',
151
+ template: JsonNodeTemplate,
152
+ props: {
153
+ nodeKey: {
154
+ type: [String, Number],
155
+ default: null
156
+ },
157
+ value: {
158
+ required: true
159
+ },
160
+ level: {
161
+ type: Number,
162
+ required: true
163
+ },
164
+ isLast: {
165
+ type: Boolean,
166
+ default: false
167
+ },
168
+ path: {
169
+ type: String,
170
+ required: true
171
+ },
172
+ toggleCollapse: {
173
+ type: Function,
174
+ required: true
175
+ },
176
+ isCollapsed: {
177
+ type: Function,
178
+ required: true
179
+ },
180
+ createChildPath: {
181
+ type: Function,
182
+ required: true
183
+ },
184
+ indentSize: {
185
+ type: Number,
186
+ required: true
187
+ },
188
+ maxTopLevelFields: {
189
+ type: Number,
190
+ default: null
191
+ },
192
+ topLevelExpanded: {
193
+ type: Boolean,
194
+ default: false
195
+ },
196
+ expandTopLevel: {
197
+ type: Function,
198
+ default: null
199
+ }
200
+ },
201
+ computed: {
202
+ hasKey() {
203
+ return this.nodeKey !== null && this.nodeKey !== undefined;
204
+ },
205
+ isRoot() {
206
+ return this.path === 'root';
207
+ },
208
+ isArray() {
209
+ return Array.isArray(this.value);
210
+ },
211
+ isObject() {
212
+ if (this.value === null || this.isArray) {
213
+ return false;
214
+ }
215
+ return Object.prototype.toString.call(this.value) === '[object Object]';
216
+ },
217
+ isComplex() {
218
+ return this.isArray || this.isObject;
219
+ },
220
+ children() {
221
+ if (!this.isComplex) {
222
+ return [];
223
+ }
224
+ if (this.isArray) {
225
+ return this.value.map((childValue, index) => ({
226
+ displayKey: null,
227
+ value: childValue,
228
+ isLast: index === this.value.length - 1,
229
+ path: this.createChildPath(this.path, index, true)
230
+ }));
231
+ }
232
+ const keys = Object.keys(this.value);
233
+ const visibleKeys = this.visibleObjectKeys(keys);
234
+ const hasHidden = this.hasHiddenRootChildren;
235
+ return visibleKeys.map((key, index) => ({
236
+ displayKey: key,
237
+ value: this.value[key],
238
+ isLast: !hasHidden && index === visibleKeys.length - 1,
239
+ path: this.createChildPath(this.path, key, false)
240
+ }));
241
+ },
242
+ hasChildren() {
243
+ return this.children.length > 0;
244
+ },
245
+ totalObjectChildCount() {
246
+ if (!this.isObject) {
247
+ return 0;
248
+ }
249
+ return Object.keys(this.value).length;
250
+ },
251
+ hasHiddenRootChildren() {
252
+ if (!this.isRoot || !this.isObject) {
253
+ return false;
254
+ }
255
+ if (this.topLevelExpanded) {
256
+ return false;
257
+ }
258
+ if (typeof this.maxTopLevelFields !== 'number') {
259
+ return false;
260
+ }
261
+ return this.totalObjectChildCount > this.maxTopLevelFields;
262
+ },
263
+ hiddenRootChildrenCount() {
264
+ if (!this.hasHiddenRootChildren) {
265
+ return 0;
266
+ }
267
+ return this.totalObjectChildCount - this.maxTopLevelFields;
268
+ },
269
+ showToggle() {
270
+ return this.hasChildren && !this.isRoot;
271
+ },
272
+ openingBracket() {
273
+ return this.isArray ? '[' : '{';
274
+ },
275
+ closingBracket() {
276
+ return this.isArray ? ']' : '}';
277
+ },
278
+ isCollapsedNode() {
279
+ return this.isCollapsed(this.path);
280
+ },
281
+ formattedValue() {
282
+ if (typeof this.value === 'bigint') {
283
+ return `${this.value.toString()}n`;
284
+ }
285
+ const stringified = JSON.stringify(this.value);
286
+ if (stringified === undefined) {
287
+ if (typeof this.value === 'symbol') {
288
+ return this.value.toString();
289
+ }
290
+ return String(this.value);
291
+ }
292
+ return stringified;
293
+ },
294
+ valueClasses() {
295
+ const classes = ['text-slate-700'];
296
+ if (this.value === null) {
297
+ classes.push('text-gray-500', 'italic');
298
+ return classes;
299
+ }
300
+ const type = typeof this.value;
301
+ if (type === 'string') {
302
+ classes.push('text-emerald-600');
303
+ return classes;
304
+ }
305
+ if (type === 'number' || type === 'bigint') {
306
+ classes.push('text-amber-600');
307
+ return classes;
308
+ }
309
+ if (type === 'boolean') {
310
+ classes.push('text-violet-600');
311
+ return classes;
312
+ }
313
+ if (type === 'undefined') {
314
+ classes.push('text-gray-500');
315
+ return classes;
316
+ }
317
+ return classes;
318
+ },
319
+ comma() {
320
+ return this.isLast ? '' : ',';
321
+ },
322
+ indentStyle() {
323
+ return {
324
+ paddingLeft: `${this.level * this.indentSize}px`
325
+ };
326
+ },
327
+ hiddenChildrenLabel() {
328
+ if (!this.hasHiddenRootChildren) {
329
+ return '';
330
+ }
331
+ const count = this.hiddenRootChildrenCount;
332
+ const suffix = count === 1 ? 'field' : 'fields';
333
+ return `${count} more ${suffix}`;
334
+ },
335
+ hiddenChildrenTooltip() {
336
+ return this.hiddenChildrenLabel;
337
+ }
338
+ },
339
+ methods: {
340
+ visibleObjectKeys(keys) {
341
+ if (!this.isRoot || this.topLevelExpanded) {
342
+ return keys;
343
+ }
344
+ if (typeof this.maxTopLevelFields !== 'number') {
345
+ return keys;
346
+ }
347
+ if (keys.length <= this.maxTopLevelFields) {
348
+ return keys;
349
+ }
350
+ return keys.slice(0, this.maxTopLevelFields);
351
+ },
352
+ handleToggle() {
353
+ if (!this.isRoot) {
354
+ this.toggleCollapse(this.path);
355
+ }
356
+ },
357
+ handleExpandTopLevel() {
358
+ if (this.isRoot && typeof this.expandTopLevel === 'function') {
359
+ this.expandTopLevel();
360
+ }
361
+ }
362
+ }
363
+ }
39
364
  }
40
365
  });
@@ -136,3 +136,4 @@ td {
136
136
  justify-content: space-around;
137
137
  align-items: center;
138
138
  }
139
+
@@ -98,14 +98,14 @@
98
98
  </button>
99
99
  <span class="isolate inline-flex rounded-md shadow-sm">
100
100
  <button
101
- @click="outputType = 'table'"
101
+ @click="setOutputType('table')"
102
102
  type="button"
103
103
  class="relative inline-flex items-center rounded-none rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
104
104
  :class="outputType === 'table' ? 'bg-gray-200' : 'bg-white'">
105
105
  <img class="h-5 w-5" src="images/table.svg">
106
106
  </button>
107
107
  <button
108
- @click="outputType = 'json'"
108
+ @click="setOutputType('json')"
109
109
  type="button"
110
110
  class="relative -ml-px inline-flex items-center rounded-none rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
111
111
  :class="outputType === 'json' ? 'bg-gray-200' : 'bg-white'">
@@ -139,8 +139,23 @@
139
139
  </tr>
140
140
  </tbody>
141
141
  </table>
142
- <div v-if="outputType === 'json'">
143
- <div v-for="document in documents" @click="handleDocumentClick(document, $event)" :key="document._id" :class="{ 'bg-blue-200': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }">
142
+ <div v-if="outputType === 'json'" class="flex flex-col space-y-6">
143
+ <div
144
+ v-for="document in documents"
145
+ :key="document._id"
146
+ @click="handleDocumentContainerClick(document, $event)"
147
+ :class="[
148
+ 'group relative transition-colors',
149
+ selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:bg-slate-100'
150
+ ]"
151
+ >
152
+ <button
153
+ type="button"
154
+ class="absolute top-2 right-2 z-10 inline-flex items-center rounded bg-ultramarine-600 px-2 py-1 text-xs font-semibold text-white shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600"
155
+ @click.stop="openDocument(document)"
156
+ >
157
+ Open this Document
158
+ </button>
144
159
  <list-json :value="filterDocument(document)">
145
160
  </list-json>
146
161
  </div>