@mongoosejs/studio 0.0.96 → 0.0.98

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,9 +1,9 @@
1
- <div class="relative flex items-start space-x-3" :class="{'justify-end': message.role === 'user'}">
1
+ <div class="relative flex items-start" :class="{'justify-end': message.role === 'user'}">
2
2
  <div
3
- class="min-w-0 max-w-[calc(100vw-4rem)]"
3
+ class="min-w-0 max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)]"
4
4
  :class="{'text-right': message.role === 'user'}">
5
5
 
6
- <div class="text-sm text-gray-900 p-3 rounded-md inline-block" :class="styleForMessage">
6
+ <div class="text-sm text-gray-900 md:p-3 rounded-md inline-block" :class="styleForMessage">
7
7
  <div v-for="part in contentSplitByScripts">
8
8
  <div v-if="part.type === 'text'" v-html="marked(part.content)">
9
9
  </div>
@@ -37,9 +37,9 @@
37
37
  </div>
38
38
  </div>
39
39
 
40
- <pre class="p-3 whitespace-pre-wrap max-h-[30vh] max-w-[calc(100vw-4rem)] overflow-y-auto" v-show="activeTab === 'code'"><code v-text="script" ref="code" :class="'language-' + language"></code></pre>
40
+ <pre class="p-3 whitespace-pre-wrap max-h-[50vh] max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] overflow-y-auto" v-show="activeTab === 'code'"><code v-text="script" ref="code" :class="'language-' + language"></code></pre>
41
41
 
42
- <div class="p-3 whitespace-pre-wrap max-h-[30vh] overflow-y-auto bg-white border-t max-w-[calc(100vw-4rem)] relative" v-show="activeTab === 'output'">
42
+ <div class="p-3 whitespace-pre-wrap max-h-[50vh] overflow-y-auto bg-white border-t max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] relative" v-show="activeTab === 'output'">
43
43
  <dashboard-chart v-if="message.executionResult?.output?.$chart" :value="message.executionResult?.output" />
44
44
  <pre v-else>{{ message.executionResult?.output || 'No output' }}</pre>
45
45
  </div>
@@ -1,9 +1,9 @@
1
1
  <div class="flex" style="height: calc(100vh - 55px)">
2
2
  <div class="fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10" @click="hideSidebar = false">
3
- <svg xmlns="http://www.w3.org/2000/svg" style="h-5 w-5" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M360-120v-720h80v720h-80Zm160-160v-400l200 200-200 200Z"/></svg>
3
+ <svg xmlns="http://www.w3.org/2000/svg" style="h-5 w-5" viewBox="0 -960 960 960" class="w-5" fill="#5f6368"><path d="M360-120v-720h80v720h-80Zm160-160v-400l200 200-200 200Z"/></svg>
4
4
  </div>
5
5
  <!-- Sidebar: Chat Threads -->
6
- <aside class="bg-gray-50 border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 md:w-64 fixed md:relative" :class="hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-64' : ''">
6
+ <aside class="bg-gray-50 border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-64 fixed lg:relative" :class="hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-64' : ''">
7
7
  <div class="flex items-center border-b border-gray-100 w-64 overflow-x-hidden">
8
8
  <div class="p-4 font-bold text-lg">Chat Threads</div>
9
9
  <button
@@ -11,7 +11,7 @@
11
11
  class="ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none"
12
12
  aria-label="Close sidebar"
13
13
  >
14
- <svg xmlns="http://www.w3.org/2000/svg" style="h-5 w-5" viewBox="0 -960 960 960" width="24px" fill="currentColor"><path d="M660-320v-320L500-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z"/></svg>
14
+ <svg xmlns="http://www.w3.org/2000/svg" style="h-5 w-5" viewBox="0 -960 960 960" class="w-5" fill="currentColor"><path d="M660-320v-320L500-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z"/></svg>
15
15
  </button>
16
16
  </div>
17
17
  <div class="p-4 w-64">
@@ -58,13 +58,17 @@
58
58
 
59
59
  <!-- Input Area -->
60
60
  <div class="border-t p-4">
