@mongoosejs/studio 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -128,6 +128,7 @@ var map = {
128
128
  "./list-default/list-default.css": "./frontend/src/list-default/list-default.css",
129
129
  "./list-default/list-default.html": "./frontend/src/list-default/list-default.html",
130
130
  "./list-default/list-default.js": "./frontend/src/list-default/list-default.js",
131
+ "./list-json/json-node.html": "./frontend/src/list-json/json-node.html",
131
132
  "./list-json/list-json": "./frontend/src/list-json/list-json.js",
132
133
  "./list-json/list-json.html": "./frontend/src/list-json/list-json.html",
133
134
  "./list-json/list-json.js": "./frontend/src/list-json/list-json.js",
@@ -1791,6 +1792,25 @@ module.exports = app => app.component('dashboard', {
1791
1792
  } finally {
1792
1793
  this.status = 'loaded';
1793
1794
  }
1795
+ },
1796
+ shouldEvaluateDashboard() {
1797
+ if (this.dashboardResults.length === 0) {
1798
+ return true;
1799
+ }
1800
+
1801
+ const finishedEvaluatingAt = this.dashboardResults[0].finishedEvaluatingAt;
1802
+ if (!finishedEvaluatingAt) {
1803
+ return true;
1804
+ }
1805
+
1806
+ const sixHoursAgo = Date.now() - 6 * 60 * 60 * 1000;
1807
+ const finishedAt = new Date(finishedEvaluatingAt).getTime();
1808
+
1809
+ if (Number.isNaN(finishedAt)) {
1810
+ return true;
1811
+ }
1812
+
1813
+ return finishedAt < sixHoursAgo;
1794
1814
  }
1795
1815
  },
1796
1816
  computed: {
@@ -1809,6 +1829,10 @@ module.exports = app => app.component('dashboard', {
1809
1829
  this.title = this.dashboard.title;
1810
1830
  this.description = this.dashboard.description ?? '';
1811
1831
  this.dashboardResults = dashboardResults;
1832
+ if (this.shouldEvaluateDashboard()) {
1833
+ await this.evaluateDashboard();
1834
+ return;
1835
+ }
1812
1836
  this.status = 'loaded';
1813
1837
  }
1814
1838
  });
@@ -3888,6 +3912,17 @@ module.exports = app => app.component('list-default', {
3888
3912
 
3889
3913
  /***/ }),
3890
3914
 
3915
+ /***/ "./frontend/src/list-json/json-node.html":
3916
+ /*!***********************************************!*\
3917
+ !*** ./frontend/src/list-json/json-node.html ***!
3918
+ \***********************************************/
3919
+ /***/ ((module) => {
3920
+
3921
+ "use strict";
3922
+ module.exports = "<div>\n <div class=\"flex items-baseline whitespace-pre\" :style=\"indentStyle\">\n <button\n v-if=\"showToggle\"\n type=\"button\"\n class=\"w-4 h-4 mr-1 inline-flex items-center justify-center leading-none text-gray-500 hover:text-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400 cursor-pointer\"\n @click.stop=\"handleToggle\"\n >\n {{ isCollapsedNode ? '+' : '-' }}\n </button>\n <span v-else class=\"w-4 h-4 mr-1 inline-flex items-center justify-center invisible flex-shrink-0\"></span>\n <template v-if=\"hasKey\">\n <span class=\"text-blue-600\">\"{{ nodeKey }}\"</span><span>: </span>\n </template>\n <template v-if=\"isComplex\">\n <template v-if=\"hasChildren\">\n <span>{{ openingBracket }}</span>\n <span v-if=\"isCollapsedNode\" class=\"mx-1\">…</span>\n <span v-if=\"isCollapsedNode\">{{ closingBracket }}{{ comma }}</span>\n </template>\n <template v-else>\n <span>{{ openingBracket }}{{ closingBracket }}{{ comma }}</span>\n </template>\n </template>\n <template v-else>\n <!--\n If value is a string and overflows its container (i.e. goes over one line), show an ellipsis.\n This is done via CSS ellipsis strategy.\n -->\n <span\n v-if=\"shouldShowReferenceLink\"\n class=\"inline-flex items-baseline group\"\n >\n <span\n :class=\"[...valueClasses, 'underline', 'decoration-dotted', 'underline-offset-2']\"\n :style=\"typeof value === 'string'\n ? {\n display: 'inline-block',\n maxWidth: '100%',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n verticalAlign: 'bottom'\n }\n : {}\"\n :title=\"typeof value === 'string' && $el && $el.scrollWidth > $el.clientWidth ? value : undefined\"\n >\n {{ formattedValue }}\n </span>\n <span>\n {{ comma }}\n </span>\n <a\n href=\"#\"\n class=\"ml-1 text-sm text-sky-700 opacity-0 group-hover:opacity-100 focus:opacity-100 transition-opacity\"\n @click.stop.prevent=\"goToReference(value)\"\n >\n View Document\n </a>\n </span>\n <span\n v-else\n :class=\"valueClasses\"\n :style=\"typeof value === 'string'\n ? {\n display: 'inline-block',\n maxWidth: '100%',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n verticalAlign: 'bottom'\n }\n : {}\"\n :title=\"typeof value === 'string' && $el && $el.scrollWidth > $el.clientWidth ? value : undefined\"\n >\n {{ formattedValue }}{{ comma }}\n </span>\n </template>\n </div>\n <template v-if=\"isComplex && hasChildren && !isCollapsedNode\">\n <json-node\n v-for=\"child in children\"\n :key=\"child.path\"\n :node-key=\"child.displayKey\"\n :value=\"child.value\"\n :level=\"level + 1\"\n :is-last=\"child.isLast\"\n :path=\"child.path\"\n :toggle-collapse=\"toggleCollapse\"\n :is-collapsed=\"isCollapsed\"\n :create-child-path=\"createChildPath\"\n :indent-size=\"indentSize\"\n :max-top-level-fields=\"maxTopLevelFields\"\n :top-level-expanded=\"topLevelExpanded\"\n :expand-top-level=\"expandTopLevel\"\n :references=\"references\"\n ></json-node>\n <div\n v-if=\"hasHiddenRootChildren\"\n class=\"flex items-baseline whitespace-pre\"\n :style=\"indentStyle\"\n >\n <span class=\"w-4 h-4 mr-1 inline-flex items-center justify-center invisible\"></span>\n <button\n type=\"button\"\n class=\"text-xs inline-flex items-center gap-1 ml-4 text-slate-500 hover:text-slate-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400\"\n :title=\"hiddenChildrenTooltip\"\n @click.stop=\"handleExpandTopLevel\"\n >\n <span aria-hidden=\"true\">{{hiddenChildrenLabel}}…</span>\n </button>\n </div>\n <div class=\"flex items-baseline whitespace-pre\" :style=\"indentStyle\">\n <span class=\"w-4 h-4 mr-1 inline-flex items-center justify-center invisible\"></span>\n <span>{{ closingBracket }}{{ comma }}</span>\n </div>\n </template>\n</div>\n";
3923
+
3924
+ /***/ }),
3925
+
3891
3926
  /***/ "./frontend/src/list-json/list-json.html":
