@mongoosejs/studio 0.1.19 → 0.2.0

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 (40) hide show
  1. package/backend/actions/ChatMessage/executeScript.js +4 -1
  2. package/backend/actions/ChatThread/createChatMessage.js +28 -22
  3. package/backend/actions/Model/getDocument.js +2 -1
  4. package/backend/actions/Model/getDocuments.js +3 -2
  5. package/backend/actions/Model/getDocumentsStream.js +3 -2
  6. package/backend/helpers/evaluateFilter.js +38 -1
  7. package/backend/helpers/getRefFromSchemaType.js +5 -0
  8. package/express.js +4 -2
  9. package/frontend/public/app.js +704 -405
  10. package/frontend/public/index.html +1 -1
  11. package/frontend/public/style.css +1 -1
  12. package/frontend/public/tw.css +81 -62
  13. package/frontend/src/_util/document-search-autocomplete.js +229 -0
  14. package/frontend/src/chat/chat-message-script/chat-message-script.html +27 -20
  15. package/frontend/src/chat/chat.html +20 -17
  16. package/frontend/src/chat/chat.js +2 -0
  17. package/frontend/src/document/document.css +1 -8
  18. package/frontend/src/document/document.html +202 -164
  19. package/frontend/src/document/document.js +1 -0
  20. package/frontend/src/document-details/document-details.html +1 -11
  21. package/frontend/src/document-details/document-details.js +43 -1
  22. package/frontend/src/document-details/document-property/document-property.html +4 -4
  23. package/frontend/src/index.js +36 -15
  24. package/frontend/src/json-node/json-node.html +118 -0
  25. package/frontend/src/json-node/json-node.js +272 -0
  26. package/frontend/src/list-array/list-array.html +15 -3
  27. package/frontend/src/list-array/list-array.js +21 -3
  28. package/frontend/src/list-default/list-default.js +2 -2
  29. package/frontend/src/list-json/json-node.html +1 -1
  30. package/frontend/src/list-json/list-json.html +1 -1
  31. package/frontend/src/list-json/list-json.js +11 -248
  32. package/frontend/src/list-subdocument/list-subdocument.html +13 -4
  33. package/frontend/src/list-subdocument/list-subdocument.js +11 -6
  34. package/frontend/src/models/document-search/document-search.html +1 -1
  35. package/frontend/src/models/document-search/document-search.js +22 -116
  36. package/frontend/src/models/models.css +5 -15
  37. package/frontend/src/models/models.html +34 -34
  38. package/frontend/src/models/models.js +1 -1
  39. package/frontend/src/navbar/navbar.html +15 -6
  40. package/package.json +3 -3
@@ -2,8 +2,6 @@
2
2
 
3
3
  const template = require('./list-json.html');
4
4
 
