@mongoosejs/studio 0.5.2 → 0.5.4

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.
@@ -1,3 +1,4 @@
1
+ <div>
1
2
  <div class="navbar w-full bg-page border-b border-edge !h-[55px] hidden md:grid grid-cols-[1fr_auto_1fr] items-stretch px-4">
2
3
  <!-- Left: Logo + Brand -->
3
4
  <div class="flex items-center gap-2 self-center">
@@ -31,11 +32,11 @@
31
32
  <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" />
32
33
  </svg>
33
34
  </span>
34
- <a v-if="hasAccess(roles, 'chat') && hasLLMAPIKey"
35
+ <a v-if="hasAccess(roles, 'chat') && supportsAI"
35
36
  href="#/chat"
36
37
  class="inline-flex items-center px-1 border-b-2 text-sm font-medium"
37
38
  :class="chatView ? 'text-content border-primary' : 'border-transparent text-content-tertiary hover:text-content'">Chat</a>
38
- <span v-else-if="hasAccess(roles, 'chat') && !hasLLMAPIKey"
39
+ <span v-else-if="hasAccess(roles, 'chat') && !supportsAI"
39
40
  class="inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-gray-300 cursor-not-allowed"
40
41
  :title="chatDisabledReason">
41
42
  Chat
@@ -67,6 +68,19 @@
67
68
  </nav>
68
69
  <!-- Right: Env Badge + User -->
69
70
  <div class="flex items-center justify-end gap-3 self-center">
71
+ <button
72
+ type="button"
73
+ class="inline-flex items-center justify-center rounded-md p-2 text-content-tertiary hover:bg-muted hover:text-content-secondary"
74
+ title="Recent pages"
75
+ aria-label="Recent pages"
76
+ aria-haspopup="dialog"
77
+ :aria-expanded="showRecentPagesModal ? 'true' : 'false'"
78
+ @click="openRecentPagesModal"
79
+ >
80
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-5 w-5" aria-hidden="true">
81
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
82
+ </svg>
83
+ </button>
70
84
  <div
71
85
  v-if="!!state.nodeEnv"
72
86
  title="NODE_ENV"
@@ -149,9 +163,22 @@
149
163
  <span class="text-base font-semibold text-content">Mongoose Studio</span>
150
164
  </router-link>
151
165
  </div>
152
- <div class="flex items-center">
166
+ <div class="flex items-center gap-1 pr-2">
167
+ <button
168
+ type="button"
169
+ class="rounded-md p-2 text-content-tertiary hover:bg-muted hover:text-content-secondary md:hidden"
170
+ title="Recent pages"
171
+ aria-label="Recent pages"
172
+ aria-haspopup="dialog"
173
+ :aria-expanded="showRecentPagesModal ? 'true' : 'false'"
174
+ @click="openRecentPagesModal"
175
+ >
176
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-6 w-6" aria-hidden="true">
177
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
178
+ </svg>
179
+ </button>
153
180
  <!-- Mobile menu toggle, controls the 'mobileMenuOpen' state. -->
154
- <button type="button" id="open-mobile-menu" class="-ml-2 rounded-md p-2 pr-4 text-gray-400">
181
+ <button type="button" id="open-mobile-menu" class="-ml-2 rounded-md p-2 text-gray-400">
155
182
  <span class="sr-only">Open menu</span>
156
183
  <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
157
184
  <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
@@ -209,11 +236,11 @@
209
236
  <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" />
210
237
  </svg>
211
238
  </span>
212
- <a v-if="hasAccess(roles, 'chat') && hasLLMAPIKey"
239
+ <a v-if="hasAccess(roles, 'chat') && supportsAI"
213
240
  href="#/chat"
214
241
  class="block px-3 py-2 rounded-md text-base font-medium"
215
242
  :class="chatView ? 'text-content bg-primary-subtle' : 'text-content-secondary hover:bg-muted'">Chat</a>
216
- <span v-else-if="hasAccess(roles, 'chat') && !hasLLMAPIKey"
243
+ <span v-else-if="hasAccess(roles, 'chat') && !supportsAI"
217
244
  class="block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed"
