@mongoosejs/studio 0.0.115 → 0.0.117

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.
@@ -41,14 +41,23 @@ module.exports = ({ db }) => async function getDashboard(params) {
41
41
  return { dashboard, error: { message: error.message } };
42
42
  }
43
43
 
44
- const { dashboardResult } = await startExec.then(({ dashboardResult }) => {
45
- if (!dashboardResult) {
46
- return;
47
- }
48
- return completeDashboardEvaluate(dashboardResult._id, $workspaceId, authorization, result);
49
- });
44
+ try {
45
+ const { dashboardResult } = await startExec.then(({ dashboardResult }) => {
46
+ if (!dashboardResult) {
47
+ return {};
48
+ }
49
+ return completeDashboardEvaluate(
50
+ dashboardResult._id,
51
+ $workspaceId,
52
+ authorization,
53
+ result
54
+ );
55
+ });
50
56
 
51
- return { dashboard, dashboardResult };
57
+ return { dashboard, dashboardResult };
58
+ } catch (error) {
59
+ return { dashboard, error: { message: error.message } };
60
+ }
52
61
  } else {
53
62
  const { dashboardResults } = await getDashboardResults(dashboardId, $workspaceId, authorization);
54
63
  return { dashboard, dashboardResults };
@@ -18,7 +18,7 @@ const dashboardSchema = new mongoose.Schema({
18
18
  });
19
19
 
20
20
  dashboardSchema.methods.evaluate = async function evaluate() {
21
- const context = vm.createContext({ db: this.constructor.db, setTimeout });
21
+ const context = vm.createContext({ db: this.constructor.db, setTimeout, ObjectId: mongoose.Types.ObjectId });
22
22
  let result = null;
23
23
  result = await vm.runInContext(formatFunction(this.code), context);
24
24
  if (result.$document?.constructor?.modelName) {
@@ -336,6 +336,7 @@ const vanillatoasts = __webpack_require__(/*! vanillatoasts */ "./node_modules/v
336
336
  module.exports = app => app.component('chat-message-script', {
337
337
  template,
338
338
  props: ['message', 'script', 'language'],
339
+ emits: ['copyMessage'],
339
340
  data() {
340
341
  return {
341
342
  activeTab: 'code',
@@ -411,9 +412,13 @@ module.exports = app => app.component('chat-message-script', {
411
412
  this.$router.push('/dashboard/' + dashboard._id);
412
413
  },
413
414
  async copyOutput() {
414
- await navigator.clipboard.writeText(this.message.executionResult.output);
415
+ let output = this.message.executionResult.output;
416
+ if (output != null && typeof output === 'object') {
417
+ output = JSON.stringify(output, null, 2);
418
+ }
419
+ await navigator.clipboard.writeText(output);
415
420
  vanillatoasts.create({
416
- title: 'Text copied!',
421
+ title: 'Code output copied!',
417
422
  type: 'success',
418
423
  timeout: 3000,
419
424
  icon: 'images/success.png',
@@ -457,6 +462,7 @@ module.exports = app => app.component('chat-message-script', {
457
462
 
458
463
  const api = __webpack_require__(/*! ../../api */ "./frontend/src/api.js");
459
464
  const marked = (__webpack_require__(/*! marked */ "./node_modules/marked/lib/marked.cjs").marked);
465
+ const vanillatoasts = __webpack_require__(/*! vanillatoasts */ "./node_modules/vanillatoasts/vanillatoasts.js");
460
466
  const template = __webpack_require__(/*! ./chat-message.html */ "./frontend/src/chat/chat-message/chat-message.html");
461
467
 
462
468
  module.exports = app => app.component('chat-message', {
@@ -464,7 +470,7 @@ module.exports = app => app.component('chat-message', {
464
470
  props: ['message'],
465
471
  computed: {
466
472
  styleForMessage() {
467
- return this.message.role === 'user' ? 'bg-gray-100' : '';
473
+ return this.message.role === 'user' ? 'p-3 bg-gray-100' : 'py-3 pr-3';
468
474
  },
469
475
  contentSplitByScripts() {
470
476
  const content = this.message.content;
@@ -516,6 +522,37 @@ module.exports = app => app.component('chat-message', {
516
522
  });
517
523
  message.executionResult = chatMessage.executionResult;
518
524
  console.log(message);
525
+ },
526
+ async copyMessage() {
527
+ const parts = this.contentSplitByScripts;
528
+ let output = '';
529
+ for (const part of parts) {
530
+ if (part.type === 'text') {
531
+ output += part.content + '\n';
532
+ } else if (part.type === 'code') {
533
+ let result = this.message.executionResult?.output;
534
+ if (result != null && typeof result === 'object') {
535
+ result = JSON.stringify(result, null, 2);
536
+ }
537
+ if (result) {
538
+ let executionOutput = this.message.executionResult?.output;
539
+ if (executionOutput != null && typeof executionOutput === 'object') {
540
+ executionOutput = JSON.stringify(executionOutput, null, 2);
541
+ }
542
+ if (executionOutput) {
543
+ output += '```\n' + executionOutput + '\n```\n';
544
+ }
545
+ }
546
+ }
547
+ }
548
+ await navigator.clipboard.writeText(output.trim());
549
+ vanillatoasts.create({
550
+ title: 'Message output copied!',
551
+ type: 'success',
552
+ timeout: 3000,
553
+ icon: 'images/success.png',
554
+ positionClass: 'bottomRight'
555
+ });
519
556
  }
520
557
  }
521
558
  });
@@ -553,6 +590,8 @@ module.exports = app => app.component('chat', {
553
590
  async sendMessage() {
554
591
  this.sendingMessage = true;
555
592
  try {
593
+ const content = this.newMessage;
594
+ this.newMessage = '';
556
595
  if (!this.chatThreadId) {
557
596
  const { chatThread } = await api.ChatThread.createChatThread();
558
597
  this.chatThreads.unshift(chatThread);
@@ -561,7 +600,7 @@ module.exports = app => app.component('chat', {
561
600
  }
562
601
 
563
602
  this.chatMessages.push({
564
- content: this.newMessage,
603
+ content,
565
604
  role: 'user'
566
605
  });
567
606
 
@@ -573,7 +612,7 @@ module.exports = app => app.component('chat', {
573
612
 
574
613
  const { chatMessages, chatThread } = await api.ChatThread.createChatMessage({
575
614
  chatThreadId: this.chatThreadId,
576
- content: this.newMessage
615
+ content
577
616
  });
578
617
  this.chatMessages.push(chatMessages[1]);
579
618
  for (const thread of this.chatThreads) {
@@ -904,9 +943,11 @@ const template = __webpack_require__(/*! ./dashboard-chart.html */ "./frontend/s
904
943
 
905
944
  module.exports = app => app.component('dashboard-chart', {
906
945
  template: template,
907
- props: ['value', 'responsive'],
946
+ props: ['value', 'fullscreen'],
947
+ emits: ['fullscreen'],
908
948
  data: () => ({
909
- chart: null
949
+ chart: null,
950
+ showDetailModal: false
910
951
  }),
911
952
  mounted() {
912
953
  const ctx = this.$refs.chart.getContext('2d');
@@ -1074,7 +1115,8 @@ const template = __webpack_require__(/*! ./dashboard-result.html */ "./frontend/
1074
1115
 
1075
1116
  module.exports = app => app.component('dashboard-result', {
1076
1117
  template: template,
1077
- props: ['result', 'finishedEvaluatingAt'],
1118
+ props: ['result', 'finishedEvaluatingAt', 'fullscreen'],
1119
+ emits: ['fullscreen'],
1078
1120
  mounted: async function() {
1079
1121
  },
1080
1122
  methods: {
@@ -1167,22 +1209,22 @@ module.exports = app => app.component('dashboard', {
1167
1209
  },
1168
1210
  async evaluateDashboard() {
1169
1211
  this.status = 'evaluating';
1212
+ this.errorMessage = null;
1170
1213
  try {
1171
1214
  const { dashboard, dashboardResult, error } = await api.Dashboard.getDashboard({ dashboardId: this.dashboardId, evaluate: true });
1172
1215
  this.dashboard = dashboard;
1173
- if (error) {
1174
- this.errorMessage = error.message;
1175
- }
1176
1216
  this.code = this.dashboard.code;
1177
1217
  this.title = this.dashboard.title;
1178
1218
  this.description = this.dashboard.description ?? '';
1179
- this.dashboardResults.unshift(dashboardResult);
1219
+ if (dashboardResult) {
1220
+ this.dashboardResults.unshift(dashboardResult);
1221
+ }
1222
+ if (error) {
1223
+ this.errorMessage = error.message;
1224
+ }
1180
1225
  } finally {
1181
1226
  this.status = 'loaded';
1182
1227
  }
1183
- },
1184
- openDetailModal() {
1185
- this.showDetailModal = true;
1186
1228
  }
1187
1229
  },
1188
1230
  computed: {
@@ -1226,6 +1268,7 @@ const template = __webpack_require__(/*! ./edit-dashboard.html */ "./frontend/sr
1226
1268
  module.exports = app => app.component('edit-dashboard', {
1227
1269
  template: template,
1228
1270
  props: ['dashboardId', 'code', 'currentDescription', 'currentTitle'],
1271
+ emits: ['close', 'clearError'],
1229
1272
  data: function() {
1230
1273
  return {
1231
1274
  status: 'loaded',
@@ -1239,6 +1282,7 @@ module.exports = app => app.component('edit-dashboard', {
1239
1282
  this.$emit('close');
1240
1283
  },
1241
1284
  async updateCode() {
1285
+ this.$emit('clearError');
1242
1286
  this.status = 'loading';
1243
1287
  try {
1244
1288
  const { doc, result, error } = await api.Dashboard.updateDashboard({
@@ -2671,13 +2715,13 @@ module.exports = app => app.component('models', {
2671
2715
  this.query.search = this.searchText;
2672
2716
  const query = this.query;
2673
2717
  const newUrl = this.$router.resolve({ query }).href;
2674
- window.history.pushState(null, '', newUrl);
2718
+ this.$router.push({ query });
2675
2719
  } else {
2676
2720
  this.filter = {};
2677
2721
  delete this.query.search;
2678
2722
  const query = this.query;
2679
2723
  const newUrl = this.$router.resolve({ query }).href;
2680
- window.history.pushState(null, '', newUrl);
2724
+ this.$router.push({ query });
2681
2725
  }
2682
2726
  this.documents = [];
2683
2727
  this.status = 'loading';
@@ -4169,7 +4213,7 @@ module.exports = "<button v-bind=\"attrsToBind\" :disabled=\"isDisabled\" @click
4169
4213
  /***/ ((module) => {
4170
4214
 
4171
4215
  "use strict";
4172
- module.exports = "<div class=\"relative border rounded bg-gray-100 text-black text-sm overflow-hidden\">\n <div class=\"flex border-b pt-[1px] text-xs font-medium bg-gray-200\">\n <button\n class=\"px-3 py-1 border-r border-gray-300 hover:bg-green-300\"\n :class=\"{'bg-gray-300': activeTab === 'code', 'bg-green-300': activeTab === 'code'}\"\n @click=\"activeTab = 'code'\">\n Code\n </button>\n <button\n class=\"px-3 py-1 hover:bg-green-300\"\n :class=\"{'bg-green-300': activeTab === 'output'}\"\n @click=\"activeTab = 'output'\">\n Output\n </button>\n <div class=\"ml-auto mr-1 flex\">\n <button\n v-if=\"activeTab === 'output'\"\n class=\"px-2 py-1 mr-1 text-xs bg-gray-500 text-white border-none rounded cursor-pointer hover:bg-gray-600 transition-colors flex items-center\"\n @click=\"copyOutput\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3\" />\n </svg>\n </button>\n <button\n v-if=\"activeTab === 'output'\"\n class=\"px-2 py-1 mr-1 text-xs bg-blue-500 text-white border-none rounded cursor-pointer hover:bg-blue-600 transition-colors flex items-center\"\n @click=\"openDetailModal\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5\" />\n </svg>\n </button>\n <async-button\n class=\"px-2 py-1 text-xs bg-green-500 text-white border-none rounded cursor-pointer hover:bg-green-600 transition-colors disabled:bg-gray-400\"\n @click=\"executeScript(message, script)\">\n Execute\n </async-button>\n <div class=\"relative ml-1\" ref=\"dropdown\">\n <button\n @click.stop=\"toggleDropdown\"\n class=\"px-1 py-1 text-xs hover:bg-gray-300 rounded flex items-center\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4z\" />\n </svg>\n </button>\n <div\n v-if=\"showDropdown\"\n class=\"absolute right-0 z-10 mt-1 w-64 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5\">\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100\"\n @click=\"openCreateDashboardModal(); showDropdown = false\">\n Create Dashboard\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <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>\n\n <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'\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" />\n <dashboard-map v-else-if=\"message.executionResult?.output?.$featureCollection\" :value=\"message.executionResult?.output\" />\n <pre v-else>{{ message.executionResult?.output || 'No output' }}</pre>\n </div>\n\n <modal ref=\"outputModal\" v-if=\"showDetailModal\" containerClass=\"!h-[90vh] !w-[90vw]\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showDetailModal = false;\">&times;</div>\n <div class=\"h-full overflow-auto\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" :responsive=\"true\" />\n <pre v-else class=\"whitespace-pre-wrap\">{{ message.executionResult?.output || 'No output' }}</pre>\n </div>\n </template>\n </modal>\n <modal v-if=\"showCreateDashboardModal\">\n <template #body>\n <div class=\"modal-exit\" @click=\"showCreateDashboardModal = false\">&times;</div>\n <div>\n <div class=\"mt-4 text-gray-900 font-semibold\">Create Dashboard</div>\n <div class=\"mt-4\">\n <label class=\"block text-sm font-medium leading-6 text-gray-900\">Title</label>\n <div class=\"mt-2\">\n <div class=\"w-full flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-teal-600\">\n <input type=\"text\" v-model=\"newDashboardTitle\" class=\"outline-none block flex-1 border-0 bg-transparent py-1.5 pl-1 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6\" placeholder=\"My Dashboard\">\n </div>\n </div>\n </div>\n <div class=\"my-4\">\n <label class=\"block text-sm font-medium leading-6 text-gray-900\">Code</label>\n <div class=\"border border-gray-200\">\n <textarea class=\"p-2 h-[300px] w-full\" ref=\"dashboardCodeEditor\"></textarea>\n </div>\n </div>\n <async-button\n @click=\"createDashboardFromScript\"\n class=\"rounded-md bg-teal-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n Submit\n </async-button>\n <div v-if=\"createErrors.length > 0\" class=\"rounded-md bg-red-50 p-4 mt-1\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <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\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">Error</h3>\n <div class=\"mt-2 text-sm text-red-700\">\n {{createError}}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n</div>\n";
4216
+ module.exports = "<div class=\"relative border rounded bg-gray-100 text-black text-sm overflow-hidden\">\n <div class=\"flex border-b pt-[1px] text-xs font-medium bg-gray-200\">\n <button\n class=\"px-3 py-1 border-r border-gray-300 hover:bg-green-300\"\n :class=\"{'bg-gray-300': activeTab === 'code', 'bg-green-300': activeTab === 'code'}\"\n @click=\"activeTab = 'code'\">\n Code\n </button>\n <button\n class=\"px-3 py-1 hover:bg-green-300\"\n :class=\"{'bg-green-300': activeTab === 'output'}\"\n @click=\"activeTab = 'output'\">\n Output\n </button>\n <div class=\"ml-auto mr-1 flex\">\n <button\n v-if=\"activeTab === 'output'\"\n class=\"px-2 py-1 mr-1 text-xs bg-gray-500 text-white border-none rounded cursor-pointer hover:bg-gray-600 transition-colors flex items-center\"\n @click=\"copyOutput\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3\" />\n </svg>\n </button>\n <button\n v-if=\"activeTab === 'output'\"\n class=\"px-2 py-1 mr-1 text-xs bg-blue-500 text-white border-none rounded cursor-pointer hover:bg-blue-600 transition-colors flex items-center\"\n @click=\"openDetailModal\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5\" />\n </svg>\n </button>\n <async-button\n class=\"px-2 py-1 text-xs bg-green-500 text-white border-none rounded cursor-pointer hover:bg-green-600 transition-colors disabled:bg-gray-400\"\n @click=\"executeScript(message, script)\">\n Execute\n </async-button>\n <div class=\"relative ml-1\" ref=\"dropdown\">\n <button\n @click.stop=\"toggleDropdown\"\n class=\"px-1 py-1 text-xs hover:bg-gray-300 rounded flex items-center\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4z\" />\n </svg>\n </button>\n <div\n v-if=\"showDropdown\"\n class=\"absolute right-0 z-10 mt-1 w-64 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5\">\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100\"\n @click=\"openCreateDashboardModal(); showDropdown = false\">\n Create Dashboard\n </button>\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100\"\n @click=\"$emit('copyMessage'); showDropdown = false\">\n Copy Full Message\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <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>\n\n <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'\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" />\n <dashboard-map v-else-if=\"message.executionResult?.output?.$featureCollection\" :value=\"message.executionResult?.output\" />\n <pre v-else>{{ message.executionResult?.output || 'No output' }}</pre>\n </div>\n\n <modal ref=\"outputModal\" v-if=\"showDetailModal\" containerClass=\"!h-[90vh] !w-[90vw]\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showDetailModal = false;\">&times;</div>\n <div class=\"h-full overflow-auto\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" :responsive=\"true\" />\n <pre v-else class=\"whitespace-pre-wrap\">{{ message.executionResult?.output || 'No output' }}</pre>\n </div>\n </template>\n </modal>\n <modal v-if=\"showCreateDashboardModal\">\n <template #body>\n <div class=\"modal-exit\" @click=\"showCreateDashboardModal = false\">&times;</div>\n <div>\n <div class=\"mt-4 text-gray-900 font-semibold\">Create Dashboard</div>\n <div class=\"mt-4\">\n <label class=\"block text-sm font-medium leading-6 text-gray-900\">Title</label>\n <div class=\"mt-2\">\n <div class=\"w-full flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-teal-600\">\n <input type=\"text\" v-model=\"newDashboardTitle\" class=\"outline-none block flex-1 border-0 bg-transparent py-1.5 pl-1 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6\" placeholder=\"My Dashboard\">\n </div>\n </div>\n </div>\n <div class=\"my-4\">\n <label class=\"block text-sm font-medium leading-6 text-gray-900\">Code</label>\n <div class=\"border border-gray-200\">\n <textarea class=\"p-2 h-[300px] w-full\" ref=\"dashboardCodeEditor\"></textarea>\n </div>\n </div>\n <async-button\n @click=\"createDashboardFromScript\"\n class=\"rounded-md bg-teal-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n Submit\n </async-button>\n <div v-if=\"createErrors.length > 0\" class=\"rounded-md bg-red-50 p-4 mt-1\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <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\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">Error</h3>\n <div class=\"mt-2 text-sm text-red-700\">\n {{createError}}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n</div>\n";
4173
4217
 
4174
4218
  /***/ }),
4175
4219
 
@@ -4180,7 +4224,7 @@ module.exports = "<div class=\"relative border rounded bg-gray-100 text-black te
4180
4224
  /***/ ((module) => {
4181
4225
 
4182
4226
  "use strict";
4183
- module.exports = "<div class=\"relative flex items-start\" :class=\"{'justify-end': message.role === 'user'}\">\n <div\n class=\"min-w-0 max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)]\"\n :class=\"{'text-right': message.role === 'user'}\">\n\n <div class=\"text-sm text-gray-900 md:p-3 rounded-md inline-block\" :class=\"styleForMessage\">\n <div v-for=\"part in contentSplitByScripts\">\n <div v-if=\"part.type === 'text'\" v-html=\"marked(part.content)\">\n </div>\n <div v-else-if=\"part.type === 'code'\">\n <chat-message-script :message=\"message\" :script=\"part.content\" :language=\"part.language\"></chat-message-script>\n </div>\n </div>\n </div>\n </div>\n</div>\n";
4227
+ module.exports = "<div class=\"relative flex items-start\" :class=\"{'justify-end': message.role === 'user'}\">\n <div\n class=\"min-w-0 max-w-[calc(100vw-3rem)] lg:max-w-[calc(100vw-15rem)]\"\n :class=\"{'text-right': message.role === 'user'}\">\n\n <div class=\"text-sm text-gray-900 rounded-md inline-block relative\" :class=\"styleForMessage\">\n <div v-for=\"part in contentSplitByScripts\">\n <div v-if=\"part.type === 'text'\" v-html=\"marked(part.content)\">\n </div>\n <div v-else-if=\"part.type === 'code'\">\n <chat-message-script :message=\"message\" :script=\"part.content\" :language=\"part.language\" @copyMessage=\"copyMessage\"></chat-message-script>\n </div>\n </div>\n </div>\n </div>\n</div>\n";
4184
4228
 
4185
4229
  /***/ }),
4186
4230
 
@@ -4191,7 +4235,7 @@ module.exports = "<div class=\"relative flex items-start\" :class=\"{'justify-en
4191
4235
  /***/ ((module) => {
4192
4236
 
4193
4237
  "use strict";
4194
- module.exports = "<div class=\"flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <div class=\"fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10\" @click=\"hideSidebar = false\">\n <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>\n </div>\n <button\n class=\"fixed top-[65px] right-4 z-10 p-2 rounded-md shadow bg-white\"\n :class=\"hasWorkspace ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 cursor-not-allowed bg-gray-50'\"\n @click=\"toggleShareThread\"\n :disabled=\"!hasWorkspace || !chatThreadId || sharingThread\"\n aria-label=\"Share thread with workspace\"\n title=\"Share thread with workspace\"\n >\n <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>\n <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>\n </button>\n <!-- Sidebar: Chat Threads -->\n <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' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-64 overflow-x-hidden\">\n <div class=\"p-4 font-bold text-lg\">Chat Threads</div>\n <button\n @click=\"hideSidebar = true\"\n class=\"ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none\"\n aria-label=\"Close sidebar\"\n >\n <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>\n </button>\n </div>\n <div class=\"p-4 w-64\">\n <async-button\n @click=\"createNewThread\"\n class=\"w-full bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700\"\n >\n Create New Thread\n </async-button>\n </div>\n <div v-if=\"status === 'loaded' && chatThreads.length === 0\" class=\"p-4 text-sm text-gray-700\">\n No threads yet\n </div>\n <ul v-if=\"status === 'loaded'\" class=\"w-64\">\n <li\n v-for=\"thread in chatThreads\"\n :key=\"thread._id\"\n @click=\"selectThread(thread._id)\"\n class=\"p-4 hover:bg-gray-200 cursor-pointer w-64\"\n :class=\"{ 'bg-gray-300': thread._id === chatThreadId }\"\n >\n {{ thread.title || 'Untitled Thread' }}\n </li>\n </ul>\n </aside>\n\n <!-- Main Chat Area -->\n <main class=\"flex-1 flex flex-col\">\n <div class=\"flex-1 overflow-y-auto p-6 space-y-4\" ref=\"messagesContainer\">\n <ul role=\"list\" class=\"space-y-4\">\n <div v-if=\"true\">\n <div class=\"flex items-center justify-center py-3 mb-4\">\n <div class=\"bg-gray-300 h-px flex-grow max-w-xs\"></div>\n <p class=\"mx-4 text-sm font-medium text-gray-500\">This is the beginning of the message thread</p>\n <div class=\"bg-gray-300 h-px flex-grow max-w-xs\"></div>\n </div>\n </div>\n <li v-for=\"message in chatMessages\" :key=\"message._id\">\n <chat-message :message=\"message\"></chat-message>\n </li>\n </ul>\n </div>\n\n\n <!-- Input Area -->\n <div class=\"border-t p-4\">\n <form @submit.prevent=\"sendMessage\" :disabled=\"sendingMessage\" class=\"flex gap-2 items-end justify-end\">\n <textarea\n v-model=\"newMessage\"\n placeholder=\"Ask something...\"\n class=\"flex-1 border rounded px-4 py-2 resize-none overflow-y-auto\"\n rows=\"1\"\n ref=\"messageInput\"\n @input=\"adjustTextareaHeight\"\n @keydown.enter.exact.prevent=\"handleEnter\"\n ></textarea>\n <button class=\"bg-blue-600 text-white px-4 h-[42px] rounded disabled:bg-gray-600\" :disabled=\"sendingMessage\">\n <svg v-if=\"sendingMessage\" style=\"height: 1em\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <g>\n <circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" opacity=\"0.3\" />\n <path d=\"M12 2a10 10 0 0 1 10 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <animateTransform attributeName=\"transform\" type=\"rotate\" from=\"0 12 12\" to=\"360 12 12\" dur=\"1s\" repeatCount=\"indefinite\" />\n </path>\n </g>\n </svg>\n <span v-else>Send</span>\n </button>\n </form>\n </div>\n </main>\n</div>\n";
4238
+ module.exports = "<div class=\"flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <div class=\"fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10\" @click=\"hideSidebar = false\">\n <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>\n </div>\n <button\n class=\"fixed top-[65px] right-4 z-10 p-2 rounded-md shadow bg-white\"\n :class=\"hasWorkspace ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 cursor-not-allowed bg-gray-50'\"\n @click=\"toggleShareThread\"\n :disabled=\"!hasWorkspace || !chatThreadId || sharingThread\"\n aria-label=\"Share thread with workspace\"\n title=\"Share thread with workspace\"\n >\n <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>\n <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>\n </button>\n <!-- Sidebar: Chat Threads -->\n <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' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-64 overflow-x-hidden\">\n <div class=\"p-4 font-bold text-lg\">Chat Threads</div>\n <button\n @click=\"hideSidebar = true\"\n class=\"ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none\"\n aria-label=\"Close sidebar\"\n >\n <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>\n </button>\n </div>\n <div class=\"p-4 w-64\">\n <async-button\n @click=\"createNewThread\"\n class=\"w-full bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700\"\n >\n Create New Thread\n </async-button>\n </div>\n <div v-if=\"status === 'loaded' && chatThreads.length === 0\" class=\"p-4 text-sm text-gray-700\">\n No threads yet\n </div>\n <ul v-if=\"status === 'loaded'\" class=\"w-64\">\n <li\n v-for=\"thread in chatThreads\"\n :key=\"thread._id\"\n @click=\"selectThread(thread._id)\"\n class=\"p-4 hover:bg-gray-200 cursor-pointer w-64\"\n :class=\"{ 'bg-gray-300': thread._id === chatThreadId }\"\n >\n {{ thread.title || 'Untitled Thread' }}\n </li>\n </ul>\n </aside>\n\n <!-- Main Chat Area -->\n <main class=\"flex-1 flex flex-col\">\n <div class=\"flex-1 overflow-y-auto p-6 space-y-4\" ref=\"messagesContainer\">\n <ul role=\"list\" class=\"space-y-4\">\n <div v-if=\"true\">\n <div class=\"flex items-center justify-center py-3 mb-4\">\n <div class=\"bg-gray-300 h-px flex-grow max-w-xs\"></div>\n <p class=\"mx-4 text-sm font-medium text-gray-500\">This is the beginning of the message thread</p>\n <div class=\"bg-gray-300 h-px flex-grow max-w-xs\"></div>\n </div>\n </div>\n <li v-for=\"message in chatMessages\" :key=\"message._id\">\n <chat-message :message=\"message\"></chat-message>\n </li>\n </ul>\n </div>\n\n\n <!-- Input Area -->\n <div class=\"border-t p-4\">\n <form @submit.prevent=\"sendMessage\" :disabled=\"sendingMessage\" class=\"flex gap-2 items-end justify-end\">\n <textarea\n v-model=\"newMessage\"\n :placeholder=\"sendingMessage ? 'Sending...' : 'Ask something...'\"\n class=\"flex-1 border rounded px-4 py-2 resize-none overflow-y-auto\"\n :disabled=\"sendingMessage\"\n rows=\"1\"\n ref=\"messageInput\"\n @input=\"adjustTextareaHeight\"\n @keydown.enter.exact.prevent=\"handleEnter\"\n ></textarea>\n <button class=\"bg-blue-600 text-white px-4 h-[42px] rounded disabled:bg-gray-600\" :disabled=\"sendingMessage\">\n <svg v-if=\"sendingMessage\" style=\"height: 1em\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <g>\n <circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" opacity=\"0.3\" />\n <path d=\"M12 2a10 10 0 0 1 10 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <animateTransform attributeName=\"transform\" type=\"rotate\" from=\"0 12 12\" to=\"360 12 12\" dur=\"1s\" repeatCount=\"indefinite\" />\n </path>\n </g>\n </svg>\n <span v-else>Send</span>\n </button>\n </form>\n </div>\n </main>\n</div>\n";
4195
4239
 
4196
4240
  /***/ }),
4197
4241
 
@@ -4257,7 +4301,7 @@ module.exports = "<div>\n <div class=\"mb-2\">\n <textarea class=\"border bo
4257
4301
  /***/ ((module) => {
4258
4302
 
4259
4303
  "use strict";
4260
- module.exports = "<div :class=\"responsive ? 'h-full' : ''\">\n <div v-if=\"header\" class=\"border-b border-gray-100 px-2 pb-2 flex items-center\">\n <div class=\"text-xl font-bold\">{{header}}</div>\n <button\n class=\"ml-auto px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors\"\n @click=\"exportPNG\"\n title=\"Export PNG\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"height: 1.5em;\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M280-280h400v-80H280v80Zm200-120 160-160-56-56-64 62v-166h-80v166l-64-62-56 56 160 160Zm0 320q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z\"/></svg>\n </button>\n </div>\n <div v-else class=\"border-b border-gray-100 px-2 pb-2 text-right\">\n <button\n class=\"mt-1 px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors\"\n @click=\"exportPNG\"\n title=\"Export PNG\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"height: 1.5em;\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M280-280h400v-80H280v80Zm200-120 160-160-56-56-64 62v-166h-80v166l-64-62-56 56 160 160Zm0 320q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z\"/></svg>\n </button>\n </div>\n <div class=\"text-xl\" :class=\"responsive ? 'h-full' : ''\">\n <canvas ref=\"chart\"></canvas>\n </div>\n</div>\n";
4304
+ module.exports = "<div :class=\"responsive ? 'h-full' : ''\">\n <div v-if=\"header && !fullscreen\" class=\"border-b border-gray-100 px-2 pb-2 flex items-center gap-1\">\n <div class=\"text-xl font-bold\">{{header}}</div>\n <button\n class=\"ml-auto px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors\"\n @click=\"exportPNG\"\n title=\"Export PNG\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"height: 1.5em;\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M280-280h400v-80H280v80Zm200-120 160-160-56-56-64 62v-166h-80v166l-64-62-56 56 160 160Zm0 320q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z\"/></svg>\n </button>\n <button\n class=\"px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors flex items-center\"\n @click=\"$emit('fullscreen')\"\n aria-label=\"Expand dashboard result\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"height: 1.5em\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5\" />\n </svg>\n </button>\n </div>\n <div v-else-if=\"!fullscreen\" class=\"pt-1 border-b border-gray-100 px-2 pb-2 text-right flex items-center justify-end gap-1 w-full\">\n <button\n class=\"px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors\"\n @click=\"exportPNG\"\n title=\"Export PNG\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"height: 1.5em;\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M280-280h400v-80H280v80Zm200-120 160-160-56-56-64 62v-166h-80v166l-64-62-56 56 160 160Zm0 320q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z\"/></svg>\n </button>\n <button\n class=\"px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors flex items-center\"\n @click=\"$emit('fullscreen')\"\n aria-label=\"Expand dashboard result\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"height: 1.5em\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5\" />\n </svg>\n </button>\n </div>\n <div :class=\"responsive ? 'relative h-full min-h-0' : ''\">\n <canvas ref=\"chart\" class=\"block w-full h-full\"></canvas>\n </div>\n</div>\n";
4261
4305
 
4262
4306
  /***/ }),
4263
4307
 
@@ -4301,7 +4345,7 @@ module.exports = "<div class=\"py-2\">\n <div v-if=\"header\" class=\"border-b
4301
4345
  /***/ ((module) => {
4302
4346
 
4303
4347
  "use strict";
4304
- module.exports = "<div>\n <div v-if=\"Array.isArray(result)\">\n <div v-for=\"el in result\" :key=\"el._id || el.finishedEvaluatingAt\">\n <component\n class=\"bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl\"\n :is=\"getComponentForValue(el)\"\n :value=\"el\">\n </component>\n </div>\n </div>\n <div v-else>\n <component\n class=\"bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl\"\n :is=\"getComponentForValue(result)\"\n :value=\"result\">\n </component>\n </div>\n <div class=\"text-right text-sm text-gray-700 mt-1\" v-if=\"finishedEvaluatingAt\">\n Last Evaluated: {{ format.isoToLongDateTime(finishedEvaluatingAt) }}\n </div>\n</div>\n";
4348
+ module.exports = "<div>\n <div v-if=\"Array.isArray(result)\">\n <div v-for=\"el in result\" :key=\"el._id || el.finishedEvaluatingAt\">\n <component\n class=\"bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl\"\n :is=\"getComponentForValue(el)\"\n :value=\"el\">\n </component>\n </div>\n </div>\n <div v-else>\n <component\n class=\"bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl\"\n :is=\"getComponentForValue(result)\"\n :value=\"result\"\n :fullscreen=\"fullscreen\"\n @fullscreen=\"$emit('fullscreen')\">\n </component>\n </div>\n <div class=\"text-right text-sm text-gray-700 mt-1\" v-if=\"finishedEvaluatingAt && !fullscreen\">\n Last Evaluated: {{ format.isoToLongDateTime(finishedEvaluatingAt) }}\n </div>\n</div>\n";
4305
4349
 
4306
4350
  /***/ }),
4307
4351
 
@@ -4323,7 +4367,7 @@ module.exports = "<div class=\"py-2\">\n <div v-if=\"header\" class=\"border-b
4323
4367
  /***/ ((module) => {
4324
4368
 
4325
4369
  "use strict";
4326
- module.exports = "<div class=\"dashboard px-1\">\n <div v-if=\"status === 'loading'\" class=\"max-w-5xl mx-auto text-center\">\n <img src=\"images/loader.gif\" class=\"inline mt-10\">\n </div>\n <div v-if=\"dashboard && status !== 'loading'\" class=\"max-w-5xl mx-auto\">\n <div class=\"flex items-center w-full\" v-if=\"!showEditor\">\n <h2 class=\"mt-4 mb-4 text-gray-900 font-semibold text-xl grow shrink\">{{title}}</h2>\n <div class=\"flex gap-2\">\n <button\n @click=\"showEditor = true\"\n type=\"button\"\n :disabled=\"status === 'evaluating'\"\n class=\"flex items-center 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-ultramarine-600 disabled:cursor-not-allowed disabled:bg-gray-600\">\n <img src=\"images/edit.svg\" class=\"inline h-[1.25em] mr-1\" /> Edit\n </button>\n\n <async-button\n @click=\"evaluateDashboard\"\n type=\"button\"\n :disabled=\"status === 'evaluating'\"\n class=\"flex items-center rounded-md bg-ultramarine-600 px-4 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-ultramarine-600 disabled:cursor-not-allowed disabled:bg-gray-600\"\n >\n <svg class=\"inline h-[1.25em] mr-1\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"m670-140 160-100-160-100v200ZM240-600h480v-80H240v80ZM720-40q-83 0-141.5-58.5T520-240q0-83 58.5-141.5T720-440q83 0 141.5 58.5T920-240q0 83-58.5 141.5T720-40ZM120-80v-680q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v267q-19-9-39-15t-41-9v-243H200v562h243q5 31 15.5 59T486-86l-6 6-60-60-60 60-60-60-60 60-60-60-60 60Zm120-200h203q3-21 9-41t15-39H240v80Zm0-160h284q38-37 88.5-58.5T720-520H240v80Zm-40 242v-562 562Z\"/></svg>\n Evaluate\n </async-button>\n </div>\n </div>\n <div v-if=\"!showEditor\" class=\"mt-4 mb-4\">\n <div v-if=\"dashboardResults.length === 0\">\n <div class=\"flex flex-col items-center justify-center py-8\">\n <p class=\"text-gray-700 text-base mb-4\">This dashboard hasn't been evaluated yet.</p>\n <async-button\n @click=\"evaluateDashboard\"\n type=\"button\"\n :disabled=\"status === 'evaluating'\"\n class=\"rounded-md bg-ultramarine-600 px-4 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 disabled:cursor-not-allowed disabled:bg-gray-600\"\n >\n Evaluate Dashboard\n </async-button>\n </div>\n </div>\n <div v-else>\n <div class=\"relative\">\n <button\n class=\"absolute top-2 right-2 px-2 py-1 text-xs bg-blue-500 text-white border-none rounded cursor-pointer hover:bg-blue-600 transition-colors flex items-center\"\n @click=\"openDetailModal\"\n aria-label=\"Expand dashboard result\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5\" />\n </svg>\n </button>\n <dashboard-result\n :key=\"dashboardResult.finishedEvaluatingAt\"\n :result=\"dashboardResult.result\"\n :finishedEvaluatingAt=\"dashboardResult.finishedEvaluatingAt\">\n </dashboard-result>\n </div>\n </div>\n </div>\n <div v-if=\"showEditor\" class=\"mt-4\">\n <edit-dashboard\n :dashboardId=\"dashboard._id\"\n :code=\"code\"\n :currentDescription=\"description\"\n :currentTitle=\"title\"\n @close=\"showEditor=false;\"\n @update=\"updateCode\"></edit-dashboard>\n </div>\n <div v-if=\"errorMessage\" class=\"rounded-md bg-red-50 p-4 mt-4\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM8.28 7.22a.75.75 0 0 0-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 1 0 1.06 1.06L10 11.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L11.06 10l1.72-1.72a.75.75 0 0 0-1.06-1.06L10 8.94 8.28 7.22Z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">{{errorMessage}}</h3>\n </div>\n </div>\n </div>\n\n </div>\n <div v-if=\"!dashboard && status !== 'loading'\">\n No dashboard with the given id could be found.\n </div>\n</div>\n\n<modal\n v-if=\"showDetailModal\"\n containerClass=\"!h-[90vh] !w-[90vw]\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Dashboard Details\"\n>\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showDetailModal = false;\" role=\"button\" aria-label=\"Close modal\">&times;</div>\n <div class=\"h-full overflow-auto\">\n <dashboard-result\n v-if=\"dashboardResult\"\n :result=\"dashboardResult.result\"\n :finishedEvaluatingAt=\"dashboardResult.finishedEvaluatingAt\">\n </dashboard-result>\n </div>\n </template>\n</modal>\n";
4370
+ module.exports = "<div class=\"dashboard px-1\">\n <div v-if=\"status === 'loading'\" class=\"max-w-5xl mx-auto text-center\">\n <img src=\"images/loader.gif\" class=\"inline mt-10\">\n </div>\n <div v-if=\"dashboard && status !== 'loading'\" class=\"max-w-5xl mx-auto\">\n <div class=\"flex items-center w-full\" v-if=\"!showEditor\">\n <h2 class=\"mt-4 mb-4 text-gray-900 font-semibold text-xl grow shrink\">{{title}}</h2>\n <div class=\"flex gap-2\">\n <button\n @click=\"showEditor = true\"\n type=\"button\"\n :disabled=\"status === 'evaluating'\"\n class=\"flex items-center 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-ultramarine-600 disabled:cursor-not-allowed disabled:bg-gray-600\">\n <img src=\"images/edit.svg\" class=\"inline h-[1.25em] mr-1\" /> Edit\n </button>\n\n <async-button\n @click=\"evaluateDashboard\"\n type=\"button\"\n :disabled=\"status === 'evaluating'\"\n class=\"flex items-center rounded-md bg-ultramarine-600 px-4 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-ultramarine-600 disabled:cursor-not-allowed disabled:bg-gray-600\"\n >\n <svg class=\"inline h-[1.25em] mr-1\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"m670-140 160-100-160-100v200ZM240-600h480v-80H240v80ZM720-40q-83 0-141.5-58.5T520-240q0-83 58.5-141.5T720-440q83 0 141.5 58.5T920-240q0 83-58.5 141.5T720-40ZM120-80v-680q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v267q-19-9-39-15t-41-9v-243H200v562h243q5 31 15.5 59T486-86l-6 6-60-60-60 60-60-60-60 60-60-60-60 60Zm120-200h203q3-21 9-41t15-39H240v80Zm0-160h284q38-37 88.5-58.5T720-520H240v80Zm-40 242v-562 562Z\"/></svg>\n Evaluate\n </async-button>\n </div>\n </div>\n <div v-if=\"!showEditor\" class=\"mt-4 mb-4\">\n <div v-if=\"dashboardResults.length === 0\">\n <div class=\"flex flex-col items-center justify-center py-8\">\n <p class=\"text-gray-700 text-base mb-4\">This dashboard hasn't been evaluated yet.</p>\n <async-button\n @click=\"evaluateDashboard\"\n type=\"button\"\n :disabled=\"status === 'evaluating'\"\n class=\"rounded-md bg-ultramarine-600 px-4 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 disabled:cursor-not-allowed disabled:bg-gray-600\"\n >\n Evaluate Dashboard\n </async-button>\n </div>\n </div>\n <div v-else>\n <div class=\"relative\">\n <dashboard-result\n :key=\"dashboardResult.finishedEvaluatingAt\"\n :result=\"dashboardResult.result\"\n :finishedEvaluatingAt=\"dashboardResult.finishedEvaluatingAt\"\n @fullscreen=\"showDetailModal = true\"\n class=\"h-[40vh]\"\n >\n </dashboard-result>\n </div>\n </div>\n </div>\n <div v-if=\"showEditor\" class=\"mt-4\">\n <edit-dashboard\n :dashboardId=\"dashboard._id\"\n :code=\"code\"\n :currentDescription=\"description\"\n :currentTitle=\"title\"\n @close=\"showEditor=false;\"\n @update=\"updateCode\"\n @clearError=\"errorMessage = null\"></edit-dashboard>\n </div>\n <div v-if=\"errorMessage\" class=\"rounded-md bg-red-50 p-4 mt-4\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM8.28 7.22a.75.75 0 0 0-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 1 0 1.06 1.06L10 11.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L11.06 10l1.72-1.72a.75.75 0 0 0-1.06-1.06L10 8.94 8.28 7.22Z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">{{errorMessage}}</h3>\n </div>\n </div>\n </div>\n\n </div>\n <div v-if=\"!dashboard && status !== 'loading'\">\n No dashboard with the given id could be found.\n </div>\n</div>\n\n<modal\n v-if=\"showDetailModal\"\n containerClass=\"!h-[90vh] !w-[90vw]\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Dashboard Details\"\n>\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showDetailModal = false;\" role=\"button\" aria-label=\"Close modal\">&times;</div>\n <div class=\"h-full overflow-auto\">\n <dashboard-result\n v-if=\"dashboardResult\"\n :result=\"dashboardResult.result\"\n :finishedEvaluatingAt=\"dashboardResult.finishedEvaluatingAt\"\n :fullscreen=\"true\"\n :responsive=\"true\">\n </dashboard-result>\n </div>\n </template>\n</modal>\n";
4327
4371
 
4328
4372
  /***/ }),
4329
4373
 
@@ -4345,7 +4389,7 @@ module.exports = "<div class=\"p-4 bg-gray-100 rounded-lg shadow-lg\">\n <div
4345
4389
  /***/ ((module) => {
4346
4390
 
4347
4391
  "use strict";
4348
- module.exports = "<div class=\"dashboards max-w-5xl mx-auto mt-8\">\n <div v-if=\"status === 'loaded' && dashboards.length === 0\">\n <div class=\"text-center\">\n <h3 class=\"mt-2 text-sm font-semibold text-gray-900\">No dashboards yet</h3>\n <p class=\"mt-1 text-sm text-gray-500\">Get started by creating a new dashboard.</p>\n <div class=\"mt-6\">\n <button type=\"button\" class=\"inline-flex items-center rounded-md bg-teal-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n <svg class=\"-ml-0.5 mr-1.5 h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z\" />\n </svg>\n New Dashboard\n </button>\n </div>\n </div>\n </div>\n\n\n <div class=\"px-4 sm:px-6 lg:px-8\">\n <div class=\"sm:flex sm:items-center\">\n <div class=\"sm:flex-auto\">\n <h1 class=\"text-base font-semibold leading-6 text-gray-900\">Dashboards</h1>\n </div>\n <div class=\"mt-4 sm:ml-16 sm:mt-0 sm:flex-none\">\n <button\n type=\"button\"\n @click=\"showCreateDashboardModal = true\"\n class=\"block rounded-md bg-teal-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">Create New Dashboard</button>\n </div>\n </div>\n <div class=\"mt-8 flow-root\">\n <div class=\"-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8\">\n <div class=\"inline-block min-w-full py-2 align-middle\">\n <table class=\"min-w-full divide-y divide-gray-300\">\n <thead>\n <tr>\n <th scope=\"col\" class=\"py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6 lg:pl-8\">Title</th>\n <th scope=\"col\" class=\"px-3 py-3.5 text-left text-sm font-semibold text-gray-900 w-[50%]\">Description</th>\n <th scope=\"col\" class=\"relative py-3.5 pl-3 pr-4 sm:pr-6 lg:pr-8\">\n </th>\n <th scope=\"col\" class=\"relative py-3.5 pl-3 pr-4 sm:pr-6 lg:pr-8\">\n </th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-gray-200 bg-white\">\n <tr v-for=\"dashboard in dashboards\">\n <td class=\"whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6 lg:pl-8\">{{dashboard.title}}</td>\n <td class=\"whitespace-nowrap px-3 py-4 text-sm text-gray-500 truncate w-[50%]\">{{dashboard.description}}</td>\n <td class=\"relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 lg:pr-8\">\n <router-link\n :to=\"'/dashboard/' + dashboard._id + '?edit=true'\"\n class=\"text-teal-600 hover:text-teal-900\">\n Edit\n </router-link>\n </td>\n <td class=\"relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 lg:pr-8\">\n <router-link\n :to=\"'/dashboard/' + dashboard._id\"\n class=\"text-teal-600 hover:text-teal-900\">\n View\n </router-link>\n </td>\n <td class=\"relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 lg:pr-8\">\n <button\n @click=\"showDeleteDashboardModal=dashboard\"\n class=\"text-teal-600 hover:text-teal-900\">\n Delete\n </button>\n </td>\n </tr>\n \n <!-- More people... -->\n </tbody>\n </table>\n </div>\n </div>\n </div>\n </div>\n\n <modal v-if=\"showCreateDashboardModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"showCreateDashboardModal = false;\">&times;</div>\n \n <create-dashboard @close=\"insertNewDashboard\"></create-dashboard>\n </template>\n </modal>\n\n <modal v-if=\"showDeleteDashboardModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"showDeleteDashboardModal = null;\">&times;</div>\n <h2>Are you sure you want to delete this dashboard titled {{showDeleteDashboardModal.title}}?</h2>\n <div class=\"flex space-x-2\">\n <button class=\"px-4 py-2 bg-red-500 text-white rounded-md hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500\" @click=\"deleteDashboard(showDeleteDashboardModal)\">Yes, delete</button>\n <button class=\"px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-600\" @click=\"showDeleteDashboardModal=null;\">Cancel</button>\n </div>\n </template>\n </modal>\n</div>";
4392
+ module.exports = "<div class=\"dashboards max-w-5xl mx-auto mt-8\">\n <div v-if=\"status === 'loading'\" class=\"text-center mt-4\">\n <svg\n class=\"inline w-8 h-8 animate-spin text-ultramarine-600\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n class=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n stroke-width=\"4\"\n ></circle>\n <path\n class=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z\"\n ></path>\n </svg>\n </div>\n <div v-if=\"status === 'loaded' && dashboards.length === 0\">\n <div class=\"text-center\">\n <h3 class=\"mt-2 text-sm font-semibold text-gray-900\">No dashboards yet</h3>\n <p class=\"mt-1 text-sm text-gray-500\">Get started by creating a new dashboard.</p>\n <div class=\"mt-6\">\n <button type=\"button\" class=\"inline-flex items-center rounded-md bg-ultramarine-600 px-3 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\">\n <svg class=\"-ml-0.5 mr-1.5 h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z\" />\n </svg>\n New Dashboard\n </button>\n </div>\n </div>\n </div>\n\n <div class=\"px-4 sm:px-6 lg:px-8\">\n <div class=\"sm:flex sm:items-center\">\n <div class=\"sm:flex-auto\">\n <h1 class=\"text-base font-semibold leading-6 text-gray-900\">Dashboards</h1>\n </div>\n <div class=\"mt-4 sm:ml-16 sm:mt-0 sm:flex-none\">\n <button\n type=\"button\"\n @click=\"showCreateDashboardModal = true\"\n class=\"block rounded-md bg-ultramarine-600 px-3 py-2 text-center 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\">Create New Dashboard</button>\n </div>\n </div>\n <div class=\"mt-8 flow-root\">\n <div class=\"-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8\">\n <div class=\"inline-block min-w-full py-2 align-middle\">\n <table class=\"min-w-full divide-y divide-gray-300\">\n <thead>\n <tr>\n <th scope=\"col\" class=\"py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6 lg:pl-8\">Title</th>\n <th scope=\"col\" class=\"px-3 py-3.5 text-left text-sm font-semibold text-gray-900 w-[50%]\">Description</th>\n <th scope=\"col\" class=\"relative py-3.5 pl-3 pr-4 sm:pr-6 lg:pr-8\">\n </th>\n <th scope=\"col\" class=\"relative py-3.5 pl-3 pr-4 sm:pr-6 lg:pr-8\">\n </th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-gray-200 bg-white\">\n <tr v-for=\"dashboard in dashboards\">\n <td class=\"whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6 lg:pl-8\">{{dashboard.title}}</td>\n <td class=\"whitespace-nowrap px-3 py-4 text-sm text-gray-500 truncate w-[50%]\">{{dashboard.description}}</td>\n <td class=\"relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 lg:pr-8\">\n <router-link\n :to=\"'/dashboard/' + dashboard._id + '?edit=true'\"\n class=\"text-ultramarine-600 hover:text-ultramarine-900\">\n Edit\n </router-link>\n </td>\n <td class=\"relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 lg:pr-8\">\n <router-link\n :to=\"'/dashboard/' + dashboard._id\"\n class=\"text-ultramarine-600 hover:text-ultramarine-900\">\n View\n </router-link>\n </td>\n <td class=\"relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 lg:pr-8\">\n <button\n @click=\"showDeleteDashboardModal=dashboard\"\n class=\"text-ultramarine-600 hover:text-ultramarine-900\">\n Delete\n </button>\n </td>\n </tr>\n\n <!-- More people... -->\n </tbody>\n </table>\n </div>\n </div>\n </div>\n </div>\n\n <modal v-if=\"showCreateDashboardModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"showCreateDashboardModal = false;\">&times;</div>\n\n <create-dashboard @close=\"insertNewDashboard\"></create-dashboard>\n </template>\n </modal>\n\n <modal v-if=\"showDeleteDashboardModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"showDeleteDashboardModal = null;\">&times;</div>\n <h2>Are you sure you want to delete this dashboard titled {{showDeleteDashboardModal.title}}?</h2>\n <div class=\"flex space-x-2\">\n <button class=\"px-4 py-2 bg-red-500 text-white rounded-md hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500\" @click=\"deleteDashboard(showDeleteDashboardModal)\">Yes, delete</button>\n <button class=\"px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-600\" @click=\"showDeleteDashboardModal=null;\">Cancel</button>\n </div>\n </template>\n </modal>\n </div>\n</div>";
4349
4393
 
4350
4394
  /***/ }),
4351
4395
 
@@ -14837,7 +14881,7 @@ var bson = /*#__PURE__*/Object.freeze({
14837
14881
  /***/ ((module) => {
14838
14882
 
14839
14883
  "use strict";
14840
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.114","description":"A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.","homepage":"https://studio.mongoosejs.io/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"dependencies":{"archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"0.0.26","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vanillatoasts":"^1.6.0","vue":"3.x","webpack":"5.x"},"peerDependencies":{"bson":"^5.5.1 || 6.x","express":"4.x","mongoose":"7.x || 8.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongoose":"8.x"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js"}}');
14884
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.117","description":"A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.","homepage":"https://studio.mongoosejs.io/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"dependencies":{"archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"0.0.26","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vanillatoasts":"^1.6.0","vue":"3.x","webpack":"5.x"},"peerDependencies":{"bson":"^5.5.1 || 6.x","express":"4.x","mongoose":"7.x || 8.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongoose":"8.x"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js"}}');
14841
14885
 
14842
14886
  /***/ })
14843
14887
 
@@ -882,6 +882,10 @@ video {
882
882
  height: 32px;
883
883
  }
884
884
 
885
+ .h-\[40vh\] {
886
+ height: 40vh;
887
+ }
888
+
885
889
  .h-\[42px\] {
886
890
  height: 42px;
887
891
  }
@@ -898,6 +902,10 @@ video {
898
902
  max-height: 50vh;
899
903
  }
900
904
 
905
+ .min-h-0 {
906
+ min-height: 0px;
907
+ }
908
+
901
909
  .\!w-0 {
902
910
  width: 0px !important;
903
911
  }
@@ -970,6 +978,10 @@ video {
970
978
  max-width: 64rem;
971
979
  }
972
980
 
981
+ .max-w-\[calc\(100vw-3rem\)\] {
982
+ max-width: calc(100vw - 3rem);
983
+ }
984
+
973
985
  .max-w-\[calc\(100vw-4rem\)\] {
974
986
  max-width: calc(100vw - 4rem);
975
987
  }
@@ -1028,6 +1040,16 @@ video {
1028
1040
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
1029
1041
  }
1030
1042
 
1043
+ @keyframes spin {
1044
+ to {
1045
+ transform: rotate(360deg);
1046
+ }
1047
+ }
1048
+
1049
+ .animate-spin {
1050
+ animation: spin 1s linear infinite;
1051
+ }
1052
+
1031
1053
  .cursor-not-allowed {
1032
1054
  cursor: not-allowed;
1033
1055
  }
@@ -1784,6 +1806,11 @@ video {
1784
1806
  color: rgb(255 255 255 / var(--tw-text-opacity));
1785
1807
  }
1786
1808
 
1809
+ .text-ultramarine-600 {
1810
+ --tw-text-opacity: 1;
1811
+ color: rgb(24 35 255 / var(--tw-text-opacity));
1812
+ }
1813
+
1787
1814
  .accent-sky-600 {
1788
1815
  accent-color: #0284c7;
1789
1816
  }
@@ -1792,6 +1819,14 @@ video {
1792
1819
  opacity: 0.5;
1793
1820
  }
1794
1821
 
1822
+ .opacity-25 {
1823
+ opacity: 0.25;
1824
+ }
1825
+
1826
+ .opacity-75 {
1827
+ opacity: 0.75;
1828
+ }
1829
+
1795
1830
  .shadow {
1796
1831
  --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
1797
1832
  --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
@@ -2069,6 +2104,11 @@ video {
2069
2104
  color: rgb(10 87 87 / var(--tw-text-opacity));
2070
2105
  }
2071
2106
 
2107
+ .hover\:text-ultramarine-900:hover {
2108
+ --tw-text-opacity: 1;
2109
+ color: rgb(6 14 172 / var(--tw-text-opacity));
2110
+ }
2111
+
2072
2112
  .focus\:z-10:focus {
2073
2113
  z-index: 10;
2074
2114
  }
@@ -2346,10 +2386,6 @@ video {
2346
2386
  .md\:hidden {
2347
2387
  display: none;
2348
2388
  }
2349
-
2350
- .md\:p-3 {
2351
- padding: 0.75rem;
2352
- }
2353
2389
  }
2354
2390
 
2355
2391
  @media (min-width: 1024px) {
@@ -2370,6 +2406,10 @@ video {
2370
2406
  width: 16rem;
2371
2407
  }
2372
2408
 
2409
+ .lg\:max-w-\[calc\(100vw-15rem\)\] {
2410
+ max-width: calc(100vw - 15rem);
2411
+ }
2412
+
2373
2413
  .lg\:max-w-\[calc\(100vw-20rem\)\] {
2374
2414
  max-width: calc(100vw - 20rem);
2375
2415
  }
@@ -1,14 +1,14 @@
1
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)] lg:max-w-[calc(100vw-20rem)]"
3
+ class="min-w-0 max-w-[calc(100vw-3rem)] lg:max-w-[calc(100vw-15rem)]"
4
4
  :class="{'text-right': message.role === 'user'}">
5
5
 
6
- <div class="text-sm text-gray-900 md:p-3 rounded-md inline-block" :class="styleForMessage">
6
+ <div class="text-sm text-gray-900 rounded-md inline-block relative" :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>
10
10
  <div v-else-if="part.type === 'code'">
11
- <chat-message-script :message="message" :script="part.content" :language="part.language"></chat-message-script>
11
+ <chat-message-script :message="message" :script="part.content" :language="part.language" @copyMessage="copyMessage"></chat-message-script>
12
12
  </div>
13
13
  </div>
14
14
  </div>
@@ -2,6 +2,7 @@
2
2
 
3
3
  const api = require('../../api');
4
4
  const marked = require('marked').marked;
5
+ const vanillatoasts = require('vanillatoasts');
5
6
  const template = require('./chat-message.html');
6
7
 
7
8
  module.exports = app => app.component('chat-message', {
@@ -9,7 +10,7 @@ module.exports = app => app.component('chat-message', {
9
10
  props: ['message'],
10
11
  computed: {
11
12
  styleForMessage() {
12
- return this.message.role === 'user' ? 'bg-gray-100' : '';
13
+ return this.message.role === 'user' ? 'p-3 bg-gray-100' : 'py-3 pr-3';
13
14
  },
14
15
  contentSplitByScripts() {
15
16
  const content = this.message.content;
@@ -61,6 +62,37 @@ module.exports = app => app.component('chat-message', {
61
62
  });
62
63
  message.executionResult = chatMessage.executionResult;
63
64
  console.log(message);
65
+ },
66
+ async copyMessage() {
67
+ const parts = this.contentSplitByScripts;
68
+ let output = '';
69
+ for (const part of parts) {
70
+ if (part.type === 'text') {
71
+ output += part.content + '\n';
72
+ } else if (part.type === 'code') {
73
+ let result = this.message.executionResult?.output;
74
+ if (result != null && typeof result === 'object') {
75
+ result = JSON.stringify(result, null, 2);
76
+ }
77
+ if (result) {
78
+ let executionOutput = this.message.executionResult?.output;
79
+ if (executionOutput != null && typeof executionOutput === 'object') {
80
+ executionOutput = JSON.stringify(executionOutput, null, 2);
81
+ }
82
+ if (executionOutput) {
83
+ output += '```\n' + executionOutput + '\n```\n';
84
+ }
85
+ }
86
+ }
87
+ }
88
+ await navigator.clipboard.writeText(output.trim());
89
+ vanillatoasts.create({
90
+ title: 'Message output copied!',
91
+ type: 'success',
92
+ timeout: 3000,
93
+ icon: 'images/success.png',
94
+ positionClass: 'bottomRight'
95
+ });
64
96
  }
65
97
  }
66
98
  });
@@ -50,6 +50,11 @@
50
50
  @click="openCreateDashboardModal(); showDropdown = false">
51
51
  Create Dashboard
52
52
  </button>
53
+ <button
54
+ class="block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100"
55
+ @click="$emit('copyMessage'); showDropdown = false">
56
+ Copy Full Message
57
+ </button>
53
58
  </div>
54
59
  </div>
55
60
  </div>
@@ -7,6 +7,7 @@ const vanillatoasts = require('vanillatoasts');
7
7
  module.exports = app => app.component('chat-message-script', {
8
8
  template,
9
9
  props: ['message', 'script', 'language'],
10
+ emits: ['copyMessage'],
10
11
  data() {
11
12
  return {
12
13
  activeTab: 'code',
@@ -82,9 +83,13 @@ module.exports = app => app.component('chat-message-script', {
82
83
  this.$router.push('/dashboard/' + dashboard._id);
83
84
  },
84
85
  async copyOutput() {
85
- await navigator.clipboard.writeText(this.message.executionResult.output);
86
+ let output = this.message.executionResult.output;
87
+ if (output != null && typeof output === 'object') {
88
+ output = JSON.stringify(output, null, 2);
89
+ }
90
+ await navigator.clipboard.writeText(output);
86
91
  vanillatoasts.create({
87
- title: 'Text copied!',
92
+ title: 'Code output copied!',
88
93
  type: 'success',
89
94
  timeout: 3000,
90
95
  icon: 'images/success.png',
@@ -72,8 +72,9 @@
72
72
  <form @submit.prevent="sendMessage" :disabled="sendingMessage" class="flex gap-2 items-end justify-end">
73
73
  <textarea
74
74
  v-model="newMessage"
75
- placeholder="Ask something..."
75
+ :placeholder="sendingMessage ? 'Sending...' : 'Ask something...'"
76
76
  class="flex-1 border rounded px-4 py-2 resize-none overflow-y-auto"
77
+ :disabled="sendingMessage"
77
78
  rows="1"
78
79
  ref="messageInput"
79
80
  @input="adjustTextareaHeight"
@@ -21,6 +21,8 @@ module.exports = app => app.component('chat', {
21
21
  async sendMessage() {
22
22
  this.sendingMessage = true;
23
23
  try {
24
+ const content = this.newMessage;
25
+ this.newMessage = '';
24
26
  if (!this.chatThreadId) {
25
27
  const { chatThread } = await api.ChatThread.createChatThread();
26
28
  this.chatThreads.unshift(chatThread);
@@ -29,7 +31,7 @@ module.exports = app => app.component('chat', {
29
31
  }
30
32
 
31
33
  this.chatMessages.push({
32
- content: this.newMessage,
34
+ content,
33
35
  role: 'user'
34
36
  });
35
37
 
@@ -41,7 +43,7 @@ module.exports = app => app.component('chat', {
41
43
 
42
44
  const { chatMessages, chatThread } = await api.ChatThread.createChatMessage({
43
45
  chatThreadId: this.chatThreadId,
44
- content: this.newMessage
46
+ content
45
47
  });
46
48
  this.chatMessages.push(chatMessages[1]);
47
49
  for (const thread of this.chatThreads) {
@@ -41,18 +41,13 @@
41
41
  </div>
42
42
  <div v-else>
43
43
  <div class="relative">
44
- <button
45
- class="absolute top-2 right-2 px-2 py-1 text-xs bg-blue-500 text-white border-none rounded cursor-pointer hover:bg-blue-600 transition-colors flex items-center"
46
- @click="openDetailModal"
47
- aria-label="Expand dashboard result">
48
- <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
49
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5" />
50
- </svg>
51
- </button>
52
44
  <dashboard-result
53
45
  :key="dashboardResult.finishedEvaluatingAt"
54
46
  :result="dashboardResult.result"
55
- :finishedEvaluatingAt="dashboardResult.finishedEvaluatingAt">
47
+ :finishedEvaluatingAt="dashboardResult.finishedEvaluatingAt"
48
+ @fullscreen="showDetailModal = true"
49
+ class="h-[40vh]"
50
+ >
56
51
  </dashboard-result>
57
52
  </div>
58
53
  </div>
@@ -64,7 +59,8 @@
64
59
  :currentDescription="description"
65
60
  :currentTitle="title"
66
61
  @close="showEditor=false;"
67
- @update="updateCode"></edit-dashboard>
62
+ @update="updateCode"
63
+ @clearError="errorMessage = null"></edit-dashboard>
68
64
  </div>
69
65
  <div v-if="errorMessage" class="rounded-md bg-red-50 p-4 mt-4">
70
66
  <div class="flex">
@@ -98,7 +94,9 @@
98
94
  <dashboard-result
99
95
  v-if="dashboardResult"
100
96
  :result="dashboardResult.result"
101
- :finishedEvaluatingAt="dashboardResult.finishedEvaluatingAt">
97
+ :finishedEvaluatingAt="dashboardResult.finishedEvaluatingAt"
98
+ :fullscreen="true"
99
+ :responsive="true">
102
100
  </dashboard-result>
103
101
  </div>
104
102
  </template>
@@ -35,22 +35,22 @@ module.exports = app => app.component('dashboard', {
35
35
  },
36
36
  async evaluateDashboard() {
37
37
  this.status = 'evaluating';
38
+ this.errorMessage = null;
38
39
  try {
39
40
  const { dashboard, dashboardResult, error } = await api.Dashboard.getDashboard({ dashboardId: this.dashboardId, evaluate: true });
40
41
  this.dashboard = dashboard;
41
- if (error) {
42
- this.errorMessage = error.message;
43
- }
44
42
  this.code = this.dashboard.code;
45
43
  this.title = this.dashboard.title;
46
44
  this.description = this.dashboard.description ?? '';
47
- this.dashboardResults.unshift(dashboardResult);
45
+ if (dashboardResult) {
46
+ this.dashboardResults.unshift(dashboardResult);
47
+ }
48
+ if (error) {
49
+ this.errorMessage = error.message;
50
+ }
48
51
  } finally {
49
52
  this.status = 'loaded';
50
53
  }
51
- },
52
- openDetailModal() {
53
- this.showDetailModal = true;
54
54
  }
55
55
  },
56
56
  computed: {
@@ -6,6 +6,7 @@ const template = require('./edit-dashboard.html');
6
6
  module.exports = app => app.component('edit-dashboard', {
7
7
  template: template,
8
8
  props: ['dashboardId', 'code', 'currentDescription', 'currentTitle'],
9
+ emits: ['close', 'clearError'],
9
10
  data: function() {
10
11
  return {
11
12
  status: 'loaded',
@@ -19,6 +20,7 @@ module.exports = app => app.component('edit-dashboard', {
19
20
  this.$emit('close');
20
21
  },
21
22
  async updateCode() {
23
+ this.$emit('clearError');
22
24
  this.status = 'loading';
23
25
  try {
24
26
  const { doc, result, error } = await api.Dashboard.updateDashboard({
@@ -1,5 +1,5 @@
1
1
  <div :class="responsive ? 'h-full' : ''">
2
- <div v-if="header" class="border-b border-gray-100 px-2 pb-2 flex items-center">
2
+ <div v-if="header && !fullscreen" class="border-b border-gray-100 px-2 pb-2 flex items-center gap-1">
3
3
  <div class="text-xl font-bold">{{header}}</div>
4
4
  <button
5
5
  class="ml-auto px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors"
@@ -7,16 +7,32 @@
7
7
  title="Export PNG">
8
8
  <svg xmlns="http://www.w3.org/2000/svg" style="height: 1.5em;" viewBox="0 -960 960 960" fill="currentColor"><path d="M280-280h400v-80H280v80Zm200-120 160-160-56-56-64 62v-166h-80v166l-64-62-56 56 160 160Zm0 320q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
9
9
  </button>
10
+ <button
11
+ class="px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors flex items-center"
12
+ @click="$emit('fullscreen')"
13
+ aria-label="Expand dashboard result">
14
+ <svg xmlns="http://www.w3.org/2000/svg" style="height: 1.5em" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
15
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5" />
16
+ </svg>
17
+ </button>
10
18
  </div>
11
- <div v-else class="border-b border-gray-100 px-2 pb-2 text-right">
19
+ <div v-else-if="!fullscreen" class="pt-1 border-b border-gray-100 px-2 pb-2 text-right flex items-center justify-end gap-1 w-full">
12
20
  <button
13
- class="mt-1 px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors"
21
+ class="px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors"
14
22
  @click="exportPNG"
15
23
  title="Export PNG">
16
24
  <svg xmlns="http://www.w3.org/2000/svg" style="height: 1.5em;" viewBox="0 -960 960 960" fill="currentColor"><path d="M280-280h400v-80H280v80Zm200-120 160-160-56-56-64 62v-166h-80v166l-64-62-56 56 160 160Zm0 320q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
17
25
  </button>
26
+ <button
27
+ class="px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors flex items-center"
28
+ @click="$emit('fullscreen')"
29
+ aria-label="Expand dashboard result">
30
+ <svg xmlns="http://www.w3.org/2000/svg" style="height: 1.5em" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
31
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5" />
32
+ </svg>
33
+ </button>
18
34
  </div>
19
- <div class="text-xl" :class="responsive ? 'h-full' : ''">
20
- <canvas ref="chart"></canvas>
35
+ <div :class="responsive ? 'relative h-full min-h-0' : ''">
36
+ <canvas ref="chart" class="block w-full h-full"></canvas>
21
37
  </div>
22
38
  </div>
@@ -4,9 +4,11 @@ const template = require('./dashboard-chart.html');
4
4
 
5
5
  module.exports = app => app.component('dashboard-chart', {
6
6
  template: template,
7
- props: ['value', 'responsive'],
7
+ props: ['value', 'fullscreen'],
8
+ emits: ['fullscreen'],
8
9
  data: () => ({
9
- chart: null
10
+ chart: null,
11
+ showDetailModal: false
10
12
  }),
11
13
  mounted() {
12
14
  const ctx = this.$refs.chart.getContext('2d');
@@ -12,10 +12,12 @@
12
12
  <component
13
13
  class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl"
14
14
  :is="getComponentForValue(result)"
15
- :value="result">
15
+ :value="result"
16
+ :fullscreen="fullscreen"
17
+ @fullscreen="$emit('fullscreen')">
16
18
  </component>
17
19
  </div>
18
- <div class="text-right text-sm text-gray-700 mt-1" v-if="finishedEvaluatingAt">
20
+ <div class="text-right text-sm text-gray-700 mt-1" v-if="finishedEvaluatingAt && !fullscreen">
19
21
  Last Evaluated: {{ format.isoToLongDateTime(finishedEvaluatingAt) }}
20
22
  </div>
21
23
  </div>
@@ -7,7 +7,8 @@ const template = require('./dashboard-result.html');
7
7
 
8
8
  module.exports = app => app.component('dashboard-result', {
9
9
  template: template,
10
- props: ['result', 'finishedEvaluatingAt'],
10
+ props: ['result', 'finishedEvaluatingAt', 'fullscreen'],
11
+ emits: ['fullscreen'],
11
12
  mounted: async function() {
12
13
  },
13
14
  methods: {
@@ -1,97 +1,119 @@
1
1
  <div class="dashboards max-w-5xl mx-auto mt-8">
2
- <div v-if="status === 'loaded' && dashboards.length === 0">
3
- <div class="text-center">
4
- <h3 class="mt-2 text-sm font-semibold text-gray-900">No dashboards yet</h3>
5
- <p class="mt-1 text-sm text-gray-500">Get started by creating a new dashboard.</p>
6
- <div class="mt-6">
7
- <button type="button" class="inline-flex items-center rounded-md bg-teal-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600">
8
- <svg class="-ml-0.5 mr-1.5 h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
9
- <path d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z" />
10
- </svg>
11
- New Dashboard
12
- </button>
2
+ <div v-if="status === 'loading'" class="text-center mt-4">
3
+ <svg
4
+ class="inline w-8 h-8 animate-spin text-ultramarine-600"
5
+ xmlns="http://www.w3.org/2000/svg"
6
+ fill="none"
7
+ viewBox="0 0 24 24"
8
+ >
9
+ <circle
10
+ class="opacity-25"
11
+ cx="12"
12
+ cy="12"
13
+ r="10"
14
+ stroke="currentColor"
15
+ stroke-width="4"
16
+ ></circle>
17
+ <path
18
+ class="opacity-75"
19
+ fill="currentColor"
20
+ d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"
21
+ ></path>
22
+ </svg>
23
+ </div>
24
+ <div v-if="status === 'loaded' && dashboards.length === 0">
25
+ <div class="text-center">
26
+ <h3 class="mt-2 text-sm font-semibold text-gray-900">No dashboards yet</h3>
27
+ <p class="mt-1 text-sm text-gray-500">Get started by creating a new dashboard.</p>
28
+ <div class="mt-6">
29
+ <button type="button" class="inline-flex items-center rounded-md bg-ultramarine-600 px-3 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">
30
+ <svg class="-ml-0.5 mr-1.5 h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
31
+ <path d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z" />
32
+ </svg>
33
+ New Dashboard
34
+ </button>
35
+ </div>
13
36
  </div>
14
37
  </div>
15
- </div>
16
-
17
38
 
18
- <div class="px-4 sm:px-6 lg:px-8">
19
- <div class="sm:flex sm:items-center">
20
- <div class="sm:flex-auto">
21
- <h1 class="text-base font-semibold leading-6 text-gray-900">Dashboards</h1>
22
- </div>
23
- <div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
24
- <button
25
- type="button"
26
- @click="showCreateDashboardModal = true"
27
- class="block rounded-md bg-teal-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600">Create New Dashboard</button>
39
+ <div class="px-4 sm:px-6 lg:px-8">
40
+ <div class="sm:flex sm:items-center">
41
+ <div class="sm:flex-auto">
42
+ <h1 class="text-base font-semibold leading-6 text-gray-900">Dashboards</h1>
43
+ </div>
44
+ <div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
45
+ <button
46
+ type="button"
47
+ @click="showCreateDashboardModal = true"
48
+ class="block rounded-md bg-ultramarine-600 px-3 py-2 text-center 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">Create New Dashboard</button>
49
+ </div>
28
50
  </div>
29
- </div>
30
- <div class="mt-8 flow-root">
31
- <div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
32
- <div class="inline-block min-w-full py-2 align-middle">
33
- <table class="min-w-full divide-y divide-gray-300">
34
- <thead>
35
- <tr>
36
- <th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6 lg:pl-8">Title</th>
37
- <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 w-[50%]">Description</th>
38
- <th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6 lg:pr-8">
39
- </th>
40
- <th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6 lg:pr-8">
41
- </th>
42
- </tr>
43
- </thead>
44
- <tbody class="divide-y divide-gray-200 bg-white">
45
- <tr v-for="dashboard in dashboards">
46
- <td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6 lg:pl-8">{{dashboard.title}}</td>
47
- <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 truncate w-[50%]">{{dashboard.description}}</td>
48
- <td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 lg:pr-8">
49
- <router-link
50
- :to="'/dashboard/' + dashboard._id + '?edit=true'"
51
- class="text-teal-600 hover:text-teal-900">
52
- Edit
53
- </router-link>
54
- </td>
55
- <td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 lg:pr-8">
56
- <router-link
57
- :to="'/dashboard/' + dashboard._id"
58
- class="text-teal-600 hover:text-teal-900">
59
- View
60
- </router-link>
61
- </td>
62
- <td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 lg:pr-8">
63
- <button
64
- @click="showDeleteDashboardModal=dashboard"
65
- class="text-teal-600 hover:text-teal-900">
66
- Delete
67
- </button>
68
- </td>
69
- </tr>
70
-
71
- <!-- More people... -->
72
- </tbody>
73
- </table>
51
+ <div class="mt-8 flow-root">
52
+ <div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
53
+ <div class="inline-block min-w-full py-2 align-middle">
54
+ <table class="min-w-full divide-y divide-gray-300">
55
+ <thead>
56
+ <tr>
57
+ <th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6 lg:pl-8">Title</th>
58
+ <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 w-[50%]">Description</th>
59
+ <th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6 lg:pr-8">
60
+ </th>
61
+ <th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6 lg:pr-8">
62
+ </th>
63
+ </tr>
64
+ </thead>
65
+ <tbody class="divide-y divide-gray-200 bg-white">
66
+ <tr v-for="dashboard in dashboards">
67
+ <td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6 lg:pl-8">{{dashboard.title}}</td>
68
+ <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 truncate w-[50%]">{{dashboard.description}}</td>
69
+ <td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 lg:pr-8">
70
+ <router-link
71
+ :to="'/dashboard/' + dashboard._id + '?edit=true'"
72
+ class="text-ultramarine-600 hover:text-ultramarine-900">
73
+ Edit
74
+ </router-link>
75
+ </td>
76
+ <td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 lg:pr-8">
77
+ <router-link
78
+ :to="'/dashboard/' + dashboard._id"
79
+ class="text-ultramarine-600 hover:text-ultramarine-900">
80
+ View
81
+ </router-link>
82
+ </td>
83
+ <td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 lg:pr-8">
84
+ <button
85
+ @click="showDeleteDashboardModal=dashboard"
86
+ class="text-ultramarine-600 hover:text-ultramarine-900">
87
+ Delete
88
+ </button>
89
+ </td>
90
+ </tr>
91
+
92
+ <!-- More people... -->
93
+ </tbody>
94
+ </table>
95
+ </div>
74
96
  </div>
75
97
  </div>
76
98
  </div>
77
- </div>
78
99
 
79
- <modal v-if="showCreateDashboardModal">
80
- <template v-slot:body>
81
- <div class="modal-exit" @click="showCreateDashboardModal = false;">&times;</div>
82
-
83
- <create-dashboard @close="insertNewDashboard"></create-dashboard>
84
- </template>
85
- </modal>
100
+ <modal v-if="showCreateDashboardModal">
101
+ <template v-slot:body>
102
+ <div class="modal-exit" @click="showCreateDashboardModal = false;">&times;</div>
86
103
 
87
- <modal v-if="showDeleteDashboardModal">
88
- <template v-slot:body>
89
- <div class="modal-exit" @click="showDeleteDashboardModal = null;">&times;</div>
90
- <h2>Are you sure you want to delete this dashboard titled {{showDeleteDashboardModal.title}}?</h2>
91
- <div class="flex space-x-2">
92
- <button class="px-4 py-2 bg-red-500 text-white rounded-md hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500" @click="deleteDashboard(showDeleteDashboardModal)">Yes, delete</button>
93
- <button class="px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-600" @click="showDeleteDashboardModal=null;">Cancel</button>
94
- </div>
95
- </template>
96
- </modal>
104
+ <create-dashboard @close="insertNewDashboard"></create-dashboard>
105
+ </template>
106
+ </modal>
107
+
108
+ <modal v-if="showDeleteDashboardModal">
109
+ <template v-slot:body>
110
+ <div class="modal-exit" @click="showDeleteDashboardModal = null;">&times;</div>
111
+ <h2>Are you sure you want to delete this dashboard titled {{showDeleteDashboardModal.title}}?</h2>
112
+ <div class="flex space-x-2">
113
+ <button class="px-4 py-2 bg-red-500 text-white rounded-md hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500" @click="deleteDashboard(showDeleteDashboardModal)">Yes, delete</button>
114
+ <button class="px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-600" @click="showDeleteDashboardModal=null;">Cancel</button>
115
+ </div>
116
+ </template>
117
+ </modal>
118
+ </div>
97
119
  </div>
@@ -194,13 +194,13 @@ module.exports = app => app.component('models', {
194
194
  this.query.search = this.searchText;
195
195
  const query = this.query;
196
196
  const newUrl = this.$router.resolve({ query }).href;
197
- window.history.pushState(null, '', newUrl);
197
+ this.$router.push({ query });
198
198
  } else {
199
199
  this.filter = {};
200
200
  delete this.query.search;
201
201
  const query = this.query;
202
202
  const newUrl = this.$router.resolve({ query }).href;
203
- window.history.pushState(null, '', newUrl);
203
+ this.$router.push({ query });
204
204
  }
205
205
  this.documents = [];
206
206
  this.status = 'loading';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.0.115",
3
+ "version": "0.0.117",
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": {