@mongoosejs/studio 0.0.110 → 0.0.112

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -32,6 +32,8 @@ If you have a Mongoose Studio Pro API key, you can set it as follows:
32
32
 
33
33
  ```javascript
34
34
  const opts = process.env.MONGOOSE_STUDIO_API_KEY ? { apiKey: process.env.MONGOOSE_STUDIO_API_KEY } : {};
35
+ // Optionally specify which ChatGPT model to use for chat messages
36
+ opts.model = 'gpt-4o-mini';
35
37
 
36
38
  // Mount Mongoose Studio on '/studio'
37
39
  app.use('/studio', await studio('/studio/api', mongoose, opts));
@@ -48,7 +50,9 @@ const { execSync } = require('child_process');
48
50
 
49
51
  // Sign up for Mongoose Studio Pro to get an API key, or omit `apiKey` for local dev.
50
52
  const opts = {
51
- apiKey: process.env.MONGOOSE_STUDIO_API_KEY
53
+ apiKey: process.env.MONGOOSE_STUDIO_API_KEY,
54
+ // Optionally specify which ChatGPT model to use for chat messages
55
+ model: 'gpt-4o-mini'
52
56
  };
53
57
  console.log('Creating Mongoose studio', opts);
54
58
  require('@mongoosejs/studio/frontend')(`/.netlify/functions/studio`, true, opts).then(() => {
@@ -65,7 +69,8 @@ require('@mongoosejs/studio/frontend')(`/.netlify/functions/studio`, true, opts)
65
69
  const mongoose = require('mongoose');
66
70
 
67
71
  const handler = require('@mongoosejs/studio/backend/netlify')({
68
- apiKey: process.env.MONGOOSE_STUDIO_API_KEY
72
+ apiKey: process.env.MONGOOSE_STUDIO_API_KEY,
73
+ model: 'gpt-4o-mini'
69
74
  }).handler;
70
75
 
71
76
  let conn = null;
@@ -61,6 +61,8 @@ module.exports = ({ db, studioConnection, options }) => async function createCha
61
61
  });
62
62
  }
63
63
 
64
+ const modelDescriptions = getModelDescriptions(db);
65
+
64
66
  // Create the chat message and get OpenAI response in parallel
65
67
  const chatMessages = await Promise.all([
66
68
  ChatMessage.create({
@@ -70,7 +72,7 @@ module.exports = ({ db, studioConnection, options }) => async function createCha
70
72
  script,
71
73
  executionResult: null
72
74
  }),
73
- createChatMessageCore(llmMessages, getModelDescriptions(db), authorization).then(res => {
75
+ createChatMessageCore(llmMessages, modelDescriptions, options?.model, authorization).then(res => {
74
76
  const content = res.response;
75
77
  return ChatMessage.create({
76
78
  chatThreadId,
@@ -106,7 +108,7 @@ async function summarizeChatThread(messages, authorization) {
106
108
  return await response.json();
107
109
  }
108
110
 
109
- async function createChatMessageCore(messages, modelDescriptions, authorization) {
111
+ async function createChatMessageCore(messages, modelDescriptions, model, authorization) {
110
112
  const headers = { 'Content-Type': 'application/json' };
111
113
  if (authorization) {
112
114
  headers.Authorization = authorization;
@@ -116,7 +118,8 @@ async function createChatMessageCore(messages, modelDescriptions, authorization)
116
118
  headers,
117
119
  body: JSON.stringify({
118
120
  messages,
119
- modelDescriptions
121
+ modelDescriptions,
122
+ model
120
123
  })
121
124
  }).then(response => {
122
125
  if (response.status < 200 || response.status >= 400) {
@@ -1,9 +1,29 @@
1
1
  'use strict';
2
2
 
3
+ const formatRef = schemaType => (schemaType.options?.ref ? ' (ref: ' + schemaType.options.ref + ')' : '');
4
+
5
+ const formatNestedSchema = schemaType => {
6
+ const nestedPaths = Object.entries(schemaType.schema.paths).map(
7
+ ([path, nestedSchemaType]) => formatSchemaPath(path, nestedSchemaType)
8
+ );
9
+ return `\n ${nestedPaths.join('\n ')}`;
10
+ };
11
+
12
+ const formatSchemaTypeInstance = schemaType => {
13
+ if (schemaType.instance === 'Array') {
14
+ const itemType = schemaType.getEmbeddedSchemaType().instance;
15
+ return itemType === 'DocumentArrayElement' ? 'Subdocument[]' : `${itemType}[]`;
16
+ }
17
+ return schemaType.instance;
18
+ };
19
+
20
+ const formatSchemaPath = (path, schemaType) => `- ${path}: ${formatSchemaTypeInstance(schemaType)}` +
21
+ formatRef(schemaType) +
22
+ (schemaType.schema ? formatNestedSchema(schemaType) : '');
23
+
3
24
  const listModelPaths = Model => [
4
25
  ...Object.entries(Model.schema.paths).map(
5
- ([path, schemaType]) => `- ${path}: ${schemaType.instance}`
6
- + (schemaType.options?.ref ? ' (ref: ' + schemaType.options.ref + ')' : '')
26
+ ([path, schemaType]) => formatSchemaPath(path, schemaType)
7
27
  ),
8
28
  ...Object.entries(Model.schema.virtuals).filter(([path, virtual]) => virtual.options?.ref).map(
9
29
  ([path, virtual]) => `- ${path}: Virtual (ref: ${virtual.options.ref})`
@@ -926,6 +926,50 @@ module.exports = app => app.component('dashboard-document', {
926
926
  });
927
927
 
928
928
 
929
+ /***/ }),
930
+
931
+ /***/ "./frontend/src/dashboard-result/dashboard-map/dashboard-map.js":
932
+ /*!**********************************************************************!*\
933
+ !*** ./frontend/src/dashboard-result/dashboard-map/dashboard-map.js ***!
934
+ \**********************************************************************/
935
+ /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
936
+
937
+ "use strict";
938
+ /* global L */
939
+
940
+
941
+ const template = __webpack_require__(/*! ./dashboard-map.html */ "./frontend/src/dashboard-result/dashboard-map/dashboard-map.html");
942
+
943
+ module.exports = app => app.component('dashboard-map', {
944
+ template: template,
945
+ props: ['value'],
946
+ mounted() {
947
+ const fc = this.value.$featureCollection.featureCollection || this.value.$featureCollection;
948
+ const map = L.map(this.$refs.map).setView([0, 0], 1);
949
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
950
+ attribution: '&copy; OpenStreetMap contributors'
951
+ }).addTo(map);
952
+ const layer = L.geoJSON(fc).addTo(map);
953
+
954
+ this.$nextTick(() => {
955
+ map.invalidateSize();
956
+ const bounds = layer.getBounds();
957
+ if (bounds.isValid()) {
958
+ map.fitBounds(bounds);
959
+ }
960
+ });
961
+ },
962
+ computed: {
963
+ header() {
964
+ if (this.value != null && this.value.$featureCollection.header) {
965
+ return this.value.$featureCollection.header;
966
+ }
967
+ return null;
968
+ }
969
+ }
970
+ });
971
+
972
+
929
973
  /***/ }),
930
974
 
931
975
  /***/ "./frontend/src/dashboard-result/dashboard-primitive/dashboard-primitive.js":
@@ -996,6 +1040,9 @@ module.exports = app => app.component('dashboard-result', {
996
1040
  if (value.$document) {
997
1041
  return 'dashboard-document';
998
1042
  }
1043
+ if (value.$featureCollection) {
1044
+ return 'dashboard-map';
1045
+ }
999
1046
  if (value.$text) {
1000
1047
  return 'dashboard-text';
1001
1048
  }
@@ -3303,6 +3350,9 @@ var map = {
3303
3350
  "./dashboard-result/dashboard-document/dashboard-document": "./frontend/src/dashboard-result/dashboard-document/dashboard-document.js",
3304
3351
  "./dashboard-result/dashboard-document/dashboard-document.html": "./frontend/src/dashboard-result/dashboard-document/dashboard-document.html",
3305
3352
  "./dashboard-result/dashboard-document/dashboard-document.js": "./frontend/src/dashboard-result/dashboard-document/dashboard-document.js",
3353
+ "./dashboard-result/dashboard-map/dashboard-map": "./frontend/src/dashboard-result/dashboard-map/dashboard-map.js",
3354
+ "./dashboard-result/dashboard-map/dashboard-map.html": "./frontend/src/dashboard-result/dashboard-map/dashboard-map.html",
3355
+ "./dashboard-result/dashboard-map/dashboard-map.js": "./frontend/src/dashboard-result/dashboard-map/dashboard-map.js",
3306
3356
  "./dashboard-result/dashboard-primitive/dashboard-primitive": "./frontend/src/dashboard-result/dashboard-primitive/dashboard-primitive.js",
3307
3357
  "./dashboard-result/dashboard-primitive/dashboard-primitive.html": "./frontend/src/dashboard-result/dashboard-primitive/dashboard-primitive.html",
3308
3358
  "./dashboard-result/dashboard-primitive/dashboard-primitive.js": "./frontend/src/dashboard-result/dashboard-primitive/dashboard-primitive.js",
@@ -4064,7 +4114,7 @@ module.exports = "<button v-bind=\"attrsToBind\" :disabled=\"isDisabled\" @click
4064
4114
  /***/ ((module) => {
4065
4115
 
4066
4116
  "use strict";
4067
- 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 <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";
4117
+ 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";
4068
4118
 
4069
4119
  /***/ }),
4070
4120
 
@@ -4167,6 +4217,17 @@ module.exports = "<div class=\"py-2\">\n <div v-if=\"header\" class=\"border-b
4167
4217
 
4168
4218
  /***/ }),
4169
4219
 
4220
+ /***/ "./frontend/src/dashboard-result/dashboard-map/dashboard-map.html":
4221
+ /*!************************************************************************!*\
4222
+ !*** ./frontend/src/dashboard-result/dashboard-map/dashboard-map.html ***!
4223
+ \************************************************************************/
4224
+ /***/ ((module) => {
4225
+
4226
+ "use strict";
4227
+ module.exports = "<div class=\"py-2\">\n <div v-if=\"header\" class=\"border-b border-gray-100 px-2 pb-2 text-xl font-bold\">\n {{header}}\n </div>\n <div class=\"text-xl\">\n <div ref=\"map\" class=\"w-full\" style=\"height: 300px;\"></div>\n </div>\n</div>\n";
4228
+
4229
+ /***/ }),
4230
+
4170
4231
  /***/ "./frontend/src/dashboard-result/dashboard-primitive/dashboard-primitive.html":
4171
4232
  /*!************************************************************************************!*\
4172
4233
  !*** ./frontend/src/dashboard-result/dashboard-primitive/dashboard-primitive.html ***!
@@ -14721,7 +14782,7 @@ var bson = /*#__PURE__*/Object.freeze({
14721
14782
  /***/ ((module) => {
14722
14783
 
14723
14784
  "use strict";
14724
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.110","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","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"}}');
14785
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.112","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"}}');
14725
14786
 
14726
14787
  /***/ })
14727
14788
 
@@ -11,11 +11,13 @@
11
11
  <link rel="stylesheet" href="tw.css">
12
12
  <link rel="stylesheet" href="vanillatoasts/vanillatoasts.css">
13
13
  <link rel="stylesheet" href="https://unpkg.com/codemirror@5.65.16/lib/codemirror.css">
14
+ <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
14
15
  <link rel="icon" href="images/logo.svg" type="image/svg+xml">
15
16
  <script src="config.js"></script>
16
17
  <script src="https://unpkg.com/vue@3.x"></script>
17
18
  <script src="https://unpkg.com/vue-router@4.0.10"></script>
18
19
  <script src="https://unpkg.com/chart.js@4.2.0/dist/chart.umd.js"></script>
20
+ <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
19
21
  <script src="https://unpkg.com/codemirror@5.65.16/lib/codemirror.js"></script>
20
22
  <script src="https://unpkg.com/codemirror@5.65.16/mode/javascript/javascript.js"></script>
21
23
  </head>
@@ -59,6 +59,7 @@
59
59
 
60
60
  <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'">
61
61
  <dashboard-chart v-if="message.executionResult?.output?.$chart" :value="message.executionResult?.output" />
62
+ <dashboard-map v-else-if="message.executionResult?.output?.$featureCollection" :value="message.executionResult?.output" />
62
63
  <pre v-else>{{ message.executionResult?.output || 'No output' }}</pre>
63
64
  </div>
64
65
 
@@ -0,0 +1,8 @@
1
+ <div class="py-2">
2
+ <div v-if="header" class="border-b border-gray-100 px-2 pb-2 text-xl font-bold">
3
+ {{header}}
4
+ </div>
5
+ <div class="text-xl">
6
+ <div ref="map" class="w-full" style="height: 300px;"></div>
7
+ </div>
8
+ </div>
@@ -0,0 +1,33 @@
1
+ /* global L */
2
+ 'use strict';
3
+
4
+ const template = require('./dashboard-map.html');
5
+
6
+ module.exports = app => app.component('dashboard-map', {
7
+ template: template,
8
+ props: ['value'],
9
+ mounted() {
10
+ const fc = this.value.$featureCollection.featureCollection || this.value.$featureCollection;
11
+ const map = L.map(this.$refs.map).setView([0, 0], 1);
12
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
13
+ attribution: '&copy; OpenStreetMap contributors'
14
+ }).addTo(map);
15
+ const layer = L.geoJSON(fc).addTo(map);
16
+
17
+ this.$nextTick(() => {
18
+ map.invalidateSize();
19
+ const bounds = layer.getBounds();
20
+ if (bounds.isValid()) {
21
+ map.fitBounds(bounds);
22
+ }
23
+ });
24
+ },
25
+ computed: {
26
+ header() {
27
+ if (this.value != null && this.value.$featureCollection.header) {
28
+ return this.value.$featureCollection.header;
29
+ }
30
+ return null;
31
+ }
32
+ }
33
+ });
@@ -24,6 +24,9 @@ module.exports = app => app.component('dashboard-result', {
24
24
  if (value.$document) {
25
25
  return 'dashboard-document';
26
26
  }
27
+ if (value.$featureCollection) {
28
+ return 'dashboard-map';
29
+ }
27
30
  if (value.$text) {
28
31
  return 'dashboard-text';
29
32
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.0.110",
3
+ "version": "0.0.112",
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": {
@@ -27,6 +27,7 @@
27
27
  "devDependencies": {
28
28
  "@masteringjs/eslint-config": "0.1.1",
29
29
  "axios": "1.2.2",
30
+ "dedent": "^1.6.0",
30
31
  "eslint": "9.30.0",
31
32
  "express": "4.x",
32
33
  "mocha": "10.2.0",