3892
3927
  /*!***********************************************!*\
3893
3928
  !*** ./frontend/src/list-json/list-json.html ***!
@@ -3895,7 +3930,7 @@ module.exports = app => app.component('list-default', {
3895
3930
  /***/ ((module) => {
3896
3931
 
3897
3932
  "use strict";
3898
- module.exports = "<div class=\"tooltip w-full font-mono text-sm py-3 text-slate-800\">\n <div class=\"w-full\">\n <json-node\n :node-key=\"null\"\n :value=\"value\"\n :level=\"0\"\n :is-last=\"true\"\n path=\"root\"\n :toggle-collapse=\"toggleCollapse\"\n :is-collapsed=\"isPathCollapsed\"\n :create-child-path=\"createChildPath\"\n :indent-size=\"indentSize\"\n :max-top-level-fields=\"maxTopLevelFields\"\n :top-level-expanded=\"topLevelExpanded\"\n :expand-top-level=\"expandTopLevel\"\n ></json-node>\n </div>\n</div>\n";
3933
+ module.exports = "<div class=\"tooltip w-full font-mono text-sm py-3 text-slate-800\">\n <div class=\"w-full\">\n <json-node\n :node-key=\"null\"\n :value=\"value\"\n :level=\"0\"\n :is-last=\"true\"\n path=\"root\"\n :toggle-collapse=\"toggleCollapse\"\n :is-collapsed=\"isPathCollapsed\"\n :create-child-path=\"createChildPath\"\n :indent-size=\"indentSize\"\n :max-top-level-fields=\"maxTopLevelFields\"\n :top-level-expanded=\"topLevelExpanded\"\n :expand-top-level=\"expandTopLevel\"\n :references=\"references\"\n ></json-node>\n </div>\n</div>\n";
3899
3934
 
3900
3935
  /***/ }),
3901
3936
 
@@ -3910,97 +3945,19 @@ module.exports = "<div class=\"tooltip w-full font-mono text-sm py-3 text-slate-
3910
3945
 
3911
3946
  const template = __webpack_require__(/*! ./list-json.html */ "./frontend/src/list-json/list-json.html");
3912
3947
 
3913
- const JsonNodeTemplate = `
3914
- <div>
3915
- <div class="flex items-baseline whitespace-pre" :style="indentStyle">
3916
- <button
3917
- v-if="showToggle"
3918
- type="button"
3919
- class="w-4 h-4 mr-1 inline-flex items-center justify-center leading-none text-gray-500 hover:text-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400 cursor-pointer"
3920
- @click.stop="handleToggle"
3921
- >
3922
- {{ isCollapsedNode ? '+' : '-' }}
3923
- </button>
3924
- <span v-else class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible flex-shrink-0"></span>
3925
- <template v-if="hasKey">
3926
- <span class="text-blue-600">"{{ nodeKey }}"</span><span>: </span>
3927
- </template>
3928
- <template v-if="isComplex">
3929
- <template v-if="hasChildren">
3930
- <span>{{ openingBracket }}</span>
3931
- <span v-if="isCollapsedNode" class="mx-1">…</span>
3932
- <span v-if="isCollapsedNode">{{ closingBracket }}{{ comma }}</span>
3933
- </template>
3934
- <template v-else>
3935
- <span>{{ openingBracket }}{{ closingBracket }}{{ comma }}</span>
3936
- </template>
3937
- </template>
3938
- <template v-else>
3939
- <!--
3940
- If value is a string and overflows its container (i.e. goes over one line), show an ellipsis.
3941
- This is done via CSS ellipsis strategy.
3942
- -->
3943
- <span
3944
- :class="valueClasses"
3945
- :style="typeof value === 'string'
3946
- ? {
3947
- display: 'inline-block',
3948
- maxWidth: '100%',
3949
- overflow: 'hidden',
3950
- textOverflow: 'ellipsis',
3951
- whiteSpace: 'nowrap',
3952
- verticalAlign: 'bottom'
3953
- }
3954
- : {}"
3955
- :title="typeof value === 'string' && $el && $el.scrollWidth > $el.clientWidth ? value : undefined"
3956
- >
3957
- {{ formattedValue }}{{ comma }}
3958
- </span>
3959
- </template>
3960
- </div>
3961
- <template v-if="isComplex && hasChildren && !isCollapsedNode">
3962
- <json-node
3963
- v-for="child in children"
3964
- :key="child.path"
3965
- :node-key="child.displayKey"
3966
- :value="child.value"
3967
- :level="level + 1"
3968
- :is-last="child.isLast"
3969
- :path="child.path"
3970
- :toggle-collapse="toggleCollapse"
3971
- :is-collapsed="isCollapsed"
3972
- :create-child-path="createChildPath"
3973
- :indent-size="indentSize"
3974
- :max-top-level-fields="maxTopLevelFields"
3975
- :top-level-expanded="topLevelExpanded"
3976
- :expand-top-level="expandTopLevel"
3977
- ></json-node>
3978
- <div
3979
- v-if="hasHiddenRootChildren"
3980
- class="flex items-baseline whitespace-pre"
3981
- :style="indentStyle"
3982
- >
3983
- <span class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible"></span>
3984
- <button
3985
- type="button"
3986
- class="text-xs inline-flex items-center gap-1 ml-4 text-slate-500 hover:text-slate-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400"
3987
- :title="hiddenChildrenTooltip"
3988
- @click.stop="handleExpandTopLevel"
3989
- >
3990
- <span aria-hidden="true">{{hiddenChildrenLabel}}…</span>
3991
- </button>
3992
- </div>
3993
- <div class="flex items-baseline whitespace-pre" :style="indentStyle">
3994
- <span class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible"></span>
3995
- <span>{{ closingBracket }}{{ comma }}</span>
3996
- </div>
3997
- </template>
3998
- </div>
3999
- `;
3948
+ const JsonNodeTemplate = __webpack_require__(/*! ./json-node.html */ "./frontend/src/list-json/json-node.html");
4000
3949
 
