@mongoosejs/studio 0.3.4 → 0.3.6
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.
- package/backend/actions/ChatThread/createChatMessage.js +12 -2
- package/backend/actions/ChatThread/streamChatMessage.js +12 -2
- package/backend/actions/Model/createChatMessage.js +12 -2
- package/backend/actions/Model/streamChatMessage.js +13 -2
- package/frontend/public/app.js +278 -86
- package/frontend/public/tw.css +5 -4
- package/frontend/src/chat/chat.html +19 -12
- package/frontend/src/chat/chat.js +9 -3
- package/frontend/src/create-document/create-document.js +3 -1
- package/frontend/src/getCurrentDateTimeContext.js +17 -0
- package/frontend/src/list-json/list-json.js +0 -7
- package/frontend/src/modal/modal.js +25 -1
- package/frontend/src/models/document-search/document-search.js +3 -0
- package/frontend/src/models/models.html +26 -6
- package/frontend/src/models/models.js +10 -64
- package/frontend/src/navbar/navbar.html +6 -1
- package/frontend/src/navbar/navbar.js +1 -6
- package/frontend/src/pro-upgrade-modal/pro-upgrade-modal.html +38 -0
- package/frontend/src/pro-upgrade-modal/pro-upgrade-modal.js +23 -0
- package/package.json +2 -1
package/frontend/public/tw.css
CHANGED
|
@@ -719,10 +719,6 @@ video {
|
|
|
719
719
|
right: 0.5rem;
|
|
720
720
|
}
|
|
721
721
|
|
|
722
|
-
.right-4 {
|
|
723
|
-
right: 1rem;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
722
|
.top-0 {
|
|
727
723
|
top: 0px;
|
|
728
724
|
}
|
|
@@ -3328,6 +3324,11 @@ video {
|
|
|
3328
3324
|
color: var(--color-primary);
|
|
3329
3325
|
}
|
|
3330
3326
|
|
|
3327
|
+
.hover\:text-red-900:hover {
|
|
3328
|
+
--tw-text-opacity: 1;
|
|
3329
|
+
color: rgb(127 29 29 / var(--tw-text-opacity));
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3331
3332
|
.hover\:text-slate-700:hover {
|
|
3332
3333
|
--tw-text-opacity: 1;
|
|
3333
3334
|
color: rgb(51 65 85 / var(--tw-text-opacity));
|
|
@@ -10,17 +10,6 @@
|
|
|
10
10
|
<path stroke-linecap="round" stroke-linejoin="round" d="m5.25 4.5 7.5 7.5-7.5 7.5m6-15 7.5 7.5-7.5 7.5" />
|
|
11
11
|
</svg>
|
|
12
12
|
</button>
|
|
13
|
-
<button
|
|
14
|
-
class="fixed top-[65px] right-4 z-10 p-2 rounded-md shadow bg-surface"
|
|
15
|
-
:class="hasWorkspace ? 'text-content-secondary hover:bg-muted' : 'text-gray-300 cursor-not-allowed bg-page'"
|
|
16
|
-
@click="toggleShareThread"
|
|
17
|
-
:disabled="!hasWorkspace || !chatThreadId || sharingThread"
|
|
18
|
-
aria-label="Share thread with workspace"
|
|
19
|
-
title="Share thread with workspace"
|
|
20
|
-
>
|
|
21
|
-
<svg v-if="hasWorkspace" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7a2.48 2.48 0 0 0 0-1.39l7.02-4.11a2.5 2.5 0 1 0-.87-1.37L8.04 9.94a2.5 2.5 0 1 0 0 4.12l7.12 4.16a2.5 2.5 0 1 0 .84-1.34l-7.05-4.12c-.04-.02-.08-.05-.11-.07a2.48 2.48 0 0 0 0-1.39c.03-.02.07-.04.11-.07l7.11-4.16c.52.47 1.2.76 1.94.76a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0-1.94.94L7.97 8.43a2.5 2.5 0 1 0 0 7.14l9.09 5.3c.52-.47 1.2-.76 1.94-.76a2.5 2.5 0 1 0 0-5z"/></svg>
|
|
22
|
-
<svg v-else xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 1a5 5 0 00-5 5v3H6a2 2 0 00-2 2v9a2 2 0 002 2h12a2 2 0 002-2v-9a2 2 0 00-2-2h-1V6a5 5 0 00-5-5zm-3 8V6a3 3 0 016 0v3H9zm9 2v9H6v-9h12z"/></svg>
|
|
23
|
-
</button>
|
|
24
13
|
<!-- Sidebar: Chat Threads -->
|
|
25
14
|
<aside
|
|
26
15
|
class="bg-page border-r overflow-hidden h-full transition-all duration-300 ease-in-out z-20 w-64 fixed lg:relative shrink-0 flex flex-col"
|
|
@@ -84,6 +73,18 @@
|
|
|
84
73
|
<path stroke-linecap="round" stroke-linejoin="round" d="m18.75 4.5-7.5 7.5 7.5 7.5m-6-15L5.25 12l7.5 7.5" />
|
|
85
74
|
</svg>
|
|
86
75
|
</button>
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
@click="hasWorkspace ? toggleShareThread() : (showProUpgradeModal = true)"
|
|
79
|
+
class="rounded p-1.5"
|
|
80
|
+
:class="hasWorkspace && chatThreadId ? 'text-gray-400 hover:text-gray-600 hover:bg-muted' : !hasWorkspace ? 'text-gray-400 hover:text-gray-600 hover:bg-muted' : 'text-gray-300 cursor-not-allowed'"
|
|
81
|
+
:disabled="hasWorkspace && !chatThreadId"
|
|
82
|
+
aria-label="Share thread with workspace"
|
|
83
|
+
:title="'Share thread with workspace' + (!hasWorkspace ? ' (requires a pro workspace)' : !chatThreadId ? ': Open a thread first!' : '')"
|
|
84
|
+
>
|
|
85
|
+
<svg v-if="hasWorkspace" xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7a2.48 2.48 0 0 0 0-1.39l7.02-4.11a2.5 2.5 0 1 0-.87-1.37L8.04 9.94a2.5 2.5 0 1 0 0 4.12l7.12 4.16a2.5 2.5 0 1 0 .84-1.34l-7.05-4.12c-.04-.02-.08-.05-.11-.07a2.48 2.48 0 0 0 0-1.39c.03-.02.07-.04.11-.07l7.11-4.16c.52.47 1.2.76 1.94.76a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0-1.94.94L7.97 8.43a2.5 2.5 0 1 0 0 7.14l9.09 5.3c.52-.47 1.2-.76 1.94-.76a2.5 2.5 0 1 0 0-5z"/></svg>
|
|
86
|
+
<svg v-else xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M12 1a5 5 0 00-5 5v3H6a2 2 0 00-2 2v9a2 2 0 002 2h12a2 2 0 002-2v-9a2 2 0 00-2-2h-1V6a5 5 0 00-5-5zm-3 8V6a3 3 0 016 0v3H9zm9 2v9H6v-9h12z"/></svg>
|
|
87
|
+
</button>
|
|
87
88
|
</div>
|
|
88
89
|
</aside>
|
|
89
90
|
|
|
@@ -118,7 +119,7 @@
|
|
|
118
119
|
@input="adjustTextareaHeight"
|
|
119
120
|
@keydown.enter.exact.prevent="handleEnter"
|
|
120
121
|
></textarea>
|
|
121
|
-
<button class="bg-
|
|
122
|
+
<button class="bg-primary hover:bg-primary-hover text-white px-4 h-[42px] rounded disabled:bg-gray-600" :disabled="sendingMessage">
|
|
122
123
|
<svg v-if="sendingMessage" style="height: 1em" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
123
124
|
<g>
|
|
124
125
|
<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2" opacity="0.3" />
|
|
@@ -132,4 +133,10 @@
|
|
|
132
133
|
</form>
|
|
133
134
|
</div>
|
|
134
135
|
</main>
|
|
136
|
+
|
|
137
|
+
<pro-upgrade-modal
|
|
138
|
+
:show="showProUpgradeModal"
|
|
139
|
+
feature-description="Sharing threads lets you collaborate with your team by sharing chat threads within your workspace."
|
|
140
|
+
@close="showProUpgradeModal = false"
|
|
141
|
+
></pro-upgrade-modal>
|
|
135
142
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const api = require('../api');
|
|
4
|
+
const getCurrentDateTimeContext = require('../getCurrentDateTimeContext');
|
|
4
5
|
const template = require('./chat.html');
|
|
5
6
|
|
|
6
7
|
module.exports = {
|
|
@@ -15,7 +16,8 @@ module.exports = {
|
|
|
15
16
|
chatMessages: [],
|
|
16
17
|
hideSidebar: null,
|
|
17
18
|
sharingThread: false,
|
|
18
|
-
threadSearch: ''
|
|
19
|
+
threadSearch: '',
|
|
20
|
+
showProUpgradeModal: false
|
|
19
21
|
}),
|
|
20
22
|
methods: {
|
|
21
23
|
async sendMessage() {
|
|
@@ -44,7 +46,11 @@ module.exports = {
|
|
|
44
46
|
}
|
|
45
47
|
});
|
|
46
48
|
|
|
47
|
-
const params = {
|
|
49
|
+
const params = {
|
|
50
|
+
chatThreadId: this.chatThreadId,
|
|
51
|
+
content,
|
|
52
|
+
currentDateTime: getCurrentDateTimeContext()
|
|
53
|
+
};
|
|
48
54
|
let userChatMessage = null;
|
|
49
55
|
let assistantChatMessage = null;
|
|
50
56
|
for await (const event of api.ChatThread.streamChatMessage(params)) {
|
|
@@ -151,7 +157,7 @@ module.exports = {
|
|
|
151
157
|
},
|
|
152
158
|
async toggleShareThread() {
|
|
153
159
|
if (!this.chatThreadId || !this.hasWorkspace) {
|
|
154
|
-
|
|
160
|
+
throw new Error('Cannot share thread: chatThreadId or hasWorkspace is missing');
|
|
155
161
|
}
|
|
156
162
|
this.sharingThread = true;
|
|
157
163
|
try {
|
|
@@ -11,6 +11,7 @@ const ObjectId = new Proxy(BSON.ObjectId, {
|
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
const appendCSS = require('../appendCSS');
|
|
14
|
+
const getCurrentDateTimeContext = require('../getCurrentDateTimeContext');
|
|
14
15
|
|
|
15
16
|
appendCSS(require('./create-document.css'));
|
|
16
17
|
|
|
@@ -49,7 +50,8 @@ module.exports = app => app.component('create-document', {
|
|
|
49
50
|
for await (const event of api.Model.streamChatMessage({
|
|
50
51
|
model: this.currentModel,
|
|
51
52
|
content: prompt,
|
|
52
|
-
documentData: this.aiOriginalDocument
|
|
53
|
+
documentData: this.aiOriginalDocument,
|
|
54
|
+
currentDateTime: getCurrentDateTimeContext()
|
|
53
55
|
})) {
|
|
54
56
|
if (event?.textPart) {
|
|
55
57
|
this.aiSuggestion += event.textPart;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const time = require('time-commando');
|
|
4
|
+
|
|
5
|
+
module.exports = function getCurrentDateTimeContext() {
|
|
6
|
+
const date = time.now();
|
|
7
|
+
const components = [
|
|
8
|
+
date.getFullYear(),
|
|
9
|
+
date.getMonth() + 1,
|
|
10
|
+
date.getDate(),
|
|
11
|
+
date.getHours(),
|
|
12
|
+
date.getMinutes(),
|
|
13
|
+
date.getSeconds()
|
|
14
|
+
].map(num => num.toString().padStart(2, '0'));
|
|
15
|
+
const [yyyy, mm, dd, hh, mi, ss] = components;
|
|
16
|
+
return `${yyyy}-${mm}-${dd}T${hh}:${mi}:${ss}`;
|
|
17
|
+
};
|
|
@@ -28,13 +28,6 @@ module.exports = app => app.component('list-json', {
|
|
|
28
28
|
topLevelExpanded: false
|
|
29
29
|
};
|
|
30
30
|
},
|
|
31
|
-
watch: {
|
|
32
|
-
value: {
|
|
33
|
-
handler() {
|
|
34
|
-
this.resetCollapse();
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
31
|
created() {
|
|
39
32
|
this.resetCollapse();
|
|
40
33
|
for (const field of this.expandedFields) {
|
|
@@ -7,5 +7,29 @@ appendCSS(require('./modal.css'));
|
|
|
7
7
|
|
|
8
8
|
module.exports = app => app.component('modal', {
|
|
9
9
|
template,
|
|
10
|
-
props: ['containerClass']
|
|
10
|
+
props: ['containerClass'],
|
|
11
|
+
mounted() {
|
|
12
|
+
window.addEventListener('keydown', this.onEscape);
|
|
13
|
+
},
|
|
14
|
+
beforeUnmount() {
|
|
15
|
+
window.removeEventListener('keydown', this.onEscape);
|
|
16
|
+
},
|
|
17
|
+
methods: {
|
|
18
|
+
onEscape(event) {
|
|
19
|
+
if (event.key !== 'Escape') {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const modalMasks = Array.from(document.querySelectorAll('.modal-mask'));
|
|
24
|
+
const currentMask = this.$el?.classList?.contains('modal-mask') ? this.$el : this.$el?.querySelector('.modal-mask') || this.$el;
|
|
25
|
+
const isTopMostModal = modalMasks.length > 0 && modalMasks[modalMasks.length - 1] === currentMask;
|
|
26
|
+
|
|
27
|
+
if (!isTopMostModal) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const closeButton = currentMask.querySelector('.modal-exit, [data-modal-close]');
|
|
32
|
+
closeButton?.click();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
11
35
|
});
|
|
@@ -226,7 +226,7 @@
|
|
|
226
226
|
@click="applyDefaultProjectionColumns()"
|
|
227
227
|
class="block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted"
|
|
228
228
|
>
|
|
229
|
-
Default
|
|
229
|
+
Default Projection
|
|
230
230
|
</async-button>
|
|
231
231
|
</div>
|
|
232
232
|
</div>
|
|
@@ -280,6 +280,24 @@
|
|
|
280
280
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 relative m-4 rounded-md" role="alert">
|
|
281
281
|
<span class="block font-bold">Error</span>
|
|
282
282
|
<span class="block">{{ error }}</span>
|
|
283
|
+
<span class="block mt-2">
|
|
284
|
+
Need help?
|
|
285
|
+
<a
|
|
286
|
+
href="https://discord.gg/P3YCfKYxpy"
|
|
287
|
+
target="_blank"
|
|
288
|
+
rel="noopener noreferrer"
|
|
289
|
+
class="underline font-medium text-red-800 hover:text-red-900"
|
|
290
|
+
>Ask in Discord</a>
|
|
291
|
+
or
|
|
292
|
+
<a
|
|
293
|
+
href="https://github.com/mongoosejs/studio/issues"
|
|
294
|
+
target="_blank"
|
|
295
|
+
rel="noopener noreferrer"
|
|
296
|
+
class="underline font-medium text-red-800 hover:text-red-900"
|
|
297
|
+
>
|
|
298
|
+
open a GitHub issue.
|
|
299
|
+
</a>
|
|
300
|
+
</span>
|
|
283
301
|
</div>
|
|
284
302
|
</div>
|
|
285
303
|
<div v-else-if="outputType === 'table'" class="flex-1 min-h-0 flex flex-col overflow-hidden">
|
|
@@ -442,13 +460,15 @@
|
|
|
442
460
|
selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:shadow-sm hover:border-slate-300 bg-surface'
|
|
443
461
|
]"
|
|
444
462
|
>
|
|
445
|
-
<
|
|
446
|
-
type="button"
|
|
463
|
+
<router-link
|
|
447
464
|
class="absolute top-2 right-2 z-10 inline-flex items-center rounded bg-primary px-2 py-1 text-xs font-semibold text-primary-text shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
|
|
448
|
-
|
|
465
|
+
:to="{ path: '/model/' + currentModel + '/document/' + document._id, query: $route.query }"
|
|
466
|
+
target="_blank"
|
|
467
|
+
rel="noopener noreferrer"
|
|
468
|
+
@click.stop
|
|
449
469
|
>
|
|
450
|
-
Open
|
|
451
|
-
</
|
|
470
|
+
Open
|
|
471
|
+
</router-link>
|
|
452
472
|
<list-json :value="filterDocument(document)" :references="referenceMap">
|
|
453
473
|
</list-json>
|
|
454
474
|
</div>
|
|
@@ -14,7 +14,6 @@ const limit = 20;
|
|
|
14
14
|
const DEFAULT_FIRST_N_FIELDS = 6;
|
|
15
15
|
const OUTPUT_TYPE_STORAGE_KEY = 'studio:model-output-type';
|
|
16
16
|
const SELECTED_GEO_FIELD_STORAGE_KEY = 'studio:model-selected-geo-field';
|
|
17
|
-
const PROJECTION_STORAGE_KEY_PREFIX = 'studio:model-projection:';
|
|
18
17
|
const SHOW_ROW_NUMBERS_STORAGE_KEY = 'studio:model-show-row-numbers';
|
|
19
18
|
const PROJECTION_MODE_QUERY_KEY = 'projectionMode';
|
|
20
19
|
const RECENTLY_VIEWED_MODELS_KEY = 'studio:recently-viewed-models';
|
|
@@ -262,7 +261,7 @@ module.exports = app => app.component('models', {
|
|
|
262
261
|
computed: {
|
|
263
262
|
referenceMap() {
|
|
264
263
|
const map = {};
|
|
265
|
-
for (const path of this.
|
|
264
|
+
for (const path of this.schemaPaths) {
|
|
266
265
|
if (path?.ref) {
|
|
267
266
|
map[path.path] = path.ref;
|
|
268
267
|
}
|
|
@@ -627,7 +626,7 @@ module.exports = app => app.component('models', {
|
|
|
627
626
|
// mount/remount respects deep-linked projections before `filteredPaths`
|
|
628
627
|
// is rehydrated from schema paths.
|
|
629
628
|
let fieldsParam = normalizeFieldsParamForApi(this.query?.fields);
|
|
630
|
-
if (!fieldsParam) {
|
|
629
|
+
if (!fieldsParam && this.isProjectionMenuSelected === true) {
|
|
631
630
|
const fieldPaths = this.filteredPaths && this.filteredPaths.length > 0
|
|
632
631
|
? this.filteredPaths.map(p => p.path).filter(Boolean)
|
|
633
632
|
: null;
|
|
@@ -678,7 +677,6 @@ module.exports = app => app.component('models', {
|
|
|
678
677
|
this.filteredPaths = urlPaths.map(path => this.schemaPaths.find(p => p.path === path)).filter(Boolean);
|
|
679
678
|
if (this.filteredPaths.length > 0) {
|
|
680
679
|
this.syncProjectionFromPaths();
|
|
681
|
-
this.saveProjectionPreference();
|
|
682
680
|
}
|
|
683
681
|
}
|
|
684
682
|
}
|
|
@@ -827,6 +825,10 @@ module.exports = app => app.component('models', {
|
|
|
827
825
|
}
|
|
828
826
|
} else {
|
|
829
827
|
delete this.query[PROJECTION_MODE_QUERY_KEY];
|
|
828
|
+
delete this.query.fields;
|
|
829
|
+
this.filteredPaths = [];
|
|
830
|
+
this.selectedPaths = [];
|
|
831
|
+
this.projectionText = '';
|
|
830
832
|
}
|
|
831
833
|
|
|
832
834
|
this.$router.push({ query: this.query });
|
|
@@ -966,34 +968,13 @@ module.exports = app => app.component('models', {
|
|
|
966
968
|
for (const { path } of this.schemaPaths) {
|
|
967
969
|
this.shouldExport[path] = true;
|
|
968
970
|
}
|
|
969
|
-
const
|
|
970
|
-
|
|
971
|
-
if (savedPaths === null) {
|
|
971
|
+
const isProjectionModeOn = this.isProjectionMenuSelected === true;
|
|
972
|
+
if (isProjectionModeOn) {
|
|
972
973
|
this.applyDefaultProjection(event.suggestedFields);
|
|
973
|
-
|
|
974
|
-
this.saveProjectionPreference();
|
|
975
|
-
}
|
|
976
|
-
} else if (Array.isArray(savedPaths) && savedPaths.length === 0) {
|
|
974
|
+
} else {
|
|
977
975
|
this.filteredPaths = [];
|
|
976
|
+
this.selectedPaths = [];
|
|
978
977
|
this.projectionText = '';
|
|
979
|
-
if (shouldUseSavedProjection) {
|
|
980
|
-
this.saveProjectionPreference();
|
|
981
|
-
}
|
|
982
|
-
} else if (savedPaths && savedPaths.length > 0) {
|
|
983
|
-
this.filteredPaths = savedPaths
|
|
984
|
-
.map(path => this.schemaPaths.find(p => p.path === path))
|
|
985
|
-
.filter(Boolean);
|
|
986
|
-
if (this.filteredPaths.length === 0) {
|
|
987
|
-
this.applyDefaultProjection(event.suggestedFields);
|
|
988
|
-
if (shouldUseSavedProjection) {
|
|
989
|
-
this.saveProjectionPreference();
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
} else {
|
|
993
|
-
this.applyDefaultProjection(event.suggestedFields);
|
|
994
|
-
if (shouldUseSavedProjection) {
|
|
995
|
-
this.saveProjectionPreference();
|
|
996
|
-
}
|
|
997
978
|
}
|
|
998
979
|
this.selectedPaths = [...this.filteredPaths];
|
|
999
980
|
this.syncProjectionFromPaths();
|
|
@@ -1073,36 +1054,6 @@ module.exports = app => app.component('models', {
|
|
|
1073
1054
|
this.filteredPaths = this.schemaPaths.filter(p => p.path === '_id');
|
|
1074
1055
|
}
|
|
1075
1056
|
},
|
|
1076
|
-
loadProjectionPreference() {
|
|
1077
|
-
if (typeof window === 'undefined' || !window.localStorage || !this.currentModel) {
|
|
1078
|
-
return null;
|
|
1079
|
-
}
|
|
1080
|
-
const key = PROJECTION_STORAGE_KEY_PREFIX + this.currentModel;
|
|
1081
|
-
const stored = window.localStorage.getItem(key);
|
|
1082
|
-
if (stored === null || stored === undefined) {
|
|
1083
|
-
return null;
|
|
1084
|
-
}
|
|
1085
|
-
if (stored === '') {
|
|
1086
|
-
return [];
|
|
1087
|
-
}
|
|
1088
|
-
try {
|
|
1089
|
-
const parsed = JSON.parse(stored);
|
|
1090
|
-
if (Array.isArray(parsed)) {
|
|
1091
|
-
return parsed.map(x => String(x).trim()).filter(Boolean);
|
|
1092
|
-
}
|
|
1093
|
-
} catch (e) {
|
|
1094
|
-
return null;
|
|
1095
|
-
}
|
|
1096
|
-
return null;
|
|
1097
|
-
},
|
|
1098
|
-
saveProjectionPreference() {
|
|
1099
|
-
if (typeof window === 'undefined' || !window.localStorage || !this.currentModel) {
|
|
1100
|
-
return;
|
|
1101
|
-
}
|
|
1102
|
-
const key = PROJECTION_STORAGE_KEY_PREFIX + this.currentModel;
|
|
1103
|
-
const paths = this.filteredPaths.map(p => p.path);
|
|
1104
|
-
window.localStorage.setItem(key, JSON.stringify(paths));
|
|
1105
|
-
},
|
|
1106
1057
|
clearProjection() {
|
|
1107
1058
|
// Keep current filter input in sync with the URL so projection reset
|
|
1108
1059
|
// does not unintentionally wipe the filter on remount.
|
|
@@ -1111,7 +1062,6 @@ module.exports = app => app.component('models', {
|
|
|
1111
1062
|
this.selectedPaths = [];
|
|
1112
1063
|
this.projectionText = '';
|
|
1113
1064
|
this.updateProjectionQuery();
|
|
1114
|
-
this.saveProjectionPreference();
|
|
1115
1065
|
},
|
|
1116
1066
|
resetFilter() {
|
|
1117
1067
|
// Reuse the existing "apply filter + update URL" flow.
|
|
@@ -1131,7 +1081,6 @@ module.exports = app => app.component('models', {
|
|
|
1131
1081
|
this.selectedPaths = [...this.filteredPaths];
|
|
1132
1082
|
this.syncProjectionFromPaths();
|
|
1133
1083
|
this.updateProjectionQuery();
|
|
1134
|
-
this.saveProjectionPreference();
|
|
1135
1084
|
},
|
|
1136
1085
|
initProjection(ev) {
|
|
1137
1086
|
if (!this.projectionText || !this.projectionText.trim()) {
|
|
@@ -1240,7 +1189,6 @@ module.exports = app => app.component('models', {
|
|
|
1240
1189
|
this.selectedPaths = [...this.filteredPaths];
|
|
1241
1190
|
this.syncProjectionFromPaths();
|
|
1242
1191
|
this.updateProjectionQuery();
|
|
1243
|
-
this.saveProjectionPreference();
|
|
1244
1192
|
},
|
|
1245
1193
|
updateProjectionQuery() {
|
|
1246
1194
|
const paths = this.filteredPaths.map(x => x.path).filter(Boolean);
|
|
@@ -1270,7 +1218,6 @@ module.exports = app => app.component('models', {
|
|
|
1270
1218
|
}
|
|
1271
1219
|
this.syncProjectionFromPaths();
|
|
1272
1220
|
this.updateProjectionQuery();
|
|
1273
|
-
this.saveProjectionPreference();
|
|
1274
1221
|
}
|
|
1275
1222
|
},
|
|
1276
1223
|
addField(schemaPath) {
|
|
@@ -1292,7 +1239,6 @@ module.exports = app => app.component('models', {
|
|
|
1292
1239
|
});
|
|
1293
1240
|
this.syncProjectionFromPaths();
|
|
1294
1241
|
this.updateProjectionQuery();
|
|
1295
|
-
this.saveProjectionPreference();
|
|
1296
1242
|
this.showAddFieldDropdown = false;
|
|
1297
1243
|
this.addFieldFilterText = '';
|
|
1298
1244
|
}
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
</svg>
|
|
43
43
|
</span>
|
|
44
44
|
<a
|
|
45
|
-
|
|
45
|
+
v-if="hasTaskVisualizer"
|
|
46
|
+
href="#/tasks"
|
|
46
47
|
class="inline-flex items-center px-1 border-b-2 text-sm font-medium"
|
|
47
48
|
:class="taskView ? 'text-content border-primary' : 'border-transparent text-content-tertiary hover:text-content'">
|
|
48
49
|
Tasks
|
|
@@ -210,6 +211,10 @@
|
|
|
210
211
|
<path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z" clip-rule="evenodd" />
|
|
211
212
|
</svg>
|
|
212
213
|
</span>
|
|
214
|
+
<a v-if="hasTaskVisualizer"
|
|
215
|
+
href="#/tasks"
|
|
216
|
+
class="block px-3 py-2 rounded-md text-base font-medium"
|
|
217
|
+
:class="taskView ? 'text-content bg-primary-subtle' : 'text-content-secondary hover:bg-muted'">Tasks</a>
|
|
213
218
|
<div v-if="!user && hasAPIKey" class="mt-4">
|
|
214
219
|
<button
|
|
215
220
|
type="button"
|
|
@@ -67,12 +67,7 @@ module.exports = app => app.component('navbar', {
|
|
|
67
67
|
return this.roles && this.roles[0] === 'dashboards' ? 'dashboards' : 'root';
|
|
68
68
|
},
|
|
69
69
|
hasTaskVisualizer() {
|
|
70
|
-
|
|
71
|
-
return '#/tasks';
|
|
72
|
-
} else {
|
|
73
|
-
return 'https://www.npmjs.com/package/@mongoosejs/task';
|
|
74
|
-
}
|
|
75
|
-
|
|
70
|
+
return !!window.MONGOOSE_STUDIO_CONFIG.enableTaskVisualizer;
|
|
76
71
|
}
|
|
77
72
|
},
|
|
78
73
|
methods: {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<modal v-if="show" containerClass="!max-w-md">
|
|
2
|
+
<template v-slot:body>
|
|
3
|
+
<div @keydown.esc="$emit('close')" tabindex="0" ref="overlay">
|
|
4
|
+
<div class="flex items-center justify-between mb-4">
|
|
5
|
+
<h3 class="text-lg font-semibold text-gray-900">Pro Feature</h3>
|
|
6
|
+
<button
|
|
7
|
+
type="button"
|
|
8
|
+
@click="$emit('close')"
|
|
9
|
+
class="text-gray-400 hover:text-gray-600 cursor-pointer"
|
|
10
|
+
aria-label="Close"
|
|
11
|
+
>
|
|
12
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
|
13
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
|
14
|
+
</svg>
|
|
15
|
+
</button>
|
|
16
|
+
</div>
|
|
17
|
+
<p class="text-gray-600 mb-6">
|
|
18
|
+
{{ featureDescription }} Upgrade to a Pro workspace to unlock this feature.
|
|
19
|
+
</p>
|
|
20
|
+
<div class="flex justify-end gap-3">
|
|
21
|
+
<button
|
|
22
|
+
type="button"
|
|
23
|
+
@click="$emit('close')"
|
|
24
|
+
class="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200 cursor-pointer"
|
|
25
|
+
>
|
|
26
|
+
Cancel
|
|
27
|
+
</button>
|
|
28
|
+
<a
|
|
29
|
+
href="https://studio.mongoosejs.io/pro"
|
|
30
|
+
target="_blank"
|
|
31
|
+
class="px-4 py-2 text-sm font-medium text-white bg-primary rounded-md hover:bg-primary-hover inline-flex items-center"
|
|
32
|
+
>
|
|
33
|
+
Upgrade to Pro
|
|
34
|
+
</a>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
38
|
+
</modal>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const template = require('./pro-upgrade-modal.html');
|
|
4
|
+
|
|
5
|
+
module.exports = app => app.component('pro-upgrade-modal', {
|
|
6
|
+
template,
|
|
7
|
+
props: {
|
|
8
|
+
show: { type: Boolean, default: false },
|
|
9
|
+
featureDescription: { type: String, default: 'This feature is available on the Pro plan.' }
|
|
10
|
+
},
|
|
11
|
+
emits: ['close'],
|
|
12
|
+
watch: {
|
|
13
|
+
show(val) {
|
|
14
|
+
if (val) {
|
|
15
|
+
this.$nextTick(() => {
|
|
16
|
+
if (this.$refs.overlay) {
|
|
17
|
+
this.$refs.overlay.focus();
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongoosejs/studio",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.",
|
|
5
5
|
"homepage": "https://mongoosestudio.app/",
|
|
6
6
|
"repository": {
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"vue": "3.x",
|
|
26
26
|
"vue-toastification": "^2.0.0-rc.5",
|
|
27
27
|
"webpack": "5.x",
|
|
28
|
+
"time-commando": "1.0.1",
|
|
28
29
|
"xss": "^1.0.15"
|
|
29
30
|
},
|
|
30
31
|
"peerDependencies": {
|