218
245
  :title="chatDisabledReason">
219
246
  Chat
@@ -266,3 +293,47 @@
266
293
  </nav>
267
294
  </div>
268
295
  </div>
296
+
297
+ <modal
298
+ v-if="showRecentPagesModal"
299
+ flush-container
300
+ containerClass="!max-w-md !w-[min(94vw,28rem)] !overflow-hidden !rounded-lg !border !border-edge !bg-surface !shadow-xl dark:!bg-shark-900 text-content"
301
+ bodyClass="modal-recent-pages-body !p-0"
302
+ >
303
+ <template v-slot:body>
304
+ <div class="modal-exit" @click="closeRecentPagesModal" role="button" aria-label="Close recent pages">&times;</div>
305
+ <div
306
+ class="shrink-0 flex items-center gap-3 border-b border-edge bg-surface px-4 py-4 pr-12 dark:bg-shark-900"
307
+ >
308
+ <h2 class="m-0 text-lg font-semibold text-content">Recent pages</h2>
309
+ <button
310
+ type="button"
311
+ class="rounded px-2 py-1 text-sm font-medium text-content-secondary hover:bg-muted disabled:opacity-50"
312
+ @click="clearRecentPagesHistory"
313
+ :disabled="recentPagesList.length === 0"
314
+ >
315
+ Clear
316
+ </button>
317
+ </div>
318
+ <div class="modal-recent-pages-scroll">
319
+ <div
320
+ v-if="recentPagesList.length === 0"
321
+ class="m-4 rounded border border-dashed border-edge px-3 py-6 text-center text-sm text-content-tertiary"
322
+ >
323
+ No recent pages yet.
324
+ </div>
325
+ <button
326
+ v-for="entry in recentPagesList"
327
+ :key="entry.path + '-' + entry.visitedAt"
328
+ type="button"
329
+ class="w-full px-4 py-2.5 text-left hover:bg-muted"
330
+ :class="entry.path === $route.fullPath ? 'bg-primary-subtle' : ''"
331
+ @click="goToRecentPage(entry)"
332
+ >
333
+ <div class="truncate text-sm font-medium text-content">{{ entry.label }}</div>
334
+ <div class="truncate font-mono text-xs text-content-tertiary">{{ entry.path }}</div>
335
+ </button>
336
+ </div>
337
+ </template>
338
+ </modal>
339
+ </div>
@@ -1,9 +1,15 @@
1
1
  'use strict';
2
2
 
3
- const api = require('../api');
4
3
  const mothership = require('../mothership');
5
4
  const template = require('./navbar.html');
6
5
  const { routes, hasAccess } = require('../routes');
6
+ const {
7
+ RECENT_PAGES_STORAGE_KEY,
8
+ RECENT_PAGES_CHANGED_EVENT,
9
+ safeReadRecentPages,
10
+ saveRecentPages,
11
+ withRecentPagesStorageLock
12
+ } = require('../_util/recent-pages-history');
7
13
 
8
14
  const appendCSS = require('../appendCSS');
9
15
 
