@mongoosejs/studio 0.2.12 → 0.3.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 (92) hide show
  1. package/backend/actions/ChatMessage/executeScript.js +5 -1
  2. package/backend/actions/ChatThread/createChatMessage.js +2 -1
  3. package/backend/actions/ChatThread/streamChatMessage.js +2 -2
  4. package/backend/actions/Model/getEstimatedDocumentCounts.js +38 -0
  5. package/backend/actions/Model/index.js +1 -0
  6. package/backend/actions/Model/streamDocumentChanges.js +8 -7
  7. package/backend/actions/Task/getTasks.js +9 -6
  8. package/backend/authorize.js +1 -0
  9. package/backend/index.js +11 -3
  10. package/eslint.config.js +5 -1
  11. package/express.js +1 -0
  12. package/frontend/public/app.js +25235 -662
  13. package/frontend/public/dark-theme.css +365 -0
  14. package/frontend/public/images/mongoose-studio.svg +4 -0
  15. package/frontend/public/index.html +21 -1
  16. package/frontend/public/style.css +5 -7
  17. package/frontend/public/theme-variables.css +294 -0
  18. package/frontend/public/tw.css +461 -239
  19. package/frontend/src/ace-editor/ace-editor.html +4 -0
  20. package/frontend/src/ace-editor/ace-editor.js +89 -0
  21. package/frontend/src/aceEditor.js +69 -0
  22. package/frontend/src/api.js +6 -0
  23. package/frontend/src/chat/chat-message/chat-message.html +1 -1
  24. package/frontend/src/chat/chat-message/chat-message.js +1 -1
  25. package/frontend/src/chat/chat-message-script/chat-message-script.html +51 -34
  26. package/frontend/src/chat/chat-message-script/chat-message-script.js +12 -55
  27. package/frontend/src/chat/chat.html +72 -37
  28. package/frontend/src/chat/chat.js +26 -2
  29. package/frontend/src/clone-document/clone-document.html +7 -2
  30. package/frontend/src/clone-document/clone-document.js +1 -8
  31. package/frontend/src/create-dashboard/create-dashboard.html +11 -6
  32. package/frontend/src/create-dashboard/create-dashboard.js +0 -7
  33. package/frontend/src/create-document/create-document.html +15 -9
  34. package/frontend/src/create-document/create-document.js +5 -12
  35. package/frontend/src/dashboard/dashboard.html +14 -12
  36. package/frontend/src/dashboard/dashboard.js +12 -4
  37. package/frontend/src/dashboard/edit-dashboard/edit-dashboard.html +13 -7
  38. package/frontend/src/dashboard/edit-dashboard/edit-dashboard.js +13 -21
  39. package/frontend/src/dashboard-result/dashboard-chart/dashboard-chart.html +19 -17
  40. package/frontend/src/dashboard-result/dashboard-chart/dashboard-chart.js +97 -2
  41. package/frontend/src/dashboard-result/dashboard-map/dashboard-map.js +27 -3
  42. package/frontend/src/dashboard-result/dashboard-result.html +3 -3
  43. package/frontend/src/dashboards/dashboards.html +101 -109
  44. package/frontend/src/dashboards/dashboards.js +25 -1
  45. package/frontend/src/detail-default/detail-default.html +2 -2
  46. package/frontend/src/detail-default/detail-default.js +24 -3
  47. package/frontend/src/document/confirm-changes/confirm-changes.html +1 -1
  48. package/frontend/src/document/confirm-delete/confirm-delete.html +1 -1
  49. package/frontend/src/document/document.css +1 -1
  50. package/frontend/src/document/document.html +53 -27
  51. package/frontend/src/document/document.js +27 -1
  52. package/frontend/src/document/execute-script/execute-script.html +20 -21
  53. package/frontend/src/document/execute-script/execute-script.js +1 -43
  54. package/frontend/src/document-details/document-details.css +4 -9
  55. package/frontend/src/document-details/document-details.html +34 -33
  56. package/frontend/src/document-details/document-details.js +2 -53
  57. package/frontend/src/document-details/document-property/document-property.html +12 -12
  58. package/frontend/src/edit-array/edit-array.html +7 -6
  59. package/frontend/src/edit-array/edit-array.js +10 -50
  60. package/frontend/src/edit-boolean/edit-boolean.html +12 -12
  61. package/frontend/src/edit-date/edit-date.html +2 -2
  62. package/frontend/src/edit-default/edit-default.html +1 -1
  63. package/frontend/src/edit-string/edit-string.html +3 -3
  64. package/frontend/src/edit-subdocument/edit-subdocument.html +5 -3
  65. package/frontend/src/edit-subdocument/edit-subdocument.js +1 -15
  66. package/frontend/src/export-query-results/export-query-results.html +3 -3
  67. package/frontend/src/json-node/json-node.html +3 -3
  68. package/frontend/src/list-json/json-node.html +1 -1
  69. package/frontend/src/models/document-search/document-search.html +3 -3
  70. package/frontend/src/models/model-switcher/model-switcher.html +53 -0
  71. package/frontend/src/models/model-switcher/model-switcher.js +123 -0
  72. package/frontend/src/models/models.css +3 -10
  73. package/frontend/src/models/models.html +146 -74
  74. package/frontend/src/models/models.js +142 -4
  75. package/frontend/src/navbar/navbar.html +157 -97
  76. package/frontend/src/navbar/navbar.js +32 -13
  77. package/frontend/src/routes.js +20 -4
  78. package/frontend/src/splash/splash.html +5 -5
  79. package/frontend/src/task-by-name/task-by-name.html +15 -0
  80. package/frontend/src/task-by-name/task-by-name.js +78 -0
  81. package/frontend/src/task-single/task-single.html +157 -0
  82. package/frontend/src/task-single/task-single.js +116 -0
  83. package/frontend/src/tasks/task-details/task-details.html +124 -73
  84. package/frontend/src/tasks/task-details/task-details.js +166 -10
  85. package/frontend/src/tasks/tasks.html +37 -48
  86. package/frontend/src/tasks/tasks.js +11 -50
  87. package/frontend/src/team/new-invitation/new-invitation.html +8 -8
  88. package/frontend/src/team/team.html +27 -27
  89. package/frontend/src/update-document/update-document.html +7 -2
  90. package/frontend/src/update-document/update-document.js +2 -11
  91. package/package.json +3 -1
  92. package/tailwind.config.js +75 -11
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const template = require('./edit-subdocument.html');
4
-
5
4
  const { BSON, EJSON } = require('mongodb/lib/bson');
