@mongoosejs/studio 0.1.16 → 0.1.18
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/backend/actions/Model/getCollectionInfo.js +49 -0
- package/backend/actions/Model/index.js +1 -0
- package/backend/actions/Model/updateDocument.js +3 -0
- package/backend/actions/Model/updateDocuments.js +3 -0
- package/frontend/public/app.js +954 -524
- package/frontend/public/tw.css +105 -19
- package/frontend/src/api.js +7 -1
- package/frontend/src/array-utils.js +66 -0
- package/frontend/src/chat/chat-message/chat-message.js +7 -0
- package/frontend/src/chat/chat-message-script/chat-message-script.js +21 -0
- package/frontend/src/chat/chat.js +22 -0
- package/frontend/src/clone-document/clone-document.js +14 -5
- package/frontend/src/create-dashboard/create-dashboard.js +8 -0
- package/frontend/src/create-document/create-document.js +14 -5
- package/frontend/src/dashboard/dashboard.js +9 -1
- package/frontend/src/dashboard/edit-dashboard/edit-dashboard.js +8 -0
- package/frontend/src/dashboards/dashboards.js +8 -0
- package/frontend/src/detail-array/detail-array.html +25 -3
- package/frontend/src/detail-array/detail-array.js +22 -6
- package/frontend/src/document/document.js +32 -20
- package/frontend/src/document-details/document-details.html +61 -12
- package/frontend/src/document-details/document-details.js +29 -13
- package/frontend/src/document-details/document-property/document-property.html +41 -3
- package/frontend/src/document-details/document-property/document-property.js +47 -2
- package/frontend/src/edit-array/edit-array.html +5 -2
- package/frontend/src/edit-array/edit-array.js +79 -23
- package/frontend/src/export-query-results/export-query-results.js +8 -1
- package/frontend/src/index.js +2 -1
- package/frontend/src/list-array/list-array.html +1 -1
- package/frontend/src/list-array/list-array.js +0 -2
- package/frontend/src/models/models.html +76 -8
- package/frontend/src/models/models.js +112 -1
- package/frontend/src/update-document/update-document.js +28 -27
- package/package.json +1 -1
- package/frontend/src/edit-array/edit-array.css +0 -3
- package/frontend/src/list-array/list-array.css +0 -8
|
@@ -1,20 +1,36 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const template = require('./detail-array.html');
|
|
4
|
-
const { inspect } = require('node-inspect-extracted');
|
|
5
4
|
|
|
6
5
|
module.exports = app => app.component('detail-array', {
|
|
7
6
|
template: template,
|
|
8
7
|
props: ['value'],
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
data() {
|
|
9
|
+
return {
|
|
10
|
+
arrayValue: []
|
|
11
|
+
};
|
|
12
|
+
},
|
|
13
|
+
methods: {
|
|
14
|
+
initializeArray() {
|
|
11
15
|
if (this.value == null) {
|
|
12
|
-
|
|
16
|
+
this.arrayValue = [];
|
|
17
|
+
} else if (Array.isArray(this.value)) {
|
|
18
|
+
this.arrayValue = this.value;
|
|
19
|
+
} else {
|
|
20
|
+
this.arrayValue = [];
|
|
13
21
|
}
|
|
14
|
-
return inspect(this.value, { maxArrayLength: 50 });
|
|
15
22
|
}
|
|
16
23
|
},
|
|
17
24
|
mounted() {
|
|
18
|
-
|
|
25
|
+
this.initializeArray();
|
|
26
|
+
},
|
|
27
|
+
watch: {
|
|
28
|
+
value: {
|
|
29
|
+
handler(newValue) {
|
|
30
|
+
this.initializeArray();
|
|
31
|
+
},
|
|
32
|
+
deep: true,
|
|
33
|
+
immediate: true
|
|
34
|
+
}
|
|
19
35
|
}
|
|
20
36
|
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const api = require('../api');
|
|
4
4
|
const mpath = require('mpath');
|
|
5
5
|
const template = require('./document.html');
|
|
6
|
-
const
|
|
6
|
+
const vanillatoasts = require('vanillatoasts');
|
|
7
7
|
|
|
8
8
|
const appendCSS = require('../appendCSS');
|
|
9
9
|
|
|
@@ -32,20 +32,24 @@ module.exports = app => app.component('document', {
|
|
|
32
32
|
window.pageState = this;
|
|
33
33
|
// Store query parameters from the route (preserved from models page)
|
|
34
34
|
this.previousQuery = Object.assign({}, this.$route.query);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
35
|
+
try {
|
|
36
|
+
const { doc, schemaPaths, virtualPaths } = await api.Model.getDocument({ model: this.model, documentId: this.documentId });
|
|
37
|
+
window.doc = doc;
|
|
38
|
+
this.document = doc;
|
|
39
|
+
this.schemaPaths = Object.keys(schemaPaths).sort((k1, k2) => {
|
|
40
|
+
if (k1 === '_id' && k2 !== '_id') {
|
|
41
|
+
return -1;
|
|
42
|
+
}
|
|
43
|
+
if (k1 !== '_id' && k2 === '_id') {
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
return 0;
|
|
47
|
+
}).map(key => schemaPaths[key]);
|
|
48
|
+
this.virtualPaths = virtualPaths || [];
|
|
49
|
+
this.status = 'loaded';
|
|
50
|
+
} finally {
|
|
51
|
+
this.status = 'loaded';
|
|
52
|
+
}
|
|
49
53
|
},
|
|
50
54
|
computed: {
|
|
51
55
|
canManipulate() {
|
|
@@ -79,6 +83,13 @@ module.exports = app => app.component('document', {
|
|
|
79
83
|
this.changes = {};
|
|
80
84
|
this.editting = false;
|
|
81
85
|
this.shouldShowConfirmModal = false;
|
|
86
|
+
vanillatoasts.create({
|
|
87
|
+
title: 'Document saved!',
|
|
88
|
+
type: 'success',
|
|
89
|
+
timeout: 3000,
|
|
90
|
+
icon: 'images/success.png',
|
|
91
|
+
positionClass: 'bottomRight'
|
|
92
|
+
});
|
|
82
93
|
},
|
|
83
94
|
async remove() {
|
|
84
95
|
const { doc } = await api.Model.deleteDocument({
|
|
@@ -88,10 +99,11 @@ module.exports = app => app.component('document', {
|
|
|
88
99
|
if (doc.acknowledged) {
|
|
89
100
|
this.editting = false;
|
|
90
101
|
this.document = {};
|
|
91
|
-
|
|
92
|
-
title: 'Document
|
|
102
|
+
vanillatoasts.create({
|
|
103
|
+
title: 'Document deleted!',
|
|
93
104
|
type: 'success',
|
|
94
105
|
timeout: 3000,
|
|
106
|
+
icon: 'images/success.png',
|
|
95
107
|
positionClass: 'bottomRight'
|
|
96
108
|
});
|
|
97
109
|
this.$router.push({
|
|
@@ -112,12 +124,12 @@ module.exports = app => app.component('document', {
|
|
|
112
124
|
});
|
|
113
125
|
this.document = doc;
|
|
114
126
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
title: 'Field Added!',
|
|
127
|
+
vanillatoasts.create({
|
|
128
|
+
title: 'Field added!',
|
|
118
129
|
text: `Field "${fieldData.name}" has been added to the document`,
|
|
119
130
|
type: 'success',
|
|
120
131
|
timeout: 3000,
|
|
132
|
+
icon: 'images/success.png',
|
|
121
133
|
positionClass: 'bottomRight'
|
|
122
134
|
});
|
|
123
135
|
},
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
|
|
53
53
|
<!-- Fields View -->
|
|
54
54
|
<div v-if="viewMode === 'fields'">
|
|
55
|
-
<!-- Schema Paths -->
|
|
55
|
+
<!-- Matched Schema Paths (shown first when searching) -->
|
|
56
56
|
<div
|
|
57
|
-
v-for="path in
|
|
57
|
+
v-for="path in matchedSchemaPaths"
|
|
58
58
|
:key="path.path || path"
|
|
59
59
|
class="value"
|
|
60
60
|
>
|
|
@@ -64,25 +64,74 @@
|
|
|
64
64
|
:schemaPaths="schemaPaths"
|
|
65
65
|
:editting="editting"
|
|
66
66
|
:changes="changes"
|
|
67
|
-
:highlight="
|
|
67
|
+
:highlight="true"
|
|
68
68
|
:invalid="invalid"></document-property>
|
|
69
69
|
</div>
|
|
70
70
|
|
|
71
|
-
<!-- Virtual Fields -->
|
|
71
|
+
<!-- Matched Virtual Fields (shown after matched schema paths when searching) -->
|
|
72
72
|
<div
|
|
73
|
-
v-for="path in
|
|
73
|
+
v-for="path in matchedVirtuals"
|
|
74
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
|
-
]"
|
|
75
|
+
class="border border-gray-200 rounded-lg mb-2 transition-all duration-200 ease-in-out mt-4"
|
|
81
76
|
>
|
|
82
77
|
<!-- Virtual Field Header (Always Visible) -->
|
|
83
78
|
<div
|
|
84
79
|
@click="toggleVirtualField(path.name)"
|
|
85
|
-
class="p-3 bg-
|
|
80
|
+
class="p-3 bg-amber-100 hover:bg-amber-200 cursor-pointer flex items-center justify-between border-b border-gray-200 transition-colors duration-200 ease-in-out"
|
|
81
|
+
>
|
|
82
|
+
<div class="flex items-center">
|
|
83
|
+
<svg
|
|
84
|
+
:class="isVirtualFieldCollapsed(path.name) ? 'rotate-0' : 'rotate-90'"
|
|
85
|
+
class="w-4 h-4 text-gray-500 mr-2 transition-transform duration-200"
|
|
86
|
+
fill="none"
|
|
87
|
+
stroke="currentColor"
|
|
88
|
+
viewBox="0 0 24 24"
|
|
89
|
+
>
|
|
90
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
|
91
|
+
</svg>
|
|
92
|
+
<span class="font-medium text-gray-900">{{path.name}}</span>
|
|
93
|
+
<span v-if="path.isVirtual" class="ml-2 text-sm text-purple-600">(virtual - {{getVirtualFieldType(path)}})</span>
|
|
94
|
+
<span v-else class="ml-2 text-sm text-blue-600">(user-added - {{getVirtualFieldType(path)}})</span>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<!-- Virtual Field Content (Collapsible) -->
|
|
99
|
+
<div v-if="!isVirtualFieldCollapsed(path.name)" class="p-3">
|
|
100
|
+
<div v-if="path.value == null" class="text-sky-800">
|
|
101
|
+
{{'' + path.value}}
|
|
102
|
+
</div>
|
|
103
|
+
<div v-else>
|
|
104
|
+
{{path.value}}
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<!-- Unmatched Schema Paths (shown after matched items when searching, or all when not searching) -->
|
|
110
|
+
<div
|
|
111
|
+
v-for="path in unmatchedSchemaPaths"
|
|
112
|
+
:key="path.path || path"
|
|
113
|
+
class="value"
|
|
114
|
+
>
|
|
115
|
+
<document-property
|
|
116
|
+
:path="path"
|
|
117
|
+
:document="document"
|
|
118
|
+
:schemaPaths="schemaPaths"
|
|
119
|
+
:editting="editting"
|
|
120
|
+
:changes="changes"
|
|
121
|
+
:highlight="false"
|
|
122
|
+
:invalid="invalid"></document-property>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<!-- Unmatched Virtual Fields (shown last) -->
|
|
126
|
+
<div
|
|
127
|
+
v-for="path in unmatchedVirtuals"
|
|
128
|
+
:key="path.name"
|
|
129
|
+
class="border border-gray-200 rounded-lg mb-2 transition-all duration-200 ease-in-out"
|
|
130
|
+
>
|
|
131
|
+
<!-- Virtual Field Header (Always Visible) -->
|
|
132
|
+
<div
|
|
133
|
+
@click="toggleVirtualField(path.name)"
|
|
134
|
+
class="p-3 bg-gray-50 hover:bg-gray-100 cursor-pointer flex items-center justify-between border-b border-gray-200 transition-colors duration-200 ease-in-out"
|
|
86
135
|
>
|
|
87
136
|
<div class="flex items-center">
|
|
88
137
|
<svg
|
|
@@ -145,6 +145,20 @@ module.exports = app => app.component('document-details', {
|
|
|
145
145
|
|
|
146
146
|
return matches.concat(nonMatches);
|
|
147
147
|
},
|
|
148
|
+
matchedSchemaPaths() {
|
|
149
|
+
if (!this.searchQuery.trim()) {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
const query = this.searchQuery.toLowerCase();
|
|
153
|
+
return this.typeFilteredSchemaPaths.filter(path => path.path.toLowerCase().includes(query));
|
|
154
|
+
},
|
|
155
|
+
unmatchedSchemaPaths() {
|
|
156
|
+
if (!this.searchQuery.trim()) {
|
|
157
|
+
return this.typeFilteredSchemaPaths;
|
|
158
|
+
}
|
|
159
|
+
const query = this.searchQuery.toLowerCase();
|
|
160
|
+
return this.typeFilteredSchemaPaths.filter(path => !path.path.toLowerCase().includes(query));
|
|
161
|
+
},
|
|
148
162
|
typeFilteredVirtuals() {
|
|
149
163
|
let virtuals = this.virtuals;
|
|
150
164
|
|
|
@@ -178,6 +192,20 @@ module.exports = app => app.component('document-details', {
|
|
|
178
192
|
|
|
179
193
|
return matches.concat(nonMatches);
|
|
180
194
|
},
|
|
195
|
+
matchedVirtuals() {
|
|
196
|
+
if (!this.searchQuery.trim()) {
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
const query = this.searchQuery.toLowerCase();
|
|
200
|
+
return this.typeFilteredVirtuals.filter(virtual => virtual.name.toLowerCase().includes(query));
|
|
201
|
+
},
|
|
202
|
+
unmatchedVirtuals() {
|
|
203
|
+
if (!this.searchQuery.trim()) {
|
|
204
|
+
return this.typeFilteredVirtuals;
|
|
205
|
+
}
|
|
206
|
+
const query = this.searchQuery.toLowerCase();
|
|
207
|
+
return this.typeFilteredVirtuals.filter(virtual => !virtual.name.toLowerCase().includes(query));
|
|
208
|
+
},
|
|
181
209
|
schemaSearchMatchSet() {
|
|
182
210
|
if (!this.searchQuery.trim()) {
|
|
183
211
|
return new Set();
|
|
@@ -338,7 +366,7 @@ module.exports = app => app.component('document-details', {
|
|
|
338
366
|
|
|
339
367
|
try {
|
|
340
368
|
const fieldData = {
|
|
341
|
-
name: this.
|
|
369
|
+
name: this.fieldData.name,
|
|
342
370
|
type: this.fieldData.type,
|
|
343
371
|
value: this.parseFieldValue(this.fieldData.value, this.fieldData.type)
|
|
344
372
|
};
|
|
@@ -384,18 +412,6 @@ module.exports = app => app.component('document-details', {
|
|
|
384
412
|
this.fieldValueEditor = null;
|
|
385
413
|
}
|
|
386
414
|
},
|
|
387
|
-
toSnakeCase(str) {
|
|
388
|
-
return str
|
|
389
|
-
.trim()
|
|
390
|
-
.replace(/\s+/g, '_') // Replace spaces with underscores
|
|
391
|
-
.replace(/[^a-zA-Z0-9_$]/g, '') // Remove invalid characters
|
|
392
|
-
.replace(/^[0-9]/, '_$&') // Prefix numbers with underscore
|
|
393
|
-
.toLowerCase();
|
|
394
|
-
},
|
|
395
|
-
getTransformedFieldName() {
|
|
396
|
-
if (!this.fieldData.name) return '';
|
|
397
|
-
return this.toSnakeCase(this.fieldData.name.trim());
|
|
398
|
-
},
|
|
399
415
|
getVirtualFieldType(virtual) {
|
|
400
416
|
const value = virtual.value;
|
|
401
417
|
if (value === null || value === undefined) {
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
<!-- Collapsible Header -->
|
|
3
3
|
<div
|
|
4
4
|
@click="toggleCollapse"
|
|
5
|
-
class="p-3
|
|
6
|
-
:class="{ 'bg-amber-100': highlight, 'bg-gray-50': !highlight }"
|
|
5
|
+
class="p-3 cursor-pointer flex items-center justify-between border-b border-gray-200 transition-colors duration-200 ease-in-out"
|
|
6
|
+
:class="{ 'bg-amber-100 hover:bg-amber-200': highlight, 'bg-gray-50 hover:bg-gray-100': !highlight }"
|
|
7
7
|
>
|
|
8
8
|
<div class="flex items-center" >
|
|
9
9
|
<svg
|
|
@@ -81,7 +81,43 @@
|
|
|
81
81
|
</div>
|
|
82
82
|
<div v-else>
|
|
83
83
|
<!-- Show truncated or full value based on needsTruncation and isValueExpanded -->
|
|
84
|
-
|
|
84
|
+
<!-- Special handling for truncated arrays -->
|
|
85
|
+
<div v-if="isArray && shouldShowTruncated" class="w-full">
|
|
86
|
+
<div class="mt-2">
|
|
87
|
+
<div
|
|
88
|
+
v-for="(item, index) in truncatedArrayItems"
|
|
89
|
+
:key="index"
|
|
90
|
+
class="mb-1.5 py-2.5 px-3 pl-4 bg-transparent border-l-[3px] border-l-blue-500 rounded-none transition-all duration-200 cursor-pointer relative hover:bg-slate-50 hover:border-l-blue-600">
|
|
91
|
+
<div class="absolute -left-2 top-1/2 -translate-y-1/2 w-5 h-5 bg-blue-500 text-white rounded-full flex items-center justify-center text-[10px] font-semibold font-mono z-10 hover:bg-blue-600">{{ index }}</div>
|
|
92
|
+
<div v-if="arrayUtils.isObjectItem(item)" class="flex flex-col gap-1 mt-1 px-2">
|
|
93
|
+
<div
|
|
94
|
+
v-for="key in arrayUtils.getItemKeys(item)"
|
|
95
|
+
:key="key"
|
|
96
|
+
class="flex items-start gap-2 text-xs font-mono">
|
|
97
|
+
<span class="font-semibold text-gray-600 flex-shrink-0 min-w-[80px]">{{ key }}:</span>
|
|
98
|
+
<span class="text-gray-800 break-words whitespace-pre-wrap flex-1">{{ arrayUtils.formatItemValue(item, key) }}</span>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
<div v-else class="text-xs py-1.5 px-2 font-mono text-gray-800 break-words whitespace-pre-wrap mt-1">{{ arrayUtils.formatValue(item) }}</div>
|
|
102
|
+
</div>
|
|
103
|
+
<div class="mb-1.5 py-2.5 px-3 pl-4 bg-transparent border-none border-l-[3px] border-l-blue-500 rounded-none transition-all duration-200 cursor-pointer relative opacity-70 hover:opacity-100">
|
|
104
|
+
<div class="text-xs py-1.5 px-2 font-mono text-gray-500 italic break-words whitespace-pre-wrap mt-1">
|
|
105
|
+
... and {{ remainingArrayCount }} more item{{ remainingArrayCount !== 1 ? 's' : '' }}
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
<button
|
|
110
|
+
@click="toggleValueExpansion"
|
|
111
|
+
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"
|
|
112
|
+
>
|
|
113
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
114
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
115
|
+
</svg>
|
|
116
|
+
Show all {{ arrayValue.length }} items
|
|
117
|
+
</button>
|
|
118
|
+
</div>
|
|
119
|
+
<!-- Non-array truncated view -->
|
|
120
|
+
<div v-else-if="shouldShowTruncated && !isArray" class="relative">
|
|
85
121
|
<div class="text-gray-700 whitespace-pre-wrap break-words font-mono text-sm">{{truncatedString}}</div>
|
|
86
122
|
<button
|
|
87
123
|
@click="toggleValueExpansion"
|
|
@@ -93,6 +129,7 @@
|
|
|
93
129
|
Show more ({{valueAsString.length}} characters)
|
|
94
130
|
</button>
|
|
95
131
|
</div>
|
|
132
|
+
<!-- Expanded view -->
|
|
96
133
|
<div v-else-if="needsTruncation && isValueExpanded" class="relative">
|
|
97
134
|
<component :is="getComponentForPath(path)" :value="getValueForPath(path.path)"></component>
|
|
98
135
|
<button
|
|
@@ -105,6 +142,7 @@
|
|
|
105
142
|
Show less
|
|
106
143
|
</button>
|
|
107
144
|
</div>
|
|
145
|
+
<!-- Full view (no truncation needed) -->
|
|
108
146
|
<div v-else>
|
|
109
147
|
<component :is="getComponentForPath(path)" :value="getValueForPath(path.path)"></component>
|
|
110
148
|
</div>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
5
|
const mpath = require('mpath');
|
|
6
|
+
const { inspect } = require('node-inspect-extracted');
|
|
6
7
|
const template = require('./document-property.html');
|
|
7
8
|
|
|
8
9
|
const appendCSS = require('../../appendCSS');
|
|
@@ -38,10 +39,32 @@ module.exports = app => app.component('document-property', {
|
|
|
38
39
|
}
|
|
39
40
|
return String(value);
|
|
40
41
|
},
|
|
42
|
+
_arrayValueData() {
|
|
43
|
+
const value = this.getValueForPath(this.path.path);
|
|
44
|
+
return {
|
|
45
|
+
value: Array.isArray(value) ? value : [],
|
|
46
|
+
isArray: Array.isArray(value)
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
isArray() {
|
|
50
|
+
return this._arrayValueData.isArray;
|
|
51
|
+
},
|
|
52
|
+
arrayValue() {
|
|
53
|
+
return this._arrayValueData.value;
|
|
54
|
+
},
|
|
41
55
|
needsTruncation() {
|
|
42
|
-
//
|
|
56
|
+
// For arrays, check if it has more than 3 items (regardless of expansion state)
|
|
57
|
+
if (this.isArray) {
|
|
58
|
+
const arr = this.arrayValue;
|
|
59
|
+
return arr && arr.length > 3;
|
|
60
|
+
}
|
|
61
|
+
// For other types, truncate if value is longer than 200 characters
|
|
43
62
|
return this.valueAsString.length > 200;
|
|
44
63
|
},
|
|
64
|
+
shouldShowTruncated() {
|
|
65
|
+
// For other types, show truncated if needs truncation and not expanded
|
|
66
|
+
return this.needsTruncation && !this.isValueExpanded;
|
|
67
|
+
},
|
|
45
68
|
displayValue() {
|
|
46
69
|
if (!this.needsTruncation || this.isValueExpanded) {
|
|
47
70
|
return this.getValueForPath(this.path.path);
|
|
@@ -51,9 +74,24 @@ module.exports = app => app.component('document-property', {
|
|
|
51
74
|
},
|
|
52
75
|
truncatedString() {
|
|
53
76
|
if (this.needsTruncation && !this.isValueExpanded) {
|
|
54
|
-
|
|
77
|
+
// Arrays are handled in template, so this is for non-arrays
|
|
78
|
+
if (!this.isArray) {
|
|
79
|
+
return this.valueAsString.substring(0, 200) + '...';
|
|
80
|
+
}
|
|
55
81
|
}
|
|
56
82
|
return this.valueAsString;
|
|
83
|
+
},
|
|
84
|
+
truncatedArrayItems() {
|
|
85
|
+
if (this.isArray && this.needsTruncation && !this.isValueExpanded) {
|
|
86
|
+
return this.arrayValue.slice(0, 2);
|
|
87
|
+
}
|
|
88
|
+
return [];
|
|
89
|
+
},
|
|
90
|
+
remainingArrayCount() {
|
|
91
|
+
if (this.isArray && this.needsTruncation && !this.isValueExpanded) {
|
|
92
|
+
return this.arrayValue.length - 2;
|
|
93
|
+
}
|
|
94
|
+
return 0;
|
|
57
95
|
}
|
|
58
96
|
},
|
|
59
97
|
methods: {
|
|
@@ -79,6 +117,9 @@ module.exports = app => app.component('document-property', {
|
|
|
79
117
|
if (path.instance === 'Embedded') {
|
|
80
118
|
return 'edit-subdocument';
|
|
81
119
|
}
|
|
120
|
+
if (path.instance === 'Mixed') {
|
|
121
|
+
return 'edit-subdocument';
|
|
122
|
+
}
|
|
82
123
|
if (path.instance === 'Boolean') {
|
|
83
124
|
return 'edit-boolean';
|
|
84
125
|
}
|
|
@@ -91,6 +132,10 @@ module.exports = app => app.component('document-property', {
|
|
|
91
132
|
props.enumValues = path.enum;
|
|
92
133
|
}
|
|
93
134
|
}
|
|
135
|
+
if (path.instance === 'Array') {
|
|
136
|
+
props.path = path;
|
|
137
|
+
props.schemaPaths = this.schemaPaths;
|
|
138
|
+
}
|
|
94
139
|
return props;
|
|
95
140
|
},
|
|
96
141
|
getValueForPath(path) {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
<div class="
|
|
1
|
+
<div class="w-full">
|
|
2
|
+
<!-- CodeMirror editor for the entire array -->
|
|
2
3
|
<textarea
|
|
3
4
|
ref="arrayEditor"
|
|
4
|
-
class="w-full border border-gray-300 p-
|
|
5
|
+
class="w-full border border-gray-300 p-2 font-mono"
|
|
6
|
+
:style="{ minHeight: '300px' }">
|
|
7
|
+
</textarea>
|
|
5
8
|
</div>
|
|
@@ -10,43 +10,99 @@ const ObjectId = new Proxy(BSON.ObjectId, {
|
|
|
10
10
|
}
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
-
const appendCSS = require('../appendCSS');
|
|
14
|
-
appendCSS(require('./edit-array.css'));
|
|
15
13
|
|
|
16
14
|
module.exports = app => app.component('edit-array', {
|
|
17
15
|
template: template,
|
|
18
16
|
props: ['value'],
|
|
19
|
-
data
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
this.$refs.arrayEditor.value = this.currentValue;
|
|
25
|
-
this.editor = CodeMirror.fromTextArea(this.$refs.arrayEditor, {
|
|
26
|
-
mode: 'javascript',
|
|
27
|
-
lineNumbers: true
|
|
28
|
-
});
|
|
29
|
-
this.editor.on('change', ev => {
|
|
30
|
-
this.currentValue = this.editor.getValue();
|
|
31
|
-
});
|
|
17
|
+
data() {
|
|
18
|
+
return {
|
|
19
|
+
arrayValue: [],
|
|
20
|
+
arrayEditor: null
|
|
21
|
+
};
|
|
32
22
|
},
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
23
|
+
methods: {
|
|
24
|
+
initializeArray() {
|
|
25
|
+
if (this.value == null) {
|
|
26
|
+
this.arrayValue = [];
|
|
27
|
+
} else if (Array.isArray(this.value)) {
|
|
28
|
+
this.arrayValue = JSON.parse(JSON.stringify(this.value));
|
|
29
|
+
} else {
|
|
30
|
+
this.arrayValue = [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Update CodeMirror editor if it exists
|
|
34
|
+
this.$nextTick(() => {
|
|
35
|
+
if (this.arrayEditor) {
|
|
36
|
+
const arrayStr = JSON.stringify(this.arrayValue, null, 2);
|
|
37
|
+
this.arrayEditor.setValue(arrayStr);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
initializeArrayEditor() {
|
|
42
|
+
this.$nextTick(() => {
|
|
43
|
+
const textareaRef = this.$refs.arrayEditor;
|
|
44
|
+
const textarea = Array.isArray(textareaRef) ? textareaRef[0] : textareaRef;
|
|
45
|
+
if (textarea && !this.arrayEditor) {
|
|
46
|
+
const arrayStr = JSON.stringify(this.arrayValue, null, 2);
|
|
47
|
+
textarea.value = arrayStr;
|
|
48
|
+
this.arrayEditor = CodeMirror.fromTextArea(textarea, {
|
|
49
|
+
mode: 'javascript',
|
|
50
|
+
lineNumbers: true
|
|
51
|
+
});
|
|
52
|
+
this.arrayEditor.on('change', () => {
|
|
53
|
+
this.updateArrayFromEditor();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
updateArrayFromEditor() {
|
|
59
|
+
if (!this.arrayEditor) {
|
|
37
60
|
return;
|
|
38
61
|
}
|
|
39
62
|
try {
|
|
40
|
-
const
|
|
41
|
-
|
|
63
|
+
const value = this.arrayEditor.getValue();
|
|
64
|
+
if (value.trim() === '') {
|
|
65
|
+
this.arrayValue = [];
|
|
66
|
+
} else {
|
|
67
|
+
this.arrayValue = JSON.parse(value);
|
|
68
|
+
}
|
|
69
|
+
this.emitUpdate();
|
|
70
|
+
} catch (err) {
|
|
71
|
+
// Invalid JSON, don't update
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
emitUpdate() {
|
|
75
|
+
try {
|
|
76
|
+
this.$emit('input', this.arrayValue);
|
|
42
77
|
} catch (err) {
|
|
43
78
|
this.$emit('error', err);
|
|
44
79
|
}
|
|
45
80
|
}
|
|
46
81
|
},
|
|
82
|
+
mounted() {
|
|
83
|
+
this.initializeArray();
|
|
84
|
+
this.initializeArrayEditor();
|
|
85
|
+
},
|
|
47
86
|
beforeDestroy() {
|
|
48
|
-
if (this.
|
|
49
|
-
this.
|
|
87
|
+
if (this.arrayEditor) {
|
|
88
|
+
this.arrayEditor.toTextArea();
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
watch: {
|
|
92
|
+
value: {
|
|
93
|
+
handler(newValue, oldValue) {
|
|
94
|
+
// Initialize array when value prop changes
|
|
95
|
+
this.initializeArray();
|
|
96
|
+
// Update array editor if it exists
|
|
97
|
+
if (this.arrayEditor) {
|
|
98
|
+
this.$nextTick(() => {
|
|
99
|
+
const arrayStr = JSON.stringify(this.arrayValue, null, 2);
|
|
100
|
+
this.arrayEditor.setValue(arrayStr);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
deep: true,
|
|
105
|
+
immediate: true
|
|
50
106
|
}
|
|
51
107
|
},
|
|
52
108
|
emits: ['input', 'error']
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const api = require('../api');
|
|
4
4
|
const template = require('./export-query-results.html');
|
|
5
|
+
const vanillatoasts = require('vanillatoasts');
|
|
5
6
|
|
|
6
7
|
const appendCSS = require('../appendCSS');
|
|
7
8
|
|
|
@@ -30,7 +31,13 @@ module.exports = app => app.component('export-query-results', {
|
|
|
30
31
|
params.searchText = this.searchText;
|
|
31
32
|
}
|
|
32
33
|
await api.Model.exportQueryResults(params);
|
|
33
|
-
|
|
34
|
+
vanillatoasts.create({
|
|
35
|
+
title: 'Export completed!',
|
|
36
|
+
type: 'success',
|
|
37
|
+
timeout: 3000,
|
|
38
|
+
icon: 'images/success.png',
|
|
39
|
+
positionClass: 'bottomRight'
|
|
40
|
+
});
|
|
34
41
|
this.$emit('done');
|
|
35
42
|
}
|
|
36
43
|
}
|
package/frontend/src/index.js
CHANGED
|
@@ -9,6 +9,7 @@ console.log(`Mongoose Studio Version ${version}`);
|
|
|
9
9
|
|
|
10
10
|
const api = require('./api');
|
|
11
11
|
const format = require('./format');
|
|
12
|
+
const arrayUtils = require('./array-utils');
|
|
12
13
|
const mothership = require('./mothership');
|
|
13
14
|
const { routes } = require('./routes');
|
|
14
15
|
const vanillatoasts = require('vanillatoasts');
|
|
@@ -149,7 +150,7 @@ router.beforeEach((to, from, next) => {
|
|
|
149
150
|
}
|
|
150
151
|
});
|
|
151
152
|
|
|
152
|
-
app.config.globalProperties = { format };
|
|
153
|
+
app.config.globalProperties = { format, arrayUtils };
|
|
153
154
|
app.use(router);
|
|
154
155
|
|
|
155
156
|
app.mount('#content');
|