@@ -15,7 +21,9 @@ module.exports = app => app.component('navbar', {
15
21
  inject: ['state'],
16
22
  data: () => ({
17
23
  showFlyout: false,
18
- darkMode: typeof localStorage !== 'undefined' && localStorage.getItem('studio-theme') === 'dark'
24
+ darkMode: typeof localStorage !== 'undefined' && localStorage.getItem('studio-theme') === 'dark',
25
+ showRecentPagesModal: false,
26
+ localRecentPages: []
19
27
  }),
20
28
  mounted: function() {
21
29
  window.navbar = this;
@@ -37,6 +45,32 @@ module.exports = app => app.component('navbar', {
37
45
  mobileMenu.classList.add('translate-x-full');
38
46
  });
39
47
  }
48
+
49
+ this.localRecentPages = safeReadRecentPages();
50
+
51
+ this._syncRecentPagesFromOtherTab = ev => {
52
+ if (ev.storageArea !== window.localStorage || ev.key !== RECENT_PAGES_STORAGE_KEY) {
53
+ return;
54
+ }
55
+ this.refreshRecentPagesFromStorage();
56
+ };
57
+ this._syncRecentPagesFromBroadcast = () => this.refreshRecentPagesFromStorage();
58
+
59
+ window.addEventListener('storage', this._syncRecentPagesFromOtherTab);
60
+ window.addEventListener(RECENT_PAGES_CHANGED_EVENT, this._syncRecentPagesFromBroadcast);
61
+ },
62
+ beforeUnmount() {
63
+ if (this._syncRecentPagesFromOtherTab) {
64
+ window.removeEventListener('storage', this._syncRecentPagesFromOtherTab);
65
+ }
66
+ if (this._syncRecentPagesFromBroadcast) {
67
+ window.removeEventListener(RECENT_PAGES_CHANGED_EVENT, this._syncRecentPagesFromBroadcast);
68
+ }
69
+ },
70
+ watch: {
71
+ '$route.fullPath'() {
72
+ this.refreshRecentPagesFromStorage();
73
+ }
40
74
  },
41
75
  computed: {
42
76
  dashboardView() {
@@ -69,8 +103,11 @@ module.exports = app => app.component('navbar', {
69
103
  hasTaskVisualizer() {
70
104
  return !!window.MONGOOSE_STUDIO_CONFIG.enableTaskVisualizer;
71
105
  },
72
- hasLLMAPIKey() {
73
- return !!window.MONGOOSE_STUDIO_CONFIG.hasLLMAPIKey;
106
+ recentPagesList() {
107
+ return this.localRecentPages;
108
+ },
109
+ supportsAI() {
110
+ return this.state.capabilities == null || this.state.capabilities.supportsAI !== false;
74
111
  },
75
112
  chatDisabledReason() {
76
113
  return 'Chat requires an Anthropic, Gemini, or OpenAI API key.';
@@ -103,6 +140,30 @@ module.exports = app => app.component('navbar', {
103
140
  const meta = document.querySelector('meta[name="theme-color"]');
104
141
  if (meta) meta.setAttribute('content', this.darkMode ? '#0f0f0f' : '#ffffff');
105
142
  document.documentElement.dispatchEvent(new CustomEvent('studio-theme-changed', { detail: { dark: this.darkMode } }));
143
+ },
144
+ refreshRecentPagesFromStorage() {
145
+ this.localRecentPages = safeReadRecentPages();
146
+ },
147
+ openRecentPagesModal() {
148
+ this.showRecentPagesModal = true;
149
+ this.refreshRecentPagesFromStorage();
150
+ },
151
+ closeRecentPagesModal() {
152
+ this.showRecentPagesModal = false;
153
+ },
154
+ clearRecentPagesHistory() {
155
+ this.localRecentPages = [];
156
+ void withRecentPagesStorageLock(() => saveRecentPages([]));
157
+ },
158
+ goToRecentPage(entry) {
159
+ if (!entry || !entry.path) {
160
+ return;
161
+ }
162
+ this.showRecentPagesModal = false;
163
+ if (entry.path === this.$route.fullPath) {
164
+ return;
165
+ }
166
+ this.$router.push(entry.path);
106
167
  }
107
168
  },
108
169
  directives: {
package/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  'use strict';
2
-
2
+
3
3
  exports.express = require('./express');
4
+ exports.enableDebugging = require('./debugging').enableDebugging;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "AI-Powered Data Workspace for MongoDB: Turn your MongoDB data into dashboards, maps, and interactive workflows - powered by your Mongoose models and AI.",
5
5
  "homepage": "https://mongoosestudio.app/",
6
6
  "repository": {
@@ -53,6 +53,7 @@
53
53
  "tailwind": "tailwindcss -o ./frontend/public/tw.css",
54
54
  "tailwind:watch": "tailwindcss -o ./frontend/public/tw.css --watch",
55
55
  "test": "mocha test/*.test.js",
56
- "test:frontend": "mocha test/frontend/*.test.js"
56
+ "test:frontend": "mocha test/frontend/*.test.js",
57
+ "eval:agent": "node eval/agent/run.js"
57
58
  }
58
59
  }