@mongoosejs/studio 0.1.5 → 0.1.7

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",
@@ -147,6 +148,9 @@ var map = {
147
148
  "./modal/modal.css": "./frontend/src/modal/modal.css",
148
149
  "./modal/modal.html": "./frontend/src/modal/modal.html",
149
150
  "./modal/modal.js": "./frontend/src/modal/modal.js",
151
+ "./models/document-search/document-search": "./frontend/src/models/document-search/document-search.js",
152
+ "./models/document-search/document-search.html": "./frontend/src/models/document-search/document-search.html",
153
+ "./models/document-search/document-search.js": "./frontend/src/models/document-search/document-search.js",
150
154
  "./models/models": "./frontend/src/models/models.js",
151
155
  "./models/models.css": "./frontend/src/models/models.css",
152
156
  "./models/models.html": "./frontend/src/models/models.html",
@@ -1791,6 +1795,25 @@ module.exports = app => app.component('dashboard', {
1791
1795
  } finally {
1792
1796
  this.status = 'loaded';
1793
1797
  }
1798
+ },
1799
+ shouldEvaluateDashboard() {
1800
+ if (this.dashboardResults.length === 0) {
1801
+ return true;
1802
+ }
1803
+
1804
+ const finishedEvaluatingAt = this.dashboardResults[0].finishedEvaluatingAt;
1805
+ if (!finishedEvaluatingAt) {
1806
+ return true;
1807
+ }
1808
+
1809
+ const sixHoursAgo = Date.now() - 6 * 60 * 60 * 1000;
1810
+ const finishedAt = new Date(finishedEvaluatingAt).getTime();
1811
+
1812
+ if (Number.isNaN(finishedAt)) {
1813
+ return true;
1814
+ }
1815
+
1816
+ return finishedAt < sixHoursAgo;
1794
1817
  }
1795
1818
  },
1796
1819
  computed: {
@@ -1809,6 +1832,10 @@ module.exports = app => app.component('dashboard', {
1809
1832
  this.title = this.dashboard.title;
1810
1833
  this.description = this.dashboard.description ?? '';
1811
1834
  this.dashboardResults = dashboardResults;
1835
+ if (this.shouldEvaluateDashboard()) {
1836
+ await this.evaluateDashboard();
1837
+ return;
1838
+ }
1812
1839
  this.status = 'loaded';
1813
1840
  }
1814
1841
  });
@@ -3888,6 +3915,17 @@ module.exports = app => app.component('list-default', {
3888
3915
 
3889
3916
  /***/ }),
3890
3917
 
3918
+ /***/ "./frontend/src/list-json/json-node.html":
3919
+ /*!***********************************************!*\
3920
+ !*** ./frontend/src/list-json/json-node.html ***!
3921
+ \***********************************************/
3922
+ /***/ ((module) => {
3923
+
3924
+ "use strict";
3925
+ 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";
3926
+
3927
+ /***/ }),
3928
+
3891
3929
  /***/ "./frontend/src/list-json/list-json.html":
3892
3930
  /*!***********************************************!*\
3893
3931
  !*** ./frontend/src/list-json/list-json.html ***!
@@ -3895,7 +3933,7 @@ module.exports = app => app.component('list-default', {
3895
3933
  /***/ ((module) => {
3896
3934
 
3897
3935
  "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";
3936
+ 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
3937
 
3900
3938
  /***/ }),
3901
3939
 
@@ -3910,97 +3948,19 @@ module.exports = "<div class=\"tooltip w-full font-mono text-sm py-3 text-slate-
3910
3948
 
3911
3949
  const template = __webpack_require__(/*! ./list-json.html */ "./frontend/src/list-json/list-json.html");
