@mongoosejs/studio 0.0.130 → 0.0.133
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/build.js +18 -0
- package/frontend/public/app.js +10518 -10436
- package/frontend/public/tw.css +29 -0
- package/frontend/src/detail-default/detail-default.html +2 -2
- package/frontend/src/detail-default/detail-default.js +11 -2
- package/frontend/src/document/confirm-delete/confirm-delete.html +17 -17
- package/frontend/src/document-details/document-details.html +21 -8
- package/frontend/src/document-details/document-details.js +89 -22
- package/frontend/src/document-details/document-property/document-property.css +0 -15
- package/frontend/src/document-details/document-property/document-property.html +15 -14
- package/frontend/src/document-details/document-property/document-property.js +2 -2
- package/frontend/src/modal/modal.css +8 -0
- package/package.json +1 -1
- package/astra.js +0 -159
- package/valnotes.md +0 -2
package/frontend/public/tw.css
CHANGED
|
@@ -1399,6 +1399,11 @@ video {
|
|
|
1399
1399
|
border-style: none;
|
|
1400
1400
|
}
|
|
1401
1401
|
|
|
1402
|
+
.border-amber-400 {
|
|
1403
|
+
--tw-border-opacity: 1;
|
|
1404
|
+
border-color: rgb(251 191 36 / var(--tw-border-opacity));
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1402
1407
|
.border-blue-300 {
|
|
1403
1408
|
--tw-border-opacity: 1;
|
|
1404
1409
|
border-color: rgb(147 197 253 / var(--tw-border-opacity));
|
|
@@ -1433,6 +1438,11 @@ video {
|
|
|
1433
1438
|
border-color: rgb(63 83 255 / var(--tw-border-opacity));
|
|
1434
1439
|
}
|
|
1435
1440
|
|
|
1441
|
+
.bg-amber-100 {
|
|
1442
|
+
--tw-bg-opacity: 1;
|
|
1443
|
+
background-color: rgb(254 243 199 / var(--tw-bg-opacity));
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1436
1446
|
.bg-black {
|
|
1437
1447
|
--tw-bg-opacity: 1;
|
|
1438
1448
|
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
|
|
@@ -2044,6 +2054,11 @@ video {
|
|
|
2044
2054
|
--tw-ring-inset: inset;
|
|
2045
2055
|
}
|
|
2046
2056
|
|
|
2057
|
+
.ring-amber-200 {
|
|
2058
|
+
--tw-ring-opacity: 1;
|
|
2059
|
+
--tw-ring-color: rgb(253 230 138 / var(--tw-ring-opacity));
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2047
2062
|
.ring-black {
|
|
2048
2063
|
--tw-ring-opacity: 1;
|
|
2049
2064
|
--tw-ring-color: rgb(0 0 0 / var(--tw-ring-opacity));
|
|
@@ -2074,6 +2089,10 @@ video {
|
|
|
2074
2089
|
--tw-ring-opacity: 0.05;
|
|
2075
2090
|
}
|
|
2076
2091
|
|
|
2092
|
+
.ring-offset-1 {
|
|
2093
|
+
--tw-ring-offset-width: 1px;
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2077
2096
|
.filter {
|
|
2078
2097
|
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
|
2079
2098
|
}
|
|
@@ -2141,6 +2160,16 @@ video {
|
|
|
2141
2160
|
--tw-ring-color: rgb(0 168 165 / var(--tw-ring-opacity));
|
|
2142
2161
|
}
|
|
2143
2162
|
|
|
2163
|
+
.hover\:translate-x-0:hover {
|
|
2164
|
+
--tw-translate-x: 0px;
|
|
2165
|
+
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
.hover\:translate-x-0\.5:hover {
|
|
2169
|
+
--tw-translate-x: 0.125rem;
|
|
2170
|
+
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2144
2173
|
.hover\:border-gray-300:hover {
|
|
2145
2174
|
--tw-border-opacity: 1;
|
|
2146
2175
|
border-color: rgb(209 213 219 / var(--tw-border-opacity));
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
<div>
|
|
2
|
-
{{
|
|
1
|
+
<div class="w-full">
|
|
2
|
+
<pre class="w-full whitespace-pre-wrap break-words font-mono text-sm text-gray-700 m-0">{{displayValue}}</pre>
|
|
3
3
|
</div>
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const template = require('./detail-default.html');
|
|
4
|
-
|
|
5
4
|
module.exports = app => app.component('detail-default', {
|
|
6
5
|
template: template,
|
|
7
6
|
props: ['value'],
|
|
@@ -13,7 +12,17 @@ module.exports = app => app.component('detail-default', {
|
|
|
13
12
|
if (this.value === undefined) {
|
|
14
13
|
return 'undefined';
|
|
15
14
|
}
|
|
16
|
-
|
|
15
|
+
if (typeof this.value === 'string') {
|
|
16
|
+
return this.value;
|
|
17
|
+
}
|
|
18
|
+
if (typeof this.value === 'number' || typeof this.value === 'boolean' || typeof this.value === 'bigint') {
|
|
19
|
+
return String(this.value);
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
return JSON.stringify(this.value, null, 2);
|
|
23
|
+
} catch (err) {
|
|
24
|
+
return String(this.value);
|
|
25
|
+
}
|
|
17
26
|
}
|
|
18
27
|
}
|
|
19
28
|
});
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
<div>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
</div>
|
|
2
|
+
<h2>
|
|
3
|
+
Are you sure you want to delete the following document?
|
|
4
|
+
</h2>
|
|
5
|
+
<pre class="max-h-[50vh] overflow-auto"><code ref="code" class="language-javascript" v-text="displayValue"></code></pre>
|
|
6
|
+
<div class="flex gap-2 mt-2">
|
|
7
|
+
<async-button
|
|
8
|
+
class="rounded-md bg-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600"
|
|
9
|
+
@click="startDelete">
|
|
10
|
+
Confirm
|
|
11
|
+
</async-button>
|
|
12
|
+
<button
|
|
13
|
+
class="rounded-md bg-gray-200 px-2.5 py-1.5 text-sm font-semibold text-black shadow-sm hover:bg-gray-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-300"
|
|
14
|
+
@click="closeDelete">
|
|
15
|
+
Cancel
|
|
16
|
+
</button>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
</div>
|
|
22
22
|
|
|
23
23
|
<!-- Data Type Filter -->
|
|
24
|
-
<div class="relative">
|
|
24
|
+
<div class="relative hidden md:block">
|
|
25
25
|
<select
|
|
26
26
|
v-model="selectedType"
|
|
27
|
-
class="
|
|
27
|
+
class="px-4 py-2 pr-8 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white min-w-[140px] appearance-none"
|
|
28
28
|
>
|
|
29
29
|
<option value="">All Types</option>
|
|
30
30
|
<option v-for="type in availableTypes" :key="type" :value="type">
|
|
@@ -41,12 +41,11 @@
|
|
|
41
41
|
<!-- Add Field Button -->
|
|
42
42
|
<button
|
|
43
43
|
@click="openAddFieldModal"
|
|
44
|
-
class="hidden md:
|
|
44
|
+
class="hidden md:flex px-4 py-2 text-sm font-medium text-white bg-green-600 hover:bg-green-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 items-center gap-2"
|
|
45
45
|
>
|
|
46
46
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
47
47
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
|
48
|
-
</svg>
|
|
49
|
-
Add Field
|
|
48
|
+
</svg> Add Field
|
|
50
49
|
</button>
|
|
51
50
|
</div>
|
|
52
51
|
</div>
|
|
@@ -54,18 +53,32 @@
|
|
|
54
53
|
<!-- Fields View -->
|
|
55
54
|
<div v-if="viewMode === 'fields'">
|
|
56
55
|
<!-- Schema Paths -->
|
|
57
|
-
<div
|
|
56
|
+
<div
|
|
57
|
+
v-for="path in filteredSchemaPaths"
|
|
58
|
+
:key="path.path || path"
|
|
59
|
+
class="value"
|
|
60
|
+
>
|
|
58
61
|
<document-property
|
|
59
62
|
:path="path"
|
|
60
63
|
:document="document"
|
|
61
64
|
:schemaPaths="schemaPaths"
|
|
62
65
|
:editting="editting"
|
|
63
66
|
:changes="changes"
|
|
67
|
+
:highlight="isSchemaPathMatched(path)"
|
|
64
68
|
:invalid="invalid"></document-property>
|
|
65
69
|
</div>
|
|
66
70
|
|
|
67
71
|
<!-- Virtual Fields -->
|
|
68
|
-
<div
|
|
72
|
+
<div
|
|
73
|
+
v-for="path in filteredVirtuals"
|
|
74
|
+
:key="path.name"
|
|
75
|
+
class="border rounded-lg mb-2 transition-all duration-200 ease-in-out"
|
|
76
|
+
:class="[
|
|
77
|
+
isVirtualMatched(path)
|
|
78
|
+
? 'border-amber-400 ring-2 ring-amber-200 ring-offset-1'
|
|
79
|
+
: 'border-gray-200'
|
|
80
|
+
]"
|
|
81
|
+
>
|
|
69
82
|
<!-- Virtual Field Header (Always Visible) -->
|
|
70
83
|
<div
|
|
71
84
|
@click="toggleVirtualField(path.name)"
|
|
@@ -99,7 +112,7 @@
|
|
|
99
112
|
</div>
|
|
100
113
|
|
|
101
114
|
<!-- No Results Message -->
|
|
102
|
-
<div v-if="searchQuery
|
|
115
|
+
<div v-if="searchQuery.trim() && !hasSearchMatches" class="text-center py-8 text-gray-500">
|
|
103
116
|
<svg class="w-12 h-12 mx-auto mb-4 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
104
117
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 12h6m-6-4h6m2 5.291A7.962 7.962 0 0112 15c-2.34 0-4.29-1.009-5.824-2.709M15 6.291A7.962 7.962 0 0012 5c-2.34 0-4.29 1.009-5.824 2.709"></path>
|
|
105
118
|
</svg>
|
|
@@ -115,38 +115,39 @@ module.exports = app => app.component('document-details', {
|
|
|
115
115
|
shouldUseDatePicker() {
|
|
116
116
|
return this.fieldData.type === 'Date';
|
|
117
117
|
},
|
|
118
|
-
|
|
118
|
+
typeFilteredSchemaPaths() {
|
|
119
119
|
let paths = this.schemaPaths || [];
|
|
120
120
|
|
|
121
|
-
// Filter by search query
|
|
122
|
-
if (this.searchQuery.trim()) {
|
|
123
|
-
const query = this.searchQuery.toLowerCase();
|
|
124
|
-
paths = paths.filter(path =>
|
|
125
|
-
path.path.toLowerCase().includes(query)
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Filter by data type
|
|
130
121
|
if (this.selectedType) {
|
|
131
|
-
paths = paths.filter(path =>
|
|
132
|
-
path.instance === this.selectedType
|
|
133
|
-
);
|
|
122
|
+
paths = paths.filter(path => path.instance === this.selectedType);
|
|
134
123
|
}
|
|
135
124
|
|
|
136
125
|
return paths;
|
|
137
126
|
},
|
|
138
|
-
|
|
139
|
-
|
|
127
|
+
filteredSchemaPaths() {
|
|
128
|
+
const paths = this.typeFilteredSchemaPaths.slice();
|
|
140
129
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const query = this.searchQuery.toLowerCase();
|
|
144
|
-
virtuals = virtuals.filter(virtual =>
|
|
145
|
-
virtual.name.toLowerCase().includes(query)
|
|
146
|
-
);
|
|
130
|
+
if (!this.searchQuery.trim()) {
|
|
131
|
+
return paths;
|
|
147
132
|
}
|
|
148
133
|
|
|
149
|
-
|
|
134
|
+
const query = this.searchQuery.toLowerCase();
|
|
135
|
+
const matches = [];
|
|
136
|
+
const nonMatches = [];
|
|
137
|
+
|
|
138
|
+
paths.forEach(path => {
|
|
139
|
+
if (path.path.toLowerCase().includes(query)) {
|
|
140
|
+
matches.push(path);
|
|
141
|
+
} else {
|
|
142
|
+
nonMatches.push(path);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return matches.concat(nonMatches);
|
|
147
|
+
},
|
|
148
|
+
typeFilteredVirtuals() {
|
|
149
|
+
let virtuals = this.virtuals;
|
|
150
|
+
|
|
150
151
|
if (this.selectedType) {
|
|
151
152
|
virtuals = virtuals.filter(virtual => {
|
|
152
153
|
const virtualType = this.getVirtualFieldType(virtual);
|
|
@@ -156,6 +157,58 @@ module.exports = app => app.component('document-details', {
|
|
|
156
157
|
|
|
157
158
|
return virtuals;
|
|
158
159
|
},
|
|
160
|
+
filteredVirtuals() {
|
|
161
|
+
const virtuals = this.typeFilteredVirtuals.slice();
|
|
162
|
+
|
|
163
|
+
if (!this.searchQuery.trim()) {
|
|
164
|
+
return virtuals;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const query = this.searchQuery.toLowerCase();
|
|
168
|
+
const matches = [];
|
|
169
|
+
const nonMatches = [];
|
|
170
|
+
|
|
171
|
+
virtuals.forEach(virtual => {
|
|
172
|
+
if (virtual.name.toLowerCase().includes(query)) {
|
|
173
|
+
matches.push(virtual);
|
|
174
|
+
} else {
|
|
175
|
+
nonMatches.push(virtual);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
return matches.concat(nonMatches);
|
|
180
|
+
},
|
|
181
|
+
schemaSearchMatchSet() {
|
|
182
|
+
if (!this.searchQuery.trim()) {
|
|
183
|
+
return new Set();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const query = this.searchQuery.toLowerCase();
|
|
187
|
+
return new Set(
|
|
188
|
+
this.typeFilteredSchemaPaths
|
|
189
|
+
.filter(path => path.path.toLowerCase().includes(query))
|
|
190
|
+
.map(path => path.path)
|
|
191
|
+
);
|
|
192
|
+
},
|
|
193
|
+
virtualSearchMatchSet() {
|
|
194
|
+
if (!this.searchQuery.trim()) {
|
|
195
|
+
return new Set();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const query = this.searchQuery.toLowerCase();
|
|
199
|
+
return new Set(
|
|
200
|
+
this.typeFilteredVirtuals
|
|
201
|
+
.filter(virtual => virtual.name.toLowerCase().includes(query))
|
|
202
|
+
.map(virtual => virtual.name)
|
|
203
|
+
);
|
|
204
|
+
},
|
|
205
|
+
hasSearchMatches() {
|
|
206
|
+
if (!this.searchQuery.trim()) {
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return this.schemaSearchMatchSet.size > 0 || this.virtualSearchMatchSet.size > 0;
|
|
211
|
+
},
|
|
159
212
|
formattedJson() {
|
|
160
213
|
if (!this.document) {
|
|
161
214
|
return '{}';
|
|
@@ -171,6 +224,20 @@ module.exports = app => app.component('document-details', {
|
|
|
171
224
|
this.collapsedVirtuals.add(fieldName);
|
|
172
225
|
}
|
|
173
226
|
},
|
|
227
|
+
isSchemaPathMatched(path) {
|
|
228
|
+
if (!path) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return this.schemaSearchMatchSet.has(path.path);
|
|
233
|
+
},
|
|
234
|
+
isVirtualMatched(virtual) {
|
|
235
|
+
if (!virtual) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return this.virtualSearchMatchSet.has(virtual.name);
|
|
240
|
+
},
|
|
174
241
|
isVirtualFieldCollapsed(fieldName) {
|
|
175
242
|
return this.collapsedVirtuals.has(fieldName);
|
|
176
243
|
},
|
|
@@ -21,18 +21,3 @@
|
|
|
21
21
|
float: right;
|
|
22
22
|
margin-top: -7px;
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
.truncated-value-container,
|
|
26
|
-
.expanded-value-container {
|
|
27
|
-
position: relative;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.truncated-value-container button,
|
|
31
|
-
.expanded-value-container button {
|
|
32
|
-
transition: all 0.2s ease;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.truncated-value-container button:hover,
|
|
36
|
-
.expanded-value-container button:hover {
|
|
37
|
-
transform: translateX(2px);
|
|
38
|
-
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
<div class="border border-gray-200 rounded-lg mb-2">
|
|
2
2
|
<!-- Collapsible Header -->
|
|
3
|
-
<div
|
|
3
|
+
<div
|
|
4
4
|
@click="toggleCollapse"
|
|
5
|
-
class="p-3
|
|
5
|
+
class="p-3 hover:bg-gray-100 cursor-pointer flex items-center justify-between border-b border-gray-200 transition-colors duration-200 ease-in-out"
|
|
6
|
+
:class="{ 'bg-amber-100': highlight, 'bg-gray-50': !highlight }"
|
|
6
7
|
>
|
|
7
|
-
<div class="flex items-center">
|
|
8
|
-
<svg
|
|
8
|
+
<div class="flex items-center" >
|
|
9
|
+
<svg
|
|
9
10
|
:class="isCollapsed ? 'rotate-0' : 'rotate-90'"
|
|
10
11
|
class="w-4 h-4 text-gray-500 mr-2 transition-transform duration-200"
|
|
11
|
-
fill="none"
|
|
12
|
-
stroke="currentColor"
|
|
12
|
+
fill="none"
|
|
13
|
+
stroke="currentColor"
|
|
13
14
|
viewBox="0 0 24 24"
|
|
14
15
|
>
|
|
15
16
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
</router-link>
|
|
28
29
|
</div>
|
|
29
30
|
</div>
|
|
30
|
-
|
|
31
|
+
|
|
31
32
|
<!-- Collapsible Content -->
|
|
32
33
|
<div v-if="!isCollapsed" class="p-3">
|
|
33
34
|
<!-- Date Type Selector (when editing dates) -->
|
|
@@ -53,7 +54,7 @@
|
|
|
53
54
|
</div>
|
|
54
55
|
</div>
|
|
55
56
|
</div>
|
|
56
|
-
|
|
57
|
+
|
|
57
58
|
<!-- Field Content -->
|
|
58
59
|
<div v-if="editting && path.path !== '_id'">
|
|
59
60
|
<component
|
|
@@ -67,11 +68,11 @@
|
|
|
67
68
|
</div>
|
|
68
69
|
<div v-else>
|
|
69
70
|
<!-- Show truncated or full value based on needsTruncation and isValueExpanded -->
|
|
70
|
-
<div v-if="needsTruncation && !isValueExpanded" class="
|
|
71
|
+
<div v-if="needsTruncation && !isValueExpanded" class="relative">
|
|
71
72
|
<div class="text-gray-700 whitespace-pre-wrap break-words font-mono text-sm">{{truncatedString}}</div>
|
|
72
|
-
<button
|
|
73
|
+
<button
|
|
73
74
|
@click="toggleValueExpansion"
|
|
74
|
-
class="mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1"
|
|
75
|
+
class="mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5"
|
|
75
76
|
>
|
|
76
77
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
77
78
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
@@ -79,11 +80,11 @@
|
|
|
79
80
|
Show more ({{valueAsString.length}} characters)
|
|
80
81
|
</button>
|
|
81
82
|
</div>
|
|
82
|
-
<div v-else-if="needsTruncation && isValueExpanded" class="
|
|
83
|
+
<div v-else-if="needsTruncation && isValueExpanded" class="relative">
|
|
83
84
|
<component :is="getComponentForPath(path)" :value="getValueForPath(path.path)"></component>
|
|
84
|
-
<button
|
|
85
|
+
<button
|
|
85
86
|
@click="toggleValueExpansion"
|
|
86
|
-
class="mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1"
|
|
87
|
+
class="mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5"
|
|
87
88
|
>
|
|
88
89
|
<svg class="w-4 h-4 rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
89
90
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
@@ -16,7 +16,7 @@ module.exports = app => app.component('document-property', {
|
|
|
16
16
|
isValueExpanded: false // Track if the value is expanded
|
|
17
17
|
};
|
|
18
18
|
},
|
|
19
|
-
props: ['path', 'document', 'schemaPaths', 'editting', 'changes', 'invalid'],
|
|
19
|
+
props: ['path', 'document', 'schemaPaths', 'editting', 'changes', 'invalid', 'highlight'],
|
|
20
20
|
computed: {
|
|
21
21
|
valueAsString() {
|
|
22
22
|
const value = this.getValueForPath(this.path.path);
|
|
@@ -94,4 +94,4 @@ module.exports = app => app.component('document-property', {
|
|
|
94
94
|
this.isValueExpanded = !this.isValueExpanded;
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
});
|
|
97
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongoosejs/studio",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.133",
|
|
4
4
|
"description": "A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.",
|
|
5
5
|
"homepage": "https://studio.mongoosejs.io/",
|
|
6
6
|
"repository": {
|
package/astra.js
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const mongoose = require('mongoose');
|
|
4
|
-
const { createAstraUri, driver, Vectorize } = require('@datastax/astra-mongoose');
|
|
5
|
-
const express = require('express');
|
|
6
|
-
const studio = require('./express');
|
|
7
|
-
|
|
8
|
-
const app = express();
|
|
9
|
-
|
|
10
|
-
const m = new mongoose.Mongoose();
|
|
11
|
-
m.setDriver(driver);
|
|
12
|
-
m.set('autoCreate', false);
|
|
13
|
-
m.set('autoIndex', false);
|
|
14
|
-
|
|
15
|
-
(function () {
|
|
16
|
-
const schema = new m.Schema({
|
|
17
|
-
email: {
|
|
18
|
-
type: String,
|
|
19
|
-
required: true,
|
|
20
|
-
unique: true
|
|
21
|
-
},
|
|
22
|
-
firstName: {
|
|
23
|
-
type: String,
|
|
24
|
-
required: true
|
|
25
|
-
},
|
|
26
|
-
lastName: {
|
|
27
|
-
type: String,
|
|
28
|
-
required: true
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
schema.virtual('displayName').get(function() {
|
|
33
|
-
return this.firstName + ' ' + this.lastName.slice(0, 1) + '.';
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
m.model('User', schema);
|
|
37
|
-
})();
|
|
38
|
-
|
|
39
|
-
(function () {
|
|
40
|
-
const schema = new m.Schema({
|
|
41
|
-
make: {
|
|
42
|
-
type: String,
|
|
43
|
-
required: true
|
|
44
|
-
},
|
|
45
|
-
model: {
|
|
46
|
-
type: String,
|
|
47
|
-
required: true
|
|
48
|
-
},
|
|
49
|
-
year: {
|
|
50
|
-
type: Number,
|
|
51
|
-
required: true,
|
|
52
|
-
validate: (v) => Number.isInteger(v) && v >= 1950
|
|
53
|
-
},
|
|
54
|
-
images: {
|
|
55
|
-
type: [String]
|
|
56
|
-
},
|
|
57
|
-
numReviews: {
|
|
58
|
-
type: Number,
|
|
59
|
-
required: true,
|
|
60
|
-
default: 0
|
|
61
|
-
},
|
|
62
|
-
averageReview: {
|
|
63
|
-
type: Number,
|
|
64
|
-
required: true,
|
|
65
|
-
default: 0
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
m.model('Vehicle', schema);
|
|
70
|
-
})();
|
|
71
|
-
|
|
72
|
-
(function () {
|
|
73
|
-
const schema = new m.Schema({
|
|
74
|
-
rating: {
|
|
75
|
-
type: Number,
|
|
76
|
-
required: true,
|
|
77
|
-
validate: (v) => Number.isInteger(v)
|
|
78
|
-
},
|
|
79
|
-
text: {
|
|
80
|
-
type: String,
|
|
81
|
-
required: true,
|
|
82
|
-
validate: (v) => v.length >= 30
|
|
83
|
-
},
|
|
84
|
-
userId: {
|
|
85
|
-
type: 'ObjectId',
|
|
86
|
-
required: true
|
|
87
|
-
},
|
|
88
|
-
vehicleId: {
|
|
89
|
-
type: 'ObjectId',
|
|
90
|
-
required: true
|
|
91
|
-
}
|
|
92
|
-
}, { timestamps: true });
|
|
93
|
-
|
|
94
|
-
schema.virtual('user', {
|
|
95
|
-
ref: 'User',
|
|
96
|
-
localField: 'userId',
|
|
97
|
-
foreignField: '_id',
|
|
98
|
-
justOne: true
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
schema.virtual('vehicle', {
|
|
102
|
-
ref: 'Vehicle',
|
|
103
|
-
localField: 'vehicleId',
|
|
104
|
-
foreignField: '_id',
|
|
105
|
-
justOne: true
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
schema.pre('save', async function updateVehicleRating() {
|
|
109
|
-
if (!this.isNew) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
const vehicle = await mongoose.model('Vehicle').findById(this.vehicleId).orFail();
|
|
113
|
-
vehicle.numReviews += 1;
|
|
114
|
-
const vehicleReviews = await mongoose.model('Review').find({ vehicleId: this.vehicleId });
|
|
115
|
-
const reviewRatings = vehicleReviews.map((entry) => entry.rating);
|
|
116
|
-
reviewRatings.push(this.rating);
|
|
117
|
-
const average = calculateAverage(reviewRatings);
|
|
118
|
-
vehicle.averageReview = average;
|
|
119
|
-
await vehicle.save();
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
function calculateAverage(ratings) {
|
|
123
|
-
if (ratings.length === 0) {
|
|
124
|
-
return 0;
|
|
125
|
-
}
|
|
126
|
-
let sum = 0;
|
|
127
|
-
for (let i = 0; i < ratings.length; i++) {
|
|
128
|
-
sum += ratings[i];
|
|
129
|
-
}
|
|
130
|
-
const average = sum / ratings.length;
|
|
131
|
-
return average;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
m.model('Review', schema);
|
|
135
|
-
})();
|
|
136
|
-
|
|
137
|
-
const uri = createAstraUri(
|
|
138
|
-
'https://fbced748-d6ed-40b1-ab9d-b618015a2c2c-us-east-2.apps.astra.datastax.com',
|
|
139
|
-
'AstraCS:kjSUArrrkLKrDlrMpUQOUYlw:50c9eda620df1099ad2dd77eb36e7755daaa48494acb158ff6de8df3deee40f6',
|
|
140
|
-
'reviews_dev2'
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
void async function main() {
|
|
144
|
-
await m.connect(uri);
|
|
145
|
-
const studioConnection = await mongoose.createConnection('mongodb://127.0.0.1:27017/mongoose_studio_test').asPromise();
|
|
146
|
-
|
|
147
|
-
app.use('/studio', await studio('/studio/api', m, { __build: true, apiKey: '123456789', studioConnection }));
|
|
148
|
-
|
|
149
|
-
await app.listen(7770);
|
|
150
|
-
console.log('Listening on port 7770');
|
|
151
|
-
}();
|
|
152
|
-
|
|
153
|
-
/*
|
|
154
|
-
# Astra Notes
|
|
155
|
-
|
|
156
|
-
1. Must use collections. Tables don't support `countDocuments()` or `estimatedDocumentCount()`.
|
|
157
|
-
2. Collections don't let you store keys that start with '$', which is problematic for `$chart`. Ended up creating separate connection to store ChatMessages in MongoDB.
|
|
158
|
-
3. `countDocuments()` with filter erroring out with more than 1000 documents caused trouble. Worked around it by converting `countDocuments()` to `find()` using Mongoose middleware.
|
|
159
|
-
*/
|
package/valnotes.md
DELETED