@mongoosejs/studio 0.1.16 → 0.1.17

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.
@@ -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']
@@ -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'],
@@ -193,7 +193,12 @@
193
193
  <div v-for="index in mongoDBIndexes" class="w-full flex items-center">
194
194
  <div class="grow shrink text-left flex justify-between items-center" v-if="index.name != '_id_'">
195
195
  <div>
196
- <div class="font-bold">{{ index.name }}</div>
196
+ <div class="font-bold flex items-center gap-2">
197
+ <div>{{ index.name }}</div>
198
+ <div v-if="isTTLIndex(index)" class="rounded-full bg-ultramarine-100 px-2 py-0.5 text-xs font-semibold text-ultramarine-700">
199
+ TTL: {{ formatTTL(index.expireAfterSeconds) }}
200
+ </div>
201
+ </div>
197
202
  <div class="text-sm font-mono">{{ JSON.stringify(index.key) }}</div>
198
203
  </div>
199
204
  <div>
@@ -237,6 +237,38 @@ module.exports = app => app.component('models', {
237
237
  this.mongoDBIndexes = mongoDBIndexes;
238
238
  this.schemaIndexes = schemaIndexes;
239
239
  },
240
+ isTTLIndex(index) {
241
+ return index != null && index.expireAfterSeconds != null;
242
+ },
243
+ formatTTL(expireAfterSeconds) {
244
+ if (typeof expireAfterSeconds !== 'number') {
245
+ return '';
246
+ }
247
+
248
+ let remaining = expireAfterSeconds;
249
+ const days = Math.floor(remaining / (24 * 60 * 60));
250
+ remaining = remaining % (24 * 60 * 60);
251
+ const hours = Math.floor(remaining / (60 * 60));
252
+ remaining = remaining % (60 * 60);
253
+ const minutes = Math.floor(remaining / 60);
254
+ const seconds = remaining % 60;
255
+
256
+ const parts = [];
257
+ if (days > 0) {
258
+ parts.push(`${days} day${days === 1 ? '' : 's'}`);
259
+ }
260
+ if (hours > 0) {
261
+ parts.push(`${hours} hour${hours === 1 ? '' : 's'}`);
262
+ }
263
+ if (minutes > 0) {
264
+ parts.push(`${minutes} minute${minutes === 1 ? '' : 's'}`);
265
+ }
266
+ if (seconds > 0 || parts.length === 0) {
267
+ parts.push(`${seconds} second${seconds === 1 ? '' : 's'}`);
268
+ }
269
+
270
+ return parts.join(', ');
271
+ },
240
272
  checkIndexLocation(indexName) {
241
273
  if (this.schemaIndexes.find(x => x.name == indexName) && this.mongoDBIndexes.find(x => x.name == indexName)) {
242
274
  return 'text-gray-500';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
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": {
@@ -1,3 +0,0 @@
1
- .edit-array button {
2
- margin-top: 0.5em;
3
- }
@@ -1,8 +0,0 @@
1
- .list-array pre {
2
- max-height: 6.5em;
3
- max-width: 30em;
4
- }
5
-
6
- .list-array pre.maximized {
7
- max-height: auto;
8
- }