@mongoosejs/studio 0.0.83 → 0.0.84

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.
@@ -1235,6 +1235,16 @@ video {
1235
1235
  border-color: rgb(63 83 255 / var(--tw-border-opacity));
1236
1236
  }
1237
1237
 
1238
+ .border-red-500 {
1239
+ --tw-border-opacity: 1;
1240
+ border-color: rgb(239 68 68 / var(--tw-border-opacity));
1241
+ }
1242
+
1243
+ .bg-blue-200 {
1244
+ --tw-bg-opacity: 1;
1245
+ background-color: rgb(191 219 254 / var(--tw-bg-opacity));
1246
+ }
1247
+
1238
1248
  .bg-blue-500 {
1239
1249
  --tw-bg-opacity: 1;
1240
1250
  background-color: rgb(59 130 246 / var(--tw-bg-opacity));
@@ -1330,6 +1340,11 @@ video {
1330
1340
  background-color: rgb(239 68 68 / var(--tw-bg-opacity));
1331
1341
  }
1332
1342
 
1343
+ .bg-red-600 {
1344
+ --tw-bg-opacity: 1;
1345
+ background-color: rgb(220 38 38 / var(--tw-bg-opacity));
1346
+ }
1347
+
1333
1348
  .bg-slate-100 {
1334
1349
  --tw-bg-opacity: 1;
1335
1350
  background-color: rgb(241 245 249 / var(--tw-bg-opacity));
@@ -1384,6 +1399,11 @@ video {
1384
1399
  background-color: rgb(253 224 71 / var(--tw-bg-opacity));
1385
1400
  }
1386
1401
 
1402
+ .bg-ultramarine-500 {
1403
+ --tw-bg-opacity: 1;
1404
+ background-color: rgb(63 83 255 / var(--tw-bg-opacity));
1405
+ }
1406
+
1387
1407
  .p-1 {
1388
1408
  padding: 0.25rem;
1389
1409
  }
@@ -1726,6 +1746,12 @@ video {
1726
1746
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
1727
1747
  }
1728
1748
 
1749
+ .ring-2 {
1750
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
1751
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
1752
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
1753
+ }
1754
+
1729
1755
  .ring-inset {
1730
1756
  --tw-ring-inset: inset;
1731
1757
  }
@@ -1751,6 +1777,16 @@ video {
1751
1777
  --tw-ring-color: rgb(22 163 74 / 0.2);
1752
1778
  }
1753
1779
 
1780
+ .ring-yellow-800 {
1781
+ --tw-ring-opacity: 1;
1782
+ --tw-ring-color: rgb(133 77 14 / var(--tw-ring-opacity));
1783
+ }
1784
+
1785
+ .ring-yellow-300 {
1786
+ --tw-ring-opacity: 1;
1787
+ --tw-ring-color: rgb(253 224 71 / var(--tw-ring-opacity));
1788
+ }
1789
+
1754
1790
  .filter {
1755
1791
  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
1756
1792
  }
@@ -1854,6 +1890,11 @@ video {
1854
1890
  background-color: rgb(22 163 74 / var(--tw-bg-opacity));
1855
1891
  }
1856
1892
 
1893
+ .hover\:bg-red-500:hover {
1894
+ --tw-bg-opacity: 1;
1895
+ background-color: rgb(239 68 68 / var(--tw-bg-opacity));
1896
+ }
1897
+
1857
1898
  .hover\:bg-red-600:hover {
1858
1899
  --tw-bg-opacity: 1;
1859
1900
  background-color: rgb(220 38 38 / var(--tw-bg-opacity));
@@ -1899,6 +1940,11 @@ video {
1899
1940
  background-color: rgb(220 73 73 / var(--tw-bg-opacity));
1900
1941
  }
1901
1942
 
1943
+ .hover\:bg-ultramarine-600:hover {
1944
+ --tw-bg-opacity: 1;
1945
+ background-color: rgb(24 35 255 / var(--tw-bg-opacity));
1946
+ }
1947
+
1902
1948
  .hover\:text-gray-700:hover {
1903
1949
  --tw-text-opacity: 1;
1904
1950
  color: rgb(55 65 81 / var(--tw-text-opacity));
@@ -2037,6 +2083,10 @@ video {
2037
2083
  outline-color: #fb923c;
2038
2084
  }
2039
2085
 
2086
+ .focus-visible\:outline-red-500:focus-visible {
2087
+ outline-color: #ef4444;
2088
+ }
2089
+
2040
2090
  .focus-visible\:outline-red-600:focus-visible {
2041
2091
  outline-color: #dc2626;
2042
2092
  }
@@ -2057,6 +2107,14 @@ video {
2057
2107
  outline-color: #1823ff;
2058
2108
  }
2059
2109
 
2110
+ .focus-visible\:outline-gray-600:focus-visible {
2111
+ outline-color: #4b5563;
2112
+ }
2113
+
2114
+ .focus-visible\:outline-gray-500:focus-visible {
2115
+ outline-color: #6b7280;
2116
+ }
2117
+
2060
2118
  .disabled\:cursor-not-allowed:disabled {
2061
2119
  cursor: not-allowed;
2062
2120
  }
@@ -75,6 +75,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
75
75
  deleteDocument(params) {
76
76
  return client.post('', { action: 'Model.deleteDocument', ...params}).then(res => res.data);
77
77
  },
78
+ deleteDocuments(params) {
79
+ return client.post('', { action: 'Model.deleteDocuments', ...params}).then(res => res.data);
80
+ },
78
81
  exportQueryResults(params) {
79
82
  const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
80
83
 
@@ -113,6 +116,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
113
116
  },
114
117
  updateDocument: function updateDocument(params) {
115
118
  return client.post('', { action: 'Model.updateDocument', ...params }).then(res => res.data);
119
+ },
120
+ updateDocuments: function updateDocuments(params) {
121
+ return client.post('', { action: 'Model.updateDocuments', ...params }).then(res => res.data);
116
122
  }
117
123
  };
118
124
  } else {
@@ -165,6 +171,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
165
171
  deleteDocument: function (params) {
166
172
  return client.post('/Model/deleteDocument', params).then(res => res.data);
167
173
  },
174
+ deleteDocuments: function(params) {
175
+ return client.post('/Model/deleteDocuments', params).then(res => res.data);
176
+ },
168
177
  exportQueryResults(params) {
169
178
  const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
170
179
 
@@ -206,6 +215,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
206
215
  },
207
216
  updateDocument: function updateDocument(params) {
208
217
  return client.post('/Model/updateDocument', params).then(res => res.data);
218
+ },
219
+ updateDocuments: function updateDocument(params) {
220
+ return client.post('/Model/updateDocuments', params).then(res => res.data);
209
221
  }
210
222
  };
211
223
  }
@@ -28,7 +28,7 @@
28
28
  <div class="documents-menu">
29
29
  <div class="flex flex-row items-center w-full gap-2">
30
30
  <form @submit.prevent="search" class="flex-grow m-0">
31
- <input ref="searchInput" 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" type="text" placeholder="Filter or text" v-model="searchText" />
31
+ <input ref="searchInput" 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" type="text" placeholder="Filter" v-model="searchText" @click="initFilter" />
32
32
  </form>
33
33
  <div>
34
34
  <span v-if="status === 'loading'">Loading ...</span>
@@ -37,24 +37,52 @@
37
37
  <button
38
38
  @click="shouldShowExportModal = true"
39
39
  type="button"
40
+ v-show="!selectMultiple"
40
41
  class="rounded bg-ultramarine-600 px-2 py-2 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-ultramarine-600">
41
42
  Export
42
43
  </button>
44
+ <button
45
+ @click="stagingSelect"
46
+ type="button"
47
+ :class="{ 'bg-ultramarine-500 ring-inset ring-2 ring-gray-300 hover:bg-ultramarine-600': selectMultiple }"
48
+ class="rounded bg-ultramarine-600 px-2 py-2 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-ultramarine-600"
49
+ >
50
+ Select
51
+ </button>
52
+ <button
53
+ v-show="selectMultiple"
54
+ @click="shouldShowUpdateMultipleModal=true;"
55
+ type="button"
56
+ class="rounded bg-green-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600"
57
+ >
58
+ Update
59
+ </button>
60
+ <button
61
+ @click="shouldShowDeleteMultipleModal=true;"
62
+ type="button"
63
+ v-show="selectMultiple"
64
+ class="rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500"
65
+ >
66
+ Delete
67
+ </button>
43
68
  <button
44
69
  @click="openIndexModal"
45
70
  type="button"
71
+ v-show="!selectMultiple"
46
72
  class="rounded bg-ultramarine-600 px-2 py-2 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-ultramarine-600">
47
73
  Indexes
48
74
  </button>
49
75
  <button
50
76
  @click="shouldShowCreateModal = true;"
51
77
  type="button"
78
+ v-show="!selectMultiple"
52
79
  class="rounded bg-ultramarine-600 px-2 py-2 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-ultramarine-600">
53
80
  Create
54
81
  </button>
55
82
  <button
56
83
  @click="openFieldSelection"
57
84
  type="button"
85
+ v-show="!selectMultiple"
58
86
  class="rounded bg-ultramarine-600 px-2 py-2 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-ultramarine-600">
59
87
  Fields
60
88
  </button>
@@ -90,8 +118,8 @@
90
118
  </th>
91
119
  </thead>
92
120
  <tbody>
93
- <tr v-for="document in documents" @click="$router.push('/model/' + currentModel + '/document/' + document._id)" :key="document._id">
94
- <td v-for="schemaPath in filteredPaths">
121
+ <tr v-for="document in documents" @click="handleDocumentClick(document)" :key="document._id">
122
+ <td v-for="schemaPath in filteredPaths" :class="{ 'bg-blue-200': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }">
95
123
  <component
96
124
  :is="getComponentForPath(schemaPath)"
97
125
  :value="getValueForPath(document, schemaPath.path)"
@@ -102,7 +130,7 @@
102
130
  </tbody>
103
131
  </table>
104
132
  <div v-if="outputType === 'json'">
105
- <div v-for="document in documents" @click="$router.push('/model/' + currentModel + '/document/' + document._id)" :key="document._id">
133
+ <div v-for="document in documents" @click="handleDocumentClick(document)" :key="document._id" :class="{ 'bg-blue-200': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }">
106
134
  <list-json :value="filterDocument(document)">
107
135
  </list-json>
108
136
  </div>
@@ -162,4 +190,27 @@
162
190
  <create-document :currentModel="currentModel" :paths="schemaPaths" @close="closeCreationModal"></create-document>
163
191
  </template>
164
192
  </modal>
193
+ <modal v-if="shouldShowUpdateMultipleModal">
194
+ <template v-slot:body>
195
+ <div class="modal-exit" @click="shouldShowUpdateMultipleModal = false;">&times;</div>
196
+ <update-document :currentModel="currentModel" :document="selectedDocuments" :multiple="true" @update="updateDocuments" @close="shouldShowUpdateMultipleModal=false;"></update-document>
197
+ </template>
198
+ </modal>
199
+ <modal v-if="shouldShowDeleteMultipleModal">
200
+ <template v-slot:body>
201
+ <div class="modal-exit" @click="shouldShowDeleteMultipleModal = false;">&times;</div>
202
+ <h2>Are you sure you want to delete {{selectedDocuments.length}} documents?</h2>
203
+ <div>
204
+ <list-json :value="selectedDocuments"></list-json>
205
+ </div>
206
+ <div class="flex gap-4">
207
+ <async-button @click="deleteDocuments" class="rounded bg-red-500 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600">
208
+ Confirm
209
+ </async-button>
210
+ <button @click="shouldShowDeleteMultipleModal = false;" class="rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500">
211
+ Cancel
212
+ </button>
213
+ </div>
214
+ </template>
215
+ </modal>
165
216
  </div>
@@ -38,11 +38,15 @@ module.exports = app => app.component('models', {
38
38
  edittingDoc: null,
39
39
  docEdits: null,
40
40
  filter: null,
41
+ selectMultiple: false,
42
+ selectedDocuments: [],
41
43
  searchText: '',
42
44
  shouldShowExportModal: false,
43
45
  shouldShowCreateModal: false,
44
46
  shouldShowFieldModal: false,
45
47
  shouldShowIndexModal: false,
48
+ shouldShowUpdateMultipleModal: false,
49
+ shouldShowDeleteMultipleModal: false,
46
50
  shouldExport: {},
47
51
  sortBy: {},
48
52
  query: {},
@@ -89,6 +93,14 @@ module.exports = app => app.component('models', {
89
93
  this.status = 'loaded';
90
94
  },
91
95
  methods: {
96
+ initFilter(ev) {
97
+ if (!this.searchText) {
98
+ this.searchText = '{}';
99
+ this.$nextTick(() => {
100
+ ev.target.setSelectionRange(1, 1);
101
+ });
102
+ }
103
+ },
92
104
  clickFilter(path) {
93
105
  if (this.searchText) {
94
106
  if (this.searchText.endsWith('}')) {
@@ -319,6 +331,46 @@ module.exports = app => app.component('models', {
319
331
  this.documents[index] = res.doc;
320
332
  }
321
333
  this.edittingDoc = null;
334
+ },
335
+ handleDocumentClick(document) {
336
+ console.log(this.selectedDocuments);
337
+ if (this.selectMultiple) {
338
+ const exists = this.selectedDocuments.find(x => x._id.toString() == document._id.toString())
339
+ if (exists) {
340
+ const index = this.selectedDocuments.findIndex(x => x._id.toString() == document._id.toString());
341
+ if (index !== -1) {
342
+ this.selectedDocuments.splice(index, 1);
343
+ }
344
+ } else {
345
+ this.selectedDocuments.push(document);
346
+ }
347
+ } else {
348
+ this.$router.push('/model/' + this.currentModel + '/document/' + document._id)
349
+ }
350
+ },
351
+ async deleteDocuments() {
352
+ const documentIds = this.selectedDocuments.map(x => x._id);
353
+ await api.Model.deleteDocuments({
354
+ documentIds,
355
+ model: this.currentModel
356
+ });
357
+ await this.getDocuments();
358
+ this.selectedDocuments.length = 0;
359
+ this.shouldShowDeleteMultipleModal = false;
360
+ this.selectMultiple = false;
361
+ },
362
+ async updateDocuments() {
363
+ await this.getDocuments();
364
+ this.selectedDocuments.length = 0;
365
+ this.selectMultiple = false;
366
+ },
367
+ stagingSelect() {
368
+ if (this.selectMultiple) {
369
+ this.selectMultiple = false;
370
+ this.selectedDocuments.length = 0;
371
+ } else {
372
+ this.selectMultiple = true;
373
+ }
322
374
  }
323
375
  }
324
376
  });
@@ -0,0 +1,25 @@
1
+ <div>
2
+ <div class="mb-2">
3
+ <textarea class="border border-gray-200 p-2 h-[300px] w-full" ref="codeEditor"></textarea>
4
+ </div>
5
+ <button @click="updateDocument()" 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">Submit</button>
6
+ <div v-if="errors.length > 0" class="rounded-md bg-red-50 p-4 mt-1">
7
+ <div class="flex">
8
+ <div class="flex-shrink-0">
9
+ <svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
10
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd" />
11
+ </svg>
12
+ </div>
13
+ <div class="ml-3">
14
+ <h3 class="text-sm font-medium text-red-800">There were {{errors.length}} errors with your submission</h3>
15
+ <div class="mt-2 text-sm text-red-700">
16
+ <ul role="list" class="list-disc space-y-1 pl-5">
17
+ <li v-for="error in errors">
18
+ {{error}}
19
+ </li>
20
+ </ul>
21
+ </div>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ </div>
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ const api = require('../api');
4
+
5
+ const { BSON, EJSON } = require('bson');
6
+
7
+ const ObjectId = new Proxy(BSON.ObjectId, {
8
+ apply (target, thisArg, argumentsList) {
9
+ return new target(...argumentsList);
10
+ }
11
+ });
12
+
13
+ const appendCSS = require('../appendCSS');
14
+
15
+ appendCSS(require('./update-document.css'));
16
+
17
+ const template = require('./update-document.html')
18
+
19
+ module.exports = app => app.component('update-document', {
20
+ props: ['currentModel', 'document', 'multiple'],
21
+ template,
22
+ data: function() {
23
+ return {
24
+ editor: null,
25
+ errors: []
26
+ }
27
+ },
28
+ methods: {
29
+ async updateDocument() {
30
+ const data = EJSON.serialize(eval(`(${this.editor.getValue()})`));
31
+ if (this.multiple) {
32
+ const ids = this.document.map(x => x._id);
33
+ await api.Model.updateDocuments({ model: this.currentModel, _id: ids, update: data }).catch(err => {
34
+ if (err.response?.data?.message) {
35
+ console.log(err.response.data);
36
+ const message = err.response.data.message.split(": ").slice(1).join(": ");
37
+ this.errors = message.split(',').map(error => {
38
+ return error.split(': ').slice(1).join(': ').trim();
39
+ })
40
+ throw new Error(err.response?.data?.message);
41
+ }
42
+ throw err;
43
+ });
44
+ } else {
45
+ await api.Model.updateDocument({ model: this.currentModel, _id: this.document._id, update: data }).catch(err => {
46
+ if (err.response?.data?.message) {
47
+ console.log(err.response.data);
48
+ const message = err.response.data.message.split(": ").slice(1).join(": ");
49
+ this.errors = message.split(',').map(error => {
50
+ return error.split(': ').slice(1).join(': ').trim();
51
+ })
52
+ throw new Error(err.response?.data?.message);
53
+ }
54
+ throw err;
55
+ });
56
+ }
57
+ this.errors.length = 0;
58
+ this.$emit('update');
59
+ this.$emit('close');
60
+ },
61
+ },
62
+ mounted: function() {
63
+ this.$refs.codeEditor.value = `{\n \n}`
64
+ this.editor = CodeMirror.fromTextArea(this.$refs.codeEditor, {
65
+ mode: 'javascript',
66
+ lineNumbers: true,
67
+ smartIndent: false
68
+ });
69
+ },
70
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.0.83",
3
+ "version": "0.0.84",
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": {