4001
3950
  module.exports = app => app.component('list-json', {
4002
3951
  template: template,
4003
- props: ['value'],
3952
+ props: {
3953
+ value: {
3954
+ required: true
3955
+ },
3956
+ references: {
3957
+ type: Object,
3958
+ default: () => ({})
3959
+ }
3960
+ },
4004
3961
  data() {
4005
3962
  return {
4006
3963
  collapsedMap: {},
@@ -4104,6 +4061,10 @@ module.exports = app => app.component('list-json', {
4104
4061
  expandTopLevel: {
4105
4062
  type: Function,
4106
4063
  default: null
4064
+ },
4065
+ references: {
4066
+ type: Object,
4067
+ default: () => ({})
4107
4068
  }
4108
4069
  },
4109
4070
  computed: {
@@ -4242,6 +4203,24 @@ module.exports = app => app.component('list-json', {
4242
4203
  },
4243
4204
  hiddenChildrenTooltip() {
4244
4205
  return this.hiddenChildrenLabel;
4206
+ },
4207
+ normalizedPath() {
4208
+ if (typeof this.path !== 'string') {
4209
+ return '';
4210
+ }
4211
+ return this.path
4212
+ .replace(/^root\.?/, '')
4213
+ .replace(/\[\d+\]/g, '')
4214
+ .replace(/^\./, '');
4215
+ },
4216
+ referenceModel() {
4217
+ if (!this.normalizedPath || !this.references) {
4218
+ return null;
4219
+ }
4220
+ return this.references[this.normalizedPath] || null;
4221
+ },
4222
+ shouldShowReferenceLink() {
4223
+ return Boolean(this.referenceModel) && typeof this.value === 'string';
4245
4224
  }
4246
4225
  },
4247
4226
  methods: {
@@ -4266,6 +4245,12 @@ module.exports = app => app.component('list-json', {
4266
4245
  if (this.isRoot && typeof this.expandTopLevel === 'function') {
4267
4246
  this.expandTopLevel();
4268
4247
  }
4248
+ },
4249
+ goToReference(id) {
4250
+ if (!this.referenceModel) {
4251
+ return;
4252
+ }
4253
+ this.$router.push({ path: `/model/${this.referenceModel}/document/${id}` });
4269
4254
  }
4270
4255
  }
4271
4256
  }
@@ -4557,7 +4542,7 @@ module.exports = ".models {\n position: relative;\n display: flex;\n flex-dir
4557
4542
  /***/ ((module) => {
4558
4543
 
4559
4544
  "use strict";
4560
- module.exports = "<div class=\"models 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 <aside class=\"bg-white border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-48 fixed lg:relative shrink-0\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-48' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-48 overflow-x-hidden\">\n <div class=\"p-4 font-bold text-lg\">Models</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 <nav class=\"flex flex-1 flex-col\">\n <ul role=\"list\" class=\"flex flex-1 flex-col gap-y-7\">\n <li>\n <ul role=\"list\">\n <li v-for=\"model in models\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"block truncate rounded-md py-2 pr-2 pl-2 text-sm font-semibold text-gray-700\"\n :class=\"model === currentModel ? 'bg-ultramarine-100 font-bold' : 'hover:bg-ultramarine-100'\">\n {{model}}\n </router-link>\n </li>\n </ul>\n </li>\n </ul>\n <div v-if=\"models.length === 0 && status === 'loaded'\" class=\"p-2 bg-red-100\">\n No models found\n </div>\n </nav>\n </aside>\n <div class=\"documents\" ref=\"documentsList\">\n <div class=\"relative h-[42px] z-10\">\n <div class=\"documents-menu\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <form @submit.prevent=\"search\" class=\"relative flex-grow m-0\">\n <input ref=\"searchInput\" class=\"w-full font-mono rounded-md p-1 border border-gray-300 outline-gray-300 text-lg focus:ring-1 focus:ring-ultramarine-200 focus:ring-offset-0 focus:outline-none\" type=\"text\" placeholder=\"Filter\" v-model=\"searchText\" @click=\"initFilter\" @input=\"updateAutocomplete\" @keydown=\"handleKeyDown\" />\n <ul v-if=\"autocompleteSuggestions.length\" class=\"absolute z-[9999] bg-white border border-gray-300 rounded mt-1 w-full max-h-40 overflow-y-auto shadow\">\n <li v-for=\"(suggestion, index) in autocompleteSuggestions\" :key=\"suggestion\" class=\"px-2 py-1 cursor-pointer\" :class=\"{ 'bg-ultramarine-100': index === autocompleteIndex }\" @mousedown.prevent=\"applySuggestion(index)\">{{ suggestion }}</li>\n </ul>\n </form>\n <div>\n <span v-if=\"numDocuments == null\">Loading ...</span>\n <span v-else-if=\"typeof numDocuments === 'number'\">{{numDocuments === 1 ? numDocuments+ ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"shouldShowExportModal = true\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 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 Export\n </button>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{ 'bg-gray-500 ring-inset ring-2 ring-gray-300 hover:bg-gray-600': selectMultiple, 'bg-ultramarine-600 hover:bg-ultramarine-500' : !selectMultiple }\"\n class=\"rounded px-2 py-2 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n >\n {{ selectMultiple ? 'Cancel' : 'Select' }}\n </button>\n <button\n v-show=\"selectMultiple\"\n @click=\"shouldShowUpdateMultipleModal=true;\"\n type=\"button\"\n class=\"rounded bg-green-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\"\n >\n Update\n </button>\n <button\n @click=\"shouldShowDeleteMultipleModal=true;\"\n type=\"button\"\n v-show=\"selectMultiple\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500\"\n >\n Delete\n </button>\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 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 Indexes\n </button>\n <button\n @click=\"shouldShowCreateModal = true;\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 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 Create\n </button>\n <button\n @click=\"openFieldSelection\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 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 Fields\n </button>\n <span class=\"isolate inline-flex rounded-md shadow-sm\">\n <button\n @click=\"setOutputType('table')\"\n type=\"button\"\n class=\"relative inline-flex items-center rounded-none rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/table.svg\">\n </button>\n <button\n @click=\"setOutputType('json')\"\n type=\"button\"\n class=\"relative -ml-px inline-flex items-center rounded-none rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n </span>\n </div>\n </div>\n </div>\n <div class=\"documents-container relative\">\n <div v-if=\"error\">\n <div class=\"bg-red-100 border border-red-400 text-red-700 px-4 py-3 relative m-4 rounded-md\" role=\"alert\">\n <span class=\"block font-bold\">Error</span>\n <span class=\"block\">{{ error }}</span>\n </div>\n </div>\n <table v-else-if=\"outputType === 'table'\">\n <thead>\n <th v-for=\"path in filteredPaths\" @click=\"clickFilter(path.path)\" class=\"cursor-pointer\">\n {{path.path}}\n <span class=\"path-type\">\n ({{(path.instance || 'unknown')}})\n </span>\n <span class=\"sort-arrow\" @click=\"sortDocs(1, path.path)\">{{sortBy[path.path] == 1 ? 'X' : '↑'}}</span>\n <span class=\"sort-arrow\" @click=\"sortDocs(-1, path.path)\">{{sortBy[path.path] == -1 ? 'X' : '↓'}}</span>\n </th>\n </thead>\n <tbody>\n <tr v-for=\"document in documents\" @click=\"handleDocumentClick(document, $event)\" :key=\"document._id\">\n <td v-for=\"schemaPath in filteredPaths\" :class=\"{ 'bg-blue-200': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }\">\n <component\n :is=\"getComponentForPath(schemaPath)\"\n :value=\"getValueForPath(document, schemaPath.path)\"\n :allude=\"getReferenceModel(schemaPath)\">\n </component>\n </td>\n </tr>\n </tbody>\n </table>\n <div v-else-if=\"outputType === 'json'\" class=\"flex flex-col space-y-6\">\n <div\n v-for=\"document in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentContainerClick(document, $event)\"\n :class=\"[\n 'group relative transition-colors',\n selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:bg-slate-100'\n ]\"\n >\n <button\n type=\"button\"\n class=\"absolute top-2 right-2 z-10 inline-flex items-center rounded bg-ultramarine-600 px-2 py-1 text-xs font-semibold text-white shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n @click.stop=\"openDocument(document)\"\n >\n Open this Document\n </button>\n <list-json :value=\"filterDocument(document)\">\n </list-json>\n </div>\n </div>\n <div v-if=\"status === 'loading'\" class=\"loader\">\n <img src=\"images/loader.gif\">\n </div>\n </div>\n </div>\n <modal v-if=\"shouldShowExportModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowExportModal = false\">&times;</div>\n <export-query-results\n :schemaPaths=\"schemaPaths\"\n :search-text=\"searchText\"\n :currentModel=\"currentModel\"\n @done=\"shouldShowExportModal = false\">\n </export-query-results>\n </template>\n </modal>\n <modal v-if=\"shouldShowIndexModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowIndexModal = false\">&times;</div>\n <div class=\"text-xl font-bold mb-2\">Indexes</div>\n <div v-for=\"index in mongoDBIndexes\" class=\"w-full flex items-center\">\n <div class=\"grow shrink text-left flex justify-between items-center\" v-if=\"index.name != '_id_'\">\n <div>\n <div class=\"font-bold\">{{ index.name }}</div>\n <div class=\"text-sm font-mono\">{{ JSON.stringify(index.key) }}</div>\n </div>\n <div>\n <async-button\n type=\"button\"\n @click=\"dropIndex(index.name)\"\n class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600 disabled:bg-gray-400 disabled:cursor-not-allowed\">\n Drop\n </async-button>\n </div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowFieldModal = false; selectedPaths = [...filteredPaths];\">&times;</div>\n <div v-for=\"(path, index) in schemaPaths\" :key=\"index\" class=\"w-5 flex items-center\">\n <input class=\"mt-0 h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-600 accent-sky-600\" type=\"checkbox\" :id=\"'path.path'+index\" @change=\"addOrRemove(path)\" :value=\"path.path\" :checked=\"isSelected(path.path)\" />\n <div class=\"ml-2 text-gray-700 grow shrink text-left\">\n <label :for=\"'path.path' + index\">{{path.path}}</label>\n </div>\n </div>\n <div class=\"mt-4 flex gap-2\">\n <button type=\"button\" @click=\"filterDocuments()\" class=\"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-teal-600\">Filter Selection</button>\n <button type=\"button\" @click=\"selectAll()\" class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">Select All</button>\n <button type=\"button\" @click=\"deselectAll()\" class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">Deselect All</button>\n <button type=\"button\" @click=\"resetDocuments()\" class=\"rounded-md bg-gray-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\" >Cancel</button>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCreateModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCreateModal = false;\">&times;</div>\n <create-document :currentModel=\"currentModel\" :paths=\"schemaPaths\" @close=\"closeCreationModal\"></create-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowUpdateMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowUpdateMultipleModal = false;\">&times;</div>\n <update-document :currentModel=\"currentModel\" :document=\"selectedDocuments\" :multiple=\"true\" @update=\"updateDocuments\" @close=\"shouldShowUpdateMultipleModal=false;\"></update-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteMultipleModal = false;\">&times;</div>\n <h2>Are you sure you want to delete {{selectedDocuments.length}} documents?</h2>\n <div>\n <list-json :value=\"selectedDocuments\"></list-json>\n </div>\n <div class=\"flex gap-4\">\n <async-button @click=\"deleteDocuments\" class=\"rounded bg-red-500 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">\n Confirm\n </async-button>\n <button @click=\"shouldShowDeleteMultipleModal = false;\" class=\"rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n</div>\n";
4545
+ module.exports = "<div class=\"models 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 <aside class=\"bg-white border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-48 fixed lg:relative shrink-0\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-48' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-48 overflow-x-hidden\">\n <div class=\"p-4 font-bold text-lg\">Models</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 <nav class=\"flex flex-1 flex-col\">\n <ul role=\"list\" class=\"flex flex-1 flex-col gap-y-7\">\n <li>\n <ul role=\"list\">\n <li v-for=\"model in models\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"block truncate rounded-md py-2 pr-2 pl-2 text-sm font-semibold text-gray-700\"\n :class=\"model === currentModel ? 'bg-ultramarine-100 font-bold' : 'hover:bg-ultramarine-100'\">\n {{model}}\n </router-link>\n </li>\n </ul>\n </li>\n </ul>\n <div v-if=\"models.length === 0 && status === 'loaded'\" class=\"p-2 bg-red-100\">\n No models found\n </div>\n </nav>\n </aside>\n <div class=\"documents\" ref=\"documentsList\">\n <div class=\"relative h-[42px] z-10\">\n <div class=\"documents-menu\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <form @submit.prevent=\"search\" class=\"relative flex-grow m-0\">\n <input ref=\"searchInput\" class=\"w-full font-mono rounded-md p-1 border border-gray-300 outline-gray-300 text-lg focus:ring-1 focus:ring-ultramarine-200 focus:ring-offset-0 focus:outline-none\" type=\"text\" placeholder=\"Filter\" v-model=\"searchText\" @click=\"initFilter\" @input=\"updateAutocomplete\" @keydown=\"handleKeyDown\" />\n <ul v-if=\"autocompleteSuggestions.length\" class=\"absolute z-[9999] bg-white border border-gray-300 rounded mt-1 w-full max-h-40 overflow-y-auto shadow\">\n <li v-for=\"(suggestion, index) in autocompleteSuggestions\" :key=\"suggestion\" class=\"px-2 py-1 cursor-pointer\" :class=\"{ 'bg-ultramarine-100': index === autocompleteIndex }\" @mousedown.prevent=\"applySuggestion(index)\">{{ suggestion }}</li>\n </ul>\n </form>\n <div>\n <span v-if=\"numDocuments == null\">Loading ...</span>\n <span v-else-if=\"typeof numDocuments === 'number'\">{{numDocuments === 1 ? numDocuments+ ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"shouldShowExportModal = true\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 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 Export\n </button>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{ 'bg-gray-500 ring-inset ring-2 ring-gray-300 hover:bg-gray-600': selectMultiple, 'bg-ultramarine-600 hover:bg-ultramarine-500' : !selectMultiple }\"\n class=\"rounded px-2 py-2 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n >\n {{ selectMultiple ? 'Cancel' : 'Select' }}\n </button>\n <button\n v-show=\"selectMultiple\"\n @click=\"shouldShowUpdateMultipleModal=true;\"\n type=\"button\"\n class=\"rounded bg-green-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\"\n >\n Update\n </button>\n <button\n @click=\"shouldShowDeleteMultipleModal=true;\"\n type=\"button\"\n v-show=\"selectMultiple\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500\"\n >\n Delete\n </button>\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 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 Indexes\n </button>\n <button\n @click=\"shouldShowCreateModal = true;\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 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 Create\n </button>\n <button\n @click=\"openFieldSelection\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 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 Fields\n </button>\n <span class=\"isolate inline-flex rounded-md shadow-sm\">\n <button\n @click=\"setOutputType('table')\"\n type=\"button\"\n class=\"relative inline-flex items-center rounded-none rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/table.svg\">\n </button>\n <button\n @click=\"setOutputType('json')\"\n type=\"button\"\n class=\"relative -ml-px inline-flex items-center rounded-none rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n </span>\n </div>\n </div>\n </div>\n <div class=\"documents-container relative\">\n <div v-if=\"error\">\n <div class=\"bg-red-100 border border-red-400 text-red-700 px-4 py-3 relative m-4 rounded-md\" role=\"alert\">\n <span class=\"block font-bold\">Error</span>\n <span class=\"block\">{{ error }}</span>\n </div>\n </div>\n <table v-else-if=\"outputType === 'table'\">\n <thead>\n <th v-for=\"path in filteredPaths\" @click=\"clickFilter(path.path)\" class=\"cursor-pointer\">\n {{path.path}}\n <span class=\"path-type\">\n ({{(path.instance || 'unknown')}})\n </span>\n <span class=\"sort-arrow\" @click=\"sortDocs(1, path.path)\">{{sortBy[path.path] == 1 ? 'X' : '↑'}}</span>\n <span class=\"sort-arrow\" @click=\"sortDocs(-1, path.path)\">{{sortBy[path.path] == -1 ? 'X' : '↓'}}</span>\n </th>\n </thead>\n <tbody>\n <tr v-for=\"document in documents\" @click=\"handleDocumentClick(document, $event)\" :key=\"document._id\">\n <td v-for=\"schemaPath in filteredPaths\" :class=\"{ 'bg-blue-200': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }\">\n <component\n :is=\"getComponentForPath(schemaPath)\"\n :value=\"getValueForPath(document, schemaPath.path)\"\n :allude=\"getReferenceModel(schemaPath)\">\n </component>\n </td>\n </tr>\n </tbody>\n </table>\n <div v-else-if=\"outputType === 'json'\" class=\"flex flex-col space-y-6\">\n <div\n v-for=\"document in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentContainerClick(document, $event)\"\n :class=\"[\n 'group relative transition-colors',\n selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:bg-slate-100'\n ]\"\n >\n <button\n type=\"button\"\n class=\"absolute top-2 right-2 z-10 inline-flex items-center rounded bg-ultramarine-600 px-2 py-1 text-xs font-semibold text-white shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n @click.stop=\"openDocument(document)\"\n >\n Open this Document\n </button>\n <list-json :value=\"filterDocument(document)\" :references=\"referenceMap\">\n </list-json>\n </div>\n </div>\n <div v-if=\"status === 'loading'\" class=\"loader\">\n <img src=\"images/loader.gif\">\n </div>\n </div>\n </div>\n <modal v-if=\"shouldShowExportModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowExportModal = false\">&times;</div>\n <export-query-results\n :schemaPaths=\"schemaPaths\"\n :search-text=\"searchText\"\n :currentModel=\"currentModel\"\n @done=\"shouldShowExportModal = false\">\n </export-query-results>\n </template>\n </modal>\n <modal v-if=\"shouldShowIndexModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowIndexModal = false\">&times;</div>\n <div class=\"text-xl font-bold mb-2\">Indexes</div>\n <div v-for=\"index in mongoDBIndexes\" class=\"w-full flex items-center\">\n <div class=\"grow shrink text-left flex justify-between items-center\" v-if=\"index.name != '_id_'\">\n <div>\n <div class=\"font-bold\">{{ index.name }}</div>\n <div class=\"text-sm font-mono\">{{ JSON.stringify(index.key) }}</div>\n </div>\n <div>\n <async-button\n type=\"button\"\n @click=\"dropIndex(index.name)\"\n class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600 disabled:bg-gray-400 disabled:cursor-not-allowed\">\n Drop\n </async-button>\n </div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowFieldModal = false; selectedPaths = [...filteredPaths];\">&times;</div>\n <div v-for=\"(path, index) in schemaPaths\" :key=\"index\" class=\"w-5 flex items-center\">\n <input class=\"mt-0 h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-600 accent-sky-600\" type=\"checkbox\" :id=\"'path.path'+index\" @change=\"addOrRemove(path)\" :value=\"path.path\" :checked=\"isSelected(path.path)\" />\n <div class=\"ml-2 text-gray-700 grow shrink text-left\">\n <label :for=\"'path.path' + index\">{{path.path}}</label>\n </div>\n </div>\n <div class=\"mt-4 flex gap-2\">\n <button type=\"button\" @click=\"filterDocuments()\" class=\"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-teal-600\">Filter Selection</button>\n <button type=\"button\" @click=\"selectAll()\" class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">Select All</button>\n <button type=\"button\" @click=\"deselectAll()\" class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">Deselect All</button>\n <button type=\"button\" @click=\"resetDocuments()\" class=\"rounded-md bg-gray-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\" >Cancel</button>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCreateModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCreateModal = false;\">&times;</div>\n <create-document :currentModel=\"currentModel\" :paths=\"schemaPaths\" @close=\"closeCreationModal\"></create-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowUpdateMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowUpdateMultipleModal = false;\">&times;</div>\n <update-document :currentModel=\"currentModel\" :document=\"selectedDocuments\" :multiple=\"true\" @update=\"updateDocuments\" @close=\"shouldShowUpdateMultipleModal=false;\"></update-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteMultipleModal = false;\">&times;</div>\n <h2>Are you sure you want to delete {{selectedDocuments.length}} documents?</h2>\n <div>\n <list-json :value=\"selectedDocuments\"></list-json>\n </div>\n <div class=\"flex gap-4\">\n <async-button @click=\"deleteDocuments\" class=\"rounded bg-red-500 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">\n Confirm\n </async-button>\n <button @click=\"shouldShowDeleteMultipleModal = false;\" class=\"rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n</div>\n";
4561
4546
 
4562
4547
  /***/ }),
4563
4548
 
@@ -4678,6 +4663,17 @@ module.exports = app => app.component('models', {
4678
4663
 
4679
4664
  await this.initSearchFromUrl();
4680
4665
  },
4666
+ computed: {
4667
+ referenceMap() {
4668
+ const map = {};
4669
+ for (const path of this.filteredPaths) {
4670
+ if (path?.ref) {
4671
+ map[path.path] = path.ref;
4672
+ }
4673
+ }
4674
+ return map;
4675
+ }
4676
+ },
4681
4677
  methods: {
4682
4678
  buildAutocompleteTrie() {
4683
4679
  this.autocompleteTrie = new Trie();
@@ -4908,9 +4904,10 @@ module.exports = app => app.component('models', {
4908
4904
  },
4909
4905
  filterDocument(doc) {
4910
4906
  const filteredDoc = {};
4911
- console.log(doc, this.filteredPaths);
4912
4907
  for (let i = 0; i < this.filteredPaths.length; i++) {
4913
- filteredDoc[this.filteredPaths[i].path] = doc[this.filteredPaths[i].path];
4908
+ const path = this.filteredPaths[i].path;
4909
+ const value = mpath.get(path, doc);
4910
+ mpath.set(path, value, filteredDoc);
4914
4911
  }
4915
4912
  return filteredDoc;
4916
4913
  },
@@ -16609,7 +16606,7 @@ module.exports = function stringToParts(str) {
16609
16606
  /***/ ((module) => {
16610
16607
 
16611
16608
  "use strict";
16612
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.1.4","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"},"license":"Apache-2.0","dependencies":{"archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.1.0","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"}}');
16609
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.1.6","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"},"license":"Apache-2.0","dependencies":{"archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","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 || ^9.0.0-0"},"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":"9.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"}}');
16613
16610
 
16614
16611
  /***/ })
16615
16612
 
@@ -2041,6 +2041,11 @@ video {
2041
2041
  color: rgb(2 132 199 / var(--tw-text-opacity));
2042
2042
  }
2043
2043
 
2044
+ .text-sky-700 {
2045
+ --tw-text-opacity: 1;
2046
+ color: rgb(3 105 161 / var(--tw-text-opacity));
2047
+ }
2048
+
2044
2049
  .text-sky-800 {
2045
2050
  --tw-text-opacity: 1;
2046
2051
  color: rgb(7 89 133 / var(--tw-text-opacity));
@@ -2086,6 +2091,18 @@ video {
2086
2091
  color: rgb(255 255 255 / var(--tw-text-opacity));
2087
2092
  }
2088
2093
 
2094
+ .underline {
2095
+ text-decoration-line: underline;
2096
+ }
2097
+
2098
+ .decoration-dotted {
2099
+ text-decoration-style: dotted;
2100
+ }
2101
+
2102
+ .underline-offset-2 {
2103
+ text-underline-offset: 2px;
2104
+ }
2105
+
2089
2106
  .accent-sky-600 {
2090
2107
  accent-color: #0284c7;
2091
2108
  }
@@ -2469,6 +2486,10 @@ video {
2469
2486
  border-color: transparent;
2470
2487
  }
2471
2488
 
2489
+ .focus\:opacity-100:focus {
2490
+ opacity: 1;
2491
+ }
2492
+
2472
2493
  .focus\:outline-none:focus {
2473
2494
  outline: 2px solid transparent;
2474
2495
  outline-offset: 2px;
@@ -44,6 +44,25 @@ module.exports = app => app.component('dashboard', {
44
44
  } finally {
45
45
  this.status = 'loaded';
46
46
  }
47
+ },
48
+ shouldEvaluateDashboard() {
49
+ if (this.dashboardResults.length === 0) {
50
+ return true;
51
+ }
52
+
53
+ const finishedEvaluatingAt = this.dashboardResults[0].finishedEvaluatingAt;
54
+ if (!finishedEvaluatingAt) {
55
+ return true;
56
+ }
57
+
58
+ const sixHoursAgo = Date.now() - 6 * 60 * 60 * 1000;
59
+ const finishedAt = new Date(finishedEvaluatingAt).getTime();
60
+
61
+ if (Number.isNaN(finishedAt)) {
62
+ return true;
63
+ }
64
+
65
+ return finishedAt < sixHoursAgo;
47
66
  }
48
67
  },
49
68
  computed: {
@@ -62,6 +81,10 @@ module.exports = app => app.component('dashboard', {
62
81
  this.title = this.dashboard.title;
63
82
  this.description = this.dashboard.description ?? '';
64
83
  this.dashboardResults = dashboardResults;
84
+ if (this.shouldEvaluateDashboard()) {
85
+ await this.evaluateDashboard();
86
+ return;
87
+ }
65
88
  this.status = 'loaded';
66
89
  }
67
90
  });
@@ -0,0 +1,118 @@
1
+ <div>
2
+ <div class="flex items-baseline whitespace-pre" :style="indentStyle">
3
+ <button
4
+ v-if="showToggle"
5
+ type="button"
6
+ class="w-4 h-4 mr-1 inline-flex items-center justify-center leading-none text-gray-500 hover:text-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400 cursor-pointer"
7
+ @click.stop="handleToggle"
8
+ >
9
+ {{ isCollapsedNode ? '+' : '-' }}
10
+ </button>
11
+ <span v-else class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible flex-shrink-0"></span>
12
+ <template v-if="hasKey">
13
+ <span class="text-blue-600">"{{ nodeKey }}"</span><span>: </span>
14
+ </template>
15
+ <template v-if="isComplex">
16
+ <template v-if="hasChildren">
17
+ <span>{{ openingBracket }}</span>
18
+ <span v-if="isCollapsedNode" class="mx-1">…</span>
19
+ <span v-if="isCollapsedNode">{{ closingBracket }}{{ comma }}</span>
20
+ </template>
21
+ <template v-else>
22
+ <span>{{ openingBracket }}{{ closingBracket }}{{ comma }}</span>
23
+ </template>
24
+ </template>
25
+ <template v-else>
26
+ <!--
27
+ If value is a string and overflows its container (i.e. goes over one line), show an ellipsis.
28
+ This is done via CSS ellipsis strategy.
29
+ -->
30
+ <span
31
+ v-if="shouldShowReferenceLink"
32
+ class="inline-flex items-baseline group"
33
+ >
34
+ <span
35
+ :class="[...valueClasses, 'underline', 'decoration-dotted', 'underline-offset-2']"
36
+ :style="typeof value === 'string'
37
+ ? {
38
+ display: 'inline-block',
39
+ maxWidth: '100%',
40
+ overflow: 'hidden',
41
+ textOverflow: 'ellipsis',
42
+ whiteSpace: 'nowrap',
43
+ verticalAlign: 'bottom'
44
+ }
45
+ : {}"
46
+ :title="typeof value === 'string' && $el && $el.scrollWidth > $el.clientWidth ? value : undefined"
47
+ >
48
+ {{ formattedValue }}
49
+ </span>
50
+ <span>
51
+ {{ comma }}
52
+ </span>
53
+ <a
54
+ href="#"
55
+ class="ml-1 text-sm text-sky-700 opacity-0 group-hover:opacity-100 focus:opacity-100 transition-opacity"
56
+ @click.stop.prevent="goToReference(value)"
57
+ >
58
+ View Document
59
+ </a>
60
+ </span>
61
+ <span
62
+ v-else
63
+ :class="valueClasses"
64
+ :style="typeof value === 'string'
65
+ ? {
66
+ display: 'inline-block',
67
+ maxWidth: '100%',
68
+ overflow: 'hidden',
69
+ textOverflow: 'ellipsis',
70
+ whiteSpace: 'nowrap',
71
+ verticalAlign: 'bottom'
72
+ }
73
+ : {}"
74
+ :title="typeof value === 'string' && $el && $el.scrollWidth > $el.clientWidth ? value : undefined"
75
+ >
76
+ {{ formattedValue }}{{ comma }}
77
+ </span>
78
+ </template>
79
+ </div>
80
+ <template v-if="isComplex && hasChildren && !isCollapsedNode">
81
+ <json-node
82
+ v-for="child in children"
83
+ :key="child.path"
84
+ :node-key="child.displayKey"
85
+ :value="child.value"
86
+ :level="level + 1"
87
+ :is-last="child.isLast"
88
+ :path="child.path"
89
+ :toggle-collapse="toggleCollapse"
90
+ :is-collapsed="isCollapsed"
91
+ :create-child-path="createChildPath"
92
+ :indent-size="indentSize"
93
+ :max-top-level-fields="maxTopLevelFields"
94
+ :top-level-expanded="topLevelExpanded"
95
+ :expand-top-level="expandTopLevel"
96
+ :references="references"
97
+ ></json-node>
98
+ <div
99
+ v-if="hasHiddenRootChildren"
100
+ class="flex items-baseline whitespace-pre"
101
+ :style="indentStyle"
102
+ >
103
+ <span class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible"></span>
104
+ <button
105
+ type="button"
106
+ class="text-xs inline-flex items-center gap-1 ml-4 text-slate-500 hover:text-slate-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400"
107
+ :title="hiddenChildrenTooltip"
108
+ @click.stop="handleExpandTopLevel"
109
+ >
110
+ <span aria-hidden="true">{{hiddenChildrenLabel}}…</span>
111
+ </button>
112
+ </div>
113
+ <div class="flex items-baseline whitespace-pre" :style="indentStyle">
114
+ <span class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible"></span>
115
+ <span>{{ closingBracket }}{{ comma }}</span>
116
+ </div>
117
+ </template>
118
+ </div>
@@ -13,6 +13,7 @@
13
13
  :max-top-level-fields="maxTopLevelFields"
14
14
  :top-level-expanded="topLevelExpanded"
15
15
  :expand-top-level="expandTopLevel"
16
+ :references="references"
16
17
  ></json-node>
17
18
  </div>
18
19
  </div>
@@ -2,97 +2,19 @@
2
2
 
3
3
  const template = require('./list-json.html');
4
4
 
5
- const JsonNodeTemplate = `
6
- <div>
7
- <div class="flex items-baseline whitespace-pre" :style="indentStyle">
8
- <button
9
- v-if="showToggle"
10
- type="button"
11
- class="w-4 h-4 mr-1 inline-flex items-center justify-center leading-none text-gray-500 hover:text-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400 cursor-pointer"
12
- @click.stop="handleToggle"
13
- >
14
- {{ isCollapsedNode ? '+' : '-' }}
15
- </button>
16
- <span v-else class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible flex-shrink-0"></span>
17
- <template v-if="hasKey">
18
- <span class="text-blue-600">"{{ nodeKey }}"</span><span>: </span>
19
- </template>
20
- <template v-if="isComplex">
21
- <template v-if="hasChildren">
22
- <span>{{ openingBracket }}</span>
23
- <span v-if="isCollapsedNode" class="mx-1">…</span>
24
- <span v-if="isCollapsedNode">{{ closingBracket }}{{ comma }}</span>
25
- </template>
26
- <template v-else>
27
- <span>{{ openingBracket }}{{ closingBracket }}{{ comma }}</span>
28
- </template>
29
- </template>
30
- <template v-else>
31
- <!--
32
- If value is a string and overflows its container (i.e. goes over one line), show an ellipsis.
33
- This is done via CSS ellipsis strategy.
34
- -->
35
- <span
36
- :class="valueClasses"
37
- :style="typeof value === 'string'
38
- ? {
39
- display: 'inline-block',
40
- maxWidth: '100%',
41
- overflow: 'hidden',
42
- textOverflow: 'ellipsis',
43
- whiteSpace: 'nowrap',
44
- verticalAlign: 'bottom'
45
- }
46
- : {}"
47
- :title="typeof value === 'string' && $el && $el.scrollWidth > $el.clientWidth ? value : undefined"
48
- >
49
- {{ formattedValue }}{{ comma }}
50
- </span>
51
- </template>
52
- </div>
53
- <template v-if="isComplex && hasChildren && !isCollapsedNode">
54
- <json-node
55
- v-for="child in children"
56
- :key="child.path"
57
- :node-key="child.displayKey"
58
- :value="child.value"
59
- :level="level + 1"
60
- :is-last="child.isLast"
61
- :path="child.path"
62
- :toggle-collapse="toggleCollapse"
63
- :is-collapsed="isCollapsed"
64
- :create-child-path="createChildPath"
65
- :indent-size="indentSize"
66
- :max-top-level-fields="maxTopLevelFields"
67
- :top-level-expanded="topLevelExpanded"
68
- :expand-top-level="expandTopLevel"
69
- ></json-node>
70
- <div
71
- v-if="hasHiddenRootChildren"
72
- class="flex items-baseline whitespace-pre"
73
- :style="indentStyle"
74
- >
75
- <span class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible"></span>
76
- <button
77
- type="button"
78
- class="text-xs inline-flex items-center gap-1 ml-4 text-slate-500 hover:text-slate-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400"
79
- :title="hiddenChildrenTooltip"
80
- @click.stop="handleExpandTopLevel"
81
- >
82
- <span aria-hidden="true">{{hiddenChildrenLabel}}…</span>
83
- </button>
84
- </div>
85
- <div class="flex items-baseline whitespace-pre" :style="indentStyle">
86
- <span class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible"></span>
87
- <span>{{ closingBracket }}{{ comma }}</span>
88
- </div>
89
- </template>
90
- </div>
91
- `;
5
+ const JsonNodeTemplate = require('./json-node.html');
92
6
 
93
7
  module.exports = app => app.component('list-json', {
94
8
  template: template,
95
- props: ['value'],
9
+ props: {
10
+ value: {
11
+ required: true
12
+ },
13
+ references: {
14
+ type: Object,
15
+ default: () => ({})
16
+ }
17
+ },
96
18
  data() {
97
19
  return {
98
20
  collapsedMap: {},
@@ -196,6 +118,10 @@ module.exports = app => app.component('list-json', {
196
118
  expandTopLevel: {
197
119
  type: Function,
198
120
  default: null
121
+ },
122
+ references: {
123
+ type: Object,
124
+ default: () => ({})
199
125
  }
200
126
  },
201
127
  computed: {
@@ -334,6 +260,24 @@ module.exports = app => app.component('list-json', {
334
260
  },
335
261
  hiddenChildrenTooltip() {
336
262
  return this.hiddenChildrenLabel;
263
+ },
264
+ normalizedPath() {
265
+ if (typeof this.path !== 'string') {
266
+ return '';
267
+ }
268
+ return this.path
269
+ .replace(/^root\.?/, '')
270
+ .replace(/\[\d+\]/g, '')
271
+ .replace(/^\./, '');
272
+ },
273
+ referenceModel() {
274
+ if (!this.normalizedPath || !this.references) {
275
+ return null;
276
+ }
277
+ return this.references[this.normalizedPath] || null;
278
+ },
279
+ shouldShowReferenceLink() {
280
+ return Boolean(this.referenceModel) && typeof this.value === 'string';
337
281
  }
338
282
  },
339
283
  methods: {
@@ -358,6 +302,12 @@ module.exports = app => app.component('list-json', {
358
302
  if (this.isRoot && typeof this.expandTopLevel === 'function') {
359
303
  this.expandTopLevel();
360
304
  }
305
+ },
306
+ goToReference(id) {
307
+ if (!this.referenceModel) {
308
+ return;
309
+ }
310
+ this.$router.push({ path: `/model/${this.referenceModel}/document/${id}` });
361
311
  }
362
312
  }
363
313
  }
@@ -165,7 +165,7 @@
165
165
  >
166
166
  Open this Document
167
167
  </button>
168
- <list-json :value="filterDocument(document)">
168
+ <list-json :value="filterDocument(document)" :references="referenceMap">
169
169
  </list-json>
170
170
  </div>
171
171
  </div>
@@ -108,6 +108,17 @@ module.exports = app => app.component('models', {
108
108
 
109
109
  await this.initSearchFromUrl();
110
110
  },
111
+ computed: {
112
+ referenceMap() {
113
+ const map = {};
114
+ for (const path of this.filteredPaths) {
115
+ if (path?.ref) {
116
+ map[path.path] = path.ref;
117
+ }
118
+ }
119
+ return map;
120
+ }
121
+ },
111
122
  methods: {
112
123
  buildAutocompleteTrie() {
113
124
  this.autocompleteTrie = new Trie();
@@ -338,9 +349,10 @@ module.exports = app => app.component('models', {
338
349
  },
339
350
  filterDocument(doc) {
340
351
  const filteredDoc = {};
341
- console.log(doc, this.filteredPaths);
342
352
  for (let i = 0; i < this.filteredPaths.length; i++) {
343
- filteredDoc[this.filteredPaths[i].path] = doc[this.filteredPaths[i].path];
353
+ const path = this.filteredPaths[i].path;
354
+ const value = mpath.get(path, doc);
355
+ mpath.set(path, value, filteredDoc);
344
356
  }
345
357
  return filteredDoc;
346
358
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
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": {
@@ -12,7 +12,7 @@
12
12
  "archetype": "0.13.1",
13
13
  "csv-stringify": "6.3.0",
14
14
  "ejson": "^2.2.3",
15
- "extrovert": "^0.1.0",
15
+ "extrovert": "^0.2.0",
16
16
  "marked": "15.0.12",
17
17
  "node-inspect-extracted": "3.x",
18
18
  "tailwindcss": "3.4.0",
@@ -23,7 +23,7 @@
23
23
  "peerDependencies": {
24
24
  "bson": "^5.5.1 || 6.x",
25
25
  "express": "4.x",
26
- "mongoose": "7.x || 8.x"
26
+ "mongoose": "7.x || 8.x || ^9.0.0-0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@masteringjs/eslint-config": "0.1.1",
@@ -32,7 +32,7 @@
32
32
  "eslint": "9.30.0",
33
33
  "express": "4.x",
34
34
  "mocha": "10.2.0",
35
- "mongoose": "8.x"
35
+ "mongoose": "9.x"
36
36
  },
37
37
  "scripts": {
38
38
  "lint": "eslint .",