@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.
Files changed (36) hide show
  1. package/backend/actions/Model/getCollectionInfo.js +49 -0
  2. package/backend/actions/Model/index.js +1 -0
  3. package/backend/actions/Model/updateDocument.js +3 -0
  4. package/backend/actions/Model/updateDocuments.js +3 -0
  5. package/frontend/public/app.js +954 -524
  6. package/frontend/public/tw.css +105 -19
  7. package/frontend/src/api.js +7 -1
  8. package/frontend/src/array-utils.js +66 -0
  9. package/frontend/src/chat/chat-message/chat-message.js +7 -0
  10. package/frontend/src/chat/chat-message-script/chat-message-script.js +21 -0
  11. package/frontend/src/chat/chat.js +22 -0
  12. package/frontend/src/clone-document/clone-document.js +14 -5
  13. package/frontend/src/create-dashboard/create-dashboard.js +8 -0
  14. package/frontend/src/create-document/create-document.js +14 -5
  15. package/frontend/src/dashboard/dashboard.js +9 -1
  16. package/frontend/src/dashboard/edit-dashboard/edit-dashboard.js +8 -0
  17. package/frontend/src/dashboards/dashboards.js +8 -0
  18. package/frontend/src/detail-array/detail-array.html +25 -3
  19. package/frontend/src/detail-array/detail-array.js +22 -6
  20. package/frontend/src/document/document.js +32 -20
  21. package/frontend/src/document-details/document-details.html +61 -12
  22. package/frontend/src/document-details/document-details.js +29 -13
  23. package/frontend/src/document-details/document-property/document-property.html +41 -3
  24. package/frontend/src/document-details/document-property/document-property.js +47 -2
  25. package/frontend/src/edit-array/edit-array.html +5 -2
  26. package/frontend/src/edit-array/edit-array.js +79 -23
  27. package/frontend/src/export-query-results/export-query-results.js +8 -1
  28. package/frontend/src/index.js +2 -1
  29. package/frontend/src/list-array/list-array.html +1 -1
  30. package/frontend/src/list-array/list-array.js +0 -2
  31. package/frontend/src/models/models.html +76 -8
  32. package/frontend/src/models/models.js +112 -1
  33. package/frontend/src/update-document/update-document.js +28 -27
  34. package/package.json +1 -1
  35. package/frontend/src/edit-array/edit-array.css +0 -3
  36. 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
- computed: {
10
- displayValue() {
8
+ data() {
9
+ return {
10
+ arrayValue: []
11
+ };
12
+ },
13
+ methods: {
14
+ initializeArray() {
11
15
  if (this.value == null) {
12
- return this.value;
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
- Prism.highlightElement(this.$refs.code);
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 vanillatoast = require('vanillatoasts');
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
- const { doc, schemaPaths, virtualPaths } = await api.Model.getDocument({ model: this.model, documentId: this.documentId });
36
- window.doc = doc;
37
- this.document = doc;
38
- this.schemaPaths = Object.keys(schemaPaths).sort((k1, k2) => {
39
- if (k1 === '_id' && k2 !== '_id') {
40
- return -1;
41
- }
42
- if (k1 !== '_id' && k2 === '_id') {
43
- return 1;
44
- }
45
- return 0;
46
- }).map(key => schemaPaths[key]);
47
- this.virtualPaths = virtualPaths || [];
48
- this.status = 'loaded';
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
- vanillatoast.create({
92
- title: 'Document Deleted!',
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
- // Show success message
116
- vanillatoast.create({
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 filteredSchemaPaths"
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="isSchemaPathMatched(path)"
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 filteredVirtuals"
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-slate-50 hover:bg-slate-100 cursor-pointer flex items-center justify-between border-b border-gray-200"
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.getTransformedFieldName(),
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 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 }"
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
- <div v-if="needsTruncation && !isValueExpanded" class="relative">
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
- // Truncate if value is longer than 200 characters
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
- return this.valueAsString.substring(0, 200) + '...';
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="edit-array">
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-1 h-[300px]"></textarea>
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: () => ({ currentValue: -1 }),
20
- mounted() {
21
- this.currentValue = this.value == null
22
- ? '' + this.value
23
- : JSON.stringify(this.value, null, ' ').trim();
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
- watch: {
34
- currentValue(newValue, oldValue) {
35
- // Hacky way of skipping initial trigger because `immediate: false` doesn't work in Vue 3
36
- if (oldValue === -1) {
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 array = eval(`(${this.currentValue})`);
41
- this.$emit('input', array);
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.editor) {
49
- this.editor.toTextArea();
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
  }
@@ -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');
@@ -1,3 +1,3 @@
1
1
  <div class="list-array">
2
- <pre><code ref="code" class="language-javascript" v-text="displayValue"></code></pre>
2
+ <pre class="max-h-[6.5em] max-w-[30em]"><code ref="code" class="language-javascript" v-text="displayValue"></code></pre>
3
3
  </div>
@@ -2,8 +2,6 @@
2
2
 
3
3
  const template = require('./list-array.html');
4
4
 
5
- require('../appendCSS')(require('./list-array.css'));
6
-
7
5
  module.exports = app => app.component('list-array', {
8
6
  template: template,
9
7
  props: ['value'],