3912
3950
 
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
- `;
3951
+ const JsonNodeTemplate = __webpack_require__(/*! ./json-node.html */ "./frontend/src/list-json/json-node.html");
4000
3952
 
4001
3953
  module.exports = app => app.component('list-json', {
4002
3954
  template: template,
4003
- props: ['value'],
3955
+ props: {
3956
+ value: {
3957
+ required: true
3958
+ },
3959
+ references: {
3960
+ type: Object,
3961
+ default: () => ({})
3962
+ }
3963
+ },
4004
3964
  data() {
4005
3965
  return {
4006
3966
  collapsedMap: {},
@@ -4104,6 +4064,10 @@ module.exports = app => app.component('list-json', {
4104
4064
  expandTopLevel: {
4105
4065
  type: Function,
4106
4066
  default: null
4067
+ },
4068
+ references: {
4069
+ type: Object,
4070
+ default: () => ({})
4107
4071
  }
4108
4072
  },
4109
4073
  computed: {
@@ -4242,6 +4206,24 @@ module.exports = app => app.component('list-json', {
4242
4206
  },
4243
4207
  hiddenChildrenTooltip() {
4244
4208
  return this.hiddenChildrenLabel;
4209
+ },
4210
+ normalizedPath() {
4211
+ if (typeof this.path !== 'string') {
4212
+ return '';
4213
+ }
4214
+ return this.path
4215
+ .replace(/^root\.?/, '')
4216
+ .replace(/\[\d+\]/g, '')
4217
+ .replace(/^\./, '');
4218
+ },
4219
+ referenceModel() {
4220
+ if (!this.normalizedPath || !this.references) {
4221
+ return null;
4222
+ }
4223
+ return this.references[this.normalizedPath] || null;
4224
+ },
4225
+ shouldShowReferenceLink() {
4226
+ return Boolean(this.referenceModel) && typeof this.value === 'string';
4245
4227
  }
4246
4228
  },
4247
4229
  methods: {
@@ -4266,6 +4248,12 @@ module.exports = app => app.component('list-json', {
4266
4248
  if (this.isRoot && typeof this.expandTopLevel === 'function') {
4267
4249
  this.expandTopLevel();
4268
4250
  }
4251
+ },
4252
+ goToReference(id) {
4253
+ if (!this.referenceModel) {
4254
+ return;
4255
+ }
4256
+ this.$router.push({ path: `/model/${this.referenceModel}/document/${id}` });
4269
4257
  }
4270
4258
  }
4271
4259
  }
@@ -4539,43 +4527,28 @@ module.exports = app => app.component('modal', {
4539
4527
 
4540
4528
  /***/ }),
4541
4529
 
4542
- /***/ "./frontend/src/models/models.css":
4543
- /*!****************************************!*\
4544
- !*** ./frontend/src/models/models.css ***!
4545
- \****************************************/
4546
- /***/ ((module) => {
4547
-
4548
- "use strict";
4549
- module.exports = ".models {\n position: relative;\n display: flex;\n flex-direction: row;\n min-height: calc(100% - 56px);\n}\n\n.models button.gray {\n color: black;\n background-color: #eee;\n}\n\n.models .model-selector {\n background-color: #eee;\n flex-grow: 0;\n padding: 15px;\n padding-top: 0px;\n}\n\n.models h1 {\n margin-top: 0px;\n}\n\n.models .documents {\n flex-grow: 1;\n overflow: scroll;\n max-height: calc(100vh - 56px);\n}\n\n.models .documents table {\n /* max-width: -moz-fit-content;\n max-width: fit-content; */\n width: 100%;\n table-layout: auto;\n font-size: small;\n padding: 0;\n margin-right: 1em;\n white-space: nowrap;\n z-index: -1;\n border-collapse: collapse;\n line-height: 1.5em;\n}\n\n.models .documents table th {\n position: sticky;\n top: 42px;\n background-color: white;\n z-index: 1;\n}\n\n.models .documents table th:after {\n content: \"\";\n position: absolute;\n left: 0;\n width: 100%;\n bottom: -1px;\n border-bottom: thin solid rgba(0, 0, 0, 0.12);\n}\n\n.models .documents table tr {\n color: black;\n border-spacing: 0px 0px;\n background-color: white;\n cursor: pointer;\n}\n\n.models .documents table tr:nth-child(even) {\n background-color: #f5f5f5;\n}\n\n.models .documents table tr:hover {\n background-color: #a7b9ff;\n}\n\n.models .documents table th,\ntd {\n border-bottom: thin solid rgba(0, 0, 0, 0.12);\n text-align: left;\n padding: 0 16px;\n height: 48px;\n}\n\n.models textarea {\n font-size: 1.2em;\n}\n\n.models .path-type {\n color: rgba(0, 0, 0, 0.36);\n font-size: 0.8em;\n}\n\n.models .documents-menu {\n position: fixed;\n background-color: white;\n z-index: 1;\n padding: 4px;\n display: flex;\n width: 100vw;\n}\n\n@media (min-width: 1024px) {\n .models .documents-menu {\n width: calc(100vw - 12rem);\n }\n}\n\n.models .documents-menu .search-input {\n flex-grow: 1;\n align-items: center;\n}\n\n.models .search-input input {\n padding: 0.25em 0.5em;\n font-size: 1.1em;\n border: 1px solid #ddd;\n border-radius: 3px;\n width: calc(100% - 1em);\n}\n\n.models .sort-arrow {\n padding-left: 10px;\n padding-right: 10px;\n}\n\n.models .loader {\n width: 100%;\n text-align: center;\n}\n\n.models .loader img {\n height: 4em;\n}\n\n.models .documents .buttons {\n display: inline-flex;\n justify-content: space-around;\n align-items: center;\n}\n\n";
4550
-
4551
- /***/ }),
4552
-
4553
- /***/ "./frontend/src/models/models.html":
4554
- /*!*****************************************!*\
4555
- !*** ./frontend/src/models/models.html ***!
4556
- \*****************************************/
4530
+ /***/ "./frontend/src/models/document-search/document-search.html":
4531
+ /*!******************************************************************!*\
4532
+ !*** ./frontend/src/models/document-search/document-search.html ***!
4533
+ \******************************************************************/
4557
4534
  /***/ ((module) => {
4558
4535
 
4559
4536
  "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";
4537
+ module.exports = "<form @submit.prevent=\"emitSearch\" class=\"relative flex-grow m-0\">\n <input\n ref=\"searchInput\"\n 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\"\n type=\"text\"\n placeholder=\"Filter\"\n v-model=\"searchText\"\n @click=\"initFilter\"\n @input=\"updateAutocomplete\"\n @keydown=\"handleKeyDown\"\n />\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\n v-for=\"(suggestion, index) in autocompleteSuggestions\"\n :key=\"suggestion\"\n class=\"px-2 py-1 cursor-pointer\"\n :class=\"{ 'bg-ultramarine-100': index === autocompleteIndex }\"\n @mousedown.prevent=\"applySuggestion(index)\"\n >\n {{ suggestion }}\n </li>\n </ul>\n</form>\n";
4561
4538
 
4562
4539
  /***/ }),
4563
4540
 
4564
- /***/ "./frontend/src/models/models.js":
4565
- /*!***************************************!*\
4566
- !*** ./frontend/src/models/models.js ***!
4567
- \***************************************/
4541
+ /***/ "./frontend/src/models/document-search/document-search.js":
4542
+ /*!****************************************************************!*\
4543
+ !*** ./frontend/src/models/document-search/document-search.js ***!
4544
+ \****************************************************************/
4568
4545
  /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
4569
4546
 
4570
4547
  "use strict";
4571
4548
 
4572
4549
 
4573
- const api = __webpack_require__(/*! ../api */ "./frontend/src/api.js");
4574
- const template = __webpack_require__(/*! ./models.html */ "./frontend/src/models/models.html");
4575
- const mpath = __webpack_require__(/*! mpath */ "./node_modules/mpath/index.js");
4576
-
4577
- const appendCSS = __webpack_require__(/*! ../appendCSS */ "./frontend/src/appendCSS.js");
4578
- const { Trie } = __webpack_require__(/*! ./trie */ "./frontend/src/models/trie.js");
4550
+ const template = __webpack_require__(/*! ./document-search.html */ "./frontend/src/models/document-search/document-search.html");
4551
+ const { Trie } = __webpack_require__(/*! ../trie */ "./frontend/src/models/trie.js");
4579
4552
 
4580
4553
  const QUERY_SELECTORS = [
4581
4554
  '$eq',
@@ -4604,165 +4577,59 @@ const QUERY_SELECTORS = [
4604
4577
  '$mod'
4605
4578
  ];
4606
4579
 
4607
-
4608
- appendCSS(__webpack_require__(/*! ./models.css */ "./frontend/src/models/models.css"));
4609
-
4610
- const limit = 20;
4611
- const OUTPUT_TYPE_STORAGE_KEY = 'studio:model-output-type';
4612
-
4613
- module.exports = app => app.component('models', {
4614
- template: template,
4615
- props: ['model', 'user', 'roles'],
4616
- data: () => ({
4617
- models: [],
4618
- currentModel: null,
4619
- documents: [],
4620
- schemaPaths: [],
4621
- filteredPaths: [],
4622
- selectedPaths: [],
4623
- numDocuments: null,
4624
- mongoDBIndexes: [],
4625
- schemaIndexes: [],
4626
- status: 'loading',
4627
- loadedAllDocs: false,
4628
- edittingDoc: null,
4629
- docEdits: null,
4630
- selectMultiple: false,
4631
- selectedDocuments: [],
4632
- searchText: '',
4633
- autocompleteSuggestions: [],
4634
- autocompleteIndex: 0,
4635
- autocompleteTrie: null,
4636
- shouldShowExportModal: false,
4637
- shouldShowCreateModal: false,
4638
- shouldShowFieldModal: false,
4639
- shouldShowIndexModal: false,
4640
- shouldShowUpdateMultipleModal: false,
4641
- shouldShowDeleteMultipleModal: false,
4642
- shouldExport: {},
4643
- sortBy: {},
4644
- query: {},
4645
- scrollHeight: 0,
4646
- interval: null,
4647
- outputType: 'table', // json, table
4648
- hideSidebar: null,
4649
- lastSelectedIndex: null,
4650
- error: null
4651
- }),
4652
- created() {
4653
- this.currentModel = this.model;
4654
- this.buildAutocompleteTrie();
4655
- this.loadOutputPreference();
4580
+ module.exports = app => app.component('document-search', {
4581
+ template,
4582
+ props: {
4583
+ value: {
4584
+ type: String,
4585
+ default: ''
4586
+ },
4587
+ schemaPaths: {
4588
+ type: Array,
4589
+ default: () => []
4590
+ }
4656
4591
  },
4657
- beforeDestroy() {
4658
- document.removeEventListener('scroll', this.onScroll, true);
4659
- window.removeEventListener('popstate', this.onPopState, true);
4592
+ data() {
4593
+ return {
4594
+ autocompleteSuggestions: [],
4595
+ autocompleteIndex: 0,
4596
+ autocompleteTrie: null,
4597
+ searchText: this.value || ''
4598
+ };
4660
4599
  },
4661
- async mounted() {
4662
- this.onScroll = () => this.checkIfScrolledToBottom();
4663
- document.addEventListener('scroll', this.onScroll, true);
4664
- this.onPopState = () => this.initSearchFromUrl();
4665
- window.addEventListener('popstate', this.onPopState, true);
4666
- const { models, readyState } = await api.Model.listModels();
4667
- this.models = models;
4668
- if (this.currentModel == null && this.models.length > 0) {
4669
- this.currentModel = this.models[0];
4670
- }
4671
- if (this.models.length === 0) {
4672
- this.status = 'loaded';
4673
- this.numDocuments = 0;
4674
- if (readyState === 0) {
4675
- this.error = 'No models found and Mongoose is not connected. Check our documentation for more information.';
4676
- }
4600
+ watch: {
4601
+ value(val) {
4602
+ this.searchText = val || '';
4603
+ },
4604
+ schemaPaths: {
4605
+ handler() {
4606
+ this.buildAutocompleteTrie();
4607
+ },
4608
+ deep: true
4677
4609
  }
4678
-
4679
- await this.initSearchFromUrl();
4610
+ },
4611
+ created() {
4612
+ this.buildAutocompleteTrie();
4680
4613
  },
4681
4614
  methods: {
4615
+ emitSearch() {
4616
+ this.$emit('input', this.searchText);
4617
+ this.$emit('search', this.searchText);
4618
+ },
4682
4619
  buildAutocompleteTrie() {
4683
4620
  this.autocompleteTrie = new Trie();
4684
- this.autocompleteTrie.bulkInsert(QUERY_SELECTORS, 5);
4621
+ this.autocompleteTrie.bulkInsert(QUERY_SELECTORS, 5, 'operator');
4685
4622
  if (Array.isArray(this.schemaPaths) && this.schemaPaths.length > 0) {
4686
4623
  const paths = this.schemaPaths
4687
4624
  .map(path => path?.path)
4688
4625
  .filter(path => typeof path === 'string' && path.length > 0);
4689
- this.autocompleteTrie.bulkInsert(paths, 10);
4690
- }
4691
- },
4692
- loadOutputPreference() {
4693
- if (typeof window === 'undefined' || !window.localStorage) {
4694
- return;
4695
- }
4696
- const storedPreference = window.localStorage.getItem(OUTPUT_TYPE_STORAGE_KEY);
4697
- if (storedPreference === 'json' || storedPreference === 'table') {
4698
- this.outputType = storedPreference;
4699
- }
4700
- },
4701
- setOutputType(type) {
4702
- if (type !== 'json' && type !== 'table') {
4703
- return;
4704
- }
4705
- this.outputType = type;
4706
- if (typeof window !== 'undefined' && window.localStorage) {
4707
- window.localStorage.setItem(OUTPUT_TYPE_STORAGE_KEY, type);
4708
- }
4709
- },
4710
- buildDocumentFetchParams(options = {}) {
4711
- const params = {
4712
- model: this.currentModel,
4713
- limit
4714
- };
4715
-
4716
- if (typeof options.skip === 'number') {
4717
- params.skip = options.skip;
4718
- }
4719
-
4720
- const sortKeys = Object.keys(this.sortBy);
4721
- if (sortKeys.length > 0) {
4722
- const key = sortKeys[0];
4723
- if (typeof key === 'string' && key.length > 0) {
4724
- params.sortKey = key;
4725
- const direction = this.sortBy[key];
4726
- if (direction !== undefined && direction !== null) {
4727
- params.sortDirection = direction;
4626
+ for (const path of this.schemaPaths) {
4627
+ if (path.schema) {
4628
+ paths.push(...Object.keys(path.schema).map(subpath => `${path.path}.${subpath}`));
4728
4629
  }
4729
4630
  }
4631
+ this.autocompleteTrie.bulkInsert(paths, 10, 'fieldName');
4730
4632
  }
4731
-
4732
- if (typeof this.searchText === 'string' && this.searchText.trim().length > 0) {
4733
- params.searchText = this.searchText;
4734
- }
4735
-
4736
- return params;
4737
- },
4738
- async initSearchFromUrl() {
4739
- this.status = 'loading';
4740
- this.query = Object.assign({}, this.$route.query); // important that this is here before the if statements
4741
- if (this.$route.query?.search) {
4742
- this.searchText = this.$route.query.search;
4743
- } else {
4744
- this.searchText = '';
4745
- }
4746
- if (this.$route.query?.sort) {
4747
- const sort = eval(`(${this.$route.query.sort})`);
4748
- const path = Object.keys(sort)[0];
4749
- const num = Object.values(sort)[0];
4750
- this.sortDocs(num, path);
4751
- }
4752
-
4753
-
4754
- if (this.currentModel != null) {
4755
- await this.getDocuments();
4756
- }
4757
- if (this.$route.query?.fields) {
4758
- const filter = this.$route.query.fields.split(',');
4759
- this.filteredPaths = this.filteredPaths.filter(x => filter.includes(x.path));
4760
- }
4761
- this.status = 'loaded';
4762
- },
4763
- async dropIndex(name) {
4764
- const { mongoDBIndexes } = await api.Model.dropIndex({ model: this.currentModel, name });
4765
- this.mongoDBIndexes = mongoDBIndexes;
4766
4633
  },
4767
4634
  initFilter(ev) {
4768
4635
  if (!this.searchText) {
@@ -4791,8 +4658,12 @@ module.exports = app => app.component('models', {
4791
4658
  this.autocompleteSuggestions = [];
4792
4659
  return;
4793
4660
  }
4661
+
4662
+ const colonMatch = before.match(/:\s*([^,\}\]]*)$/);
4663
+ const role = colonMatch ? 'operator' : 'fieldName';
4664
+
4794
4665
  if (this.autocompleteTrie) {
4795
- const primarySuggestions = this.autocompleteTrie.getSuggestions(term, 10);
4666
+ const primarySuggestions = this.autocompleteTrie.getSuggestions(term, 10, role);
4796
4667
  const suggestionsSet = new Set(primarySuggestions);
4797
4668
  if (Array.isArray(this.schemaPaths) && this.schemaPaths.length > 0) {
4798
4669
  for (const schemaPath of this.schemaPaths) {
@@ -4877,7 +4748,7 @@ module.exports = app => app.component('models', {
4877
4748
  });
4878
4749
  this.autocompleteSuggestions = [];
4879
4750
  },
4880
- clickFilter(path) {
4751
+ addPathFilter(path) {
4881
4752
  if (this.searchText) {
4882
4753
  if (this.searchText.endsWith('}')) {
4883
4754
  this.searchText = this.searchText.slice(0, -1) + `, ${path}: }`;
@@ -4898,6 +4769,204 @@ module.exports = app => app.component('models', {
4898
4769
  input.focus();
4899
4770
  input.setSelectionRange(cursorIndex, cursorIndex);
4900
4771
  });
4772
+ }
4773
+ }
4774
+ });
4775
+
4776
+
4777
+ /***/ }),
4778
+
4779
+ /***/ "./frontend/src/models/models.css":
4780
+ /*!****************************************!*\
4781
+ !*** ./frontend/src/models/models.css ***!
4782
+ \****************************************/
4783
+ /***/ ((module) => {
4784
+
4785
+ "use strict";
4786
+ module.exports = ".models {\n position: relative;\n display: flex;\n flex-direction: row;\n min-height: calc(100% - 56px);\n}\n\n.models button.gray {\n color: black;\n background-color: #eee;\n}\n\n.models .model-selector {\n background-color: #eee;\n flex-grow: 0;\n padding: 15px;\n padding-top: 0px;\n}\n\n.models h1 {\n margin-top: 0px;\n}\n\n.models .documents {\n flex-grow: 1;\n overflow: scroll;\n max-height: calc(100vh - 56px);\n}\n\n.models .documents table {\n /* max-width: -moz-fit-content;\n max-width: fit-content; */\n width: 100%;\n table-layout: auto;\n font-size: small;\n padding: 0;\n margin-right: 1em;\n white-space: nowrap;\n z-index: -1;\n border-collapse: collapse;\n line-height: 1.5em;\n}\n\n.models .documents table th {\n position: sticky;\n top: 42px;\n background-color: white;\n z-index: 1;\n}\n\n.models .documents table th:after {\n content: \"\";\n position: absolute;\n left: 0;\n width: 100%;\n bottom: -1px;\n border-bottom: thin solid rgba(0, 0, 0, 0.12);\n}\n\n.models .documents table tr {\n color: black;\n border-spacing: 0px 0px;\n background-color: white;\n cursor: pointer;\n}\n\n.models .documents table tr:nth-child(even) {\n background-color: #f5f5f5;\n}\n\n.models .documents table tr:hover {\n background-color: #a7b9ff;\n}\n\n.models .documents table th,\ntd {\n border-bottom: thin solid rgba(0, 0, 0, 0.12);\n text-align: left;\n padding: 0 16px;\n height: 48px;\n}\n\n.models textarea {\n font-size: 1.2em;\n}\n\n.models .path-type {\n color: rgba(0, 0, 0, 0.36);\n font-size: 0.8em;\n}\n\n.models .documents-menu {\n position: fixed;\n background-color: white;\n z-index: 1;\n padding: 4px;\n display: flex;\n width: 100vw;\n}\n\n@media (min-width: 1024px) {\n .models .documents-menu {\n width: calc(100vw - 12rem);\n }\n}\n\n.models .documents-menu .search-input {\n flex-grow: 1;\n align-items: center;\n}\n\n.models .search-input input {\n padding: 0.25em 0.5em;\n font-size: 1.1em;\n border: 1px solid #ddd;\n border-radius: 3px;\n width: calc(100% - 1em);\n}\n\n.models .sort-arrow {\n padding-left: 10px;\n padding-right: 10px;\n}\n\n.models .loader {\n width: 100%;\n text-align: center;\n}\n\n.models .loader img {\n height: 4em;\n}\n\n.models .documents .buttons {\n display: inline-flex;\n justify-content: space-around;\n align-items: center;\n}\n\n";
4787
+
4788
+ /***/ }),
4789
+
4790
+ /***/ "./frontend/src/models/models.html":
4791
+ /*!*****************************************!*\
4792
+ !*** ./frontend/src/models/models.html ***!
4793
+ \*****************************************/
4794
+ /***/ ((module) => {
4795
+
4796
+ "use strict";
4797
+ 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 <document-search\n ref=\"documentSearch\"\n :value=\"searchText\"\n :schema-paths=\"schemaPaths\"\n @search=\"search\"\n >\n </document-search>\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=\"addPathFilter(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";
4798
+
4799
+ /***/ }),
4800
+
4801
+ /***/ "./frontend/src/models/models.js":
4802
+ /*!***************************************!*\
4803
+ !*** ./frontend/src/models/models.js ***!
4804
+ \***************************************/
4805
+ /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
4806
+
4807
+ "use strict";
4808
+
4809
+
4810
+ const api = __webpack_require__(/*! ../api */ "./frontend/src/api.js");
4811
+ const template = __webpack_require__(/*! ./models.html */ "./frontend/src/models/models.html");
4812
+ const mpath = __webpack_require__(/*! mpath */ "./node_modules/mpath/index.js");
4813
+
4814
+ const appendCSS = __webpack_require__(/*! ../appendCSS */ "./frontend/src/appendCSS.js");
4815
+ appendCSS(__webpack_require__(/*! ./models.css */ "./frontend/src/models/models.css"));
4816
+
4817
+ const limit = 20;
4818
+ const OUTPUT_TYPE_STORAGE_KEY = 'studio:model-output-type';
4819
+
4820
+ module.exports = app => app.component('models', {
4821
+ template: template,
4822
+ props: ['model', 'user', 'roles'],
4823
+ data: () => ({
4824
+ models: [],
4825
+ currentModel: null,
4826
+ documents: [],
4827
+ schemaPaths: [],
4828
+ filteredPaths: [],
4829
+ selectedPaths: [],
4830
+ numDocuments: null,
4831
+ mongoDBIndexes: [],
4832
+ schemaIndexes: [],
4833
+ status: 'loading',
4834
+ loadedAllDocs: false,
4835
+ edittingDoc: null,
4836
+ docEdits: null,
4837
+ selectMultiple: false,
4838
+ selectedDocuments: [],
4839
+ searchText: '',
4840
+ shouldShowExportModal: false,
4841
+ shouldShowCreateModal: false,
4842
+ shouldShowFieldModal: false,
4843
+ shouldShowIndexModal: false,
4844
+ shouldShowUpdateMultipleModal: false,
4845
+ shouldShowDeleteMultipleModal: false,
4846
+ shouldExport: {},
4847
+ sortBy: {},
4848
+ query: {},
4849
+ scrollHeight: 0,
4850
+ interval: null,
4851
+ outputType: 'table', // json, table
4852
+ hideSidebar: null,
4853
+ lastSelectedIndex: null,
4854
+ error: null
4855
+ }),
4856
+ created() {
4857
+ this.currentModel = this.model;
4858
+ this.loadOutputPreference();
4859
+ },
4860
+ beforeDestroy() {
4861
+ document.removeEventListener('scroll', this.onScroll, true);
4862
+ window.removeEventListener('popstate', this.onPopState, true);
4863
+ },
4864
+ async mounted() {
4865
+ this.onScroll = () => this.checkIfScrolledToBottom();
4866
+ document.addEventListener('scroll', this.onScroll, true);
4867
+ this.onPopState = () => this.initSearchFromUrl();
4868
+ window.addEventListener('popstate', this.onPopState, true);
4869
+ const { models, readyState } = await api.Model.listModels();
4870
+ this.models = models;
4871
+ if (this.currentModel == null && this.models.length > 0) {
4872
+ this.currentModel = this.models[0];
4873
+ }
4874
+ if (this.models.length === 0) {
4875
+ this.status = 'loaded';
4876
+ this.numDocuments = 0;
4877
+ if (readyState === 0) {
4878
+ this.error = 'No models found and Mongoose is not connected. Check our documentation for more information.';
4879
+ }
4880
+ }
4881
+
4882
+ await this.initSearchFromUrl();
4883
+ },
4884
+ computed: {
4885
+ referenceMap() {
4886
+ const map = {};
4887
+ for (const path of this.filteredPaths) {
4888
+ if (path?.ref) {
4889
+ map[path.path] = path.ref;
4890
+ }
4891
+ }
4892
+ return map;
4893
+ }
4894
+ },
4895
+ methods: {
4896
+ loadOutputPreference() {
4897
+ if (typeof window === 'undefined' || !window.localStorage) {
4898
+ return;
4899
+ }
4900
+ const storedPreference = window.localStorage.getItem(OUTPUT_TYPE_STORAGE_KEY);
4901
+ if (storedPreference === 'json' || storedPreference === 'table') {
4902
+ this.outputType = storedPreference;
4903
+ }
4904
+ },
4905
+ setOutputType(type) {
4906
+ if (type !== 'json' && type !== 'table') {
4907
+ return;
4908
+ }
4909
+ this.outputType = type;
4910
+ if (typeof window !== 'undefined' && window.localStorage) {
4911
+ window.localStorage.setItem(OUTPUT_TYPE_STORAGE_KEY, type);
4912
+ }
4913
+ },
4914
+ buildDocumentFetchParams(options = {}) {
4915
+ const params = {
4916
+ model: this.currentModel,
4917
+ limit
4918
+ };
4919
+
4920
+ if (typeof options.skip === 'number') {
4921
+ params.skip = options.skip;
4922
+ }
4923
+
4924
+ const sortKeys = Object.keys(this.sortBy);
4925
+ if (sortKeys.length > 0) {
4926
+ const key = sortKeys[0];
4927
+ if (typeof key === 'string' && key.length > 0) {
4928
+ params.sortKey = key;
4929
+ const direction = this.sortBy[key];
4930
+ if (direction !== undefined && direction !== null) {
4931
+ params.sortDirection = direction;
4932
+ }
4933
+ }
4934
+ }
4935
+
4936
+ if (typeof this.searchText === 'string' && this.searchText.trim().length > 0) {
4937
+ params.searchText = this.searchText;
4938
+ }
4939
+
4940
+ return params;
4941
+ },
4942
+ async initSearchFromUrl() {
4943
+ this.status = 'loading';
4944
+ this.query = Object.assign({}, this.$route.query); // important that this is here before the if statements
4945
+ if (this.$route.query?.search) {
4946
+ this.searchText = this.$route.query.search;
4947
+ } else {
4948
+ this.searchText = '';
4949
+ }
4950
+ if (this.$route.query?.sort) {
4951
+ const sort = eval(`(${this.$route.query.sort})`);
4952
+ const path = Object.keys(sort)[0];
4953
+ const num = Object.values(sort)[0];
4954
+ this.sortDocs(num, path);
4955
+ }
4956
+
4957
+
4958
+ if (this.currentModel != null) {
4959
+ await this.getDocuments();
4960
+ }
4961
+ if (this.$route.query?.fields) {
4962
+ const filter = this.$route.query.fields.split(',');
4963
+ this.filteredPaths = this.filteredPaths.filter(x => filter.includes(x.path));
4964
+ }
4965
+ this.status = 'loaded';
4966
+ },
4967
+ async dropIndex(name) {
4968
+ const { mongoDBIndexes } = await api.Model.dropIndex({ model: this.currentModel, name });
4969
+ this.mongoDBIndexes = mongoDBIndexes;
4901
4970
  },
4902
4971
  async closeCreationModal() {
4903
4972
  this.shouldShowCreateModal = false;
@@ -4908,9 +4977,10 @@ module.exports = app => app.component('models', {
4908
4977
  },
4909
4978
  filterDocument(doc) {
4910
4979
  const filteredDoc = {};
4911
- console.log(doc, this.filteredPaths);
4912
4980
  for (let i = 0; i < this.filteredPaths.length; i++) {
4913
- filteredDoc[this.filteredPaths[i].path] = doc[this.filteredPaths[i].path];
4981
+ const path = this.filteredPaths[i].path;
4982
+ const value = mpath.get(path, doc);
4983
+ mpath.set(path, value, filteredDoc);
4914
4984
  }
4915
4985
  return filteredDoc;
4916
4986
  },
@@ -4947,7 +5017,8 @@ module.exports = app => app.component('models', {
4947
5017
  }
4948
5018
  await this.loadMoreDocuments();
4949
5019
  },
4950
- async search() {
5020
+ async search(searchText) {
5021
+ this.searchText = searchText;
4951
5022
  const hasSearch = typeof this.searchText === 'string' && this.searchText.trim().length > 0;
4952
5023
  if (hasSearch) {
4953
5024
  this.query.search = this.searchText;
@@ -4962,6 +5033,11 @@ module.exports = app => app.component('models', {
4962
5033
  await this.loadMoreDocuments();
4963
5034
  this.status = 'loaded';
4964
5035
  },
5036
+ addPathFilter(path) {
5037
+ if (this.$refs.documentSearch?.addPathFilter) {
5038
+ this.$refs.documentSearch.addPathFilter(path);
5039
+ }
5040
+ },
4965
5041
  async openIndexModal() {
4966
5042
  this.shouldShowIndexModal = true;
4967
5043
  const { mongoDBIndexes, schemaIndexes } = await api.Model.getIndexes({ model: this.currentModel });
@@ -4981,7 +5057,6 @@ module.exports = app => app.component('models', {
4981
5057
  // Clear previous data
4982
5058
  this.documents = [];
4983
5059
  this.schemaPaths = [];
4984
- this.buildAutocompleteTrie();
4985
5060
  this.numDocuments = null;
4986
5061
  this.loadedAllDocs = false;
4987
5062
  this.lastSelectedIndex = null;
@@ -5009,7 +5084,6 @@ module.exports = app => app.component('models', {
5009
5084
  }
5010
5085
  this.filteredPaths = [...this.schemaPaths];
5011
5086
  this.selectedPaths = [...this.schemaPaths];
5012
- this.buildAutocompleteTrie();
5013
5087
  schemaPathsReceived = true;
5014
5088
  }
5015
5089
  if (event.numDocs !== undefined) {
@@ -5239,6 +5313,7 @@ class TrieNode {
5239
5313
  this.children = Object.create(null);
5240
5314
  this.isEnd = false;
5241
5315
  this.freq = 0;
5316
+ this.roles = new Set(); // semantic roles like 'fieldName', 'operator'
5242
5317
  }
5243
5318
  }
5244
5319
 
@@ -5247,7 +5322,7 @@ class Trie {
5247
5322
  this.root = new TrieNode();
5248
5323
  }
5249
5324
 
5250
- insert(word, freq = 1) {
5325
+ insert(word, freq = 1, role = null) {
5251
5326
  if (!word) {
5252
5327
  return;
5253
5328
  }
@@ -5260,27 +5335,25 @@ class Trie {
5260
5335
  }
5261
5336
  node.isEnd = true;
5262
5337
  node.freq += freq;
5338
+ if (role) {
5339
+ node.roles.add(role);
5340
+ }
5263
5341
  }
5264
5342
 
5265
- bulkInsert(words, freq = 1) {
5266
- if (!Array.isArray(words)) {
5267
- return;
5268
- }
5269
- for (const word of words) {
5270
- this.insert(word, freq);
5271
- }
5343
+ bulkInsert(words, freq = 1, role = null) {
5344
+ for (const word of words) this.insert(word, freq, role);
5272
5345
  }
5273
5346
 
5274
- collect(node, prefix, out) {
5275
- if (node.isEnd) {
5347
+ collect(node, prefix, out, role) {
5348
+ if (node.isEnd && (role == null || node.roles.has(role))) {
5276
5349
  out.push([prefix, node.freq]);
5277
5350
  }
5278
5351
  for (const [ch, child] of Object.entries(node.children)) {
5279
- this.collect(child, prefix + ch, out);
5352
+ this.collect(child, prefix + ch, out, role);
5280
5353
  }
5281
5354
  }
5282
5355
 
5283
- suggest(prefix, limit = 10) {
5356
+ suggest(prefix, limit = 10, role = null) {
5284
5357
  let node = this.root;
5285
5358
  for (const ch of prefix) {
5286
5359
  if (!node.children[ch]) {
@@ -5289,19 +5362,19 @@ class Trie {
5289
5362
  node = node.children[ch];
5290
5363
  }
5291
5364
  const results = [];
5292
- this.collect(node, prefix, results);
5365
+ this.collect(node, prefix, results, role);
5293
5366
  results.sort((a, b) => b[1] - a[1]);
5294
5367
  return results.slice(0, limit).map(([word]) => word);
5295
5368
  }
5296
5369
 
5297
- fuzzySuggest(prefix, limit = 10) {
5370
+ fuzzySuggest(prefix, limit = 10, role = null) {
5298
5371
  const results = new Set();
5299
5372
 
5300
5373
  const dfs = (node, path, edits) => {
5301
5374
  if (edits > 1) {
5302
5375
  return;
5303
5376
  }
5304
- if (node.isEnd && Math.abs(path.length - prefix.length) <= 1) {
5377
+ if (node.isEnd && Math.abs(path.length - prefix.length) <= 1 && (role == null || node.roles.has(role))) {
5305
5378
  const dist = levenshtein(prefix, path);
5306
5379
  if (dist <= 1) {
5307
5380
  results.add(path);
@@ -5317,17 +5390,44 @@ class Trie {
5317
5390
  return Array.from(results).slice(0, limit);
5318
5391
  }
5319
5392
 
5320
- getSuggestions(prefix, limit = 10) {
5393
+ getSuggestions(prefix, limit = 10, role = null) {
5321
5394
  if (!prefix) {
5322
5395
  return [];
5323
5396
  }
5324
- const exact = this.suggest(prefix, limit);
5397
+ const exact = this.suggest(prefix, limit, role);
5325
5398
  if (exact.length >= limit) {
5326
5399
  return exact;
5327
5400
  }
5328
- const fuzzy = this.fuzzySuggest(prefix, limit - exact.length);
5401
+ const fuzzy = this.fuzzySuggest(prefix, limit - exact.length, role);
5329
5402
  return [...exact, ...fuzzy];
5330
5403
  }
5404
+
5405
+ toString() {
5406
+ const lines = [];
5407
+ function dfs(node, prefix, depth) {
5408
+ let line = ' '.repeat(depth);
5409
+ if (prefix.length > 0) {
5410
+ line += prefix[prefix.length - 1];
5411
+ } else {
5412
+ line += '(root)';
5413
+ }
5414
+ if (node.isEnd) {
5415
+ line += ' *';
5416
+ }
5417
+ if (node.roles.size > 0) {
5418
+ line += ' [' + Array.from(node.roles).join(',') + ']';
5419
+ }
5420
+ if (node.freq > 0) {
5421
+ line += ` {freq:${node.freq}}`;
5422
+ }
5423
+ lines.push(line);
5424
+ for (const ch of Object.keys(node.children).sort()) {
5425
+ dfs(node.children[ch], prefix + ch, depth + 1);
5426
+ }
5427
+ }
5428
+ dfs(this.root, '', 0);
5429
+ return lines.join('\n');
5430
+ }
5331
5431
  }
5332
5432
 
5333
5433
  function levenshtein(a, b) {
@@ -16609,7 +16709,7 @@ module.exports = function stringToParts(str) {
16609
16709
  /***/ ((module) => {
16610
16710
 
16611
16711
  "use strict";
16612
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.1.5","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 || ^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":"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"}}');
16712
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.1.7","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
16713
 
16614
16714
  /***/ })
16615
16715