6
5
 
7
6
  const ObjectId = new Proxy(BSON.ObjectId, {
@@ -13,19 +12,11 @@ const ObjectId = new Proxy(BSON.ObjectId, {
13
12
  module.exports = app => app.component('edit-subdocument', {
14
13
  template: template,
15
14
  props: ['value'],
16
- data: () => ({ currentValue: null, status: 'init' }),
15
+ data: () => ({ currentValue: '', status: 'init' }),
17
16
  mounted() {
18
17
  this.currentValue = this.value == null
19
18
  ? '' + this.value
20
19
  : JSON.stringify(this.value, null, ' ').trim();
21
- this.$refs.editor.value = this.currentValue;
22
- this.editor = CodeMirror.fromTextArea(this.$refs.editor, {
23
- mode: 'javascript',
24
- lineNumbers: true
25
- });
26
- this.editor.on('change', ev => {
27
- this.currentValue = this.editor.getValue();
28
- });
29
20
  this.status = 'loaded';
30
21
  },
31
22
  watch: {
@@ -41,10 +32,5 @@ module.exports = app => app.component('edit-subdocument', {
41
32
  }
42
33
  }
43
34
  },
44
- beforeDestroy() {
45
- if (this.editor) {
46
- this.editor.toTextArea();
47
- }
48
- },
49
35
  emits: ['input', 'error']
50
36
  });
@@ -4,10 +4,10 @@
4
4
  Choose fields to export
5
5
  </div>
6
6
  <div v-for="(schemaPath,index) in schemaPaths" class="w-5 flex items-center">
7
- <input type="checkbox" class="mt-0 h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-600 accent-sky-600" v-model="shouldExport[schemaPath.path]" :id="'schemaPath.path'+index">
8
- <div class="ml-2 text-gray-700 grow shrink text-left">
7
+ <input type="checkbox" class="mt-0 h-4 w-4 rounded border-edge-strong text-sky-600 focus:ring-sky-600 accent-sky-600" v-model="shouldExport[schemaPath.path]" :id="'schemaPath.path'+index">
8
+ <div class="ml-2 text-content-secondary grow shrink text-left">
9
9
  <label :for="'schemaPath.path'+index">{{schemaPath.path}}</label>
10
10
  </div>
11
11
  </div>
12
- <async-button class="rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600" @click="exportQueryResults">Export</async-button>
12
+ <async-button class="rounded-md bg-primary px-2.5 py-1.5 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600" @click="exportQueryResults">Export</async-button>
13
13
  </div>
@@ -3,14 +3,14 @@
3
3
  <button
4
4
  v-if="showToggle"
5
5
  type="button"
6
- class="w-4 h-4 mr-1 inline-flex items-center justify-center leading-none text-gray-500 hover:text-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400 cursor-pointer"
6
+ class="w-4 h-4 mr-1 inline-flex items-center justify-center leading-none text-content-tertiary hover:text-content-secondary focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400 cursor-pointer"
7
7
  @click.stop="handleToggle"
8
8
  >
9
9
  {{ isCollapsedNode ? '+' : '-' }}
10
10
  </button>
11
11
  <span v-else class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible flex-shrink-0"></span>
12
12
  <template v-if="hasKey">
13
- <span class="text-blue-600">"{{ nodeKey }}"</span><span>: </span>
13
+ <span class="text-primary">"{{ nodeKey }}"</span><span>: </span>
14
14
  </template>
15
15
  <template v-if="isComplex">
16
16
  <template v-if="hasChildren">
@@ -52,7 +52,7 @@
52
52
  </span>
53
53
  <a
54
54
  href="#"
55
- class="ml-1 text-sm text-sky-700 opacity-0 group-hover:opacity-100 focus:opacity-100 transition-opacity"
55
+ class="ml-1 text-sm text-primary opacity-0 group-hover:opacity-100 focus:opacity-100 transition-opacity"
56
56
  @click.stop.prevent="goToReference()"
57
57
  >
58
58
  View Document
@@ -3,7 +3,7 @@
3
3
  <button
4
4
  v-if="showToggle"
5
5
  type="button"
6
- class="w-4 h-4 mr-1 inline-flex items-center justify-center leading-none text-gray-500 hover:text-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400 cursor-pointer"
6
+ class="w-4 h-4 mr-1 inline-flex items-center justify-center leading-none text-content-tertiary hover:text-content-secondary focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400 cursor-pointer"
7
7
  @click.stop="handleToggle"
8
8
  >
9
9
  {{ isCollapsedNode ? '+' : '-' }}
@@ -1,7 +1,7 @@
1
1
  <form @submit.prevent="emitSearch" class="relative flex-grow m-0">
2
2
  <input
3
3
  ref="searchInput"
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"
4
+ class="w-full font-mono rounded-md p-1 border border-edge-strong outline-edge-strong text-lg focus:ring-1 focus:ring-primary-subtle focus:ring-offset-0 focus:outline-none"
5
5
  type="text"
6
6
  placeholder="Filter (supports JS, dates, ObjectIds, and more)"
7
7
  v-model="searchText"
@@ -9,12 +9,12 @@
9
9
  @input="updateAutocomplete"
10
10
  @keydown="handleKeyDown"
11
11
  />
12
- <ul v-if="autocompleteSuggestions.length" class="absolute z-[9999] bg-white border border-gray-300 rounded mt-1 w-full max-h-40 overflow-y-auto shadow">
12
+ <ul v-if="autocompleteSuggestions.length" class="absolute z-[9999] bg-surface border border-edge-strong rounded mt-1 w-full max-h-40 overflow-y-auto shadow">
13
13
  <li
14
14
  v-for="(suggestion, index) in autocompleteSuggestions"
15
15
  :key="suggestion"
16
16
  class="px-2 py-1 cursor-pointer"
17
- :class="{ 'bg-ultramarine-100': index === autocompleteIndex }"
17
+ :class="{ 'bg-primary-subtle': index === autocompleteIndex }"
18
18
  @mousedown.prevent="applySuggestion(index)"
19
19
  >
20
20
  {{ suggestion }}
@@ -0,0 +1,53 @@
1
+ <div v-if="show" class="fixed inset-0 z-[9999]">
2
+ <div class="fixed inset-0 bg-black/40" @click="$emit('close')"></div>
3
+ <div
4
+ class="fixed top-[15%] left-1/2 -translate-x-1/2 w-full max-w-lg bg-surface rounded-lg shadow-2xl border border-edge overflow-hidden"
5
+ role="dialog"
6
+ aria-modal="true"
7
+ aria-label="Model switcher"
8
+ >
9
+ <div class="p-3 border-b border-gray-100">
10
+ <input
11
+ ref="searchInput"
12
+ v-model="search"
13
+ type="text"
14
+ placeholder="Type a model name..."
15
+ @keydown="handleKeydown"
16
+ class="w-full rounded-md border border-edge bg-page py-2 px-3 text-sm text-content placeholder:text-gray-400 focus:border-edge-strong focus:outline-none focus:ring-1 focus:ring-gray-300"
17
+ />
18
+ </div>
19
+ <div ref="list" class="max-h-80 overflow-y-auto">
20
+ <template v-for="(item, index) in items" :key="item.section + '-' + item.model">
21
+ <div
22
+ v-if="index === 0 || items[index - 1].section !== item.section"
23
+ class="px-3 pt-2 pb-1 text-xs font-semibold text-gray-400 uppercase tracking-wider"
24
+ >
25
+ {{ item.section }}
26
+ </div>
27
+ <div
28
+ :data-selected="index === selectedIndex"
29
+ @click="selectModel(item.model)"
30
+ @mouseenter="onMouseEnter(index)"
31
+ class="flex items-center px-3 py-1.5 mx-2 rounded-md cursor-pointer text-sm"
32
+ :class="index === selectedIndex ? 'bg-muted text-content' : 'text-content-secondary hover:bg-page'"
33
+ >
34
+ <span class="truncate" v-html="highlightMatch(item.model)"></span>
35
+ <span
36
+ v-if="modelDocumentCounts && modelDocumentCounts[item.model] !== undefined"
37
+ class="ml-auto text-xs text-gray-400 bg-muted rounded px-1.5 py-[1px]"
38
+ >
39
+ {{ formatCompactCount(modelDocumentCounts[item.model]) }}
40
+ </span>
41
+ </div>
42
+ </template>
43
+ <div v-if="items.length === 0" class="px-3 py-4 text-sm text-content-tertiary text-center">
44
+ No models match "{{ search }}"
45
+ </div>
46
+ </div>
47
+ <div class="border-t border-gray-100 px-3 py-2 text-xs text-gray-400 flex gap-3">
48
+ <span>↑↓ navigate</span>
49
+ <span>↵ select</span>
50
+ <span>esc close</span>
51
+ </div>
52
+ </div>
53
+ </div>
@@ -0,0 +1,123 @@
1
+ 'use strict';
2
+
3
+ const template = require('./model-switcher.html');
4
+ const xss = require('xss');
5
+
6
+ module.exports = app => app.component('model-switcher', {
7
+ template: template,
8
+ props: ['show', 'models', 'recentlyViewedModels', 'modelDocumentCounts'],
9
+ emits: ['close', 'select'],
10
+ data: () => ({
11
+ search: '',
12
+ selectedIndex: 0
13
+ }),
14
+ mounted() {
15
+ this.lastMouseMove = 0;
16
+ this.trackLastMouseMove = () => {
17
+ this.lastMouseMove = Date.now();
18
+ };
19
+
20
+ window.addEventListener('mousemove', this.trackLastMouseMove);
21
+ },
22
+ beforeUnmount() {
23
+ window.removeEventListener('mousemove', this.trackLastMouseMove);
24
+ },
25
+ watch: {
26
+ search() {
27
+ this.selectedIndex = 0;
28
+ },
29
+ show(val) {
30
+ if (val) {
31
+ this.search = '';
32
+ this.selectedIndex = 0;
33
+ this.$nextTick(() => {
34
+ if (this.$refs.searchInput) this.$refs.searchInput.focus();
35
+ });
36
+ }
37
+ }
38
+ },
39
+ computed: {
40
+ items() {
41
+ const items = [];
42
+ const search = this.search.trim().toLowerCase();
43
+ const recent = (this.recentlyViewedModels || []).filter(m => this.models.includes(m));
44
+
45
+ if (!search && recent.length > 0) {
46
+ for (const model of recent) {
47
+ items.push({ model, section: 'Recently Viewed' });
48
+ }
49
+ }
50
+
51
+ const filtered = search
52
+ ? this.models.filter(m => m.toLowerCase().includes(search))
53
+ : this.models;
54
+
55
+ for (const model of filtered) {
56
+ items.push({ model, section: search ? 'Search Results' : 'All Models' });
57
+ }
58
+
59
+ return items;
60
+ }
61
+ },
62
+ methods: {
63
+ selectModel(model) {
64
+ this.$emit('select', model);
65
+ },
66
+ onMouseEnter(index) {
67
+ // If user hasn't moved mouse recently, then this event was likely triggered by scroll
68
+ if (this.lastMouseMove <= Date.now() - 500) {
69
+ return;
70
+ }
71
+ this.selectedIndex = index;
72
+ },
73
+ handleKeydown(event) {
74
+ const items = this.items;
75
+ if (event.key === 'ArrowDown') {
76
+ event.preventDefault();
77
+ this.selectedIndex = Math.min(this.selectedIndex + 1, items.length - 1);
78
+ this.scrollSelectedIntoView();
79
+ } else if (event.key === 'ArrowUp') {
80
+ event.preventDefault();
81
+ this.selectedIndex = Math.max(this.selectedIndex - 1, 0);
82
+ this.scrollSelectedIntoView();
83
+ } else if (event.key === 'Enter') {
84
+ event.preventDefault();
85
+ if (items.length > 0 && items[this.selectedIndex]) {
86
+ this.selectModel(items[this.selectedIndex].model);
87
+ }
88
+ } else if (event.key === 'Escape') {
89
+ event.preventDefault();
90
+ this.$emit('close');
91
+ }
92
+ },
93
+ scrollSelectedIntoView() {
94
+ this.$nextTick(() => {
95
+ const container = this.$refs.list;
96
+ if (!container) return;
97
+ const selected = container.querySelector('[data-selected="true"]');
98
+ if (selected) selected.scrollIntoView({ block: 'nearest' });
99
+ });
100
+ },
101
+ highlightMatch(model) {
102
+ const search = this.search.trim();
103
+ if (!search) return model;
104
+ const idx = model.toLowerCase().indexOf(search.toLowerCase());
105
+ if (idx === -1) return model;
106
+ const before = model.slice(0, idx);
107
+ const match = model.slice(idx, idx + search.length);
108
+ const after = model.slice(idx + search.length);
109
+ return `${xss(before)}<strong>${xss(match)}</strong>${xss(after)}`;
110
+ },
111
+ formatCompactCount(value) {
112
+ if (typeof value !== 'number') return '—';
113
+ if (value < 1000) return `${value}`;
114
+ const formatValue = (number, suffix) => {
115
+ const rounded = (Math.round(number * 10) / 10).toFixed(1).replace(/\.0$/, '');
116
+ return `${rounded}${suffix}`;
117
+ };
118
+ if (value < 1000000) return formatValue(value / 1000, 'k');
119
+ if (value < 1000000000) return formatValue(value / 1000000, 'M');
120
+ return formatValue(value / 1000000000, 'B');
121
+ }
122
+ }
123
+ });
@@ -11,7 +11,6 @@
11
11
  }
12
12
 
13
13
  .models .model-selector {
14
- background-color: #eee;
15
14
  flex-grow: 0;
16
15
  padding: 15px;
17
16
  padding-top: 0px;
@@ -82,17 +81,11 @@
82
81
  }
83
82
 
84
83
  .models .documents-menu {
85
- position: fixed;
86
- z-index: 1;
84
+ position: sticky;
85
+ top: 0;
86
+ z-index: 2;
87
87
  padding: 4px;
88
88
  display: flex;
89
- width: 100vw;
90
- }
91
-
92
- @media (min-width: 1024px) {
93
- .models .documents-menu {
94
- width: calc(100vw - 12rem);
95
- }
96
89
  }
97
90
 
98
91
  .models .documents-menu .search-input {