5
- const JsonNodeTemplate = require('./json-node.html');
6
-
7
5
  module.exports = app => app.component('list-json', {
8
6
  template: template,
9
7
  props: {
@@ -13,13 +11,20 @@ module.exports = app => app.component('list-json', {
13
11
  references: {
14
12
  type: Object,
15
13
  default: () => ({})
14
+ },
15
+ maxTopLevelFields: {
16
+ type: Number,
17
+ default: 15
18
+ },
19
+ expandedFields: {
20
+ type: Array,
21
+ default: () => []
16
22
  }
17
23
  },
18
24
  data() {
19
25
  return {
20
26
  collapsedMap: {},
21
27
  indentSize: 16,
22
- maxTopLevelFields: 15,
23
28
  topLevelExpanded: false
24
29
  };
25
30
  },
@@ -32,6 +37,9 @@ module.exports = app => app.component('list-json', {
32
37
  },
33
38
  created() {
34
39
  this.resetCollapse();
40
+ for (const field of this.expandedFields) {
41
+ this.toggleCollapse(field);
42
+ }
35
43
  },
36
44
  methods: {
37
45
  resetCollapse() {
@@ -66,250 +74,5 @@ module.exports = app => app.component('list-json', {
66
74
  expandTopLevel() {
67
75
  this.topLevelExpanded = true;
68
76
  }
69
- },
70
- components: {
71
- JsonNode: {
72
- name: 'JsonNode',
73
- template: JsonNodeTemplate,
74
- props: {
75
- nodeKey: {
76
- type: [String, Number],
77
- default: null
78
- },
79
- value: {
80
- required: true
81
- },
82
- level: {
83
- type: Number,
84
- required: true
85
- },
86
- isLast: {
87
- type: Boolean,
88
- default: false
89
- },
90
- path: {
91
- type: String,
92
- required: true
93
- },
94
- toggleCollapse: {
95
- type: Function,
96
- required: true
97
- },
98
- isCollapsed: {
99
- type: Function,
100
- required: true
101
- },
102
- createChildPath: {
103
- type: Function,
104
- required: true
105
- },
106
- indentSize: {
107
- type: Number,
108
- required: true
109
- },
110
- maxTopLevelFields: {
111
- type: Number,
112
- default: null
113
- },
114
- topLevelExpanded: {
115
- type: Boolean,
116
- default: false
117
- },
118
- expandTopLevel: {
119
- type: Function,
120
- default: null
121
- },
122
- references: {
123
- type: Object,
124
- default: () => ({})
125
- }
126
- },
127
- computed: {
128
- hasKey() {
129
- return this.nodeKey !== null && this.nodeKey !== undefined;
130
- },
131
- isRoot() {
132
- return this.path === 'root';
133
- },
134
- isArray() {
135
- return Array.isArray(this.value);
136
- },
137
- isObject() {
138
- if (this.value === null || this.isArray) {
139
- return false;
140
- }
141
- return Object.prototype.toString.call(this.value) === '[object Object]';
142
- },
143
- isComplex() {
144
- return this.isArray || this.isObject;
145
- },
146
- children() {
147
- if (!this.isComplex) {
148
- return [];
149
- }
150
- if (this.isArray) {
151
- return this.value.map((childValue, index) => ({
152
- displayKey: null,
153
- value: childValue,
154
- isLast: index === this.value.length - 1,
155
- path: this.createChildPath(this.path, index, true)
156
- }));
157
- }
158
- const keys = Object.keys(this.value);
159
- const visibleKeys = this.visibleObjectKeys(keys);
160
- const hasHidden = this.hasHiddenRootChildren;
161
- return visibleKeys.map((key, index) => ({
162
- displayKey: key,
163
- value: this.value[key],
164
- isLast: !hasHidden && index === visibleKeys.length - 1,
165
- path: this.createChildPath(this.path, key, false)
166
- }));
167
- },
168
- hasChildren() {
169
- return this.children.length > 0;
170
- },
171
- totalObjectChildCount() {
172
- if (!this.isObject) {
173
- return 0;
174
- }
175
- return Object.keys(this.value).length;
176
- },
177
- hasHiddenRootChildren() {
178
- if (!this.isRoot || !this.isObject) {
179
- return false;
180
- }
181
- if (this.topLevelExpanded) {
182
- return false;
183
- }
184
- if (typeof this.maxTopLevelFields !== 'number') {
185
- return false;
186
- }
187
- return this.totalObjectChildCount > this.maxTopLevelFields;
188
- },
189
- hiddenRootChildrenCount() {
190
- if (!this.hasHiddenRootChildren) {
191
- return 0;
192
- }
193
- return this.totalObjectChildCount - this.maxTopLevelFields;
194
- },
195
- showToggle() {
196
- return this.hasChildren && !this.isRoot;
197
- },
198
- openingBracket() {
199
- return this.isArray ? '[' : '{';
200
- },
201
- closingBracket() {
202
- return this.isArray ? ']' : '}';
203
- },
204
- isCollapsedNode() {
205
- return this.isCollapsed(this.path);
206
- },
207
- formattedValue() {
208
- if (typeof this.value === 'bigint') {
209
- return `${this.value.toString()}n`;
210
- }
211
- const stringified = JSON.stringify(this.value);
212
- if (stringified === undefined) {
213
- if (typeof this.value === 'symbol') {
214
- return this.value.toString();
215
- }
216
- return String(this.value);
217
- }
218
- return stringified;
219
- },
220
- valueClasses() {
221
- const classes = ['text-slate-700'];
222
- if (this.value === null) {
223
- classes.push('text-gray-500', 'italic');
224
- return classes;
225
- }
226
- const type = typeof this.value;
227
- if (type === 'string') {
228
- classes.push('text-emerald-600');
229
- return classes;
230
- }
231
- if (type === 'number' || type === 'bigint') {
232
- classes.push('text-amber-600');
233
- return classes;
234
- }
235
- if (type === 'boolean') {
236
- classes.push('text-violet-600');
237
- return classes;
238
- }
239
- if (type === 'undefined') {
240
- classes.push('text-gray-500');
241
- return classes;
242
- }
243
- return classes;
244
- },
245
- comma() {
246
- return this.isLast ? '' : ',';
247
- },
248
- indentStyle() {
249
- return {
250
- paddingLeft: `${this.level * this.indentSize}px`
251
- };
252
- },
253
- hiddenChildrenLabel() {
254
- if (!this.hasHiddenRootChildren) {
255
- return '';
256
- }
257
- const count = this.hiddenRootChildrenCount;
258
- const suffix = count === 1 ? 'field' : 'fields';
259
- return `${count} more ${suffix}`;
260
- },
261
- hiddenChildrenTooltip() {
262
- return this.hiddenChildrenLabel;
263
- },
264
- normalizedPath() {
265
- if (typeof this.path !== 'string') {
266
- return '';
267
- }
268
- return this.path
269
- .replace(/^root\.?/, '')
270
- .replace(/\[\d+\]/g, '')
271
- .replace(/^\./, '');
272
- },
273
- referenceModel() {
274
- if (!this.normalizedPath || !this.references) {
275
- return null;
276
- }
277
- return this.references[this.normalizedPath] || null;
278
- },
279
- shouldShowReferenceLink() {
280
- return Boolean(this.referenceModel) && typeof this.value === 'string';
281
- }
282
- },
283
- methods: {
284
- visibleObjectKeys(keys) {
285
- if (!this.isRoot || this.topLevelExpanded) {
286
- return keys;
287
- }
288
- if (typeof this.maxTopLevelFields !== 'number') {
289
- return keys;
290
- }
291
- if (keys.length <= this.maxTopLevelFields) {
292
- return keys;
293
- }
294
- return keys.slice(0, this.maxTopLevelFields);
295
- },
296
- handleToggle() {
297
- if (!this.isRoot) {
298
- this.toggleCollapse(this.path);
299
- }
300
- },
301
- handleExpandTopLevel() {
302
- if (this.isRoot && typeof this.expandTopLevel === 'function') {
303
- this.expandTopLevel();
304
- }
305
- },
306
- goToReference(id) {
307
- if (!this.referenceModel) {
308
- return;
309
- }
310
- this.$router.push({ path: `/model/${this.referenceModel}/document/${id}` });
311
- }
312
- }
313
- }
314
77
  }
315
78
  });
@@ -1,6 +1,15 @@
1
1
  <div class="list-subdocument tooltip">
2
- <pre>
3
- <code ref="SubDocCode" class="language-javascript">{{shortenValue}}</code>
4
- <span class="tooltiptext" @click.stop="copyText(value)">copy &#x1F4CB;</span>
5
- </pre>
2
+ <div>{{shortenValue}}</div>
3
+ <div class="tooltiptext" style="display:flex; width: 100%; justify-content: space-around; align-items: center; min-width: 180px;">
4
+ <div class="tooltiptextchild" @click.stop="showViewDataModal = true">View data</div>
5
+ <div class="tooltiptextchild" @click.stop="copyText(value)">Copy &#x1F4CB;</div>
6
+ </div>
7
+ <modal ref="viewDataModal" v-if="showViewDataModal" containerClass="!h-[90vh] !w-[90vw]">
8
+ <template #body>
9
+ <div @click.stop class="w-full h-full cursor-auto">
10
+ <div class="modal-exit" @click="showViewDataModal = false">&times;</div>
11
+ <list-json :value="value" :maxTopLevelFields="null" />
12
+ </div>
13
+ </template>
14
+ </modal>
6
15
  </div>
@@ -8,16 +8,24 @@ require('../appendCSS')(require('./list-subdocument.css'));
8
8
  module.exports = app => app.component('list-subdocument', {
9
9
  template: template,
10
10
  props: ['value'],
11
+ data: () => ({ showViewDataModal: false }),
11
12
  computed: {
12
13
  shortenValue() {
13
- return this.value;
14
+ if (this.value == null) {
15
+ return this.value;
16
+ }
17
+ const value = JSON.stringify(this.value);
18
+ if (value.length > 50) {
19
+ return `${value.slice(0, 50)}…`;
20
+ }
21
+ return value;
14
22
  }
15
23
  },
16
24
  methods: {
17
25
  copyText(value) {
18
26
  const storage = document.createElement('textarea');
19
27
  storage.value = JSON.stringify(value);
20
- const elem = this.$refs.SubDocCode;
28
+ const elem = this.$el;
21
29
  elem.appendChild(storage);
22
30
  storage.select();
23
31
  storage.setSelectionRange(0, 99999);
@@ -25,8 +33,5 @@ module.exports = app => app.component('list-subdocument', {
25
33
  elem.removeChild(storage);
26
34
  this.$toast.success('Text copied!');
27
35
  }
28
- },
29
- mounted: function() {
30
- Prism.highlightElement(this.$refs.SubDocCode);
31
36
  }
32
- });
37
+ });
@@ -3,7 +3,7 @@
3
3
  ref="searchInput"
4
4
  class="w-full font-mono rounded-md p-1 border border-gray-300 outline-gray-300 text-lg focus:ring-1 focus:ring-ultramarine-200 focus:ring-offset-0 focus:outline-none"
5
5
  type="text"
6
- placeholder="Filter"
6
+ placeholder="Filter (supports JS, dates, ObjectIds, and more)"
7
7
  v-model="searchText"
8
8
  @click="initFilter"
9
9
  @input="updateAutocomplete"
@@ -1,34 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  const template = require('./document-search.html');
4
- const { Trie } = require('../trie');
5
-
6
- const QUERY_SELECTORS = [
7
- '$eq',
8
- '$ne',
9
- '$gt',
10
- '$gte',
11
- '$lt',
12
- '$lte',
13
- '$in',
14
- '$nin',
15
- '$exists',
16
- '$regex',
17
- '$options',
18
- '$text',
19
- '$search',
20
- '$and',
21
- '$or',
22
- '$nor',
23
- '$not',
24
- '$elemMatch',
25
- '$size',
26
- '$all',
27
- '$type',
28
- '$expr',
29
- '$jsonSchema',
30
- '$mod'
31
- ];
4
+ const {
5
+ buildAutocompleteTrie,
6
+ getAutocompleteSuggestions,
7
+ applySuggestion
8
+ } = require('../../_util/document-search-autocomplete');
32
9
 
33
10
  module.exports = app => app.component('document-search', {
34
11
  template,
@@ -70,19 +47,7 @@ module.exports = app => app.component('document-search', {
70
47
  this.$emit('search', this.searchText);
71
48
  },
72
49
  buildAutocompleteTrie() {
73
- this.autocompleteTrie = new Trie();
74
- this.autocompleteTrie.bulkInsert(QUERY_SELECTORS, 5, 'operator');
75
- if (Array.isArray(this.schemaPaths) && this.schemaPaths.length > 0) {
76
- const paths = this.schemaPaths
77
- .map(path => path?.path)
78
- .filter(path => typeof path === 'string' && path.length > 0);
79
- for (const path of this.schemaPaths) {
80
- if (path.schema) {
81
- paths.push(...Object.keys(path.schema).map(subpath => `${path.path}.${subpath}`));
82
- }
83
- }
84
- this.autocompleteTrie.bulkInsert(paths, 10, 'fieldName');
85
- }
50
+ this.autocompleteTrie = buildAutocompleteTrie(this.schemaPaths);
86
51
  },
87
52
  initFilter(ev) {
88
53
  if (!this.searchText) {
@@ -95,60 +60,18 @@ module.exports = app => app.component('document-search', {
95
60
  updateAutocomplete() {
96
61
  const input = this.$refs.searchInput;
97
62
  const cursorPos = input ? input.selectionStart : 0;
98
- const before = this.searchText.slice(0, cursorPos);
99
- const match = before.match(/(?:\{|,)\s*([^:\s]*)$/);
100
- if (match && match[1]) {
101
- const token = match[1];
102
- const leadingQuoteMatch = token.match(/^["']/);
103
- const trailingQuoteMatch = token.length > 1 && /["']$/.test(token)
104
- ? token[token.length - 1]
105
- : '';
106
- const term = token
107
- .replace(/^["']/, '')
108
- .replace(trailingQuoteMatch ? new RegExp(`[${trailingQuoteMatch}]$`) : '', '')
109
- .trim();
110
- if (!term) {
111
- this.autocompleteSuggestions = [];
112
- return;
113
- }
114
63
 
115
- const colonMatch = before.match(/:\s*([^,\}\]]*)$/);
116
- const role = colonMatch ? 'operator' : 'fieldName';
117
-
118
- if (this.autocompleteTrie) {
119
- const primarySuggestions = this.autocompleteTrie.getSuggestions(term, 10, role);
120
- const suggestionsSet = new Set(primarySuggestions);
121
- if (Array.isArray(this.schemaPaths) && this.schemaPaths.length > 0) {
122
- for (const schemaPath of this.schemaPaths) {
123
- const path = schemaPath?.path;
124
- if (
125
- typeof path === 'string' &&
126
- path.startsWith(`${term}.`) &&
127
- !suggestionsSet.has(path)
128
- ) {
129
- suggestionsSet.add(path);
130
- if (suggestionsSet.size >= 10) {
131
- break;
132
- }
133
- }
134
- }
135
- }
136
- let suggestions = Array.from(suggestionsSet);
137
- if (leadingQuoteMatch) {
138
- const leadingQuote = leadingQuoteMatch[0];
139
- suggestions = suggestions.map(suggestion => `${leadingQuote}${suggestion}`);
140
- }
141
- if (trailingQuoteMatch) {
142
- suggestions = suggestions.map(suggestion =>
143
- suggestion.endsWith(trailingQuoteMatch) ? suggestion : `${suggestion}${trailingQuoteMatch}`
144
- );
145
- }
146
- this.autocompleteSuggestions = suggestions;
147
- this.autocompleteIndex = 0;
148
- return;
149
- }
64
+ if (this.autocompleteTrie) {
65
+ this.autocompleteSuggestions = getAutocompleteSuggestions(
66
+ this.autocompleteTrie,
67
+ this.searchText,
68
+ cursorPos,
69
+ this.schemaPaths
70
+ );
71
+ this.autocompleteIndex = 0;
72
+ } else {
73
+ this.autocompleteSuggestions = [];
150
74
  }
151
- this.autocompleteSuggestions = [];
152
75
  },
153
76
  handleKeyDown(ev) {
154
77
  if (this.autocompleteSuggestions.length === 0) {
@@ -172,32 +95,15 @@ module.exports = app => app.component('document-search', {
172
95
  }
173
96
  const input = this.$refs.searchInput;
174
97
  const cursorPos = input.selectionStart;
175
- const before = this.searchText.slice(0, cursorPos);
176
- const after = this.searchText.slice(cursorPos);
177
- const match = before.match(/(?:\{|,)\s*([^:\s]*)$/);
178
- const colonNeeded = !/^\s*:/.test(after);
179
- if (!match) {
98
+
99
+ const result = applySuggestion(this.searchText, cursorPos, suggestion);
100
+ if (!result) {
180
101
  return;
181
102
  }
182
- const token = match[1];
183
- const start = cursorPos - token.length;
184
- let replacement = suggestion;
185
- const leadingQuote = token.startsWith('"') || token.startsWith('\'') ? token[0] : '';
186
- const trailingQuote = token.length > 1 && (token.endsWith('"') || token.endsWith('\'')) ? token[token.length - 1] : '';
187
- if (leadingQuote && !replacement.startsWith(leadingQuote)) {
188
- replacement = `${leadingQuote}${replacement}`;
189
- }
190
- if (trailingQuote && !replacement.endsWith(trailingQuote)) {
191
- replacement = `${replacement}${trailingQuote}`;
192
- }
193
- // Only insert : if we know the user isn't entering in a nested path
194
- if (colonNeeded && (!leadingQuote || trailingQuote)) {
195
- replacement = `${replacement}:`;
196
- }
197
- this.searchText = this.searchText.slice(0, start) + replacement + after;
103
+
104
+ this.searchText = result.text;
198
105
  this.$nextTick(() => {
199
- const pos = start + replacement.length;
200
- input.setSelectionRange(pos, pos);
106
+ input.setSelectionRange(result.newCursorPos, result.newCursorPos);
201
107
  });
202
108
  this.autocompleteSuggestions = [];
203
109
  },
@@ -44,7 +44,6 @@
44
44
  .models .documents table th {
45
45
  position: sticky;
46
46
  top: 42px;
47
- background-color: white;
48
47
  z-index: 1;
49
48
  }
50
49
 
@@ -60,24 +59,17 @@
60
59
  .models .documents table tr {
61
60
  color: black;
62
61
  border-spacing: 0px 0px;
63
- background-color: white;
64
- cursor: pointer;
65
62
  }
66
63
 
67
- .models .documents table tr:nth-child(even) {
68
- background-color: #f5f5f5;
69
- }
70
-
71
- .models .documents table tr:hover {
72
- background-color: #a7b9ff;
64
+ .models .documents table th {
65
+ border-bottom: thin solid rgba(0, 0, 0, 0.12);
66
+ text-align: left;
67
+ height: 48px;
73
68
  }
74
69
 
75
- .models .documents table th,
76
- td {
70
+ .models .documents table td {
77
71
  border-bottom: thin solid rgba(0, 0, 0, 0.12);
78
72
  text-align: left;
79
- padding: 0 16px;
80
- height: 48px;
81
73
  }
82
74
 
83
75
  .models textarea {
@@ -91,7 +83,6 @@ td {
91
83
 
92
84
  .models .documents-menu {
93
85
  position: fixed;
94
- background-color: white;
95
86
  z-index: 1;
96
87
  padding: 4px;
97
88
  display: flex;
@@ -136,4 +127,3 @@ td {
136
127
  justify-content: space-around;
137
128
  align-items: center;
138
129
  }
139
-