61
- <form @submit.prevent="sendMessage" :disabled="sendingMessage" class="flex gap-2">
62
- <input
61
+ <form @submit.prevent="sendMessage" :disabled="sendingMessage" class="flex gap-2 items-end justify-end">
62
+ <textarea
63
63
  v-model="newMessage"
64
64
  placeholder="Ask something..."
65
- class="flex-1 border rounded px-4 py-2"
66
- />
67
- <button class="bg-blue-600 text-white px-4 py-2 rounded disabled:bg-gray-600" :disabled="sendingMessage">
65
+ class="flex-1 border rounded px-4 py-2 resize-none overflow-y-auto"
66
+ rows="1"
67
+ ref="messageInput"
68
+ @input="adjustTextareaHeight"
69
+ @keydown.enter.exact.prevent="handleEnter"
70
+ ></textarea>
71
+ <button class="bg-blue-600 text-white px-4 h-[42px] rounded disabled:bg-gray-600" :disabled="sendingMessage">
68
72
  <svg v-if="sendingMessage" style="height: 1em" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
69
73
  <g>
70
74
  <circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2" opacity="0.3" />
@@ -44,6 +44,11 @@ module.exports = app => app.component('chat', {
44
44
  this.chatMessages.push(chatMessages[1]);
45
45
 
46
46
  this.newMessage = '';
47
+ this.$nextTick(() => {
48
+ if (this.$refs.messageInput) {
49
+ this.$refs.messageInput.style.height = 'auto';
50
+ }
51
+ });
47
52
 
48
53
  this.$nextTick(() => {
49
54
  if (this.$refs.messagesContainer) {
@@ -54,6 +59,18 @@ module.exports = app => app.component('chat', {
54
59
  this.sendingMessage = false;
55
60
  }
56
61
  },
62
+ handleEnter(ev) {
63
+ if (!ev.shiftKey) {
64
+ this.sendMessage();
65
+ }
66
+ },
67
+ adjustTextareaHeight(ev) {
68
+ const textarea = ev.target;
69
+ textarea.style.height = 'auto';
70
+ const lineHeight = parseInt(window.getComputedStyle(textarea).lineHeight, 10);
71
+ const maxHeight = lineHeight * 5;
72
+ textarea.style.height = Math.min(textarea.scrollHeight, maxHeight) + 'px';
73
+ },
57
74
  selectThread(threadId) {
58
75
  this.$router.push('/chat/' + threadId);
59
76
  },
@@ -67,8 +67,8 @@
67
67
  </modal>
68
68
  <modal v-if="shouldShowDeleteModal">
69
69
  <template v-slot:body>
70
- <div class="modal-exit" @click="shouldShowConfirmModal = false;">&times;</div>
71
- <confirm-delete @close="shouldShowConfirmModal = false;" @remove="remove" :value="document"></confirm-delete>
70
+ <div class="modal-exit" @click="shouldShowDeleteModal = false;">&times;</div>
71
+ <confirm-delete @close="shouldShowDeleteModal = false;" @remove="remove" :value="document"></confirm-delete>
72
72
  </template>
73
73
  </modal>
74
74
  <modal v-if="shouldShowCloneModal">
@@ -6,6 +6,7 @@ if (typeof process === 'undefined') {
6
6
 
7
7
  const api = require('./api');
8
8
  const mothership = require('./mothership');
9
+ const { routes } = require('./routes');
9
10
  const vanillatoasts = require('vanillatoasts');
10
11
 
11
12
  const app = Vue.createApp({
@@ -67,7 +68,7 @@ app.component('app-component', {
67
68
  window.state = this;
68
69
 
69
70
  if (mothership.hasAPIKey) {
70
- const hash = window.location.hash.replace(/^#?\/?\??/, '') || '';
71
+ const hash = window.location.hash.replace(/^#?\/?/, '') || '';
71
72
  const hashQuery = hash.split('?')[1] || '';
72
73
  const hashParams = new URLSearchParams(hashQuery);
73
74
  if (hashParams.has('code')) {
@@ -110,6 +111,7 @@ app.component('app-component', {
110
111
  const { nodeEnv } = await api.status();
111
112
  this.nodeEnv = nodeEnv;
112
113
  }
114
+
113
115
  this.status = 'loaded';
114
116
  },
115
117
  setup() {
@@ -126,7 +128,6 @@ app.component('app-component', {
126
128
  }
127
129
  });
128
130
 
129
- const { routes } = require('./routes');
130
131
  const router = VueRouter.createRouter({
131
132
  history: VueRouter.createWebHashHistory(),
132
133
  routes: routes.map(route => ({
@@ -136,6 +137,14 @@ const router = VueRouter.createRouter({
136
137
  }))
137
138
  });
138
139
 
140
+ router.beforeEach((to, from, next) => {
141
+ if (to.name === 'root' && window.state.roles && window.state.roles[0] === 'dashboards') {
142
+ return next({ name: 'dashboards' });
143
+ } else {
144
+ next();
145
+ }
146
+ });
147
+
139
148
  app.use(router);
140
149
 
141
150
  app.mount('#content');
@@ -3,7 +3,7 @@
3
3
  <router-link :to="{ name: defaultRoute }">
4
4
  <img src="images/logo.svg" class="h-[32px] mr-1" alt="Mongoose Studio Logo" />
5
5
  </router-link>
6
- <div v-if="!!state.nodeEnv" class="inline-flex items-center rounded-md px-2 py-1 text-sm font-medium text-gray-900" :class="warnEnv ? 'bg-red-300' : 'bg-yellow-300'">
6
+ <div v-if="!!state.nodeEnv" title="NODE_ENV" class="inline-flex items-center rounded-md px-2 py-1 text-sm font-medium text-gray-900" :class="warnEnv ? 'bg-red-300' : 'bg-yellow-300'">
7
7
  {{state.nodeEnv}}
8
8
  </div>
9
9
  </div>
@@ -13,14 +13,32 @@
13
13
  href="#/"
14
14
  class="inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium"
15
15
  :class="documentView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'">Documents</a>
16
+ <span v-else class="inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium text-gray-300 cursor-not-allowed" aria-disabled="true">
17
+ Documents
18
+ <svg class="h-4 w-4 ml-1" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
19
+ <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" />
20
+ </svg>
21
+ </span>
16
22
  <a v-if="hasAccess(roles, 'dashboards')"
17
23
  href="#/dashboards"
18
24
  class="inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium"
19
25
  :class="dashboardView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'">Dashboards</a>
26
+ <span v-else class="inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium text-gray-300 cursor-not-allowed">
27
+ Dashboards
28
+ <svg class="h-4 w-4 ml-1" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
29
+ <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" />
30
+ </svg>
31
+ </span>
20
32
  <a v-if="hasAccess(roles, 'chat')"
21
33
  href="#/chat"
22
34
  class="inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium"
23
35
  :class="chatView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'">Chat</a>
36
+ <span v-else class="inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium text-gray-300 cursor-not-allowed">
37
+ Chat
38
+ <svg class="h-4 w-4 ml-1" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
39
+ <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" />
40
+ </svg>
41
+ </span>
24
42
 
25
43
  <div class="h-full flex items-center" v-if="!user && hasAPIKey">
26
44
  <button
@@ -41,6 +59,12 @@
41
59
 
42
60
  <div v-if="showFlyout" class="absolute right-0 z-10 top-[90%] w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1">
43
61
  <router-link to="/team" v-if="hasAccess(roles, 'team')" @click="showFlyout = false" class="cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200" role="menuitem" tabindex="-1" id="user-menu-item-2">Team</router-link>
62
+ <span v-else class="block px-4 py-2 text-sm text-gray-300 cursor-not-allowed" role="menuitem" tabindex="-1" id="user-menu-item-2">
63
+ Team
64
+ <svg class="h-4 w-4 ml-1 inline" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
65
+ <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" />
66
+ </svg>
67
+ </span>
44
68
  <span @click="logout" class="cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200" role="menuitem" tabindex="-1" id="user-menu-item-2">Sign out</span>
45
69
  </div>
46
70
  </div>
@@ -77,14 +101,32 @@
77
101
  href="#/"
78
102
  class="block px-3 py-2 rounded-md text-base font-medium"
79
103
  :class="documentView ? 'text-ultramarine-700 bg-ultramarine-100' : 'text-gray-700 hover:bg-gray-100'">Documents</a>
104
+ <span v-else class="block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed">
105
+ Documents
106
+ <svg class="h-4 w-4 ml-1 inline" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
107
+ <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" />
108
+ </svg>
109
+ </span>
80
110
  <a v-if="hasAccess(roles, 'dashboards')"
81
111
  href="#/dashboards"
82
112
  class="block px-3 py-2 rounded-md text-base font-medium"
83
113
  :class="dashboardView ? 'text-ultramarine-700 bg-ultramarine-100' : 'text-gray-700 hover:bg-gray-100'">Dashboards</a>
114
+ <span v-else class="block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed">
115
+ Dashboards
116
+ <svg class="h-4 w-4 ml-1 inline" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
117
+ <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" />
118
+ </svg>
119
+ </span>
84
120
  <a v-if="hasAccess(roles, 'chat')"
85
121
  href="#/chat"
86
122
  class="block px-3 py-2 rounded-md text-base font-medium"
87
123
  :class="chatView ? 'text-ultramarine-700 bg-ultramarine-100' : 'text-gray-700 hover:bg-gray-100'">Chat</a>
124
+ <span v-else class="block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed">
125
+ Chat
126
+ <svg class="h-4 w-4 ml-1 inline" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
127
+ <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" />
128
+ </svg>
129
+ </span>
88
130
  <div v-if="!user && hasAPIKey" class="mt-4">
89
131
  <button
90
132
  type="button"
@@ -100,6 +142,12 @@
100
142
  </div>
101
143
  <div class="mt-2 space-y-1">
102
144
  <router-link to="/team" v-if="hasAccess(roles, 'team')" class="block px-3 py-2 rounded-md text-base text-gray-700 hover:bg-ultramarine-100">Team</router-link>
145
+ <span v-else class="block px-3 py-2 rounded-md text-base text-gray-300 cursor-not-allowed">
146
+ Team
147
+ <svg class="h-4 w-4 ml-1 inline" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
148
+ <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" />
149
+ </svg>
150
+ </span>
103
151
  <span @click="logout" class="block px-3 py-2 rounded-md text-base text-gray-700 hover:bg-ultramarine-100 cursor-pointer">Sign out</span>
104
152
  </div>
105
153
  </div>
@@ -15,14 +15,6 @@ module.exports = app => app.component('navbar', {
15
15
  inject: ['state'],
16
16
  data: () => ({ showFlyout: false }),
17
17
  mounted: function() {
18
- // Redirect to first allowed route if current route is not allowed
19
- if (!this.hasAccess(this.roles, this.$route.name)) {
20
- const firstAllowedRoute = this.allowedRoutes[0];
21
- if (firstAllowedRoute) {
22
- this.$router.push({ name: firstAllowedRoute.name });
23
- }
24
- }
25
-
26
18
  const mobileMenuMask = document.querySelector('#mobile-menu-mask');
27
19
  const mobileMenu = document.querySelector('#mobile-menu');
28
20
 
@@ -61,11 +53,8 @@ module.exports = app => app.component('navbar', {
61
53
  canViewTeam() {
62
54
  return this.hasAccess(this.roles, 'team');
63
55
  },
64
- allowedRoutes() {
65
- return routes.filter(route => this.hasAccess(this.roles, route.name));
66
- },
67
56
  defaultRoute() {
68
- return this.allowedRoutes[0]?.name || 'dashboards';
57
+ return this.roles && this.roles[0] === 'dashboards' ? 'dashboards' : 'root';
69
58
  }
70
59
  },
71
60
  methods: {
@@ -9,10 +9,12 @@ const roleAccess = {
9
9
  dashboards: ['dashboards', 'dashboard']
10
10
  };
11
11
 
12
+ const allowedRoutesForLocalDev = ['document', 'root', 'chat'];
13
+
12
14
  // Helper function to check if a role has access to a route
13
15
  function hasAccess(roles, routeName) {
14
16
  // change to true for local development
15
- if (!roles) return false;
17
+ if (!roles) return allowedRoutesForLocalDev.includes(routeName);
16
18
  return roles.some(role => roleAccess[role]?.includes(routeName));
17
19
  }
18
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.0.96",
3
+ "version": "0.0.98",
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": {