@mongoosejs/studio 0.0.18 → 0.0.20

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.
@@ -245,12 +245,13 @@ module.exports = app => app.component('document', {
245
245
  status: 'init',
246
246
  document: null,
247
247
  changes: {},
248
- editting: false
248
+ editting: false,
249
+ virtuals: []
249
250
  }),
250
251
  async mounted() {
251
252
  const { doc, schemaPaths } = await api.Model.getDocument({ model: this.model, documentId: this.documentId });
252
253
  this.document = doc;
253
- this.schemaPaths = Object.keys(schemaPaths).sort((k1, k2) => {
254
+ this.schemaPaths = await Object.keys(schemaPaths).sort((k1, k2) => {
254
255
  if (k1 === '_id' && k2 !== '_id') {
255
256
  return -1;
256
257
  }
@@ -259,7 +260,7 @@ module.exports = app => app.component('document', {
259
260
  }
260
261
  return 0;
261
262
  }).map(key => schemaPaths[key]);
262
-
263
+ this.getVirtuals();
263
264
  this.status = 'loaded';
264
265
  },
265
266
  methods: {
@@ -287,6 +288,15 @@ module.exports = app => app.component('document', {
287
288
  getEditValueForPath({ path }) {
288
289
  return path in this.changes ? this.changes[path] : mpath.get(path, this.document);
289
290
  },
291
+ getVirtuals() {
292
+ const exists = this.schemaPaths.map(x => x.path);
293
+ const docKeys = Object.keys(this.document);
294
+ for (let i = 0; i < docKeys.length; i++) {
295
+ if (!exists.includes(docKeys[i])) {
296
+ this.virtuals.push({ name: docKeys[i], value: this.document[docKeys[i]] });
297
+ }
298
+ }
299
+ },
290
300
  cancelEdit() {
291
301
  this.changes = {};
292
302
  this.editting = false;
@@ -847,31 +857,18 @@ module.exports = app => app.component('models', {
847
857
  this.sortDocs(num, path);
848
858
  }
849
859
 
860
+
850
861
  if (this.currentModel != null) {
851
862
  await this.getDocuments();
852
863
  }
853
- this.applyQueryParams();
864
+ if (this.$route.query?.fields) {
865
+ const filter = this.$route.query.fields.split(',');
866
+ this.filteredPaths = this.filteredPaths.filter(x => filter.includes(x.path))
867
+ }
854
868
 
855
869
  this.status = 'loaded';
856
870
  },
857
871
  methods: {
858
- applyQueryParams() {
859
- const hashUrl = window.location.hash.replace(/^#/, '');
860
- if (hashUrl.indexOf('?') !== -1) {
861
- const searchParams = new URLSearchParams(
862
- hashUrl.slice(hashUrl.indexOf('?') + 1)
863
- );
864
- if (searchParams.has('fields')) {
865
- const filter = searchParams.get('fields').split(',');
866
- this.filteredPaths = this.filteredPaths.filter(x => filter.includes(x.path))
867
- }
868
- if (searchParams.has('search')) {
869
- this.searchText = searchParams.get('search');
870
- this.filter = eval(`(${this.searchText})`);
871
- this.filter = EJSON.stringify(this.filter);
872
- }
873
- }
874
- },
875
872
  async onScroll() {
876
873
  if (this.status === 'loading' || this.loadedAllDocs) {
877
874
  return;
@@ -915,22 +912,11 @@ module.exports = app => app.component('models', {
915
912
  this.filter = eval(`(${this.searchText})`);
916
913
  this.filter = EJSON.stringify(this.filter);
917
914
  this.query.search = this.searchText;
915
+ this.$router.push({ query: this.query });
918
916
  } else {
919
917
  this.filter = {};
920
918
  delete this.query.search;
921
- }
922
-
923
- const hashUrl = window.location.hash.replace(/^#/, '');
924
- if (hashUrl.indexOf('?') === -1) {
925
- window.history.pushState({}, '', window.location.pathname + '#' + hashUrl + '?search=' + this.query.search);
926
- } else {
927
- const searchParams = new URLSearchParams(
928
- hashUrl.indexOf('?') === -1 ? '' : hashUrl.slice(hashUrl.indexOf('?') + 1)
929
- );
930
- const hashUrlWithoutSearchParams = hashUrl.slice(0, hashUrl.indexOf('?'));
931
-
932
- searchParams.set('search', this.query.search);
933
- window.history.pushState({}, '', window.location.pathname + '#' + hashUrlWithoutSearchParams + '?' + searchParams);
919
+ this.$router.push({ query: this.query });
934
920
  }
935
921
  await this.loadMoreDocuments();
936
922
  },
@@ -997,23 +983,13 @@ module.exports = app => app.component('models', {
997
983
  this.filteredPaths = [...this.selectedPaths];
998
984
  this.shouldShowFieldModal = false;
999
985
  const selectedParams = this.filteredPaths.map(x => x.path).join(',');
1000
- // sets the query params
1001
- const hashUrl = window.location.hash.replace(/^#/, '');
1002
- if (hashUrl.indexOf('?') === -1) {
1003
- window.history.pushState({}, '', window.location.pathname + '#' + hashUrl + '?fields=' + selectedParams);
1004
- } else {
1005
- const searchParams = new URLSearchParams(
1006
- hashUrl.indexOf('?') === -1 ? '' : hashUrl.slice(hashUrl.indexOf('?') + 1)
1007
- );
1008
- const hashUrlWithoutSearchParams = hashUrl.slice(0, hashUrl.indexOf('?'));
1009
-
1010
- searchParams.set('fields', selectedParams);
1011
- window.history.pushState({}, '', window.location.pathname + '#' + hashUrlWithoutSearchParams + '?' + searchParams);
1012
- }
1013
-
986
+ this.query.fields = selectedParams;
987
+ this.$router.push({ query: this.query });
1014
988
  },
1015
989
  resetDocuments() {
1016
990
  this.selectedPaths = [...this.filteredPaths];
991
+ this.query.fields = {};
992
+ this.$router.push({ query: this.query });
1017
993
  this.shouldShowFieldModal = false;
1018
994
  },
1019
995
  deselectAll() {
@@ -1780,7 +1756,7 @@ module.exports = ".document {\n max-width: 1200px;\n margin-left: auto;\n mar
1780
1756
  /***/ ((module) => {
1781
1757
 
1782
1758
  "use strict";
1783
- module.exports = "<div class=\"document\">\n <div class=\"document-menu\">\n <div class=\"left\">\n <button @click=\"$router.push('/model/' + this.model)\">\n &lsaquo; Back\n </button>\n </div>\n\n <div class=\"right\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true\"\n type=\"button\"\n class=\"rounded-md bg-puerto-rico-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-puerto-rico-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-puerto-rico-600\">\n <img src=\"images/edit.svg\" class=\"inline\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"save\"\n type=\"button\"\n class=\"rounded-md bg-slate-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n &times; Cancel\n </button>\n <button\n v-if=\"editting\"\n @click=\"save\"\n type=\"button\"\n class=\"rounded-md bg-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\">\n <img src=\"images/save.svg\" class=\"inline\" /> Save\n </button>\n <button\n @click=\"remove\"\n type=\"button\"\n class=\"rounded-md bg-red-600 px-2.5 py-1.5 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-600\">\n <img src=\"images/delete.svg\" class=\"inline\" /> Delete\n </button>\n </div>\n </div>\n <div v-if=\"status === 'loaded'\">\n <div v-for=\"path in schemaPaths\" class=\"value\">\n <div class=\"path-key\">\n {{path.path}}\n <span class=\"path-type\">\n ({{(path.instance || 'unknown').toLowerCase()}})\n </span>\n </div>\n <div v-if=\"editting && path.path !== '_id'\">\n <component\n :is=\"getEditComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n @input=\"changes[path.path] = $event;\"\n >\n </component>\n </div>\n <div v-else>\n <component :is=\"getComponentForPath(path)\" :value=\"getValueForPath(path.path)\"></component>\n </div>\n </div>\n </div>\n</div>\n";
1759
+ module.exports = "<div class=\"document\">\n <div class=\"document-menu\">\n <div class=\"left\">\n <button @click=\"$router.push('/model/' + this.model)\">\n &lsaquo; Back\n </button>\n </div>\n\n <div class=\"right\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true\"\n type=\"button\"\n class=\"rounded-md bg-puerto-rico-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-puerto-rico-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-puerto-rico-600\">\n <img src=\"images/edit.svg\" class=\"inline\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"save\"\n type=\"button\"\n class=\"rounded-md bg-slate-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n &times; Cancel\n </button>\n <button\n v-if=\"editting\"\n @click=\"save\"\n type=\"button\"\n class=\"rounded-md bg-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\">\n <img src=\"images/save.svg\" class=\"inline\" /> Save\n </button>\n <button\n @click=\"remove\"\n type=\"button\"\n class=\"rounded-md bg-red-600 px-2.5 py-1.5 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-600\">\n <img src=\"images/delete.svg\" class=\"inline\" /> Delete\n </button>\n </div>\n </div>\n <div v-if=\"status === 'loaded'\">\n <div v-for=\"path in schemaPaths\" class=\"value\">\n <div class=\"path-key\">\n {{path.path}}\n <span class=\"path-type\">\n ({{(path.instance || 'unknown').toLowerCase()}})\n </span>\n </div>\n <div v-if=\"editting && path.path !== '_id'\">\n <component\n :is=\"getEditComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n @input=\"changes[path.path] = $event;\"\n >\n </component>\n </div>\n <div v-else>\n <component :is=\"getComponentForPath(path)\" :value=\"getValueForPath(path.path)\"></component>\n </div>\n </div>\n <div v-for=\"path in virtuals\" class=\"mb-2\">\n <div class=\"p-1 mb-1 bg-slate-100\">\n {{path.name}}\n <span class=\"path-type\">\n (virtual)\n </span>\n </div>\n <div v-if=\"path.value == null\" class=\"text-sky-800\">\n {{'' + path.value}}\n </div>\n <div v-else>\n {{path.value}}\n </div>\n </div>\n </div>\n</div>\n";
1784
1760
 
1785
1761
  /***/ }),
1786
1762
 
@@ -2000,7 +1976,7 @@ module.exports = "<transition name=\"modal\">\n <div class=\"modal-mask\">\n
2000
1976
  /***/ ((module) => {
2001
1977
 
2002
1978
  "use strict";
2003
- 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 .documents-container {\n margin-top: 60px;\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: 0px;\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,.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: #55A3D4;\n}\n\n.models .documents table th, td {\n border-bottom: thin solid rgba(0,0,0,.12);\n text-align: left;\n padding: 0 16px;\n height: 48px;\n}\n\n.models textarea {\n width: 100%;\n height: 600px;\n font-size: 1.2em;\n}\n\n.models .path-type {\n color: rgba(0,0,0,.36);\n font-size: 0.8em;\n}\n\n.models .documents-menu {\n display: flex;\n margin: 0.25em;\n position: fixed;\n width: calc(100vw - 220px);\n}\n\n.models .documents-menu .search-input {\n flex-grow: 1;\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: baseline;\n}\n\n.models .documents .buttons button:not(:last-child) {\n margin-right: 8px;\n}";
1979
+ 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 .documents-container {\n margin-top: 60px;\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: 0px;\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,.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: #55A3D4;\n}\n\n.models .documents table th, td {\n border-bottom: thin solid rgba(0,0,0,.12);\n text-align: left;\n padding: 0 16px;\n height: 48px;\n}\n\n.models textarea {\n width: 100%;\n height: 600px;\n font-size: 1.2em;\n}\n\n.models .path-type {\n color: rgba(0,0,0,.36);\n font-size: 0.8em;\n}\n\n.models .documents-menu {\n display: flex;\n margin: 0.25em;\n position: fixed;\n width: calc(100vw - 220px);\n}\n\n.models .documents-menu .search-input {\n flex-grow: 1;\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: baseline;\n}";
2004
1980
 
2005
1981
  /***/ }),
2006
1982
 
@@ -2011,7 +1987,7 @@ module.exports = ".models {\n position: relative;\n display: flex;\n flex-dir
2011
1987
  /***/ ((module) => {
2012
1988
 
2013
1989
  "use strict";
2014
- module.exports = "<div class=\"models\">\n <div class=\"model-selector\">\n <h1>Models</h1>\n <div v-for=\"model in models\">\n <router-link :to=\"'/model/' + model\" :class=\"model === currentModel ? 'bold' : ''\">\n {{model}}\n </router-link>\n </div>\n </div>\n <div class=\"documents\" ref=\"documentsList\">\n <div>\n <div class=\"documents-menu\">\n <div class=\"search-input\">\n <form @submit.prevent=\"search\">\n <input class=\"search-text\" type=\"text\" placeholder=\"Filter or text\" v-model=\"searchText\" />\n <div>Number of Documents: {{numDocuments}}</div>\n </form>\n\n </div>\n <div class=\"buttons\">\n <button\n @click=\"shouldShowExportModal = true\"\n type=\"button\"\n class=\"rounded bg-puerto-rico-600 px-2 py-1 text-sm font-semibold text-white shadow-sm hover:bg-puerto-rico-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-puerto-rico-600\">\n Export\n </button>\n <button\n @click=\"shouldShowFieldModal = true\"\n type=\"button\"\n class=\"rounded bg-puerto-rico-600 px-2 py-1 text-sm font-semibold text-white shadow-sm hover:bg-puerto-rico-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-puerto-rico-600\">\n Fields\n </button>\n </div>\n </div>\n </div>\n <div class=\"documents-container\">\n <table>\n <thead>\n <th v-for=\"path in filteredPaths\">\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=\"$router.push('/model/' + currentModel + '/document/' + document._id)\" :key=\"document._id\">\n <td v-for=\"schemaPath in filteredPaths\">\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-if=\"status === 'loading'\" class=\"loader\">\n <img src=\"images/loader.gif\">\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 :filter=\"filter\"\n :currentModel=\"currentModel\"\n @done=\"shouldShowExportModal = false\">\n </export-query-results>\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\" style=\"margin-bottom: 0.5em\">\n <input type=\"checkbox\" :id=\"'path.path'+index\" @change=\"addOrRemove(path)\" :value=\"path.path\" :checked=\"isSelected(path.path)\" />\n <label :for=\"'path' + index\">{{path.path}}</label>\n </div>\n <div style=\"margin-top: 1em\">\n <button type=\"submit\" @click=\"filterDocuments()\" style=\"color: black;margin-right: 0.5em\">Filter Selection</button>\n <button type=\"submit\" @click=\"deselectAll()\" class=\"gray\" style=\"margin-right: 0.5em\">Deselect All</button>\n <button type=\"submit\" @click=\"resetDocuments()\" class=\"gray\">Cancel</button>\n \n </div>\n </template>\n </modal>\n </div>\n</div>";
1990
+ module.exports = "<div class=\"models\">\n <div>\n <div class=\"flex grow flex-col gap-y-5 overflow-y-auto border-r border-gray-200 bg-white px-2 h-[calc(100vh-55px)]\">\n <div class=\"flex font-bold font-xl mt-4 pl-2\">\n Models\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 rounded-md py-2 pr-2 pl-2 text-sm font-semibold text-gray-700\"\n :class=\"model === currentModel ? 'bg-puerto-rico-100 font-bold' : 'hover:bg-puerto-rico-100'\">\n {{model}}\n </router-link>\n </li>\n </ul>\n </li>\n </ul>\n </nav>\n </div>\n \n </div>\n <div class=\"documents\" ref=\"documentsList\">\n <div>\n <div class=\"documents-menu\">\n <div class=\"search-input\">\n <form @submit.prevent=\"search\">\n <input class=\"search-text\" type=\"text\" placeholder=\"Filter or text\" v-model=\"searchText\" />\n </form>\n </div>\n <div class=\"buttons\">\n <div class=\"mr-2\">\n <span v-if=\"status === 'loading'\">Loading ...</span>\n <span v-if=\"status === 'loaded'\">{{numDocuments === 1 ? numDocuments+ ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"shouldShowExportModal = true\"\n type=\"button\"\n class=\"mr-2 rounded bg-puerto-rico-600 px-2 py-1 text-sm font-semibold text-white shadow-sm hover:bg-puerto-rico-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-puerto-rico-600\">\n Export\n </button>\n <button\n @click=\"shouldShowFieldModal = true\"\n type=\"button\"\n class=\"rounded bg-puerto-rico-600 px-2 py-1 text-sm font-semibold text-white shadow-sm hover:bg-puerto-rico-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-puerto-rico-600\">\n Fields\n </button>\n </div>\n </div>\n </div>\n <div class=\"documents-container\">\n <table>\n <thead>\n <th v-for=\"path in filteredPaths\">\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=\"$router.push('/model/' + currentModel + '/document/' + document._id)\" :key=\"document._id\">\n <td v-for=\"schemaPath in filteredPaths\">\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-if=\"status === 'loading'\" class=\"loader\">\n <img src=\"images/loader.gif\">\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 :filter=\"filter\"\n :currentModel=\"currentModel\"\n @done=\"shouldShowExportModal = false\">\n </export-query-results>\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\" style=\"margin-bottom: 0.5em\">\n <input type=\"checkbox\" :id=\"'path.path'+index\" @change=\"addOrRemove(path)\" :value=\"path.path\" :checked=\"isSelected(path.path)\" />\n <label :for=\"'path' + index\">{{path.path}}</label>\n </div>\n <div style=\"margin-top: 1em\">\n <button type=\"submit\" @click=\"filterDocuments()\" style=\"color: black;margin-right: 0.5em\">Filter Selection</button>\n <button type=\"submit\" @click=\"deselectAll()\" class=\"gray\" style=\"margin-right: 0.5em\">Deselect All</button>\n <button type=\"submit\" @click=\"resetDocuments()\" class=\"gray\">Cancel</button>\n \n </div>\n </template>\n </modal>\n </div>\n</div>";
2015
1991
 
2016
1992
  /***/ }),
2017
1993
 
@@ -578,14 +578,66 @@ video {
578
578
  }
579
579
  }
580
580
 
581
+ .mb-1 {
582
+ margin-bottom: 0.25rem;
583
+ }
584
+
585
+ .mb-2 {
586
+ margin-bottom: 0.5rem;
587
+ }
588
+
589
+ .mr-2 {
590
+ margin-right: 0.5rem;
591
+ }
592
+
593
+ .mt-4 {
594
+ margin-top: 1rem;
595
+ }
596
+
597
+ .block {
598
+ display: block;
599
+ }
600
+
581
601
  .inline {
582
602
  display: inline;
583
603
  }
584
604
 
605
+ .flex {
606
+ display: flex;
607
+ }
608
+
585
609
  .table {
586
610
  display: table;
587
611
  }
588
612
 
613
+ .h-\[calc\(100vh-55px\)\] {
614
+ height: calc(100vh - 55px);
615
+ }
616
+
617
+ .flex-1 {
618
+ flex: 1 1 0%;
619
+ }
620
+
621
+ .grow {
622
+ flex-grow: 1;
623
+ }
624
+
625
+ .flex-col {
626
+ flex-direction: column;
627
+ }
628
+
629
+ .gap-y-5 {
630
+ row-gap: 1.25rem;
631
+ }
632
+
633
+ .gap-y-7 {
634
+ row-gap: 1.75rem;
635
+ }
636
+
637
+ .overflow-y-auto {
638
+ overflow-y: auto;
639
+ }
640
+
589
641
  .rounded {
590
642
  border-radius: 0.25rem;
591
643
  }
@@ -594,19 +646,28 @@ video {
594
646
  border-radius: 0.375rem;
595
647
  }
596
648
 
649
+ .border-r {
650
+ border-right-width: 1px;
651
+ }
652
+
653
+ .border-gray-200 {
654
+ --tw-border-opacity: 1;
655
+ border-color: rgb(229 231 235 / var(--tw-border-opacity));
656
+ }
657
+
597
658
  .bg-green-600 {
598
659
  --tw-bg-opacity: 1;
599
660
  background-color: rgb(22 163 74 / var(--tw-bg-opacity));
600
661
  }
601
662
 
602
- .bg-puerto-rico-600 {
663
+ .bg-puerto-rico-100 {
603
664
  --tw-bg-opacity: 1;
604
- background-color: rgb(0 164 145 / var(--tw-bg-opacity));
665
+ background-color: rgb(198 255 243 / var(--tw-bg-opacity));
605
666
  }
606
667
 
607
- .bg-indigo-600 {
668
+ .bg-puerto-rico-600 {
608
669
  --tw-bg-opacity: 1;
609
- background-color: rgb(79 70 229 / var(--tw-bg-opacity));
670
+ background-color: rgb(0 164 145 / var(--tw-bg-opacity));
610
671
  }
611
672
 
612
673
  .bg-red-600 {
@@ -614,40 +675,81 @@ video {
614
675
  background-color: rgb(220 38 38 / var(--tw-bg-opacity));
615
676
  }
616
677
 
678
+ .bg-slate-100 {
679
+ --tw-bg-opacity: 1;
680
+ background-color: rgb(241 245 249 / var(--tw-bg-opacity));
681
+ }
682
+
617
683
  .bg-slate-600 {
618
684
  --tw-bg-opacity: 1;
619
685
  background-color: rgb(71 85 105 / var(--tw-bg-opacity));
620
686
  }
621
687
 
688
+ .bg-white {
689
+ --tw-bg-opacity: 1;
690
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity));
691
+ }
692
+
693
+ .p-1 {
694
+ padding: 0.25rem;
695
+ }
696
+
622
697
  .px-2 {
623
698
  padding-left: 0.5rem;
624
699
  padding-right: 0.5rem;
625
700
  }
626
701
 
627
- .py-1 {
628
- padding-top: 0.25rem;
629
- padding-bottom: 0.25rem;
630
- }
631
-
632
702
  .px-2\.5 {
633
703
  padding-left: 0.625rem;
634
704
  padding-right: 0.625rem;
635
705
  }
636
706
 
707
+ .py-1 {
708
+ padding-top: 0.25rem;
709
+ padding-bottom: 0.25rem;
710
+ }
711
+
637
712
  .py-1\.5 {
638
713
  padding-top: 0.375rem;
639
714
  padding-bottom: 0.375rem;
640
715
  }
641
716
 
717
+ .py-2 {
718
+ padding-top: 0.5rem;
719
+ padding-bottom: 0.5rem;
720
+ }
721
+
722
+ .pl-2 {
723
+ padding-left: 0.5rem;
724
+ }
725
+
726
+ .pr-2 {
727
+ padding-right: 0.5rem;
728
+ }
729
+
642
730
  .text-sm {
643
731
  font-size: 0.875rem;
644
732
  line-height: 1.25rem;
645
733
  }
646
734
 
735
+ .font-bold {
736
+ font-weight: 700;
737
+ }
738
+
647
739
  .font-semibold {
648
740
  font-weight: 600;
649
741
  }
650
742
 
743
+ .text-gray-700 {
744
+ --tw-text-opacity: 1;
745
+ color: rgb(55 65 81 / var(--tw-text-opacity));
746
+ }
747
+
748
+ .text-sky-800 {
749
+ --tw-text-opacity: 1;
750
+ color: rgb(7 89 133 / var(--tw-text-opacity));
751
+ }
752
+
651
753
  .text-white {
652
754
  --tw-text-opacity: 1;
653
755
  color: rgb(255 255 255 / var(--tw-text-opacity));
@@ -676,14 +778,14 @@ video {
676
778
  background-color: rgb(34 197 94 / var(--tw-bg-opacity));
677
779
  }
678
780
 
679
- .hover\:bg-puerto-rico-500:hover {
781
+ .hover\:bg-puerto-rico-100:hover {
680
782
  --tw-bg-opacity: 1;
681
- background-color: rgb(0 204 176 / var(--tw-bg-opacity));
783
+ background-color: rgb(198 255 243 / var(--tw-bg-opacity));
682
784
  }
683
785
 
684
- .hover\:bg-indigo-500:hover {
786
+ .hover\:bg-puerto-rico-500:hover {
685
787
  --tw-bg-opacity: 1;
686
- background-color: rgb(99 102 241 / var(--tw-bg-opacity));
788
+ background-color: rgb(0 204 176 / var(--tw-bg-opacity));
687
789
  }
688
790
 
689
791
  .hover\:bg-red-500:hover {
@@ -716,10 +818,6 @@ video {
716
818
  outline-color: #00a491;
717
819
  }
718
820
 
719
- .focus-visible\:outline-indigo-600:focus-visible {
720
- outline-color: #4f46e5;
721
- }
722
-
723
821
  .focus-visible\:outline-red-600:focus-visible {
724
822
  outline-color: #dc2626;
725
823
  }
@@ -56,5 +56,19 @@
56
56
  <component :is="getComponentForPath(path)" :value="getValueForPath(path.path)"></component>
57
57
  </div>
58
58
  </div>
59
+ <div v-for="path in virtuals" class="mb-2">
60
+ <div class="p-1 mb-1 bg-slate-100">
61
+ {{path.name}}
62
+ <span class="path-type">
63
+ (virtual)
64
+ </span>
65
+ </div>
66
+ <div v-if="path.value == null" class="text-sky-800">
67
+ {{'' + path.value}}
68
+ </div>
69
+ <div v-else>
70
+ {{path.value}}
71
+ </div>
72
+ </div>
59
73
  </div>
60
74
  </div>
@@ -17,12 +17,13 @@ module.exports = app => app.component('document', {
17
17
  status: 'init',
18
18
  document: null,
19
19
  changes: {},
20
- editting: false
20
+ editting: false,
21
+ virtuals: []
21
22
  }),
22
23
  async mounted() {
23
24
  const { doc, schemaPaths } = await api.Model.getDocument({ model: this.model, documentId: this.documentId });
24
25
  this.document = doc;
25
- this.schemaPaths = Object.keys(schemaPaths).sort((k1, k2) => {
26
+ this.schemaPaths = await Object.keys(schemaPaths).sort((k1, k2) => {
26
27
  if (k1 === '_id' && k2 !== '_id') {
27
28
  return -1;
28
29
  }
@@ -31,7 +32,7 @@ module.exports = app => app.component('document', {
31
32
  }
32
33
  return 0;
33
34
  }).map(key => schemaPaths[key]);
34
-
35
+ this.getVirtuals();
35
36
  this.status = 'loaded';
36
37
  },
37
38
  methods: {
@@ -59,6 +60,15 @@ module.exports = app => app.component('document', {
59
60
  getEditValueForPath({ path }) {
60
61
  return path in this.changes ? this.changes[path] : mpath.get(path, this.document);
61
62
  },
63
+ getVirtuals() {
64
+ const exists = this.schemaPaths.map(x => x.path);
65
+ const docKeys = Object.keys(this.document);
66
+ for (let i = 0; i < docKeys.length; i++) {
67
+ if (!exists.includes(docKeys[i])) {
68
+ this.virtuals.push({ name: docKeys[i], value: this.document[docKeys[i]] });
69
+ }
70
+ }
71
+ },
62
72
  cancelEdit() {
63
73
  this.changes = {};
64
74
  this.editting = false;
@@ -131,8 +131,4 @@
131
131
  display: inline-flex;
132
132
  justify-content: space-around;
133
133
  align-items: baseline;
134
- }
135
-
136
- .models .documents .buttons button:not(:last-child) {
137
- margin-right: 8px;
138
134
  }
@@ -1,11 +1,27 @@
1
1
  <div class="models">
2
- <div class="model-selector">
3
- <h1>Models</h1>
4
- <div v-for="model in models">
5
- <router-link :to="'/model/' + model" :class="model === currentModel ? 'bold' : ''">
6
- {{model}}
7
- </router-link>
2
+ <div>
3
+ <div class="flex grow flex-col gap-y-5 overflow-y-auto border-r border-gray-200 bg-white px-2 h-[calc(100vh-55px)]">
4
+ <div class="flex font-bold font-xl mt-4 pl-2">
5
+ Models
6
+ </div>
7
+ <nav class="flex flex-1 flex-col">
8
+ <ul role="list" class="flex flex-1 flex-col gap-y-7">
9
+ <li>
10
+ <ul role="list">
11
+ <li v-for="model in models">
12
+ <router-link
13
+ :to="'/model/' + model"
14
+ class="block rounded-md py-2 pr-2 pl-2 text-sm font-semibold text-gray-700"
15
+ :class="model === currentModel ? 'bg-puerto-rico-100 font-bold' : 'hover:bg-puerto-rico-100'">
16
+ {{model}}
17
+ </router-link>
18
+ </li>
19
+ </ul>
20
+ </li>
21
+ </ul>
22
+ </nav>
8
23
  </div>
24
+
9
25
  </div>
10
26
  <div class="documents" ref="documentsList">
11
27
  <div>
@@ -13,15 +29,17 @@
13
29
  <div class="search-input">
14
30
  <form @submit.prevent="search">
15
31
  <input class="search-text" type="text" placeholder="Filter or text" v-model="searchText" />
16
- <div>Number of Documents: {{numDocuments}}</div>
17
32
  </form>
18
-
19
33
  </div>
20
34
  <div class="buttons">
35
+ <div class="mr-2">
36
+ <span v-if="status === 'loading'">Loading ...</span>
37
+ <span v-if="status === 'loaded'">{{numDocuments === 1 ? numDocuments+ ' document' : numDocuments + ' documents'}}</span>
38
+ </div>
21
39
  <button
22
40
  @click="shouldShowExportModal = true"
23
41
  type="button"
24
- class="rounded bg-puerto-rico-600 px-2 py-1 text-sm font-semibold text-white shadow-sm hover:bg-puerto-rico-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-puerto-rico-600">
42
+ class="mr-2 rounded bg-puerto-rico-600 px-2 py-1 text-sm font-semibold text-white shadow-sm hover:bg-puerto-rico-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-puerto-rico-600">
25
43
  Export
26
44
  </button>
27
45
  <button
@@ -68,31 +68,18 @@ module.exports = app => app.component('models', {
68
68
  this.sortDocs(num, path);
69
69
  }
70
70
 
71
+
71
72
  if (this.currentModel != null) {
72
73
  await this.getDocuments();
73
74
  }
74
- this.applyQueryParams();
75
+ if (this.$route.query?.fields) {
76
+ const filter = this.$route.query.fields.split(',');
77
+ this.filteredPaths = this.filteredPaths.filter(x => filter.includes(x.path))
78
+ }
75
79
 
76
80
  this.status = 'loaded';
77
81
  },
78
82
  methods: {
79
- applyQueryParams() {
80
- const hashUrl = window.location.hash.replace(/^#/, '');
81
- if (hashUrl.indexOf('?') !== -1) {
82
- const searchParams = new URLSearchParams(
83
- hashUrl.slice(hashUrl.indexOf('?') + 1)
84
- );
85
- if (searchParams.has('fields')) {
86
- const filter = searchParams.get('fields').split(',');
87
- this.filteredPaths = this.filteredPaths.filter(x => filter.includes(x.path))
88
- }
89
- if (searchParams.has('search')) {
90
- this.searchText = searchParams.get('search');
91
- this.filter = eval(`(${this.searchText})`);
92
- this.filter = EJSON.stringify(this.filter);
93
- }
94
- }
95
- },
96
83
  async onScroll() {
97
84
  if (this.status === 'loading' || this.loadedAllDocs) {
98
85
  return;
@@ -136,22 +123,11 @@ module.exports = app => app.component('models', {
136
123
  this.filter = eval(`(${this.searchText})`);
137
124
  this.filter = EJSON.stringify(this.filter);
138
125
  this.query.search = this.searchText;
126
+ this.$router.push({ query: this.query });
139
127
  } else {
140
128
  this.filter = {};
141
129
  delete this.query.search;
142
- }
143
-
144
- const hashUrl = window.location.hash.replace(/^#/, '');
145
- if (hashUrl.indexOf('?') === -1) {
146
- window.history.pushState({}, '', window.location.pathname + '#' + hashUrl + '?search=' + this.query.search);
147
- } else {
148
- const searchParams = new URLSearchParams(
149
- hashUrl.indexOf('?') === -1 ? '' : hashUrl.slice(hashUrl.indexOf('?') + 1)
150
- );
151
- const hashUrlWithoutSearchParams = hashUrl.slice(0, hashUrl.indexOf('?'));
152
-
153
- searchParams.set('search', this.query.search);
154
- window.history.pushState({}, '', window.location.pathname + '#' + hashUrlWithoutSearchParams + '?' + searchParams);
130
+ this.$router.push({ query: this.query });
155
131
  }
156
132
  await this.loadMoreDocuments();
157
133
  },
@@ -218,23 +194,13 @@ module.exports = app => app.component('models', {
218
194
  this.filteredPaths = [...this.selectedPaths];
219
195
  this.shouldShowFieldModal = false;
220
196
  const selectedParams = this.filteredPaths.map(x => x.path).join(',');
221
- // sets the query params
222
- const hashUrl = window.location.hash.replace(/^#/, '');
223
- if (hashUrl.indexOf('?') === -1) {
224
- window.history.pushState({}, '', window.location.pathname + '#' + hashUrl + '?fields=' + selectedParams);
225
- } else {
226
- const searchParams = new URLSearchParams(
227
- hashUrl.indexOf('?') === -1 ? '' : hashUrl.slice(hashUrl.indexOf('?') + 1)
228
- );
229
- const hashUrlWithoutSearchParams = hashUrl.slice(0, hashUrl.indexOf('?'));
230
-
231
- searchParams.set('fields', selectedParams);
232
- window.history.pushState({}, '', window.location.pathname + '#' + hashUrlWithoutSearchParams + '?' + searchParams);
233
- }
234
-
197
+ this.query.fields = selectedParams;
198
+ this.$router.push({ query: this.query });
235
199
  },
236
200
  resetDocuments() {
237
201
  this.selectedPaths = [...this.filteredPaths];
202
+ this.query.fields = {};
203
+ this.$router.push({ query: this.query });
238
204
  this.shouldShowFieldModal = false;
239
205
  },
240
206
  deselectAll() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
4
4
  "dependencies": {
5
5
  "archetype": "0.13.0",
6
6
  "csv-stringify": "6.3.0",
@@ -10,7 +10,7 @@
10
10
  "vanillatoasts": "^1.6.0"
11
11
  },
12
12
  "peerDependencies": {
13
- "bson": "^5.5.1",
13
+ "bson": "^5.5.1 || 6.x",
14
14
  "express": "4.x",
15
15
  "mongoose": "7.x || 8.0.0-rc0 || 8.x"
16
16
  },