@mongoosejs/studio 0.0.136 → 0.0.138

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.
@@ -126,7 +126,6 @@ var map = {
126
126
  "./list-default/list-default.html": "./frontend/src/list-default/list-default.html",
127
127
  "./list-default/list-default.js": "./frontend/src/list-default/list-default.js",
128
128
  "./list-json/list-json": "./frontend/src/list-json/list-json.js",
129
- "./list-json/list-json.css": "./frontend/src/list-json/list-json.css",
130
129
  "./list-json/list-json.html": "./frontend/src/list-json/list-json.html",
131
130
  "./list-json/list-json.js": "./frontend/src/list-json/list-json.js",
132
131
  "./list-mixed/list-mixed": "./frontend/src/list-mixed/list-mixed.js",
@@ -3565,17 +3564,6 @@ module.exports = app => app.component('list-default', {
3565
3564
 
3566
3565
  /***/ }),
3567
3566
 
3568
- /***/ "./frontend/src/list-json/list-json.css":
3569
- /*!**********************************************!*\
3570
- !*** ./frontend/src/list-json/list-json.css ***!
3571
- \**********************************************/
3572
- /***/ ((module) => {
3573
-
3574
- "use strict";
3575
- module.exports = ".list-json {\n width: 100%;\n}";
3576
-
3577
- /***/ }),
3578
-
3579
3567
  /***/ "./frontend/src/list-json/list-json.html":
3580
3568
  /*!***********************************************!*\
3581
3569
  !*** ./frontend/src/list-json/list-json.html ***!
@@ -3583,7 +3571,7 @@ module.exports = ".list-json {\n width: 100%;\n}";
3583
3571
  /***/ ((module) => {
3584
3572
 
3585
3573
  "use strict";
3586
- module.exports = "<div class=\"list-json tooltip\">\n <pre><code ref=\"JSONCode\" class=\"language-javascript\">{{shortenValue}}</code></pre>\n</div>\n ";
3574
+ 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";
3587
3575
 
3588
3576
  /***/ }),
3589
3577
 
@@ -3596,42 +3584,367 @@ module.exports = "<div class=\"list-json tooltip\">\n <pre><code ref=\"JSONCode
3596
3584
  "use strict";
3597
3585
 
3598
3586
 
3599
- const api = __webpack_require__(/*! ../api */ "./frontend/src/api.js");
3600
3587
  const template = __webpack_require__(/*! ./list-json.html */ "./frontend/src/list-json/list-json.html");
3601
3588
 
3602
- const vanillatoast = __webpack_require__(/*! vanillatoasts */ "./node_modules/vanillatoasts/vanillatoasts.js");
3603
-
3604
- __webpack_require__(/*! ../appendCSS */ "./frontend/src/appendCSS.js")(__webpack_require__(/*! ./list-json.css */ "./frontend/src/list-json/list-json.css"));
3589
+ const JsonNodeTemplate = `
3590
+ <div>
3591
+ <div class="flex items-baseline whitespace-pre" :style="indentStyle">
3592
+ <button
3593
+ v-if="showToggle"
3594
+ type="button"
3595
+ 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"
3596
+ @click.stop="handleToggle"
3597
+ >
3598
+ {{ isCollapsedNode ? '+' : '-' }}
3599
+ </button>
3600
+ <span v-else class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible flex-shrink-0"></span>
3601
+ <template v-if="hasKey">
3602
+ <span class="text-blue-600">"{{ nodeKey }}"</span><span>: </span>
3603
+ </template>
3604
+ <template v-if="isComplex">
3605
+ <template v-if="hasChildren">
3606
+ <span>{{ openingBracket }}</span>
3607
+ <span v-if="isCollapsedNode" class="mx-1">…</span>
3608
+ <span v-if="isCollapsedNode">{{ closingBracket }}{{ comma }}</span>
3609
+ </template>
3610
+ <template v-else>
3611
+ <span>{{ openingBracket }}{{ closingBracket }}{{ comma }}</span>
3612
+ </template>
3613
+ </template>
3614
+ <template v-else>
3615
+ <!--
3616
+ If value is a string and overflows its container (i.e. goes over one line), show an ellipsis.
3617
+ This is done via CSS ellipsis strategy.
3618
+ -->
3619
+ <span
3620
+ :class="valueClasses"
3621
+ :style="typeof value === 'string'
3622
+ ? {
3623
+ display: 'inline-block',
3624
+ maxWidth: '100%',
3625
+ overflow: 'hidden',
3626
+ textOverflow: 'ellipsis',
3627
+ whiteSpace: 'nowrap',
3628
+ verticalAlign: 'bottom'
3629
+ }
3630
+ : {}"
3631
+ :title="typeof value === 'string' && $el && $el.scrollWidth > $el.clientWidth ? value : undefined"
3632
+ >
3633
+ {{ formattedValue }}{{ comma }}
3634
+ </span>
3635
+ </template>
3636
+ </div>
3637
+ <template v-if="isComplex && hasChildren && !isCollapsedNode">
3638
+ <json-node
3639
+ v-for="child in children"
3640
+ :key="child.path"
3641
+ :node-key="child.displayKey"
3642
+ :value="child.value"
3643
+ :level="level + 1"
3644
+ :is-last="child.isLast"
3645
+ :path="child.path"
3646
+ :toggle-collapse="toggleCollapse"
3647
+ :is-collapsed="isCollapsed"
3648
+ :create-child-path="createChildPath"
3649
+ :indent-size="indentSize"
3650
+ :max-top-level-fields="maxTopLevelFields"
3651
+ :top-level-expanded="topLevelExpanded"
3652
+ :expand-top-level="expandTopLevel"
3653
+ ></json-node>
3654
+ <div
3655
+ v-if="hasHiddenRootChildren"
3656
+ class="flex items-baseline whitespace-pre"
3657
+ :style="indentStyle"
3658
+ >
3659
+ <span class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible"></span>
3660
+ <button
3661
+ type="button"
3662
+ 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"
3663
+ :title="hiddenChildrenTooltip"
3664
+ @click.stop="handleExpandTopLevel"
3665
+ >
3666
+ <span aria-hidden="true">{{hiddenChildrenLabel}}…</span>
3667
+ </button>
3668
+ </div>
3669
+ <div class="flex items-baseline whitespace-pre" :style="indentStyle">
3670
+ <span class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible"></span>
3671
+ <span>{{ closingBracket }}{{ comma }}</span>
3672
+ </div>
3673
+ </template>
3674
+ </div>
3675
+ `;
3605
3676
 
3606
3677
  module.exports = app => app.component('list-json', {
3607
3678
  template: template,
3608
3679
  props: ['value'],
3609
- computed: {
3610
- shortenValue() {
3611
- return JSON.stringify(this.value, null, 4);
3680
+ data() {
3681
+ return {
3682
+ collapsedMap: {},
3683
+ indentSize: 16,
3684
+ maxTopLevelFields: 15,
3685
+ topLevelExpanded: false
3686
+ };
3687
+ },
3688
+ watch: {
3689
+ value: {
3690
+ handler() {
3691
+ this.resetCollapse();
3692
+ }
3612
3693
  }
3613
3694
  },
3695
+ created() {
3696
+ this.resetCollapse();
3697
+ },
3614
3698
  methods: {
3615
- copyText(value) {
3616
- const storage = document.createElement('textarea');
3617
- storage.value = JSON.stringify(value);
3618
- const elem = this.$refs.JSONCode;
3619
- elem.appendChild(storage);
3620
- storage.select();
3621
- storage.setSelectionRange(0, 99999);
3622
- document.execCommand('copy');
3623
- elem.removeChild(storage);
3624
- vanillatoast.create({
3625
- title: 'Text copied!',
3626
- type: 'success',
3627
- timeout: 3000,
3628
- icon: 'images/success.png',
3629
- positionClass: 'bottomRight'
3630
- });
3699
+ resetCollapse() {
3700
+ this.collapsedMap = {};
3701
+ this.topLevelExpanded = false;
3702
+ },
3703
+ toggleCollapse(path) {
3704
+ const current = this.isPathCollapsed(path);
3705
+ this.collapsedMap = Object.assign({}, this.collapsedMap, { [path]: !current });
3706
+ },
3707
+ isPathCollapsed(path) {
3708
+ if (path === 'root') {
3709
+ return false;
3710
+ }
3711
+ if (Object.prototype.hasOwnProperty.call(this.collapsedMap, path)) {
3712
+ return this.collapsedMap[path];
3713
+ }
3714
+ return true;
3715
+ },
3716
+ createChildPath(parentPath, childKey, isArray) {
3717
+ if (parentPath == null || parentPath === '') {
3718
+ return isArray ? `[${childKey}]` : `${childKey}`;
3719
+ }
3720
+ if (parentPath === 'root') {
3721
+ return isArray ? `root[${childKey}]` : `root.${childKey}`;
3722
+ }
3723
+ if (isArray) {
3724
+ return `${parentPath}[${childKey}]`;
3725
+ }
3726
+ return `${parentPath}.${childKey}`;
3727
+ },
3728
+ expandTopLevel() {
3729
+ this.topLevelExpanded = true;
3631
3730
  }
3632
3731
  },
3633
- mounted: function() {
3634
- Prism.highlightElement(this.$refs.JSONCode);
3732
+ components: {
3733
+ JsonNode: {
3734
+ name: 'JsonNode',
3735
+ template: JsonNodeTemplate,
3736
+ props: {
3737
+ nodeKey: {
3738
+ type: [String, Number],
3739
+ default: null
3740
+ },
3741
+ value: {
3742
+ required: true
3743
+ },
3744
+ level: {
3745
+ type: Number,
3746
+ required: true
3747
+ },
3748
+ isLast: {
3749
+ type: Boolean,
3750
+ default: false
3751
+ },
3752
+ path: {
3753
+ type: String,
3754
+ required: true
3755
+ },
3756
+ toggleCollapse: {
3757
+ type: Function,
3758
+ required: true
3759
+ },
3760
+ isCollapsed: {
3761
+ type: Function,
3762
+ required: true
3763
+ },
3764
+ createChildPath: {
3765
+ type: Function,
3766
+ required: true
3767
+ },
3768
+ indentSize: {
3769
+ type: Number,
3770
+ required: true
3771
+ },
3772
+ maxTopLevelFields: {
3773
+ type: Number,
3774
+ default: null
3775
+ },
3776
+ topLevelExpanded: {
3777
+ type: Boolean,
3778
+ default: false
3779
+ },
3780
+ expandTopLevel: {
3781
+ type: Function,
3782
+ default: null
3783
+ }
3784
+ },
3785
+ computed: {
3786
+ hasKey() {
3787
+ return this.nodeKey !== null && this.nodeKey !== undefined;
3788
+ },
3789
+ isRoot() {
3790
+ return this.path === 'root';
3791
+ },
3792
+ isArray() {
3793
+ return Array.isArray(this.value);
3794
+ },
3795
+ isObject() {
3796
+ if (this.value === null || this.isArray) {
3797
+ return false;
3798
+ }
3799
+ return Object.prototype.toString.call(this.value) === '[object Object]';
3800
+ },
3801
+ isComplex() {
3802
+ return this.isArray || this.isObject;
3803
+ },
3804
+ children() {
3805
+ if (!this.isComplex) {
3806
+ return [];
3807
+ }
3808
+ if (this.isArray) {
3809
+ return this.value.map((childValue, index) => ({
3810
+ displayKey: null,
3811
+ value: childValue,
3812
+ isLast: index === this.value.length - 1,
3813
+ path: this.createChildPath(this.path, index, true)
3814
+ }));
3815
+ }
3816
+ const keys = Object.keys(this.value);
3817
+ const visibleKeys = this.visibleObjectKeys(keys);
3818
+ const hasHidden = this.hasHiddenRootChildren;
3819
+ return visibleKeys.map((key, index) => ({
3820
+ displayKey: key,
3821
+ value: this.value[key],
3822
+ isLast: !hasHidden && index === visibleKeys.length - 1,
3823
+ path: this.createChildPath(this.path, key, false)
3824
+ }));
3825
+ },
3826
+ hasChildren() {
3827
+ return this.children.length > 0;
3828
+ },
3829
+ totalObjectChildCount() {
3830
+ if (!this.isObject) {
3831
+ return 0;
3832
+ }
3833
+ return Object.keys(this.value).length;
3834
+ },
3835
+ hasHiddenRootChildren() {
3836
+ if (!this.isRoot || !this.isObject) {
3837
+ return false;
3838
+ }
3839
+ if (this.topLevelExpanded) {
3840
+ return false;
3841
+ }
3842
+ if (typeof this.maxTopLevelFields !== 'number') {
3843
+ return false;
3844
+ }
3845
+ return this.totalObjectChildCount > this.maxTopLevelFields;
3846
+ },
3847
+ hiddenRootChildrenCount() {
3848
+ if (!this.hasHiddenRootChildren) {
3849
+ return 0;
3850
+ }
3851
+ return this.totalObjectChildCount - this.maxTopLevelFields;
3852
+ },
3853
+ showToggle() {
3854
+ return this.hasChildren && !this.isRoot;
3855
+ },
3856
+ openingBracket() {
3857
+ return this.isArray ? '[' : '{';
3858
+ },
3859
+ closingBracket() {
3860
+ return this.isArray ? ']' : '}';
3861
+ },
3862
+ isCollapsedNode() {
3863
+ return this.isCollapsed(this.path);
3864
+ },
3865
+ formattedValue() {
3866
+ if (typeof this.value === 'bigint') {
3867
+ return `${this.value.toString()}n`;
3868
+ }
3869
+ const stringified = JSON.stringify(this.value);
3870
+ if (stringified === undefined) {
3871
+ if (typeof this.value === 'symbol') {
3872
+ return this.value.toString();
3873
+ }
3874
+ return String(this.value);
3875
+ }
3876
+ return stringified;
3877
+ },
3878
+ valueClasses() {
3879
+ const classes = ['text-slate-700'];
3880
+ if (this.value === null) {
3881
+ classes.push('text-gray-500', 'italic');
3882
+ return classes;
3883
+ }
3884
+ const type = typeof this.value;
3885
+ if (type === 'string') {
3886
+ classes.push('text-emerald-600');
3887
+ return classes;
3888
+ }
3889
+ if (type === 'number' || type === 'bigint') {
3890
+ classes.push('text-amber-600');
3891
+ return classes;
3892
+ }
3893
+ if (type === 'boolean') {
3894
+ classes.push('text-violet-600');
3895
+ return classes;
3896
+ }
3897
+ if (type === 'undefined') {
3898
+ classes.push('text-gray-500');
3899
+ return classes;
3900
+ }
3901
+ return classes;
3902
+ },
3903
+ comma() {
3904
+ return this.isLast ? '' : ',';
3905
+ },
3906
+ indentStyle() {
3907
+ return {
3908
+ paddingLeft: `${this.level * this.indentSize}px`
3909
+ };
3910
+ },
3911
+ hiddenChildrenLabel() {
3912
+ if (!this.hasHiddenRootChildren) {
3913
+ return '';
3914
+ }
3915
+ const count = this.hiddenRootChildrenCount;
3916
+ const suffix = count === 1 ? 'field' : 'fields';
3917
+ return `${count} more ${suffix}`;
3918
+ },
3919
+ hiddenChildrenTooltip() {
3920
+ return this.hiddenChildrenLabel;
3921
+ }
3922
+ },
3923
+ methods: {
3924
+ visibleObjectKeys(keys) {
3925
+ if (!this.isRoot || this.topLevelExpanded) {
3926
+ return keys;
3927
+ }
3928
+ if (typeof this.maxTopLevelFields !== 'number') {
3929
+ return keys;
3930
+ }
3931
+ if (keys.length <= this.maxTopLevelFields) {
3932
+ return keys;
3933
+ }
3934
+ return keys.slice(0, this.maxTopLevelFields);
3935
+ },
3936
+ handleToggle() {
3937
+ if (!this.isRoot) {
3938
+ this.toggleCollapse(this.path);
3939
+ }
3940
+ },
3941
+ handleExpandTopLevel() {
3942
+ if (this.isRoot && typeof this.expandTopLevel === 'function') {
3943
+ this.expandTopLevel();
3944
+ }
3945
+ }
3946
+ }
3947
+ }
3635
3948
  }
3636
3949
  });
3637
3950
 
@@ -3909,7 +4222,7 @@ module.exports = app => app.component('modal', {
3909
4222
  /***/ ((module) => {
3910
4223
 
3911
4224
  "use strict";
3912
- 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";
4225
+ 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";
3913
4226
 
3914
4227
  /***/ }),
3915
4228
 
@@ -3920,7 +4233,7 @@ module.exports = ".models {\n position: relative;\n display: flex;\n flex-dir
3920
4233
  /***/ ((module) => {
3921
4234
 
3922
4235
  "use strict";
3923
- 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 </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=\"outputType = '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=\"outputType = '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 <table v-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-if=\"outputType === 'json'\">\n <div v-for=\"document in documents\" @click=\"handleDocumentClick(document, $event)\" :key=\"document._id\" :class=\"{ 'bg-blue-200': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }\">\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";
4236
+ 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 </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 <table v-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-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";
3924
4237
 
3925
4238
  /***/ }),
3926
4239
 
@@ -3971,6 +4284,7 @@ const QUERY_SELECTORS = [
3971
4284
  appendCSS(__webpack_require__(/*! ./models.css */ "./frontend/src/models/models.css"));
3972
4285
 
3973
4286
  const limit = 20;
4287
+ const OUTPUT_TYPE_STORAGE_KEY = 'studio:model-output-type';
3974
4288
 
3975
4289
  module.exports = app => app.component('models', {
3976
4290
  template: template,
@@ -4013,6 +4327,7 @@ module.exports = app => app.component('models', {
4013
4327
  created() {
4014
4328
  this.currentModel = this.model;
4015
4329
  this.buildAutocompleteTrie();
4330
+ this.loadOutputPreference();
4016
4331
  },
4017
4332
  beforeDestroy() {
4018
4333
  document.removeEventListener('scroll', this.onScroll, true);
@@ -4041,6 +4356,24 @@ module.exports = app => app.component('models', {
4041
4356
  this.autocompleteTrie.bulkInsert(paths, 10);
4042
4357
  }
4043
4358
  },
4359
+ loadOutputPreference() {
4360
+ if (typeof window === 'undefined' || !window.localStorage) {
4361
+ return;
4362
+ }
4363
+ const storedPreference = window.localStorage.getItem(OUTPUT_TYPE_STORAGE_KEY);
4364
+ if (storedPreference === 'json' || storedPreference === 'table') {
4365
+ this.outputType = storedPreference;
4366
+ }
4367
+ },
4368
+ setOutputType(type) {
4369
+ if (type !== 'json' && type !== 'table') {
4370
+ return;
4371
+ }
4372
+ this.outputType = type;
4373
+ if (typeof window !== 'undefined' && window.localStorage) {
4374
+ window.localStorage.setItem(OUTPUT_TYPE_STORAGE_KEY, type);
4375
+ }
4376
+ },
4044
4377
  buildDocumentFetchParams(options = {}) {
4045
4378
  const params = {
4046
4379
  model: this.currentModel,
@@ -4480,41 +4813,52 @@ module.exports = app => app.component('models', {
4480
4813
  },
4481
4814
  handleDocumentClick(document, event) {
4482
4815
  if (this.selectMultiple) {
4483
- const documentIndex = this.documents.findIndex(doc => doc._id.toString() == document._id.toString());
4484
- if (event?.shiftKey && this.selectedDocuments.length > 0) {
4485
- const anchorIndex = this.lastSelectedIndex;
4486
- if (anchorIndex != null && anchorIndex !== -1 && documentIndex !== -1) {
4487
- const start = Math.min(anchorIndex, documentIndex);
4488
- const end = Math.max(anchorIndex, documentIndex);
4489
- const selectedDocumentIds = new Set(this.selectedDocuments.map(doc => doc._id.toString()));
4490
- for (let i = start; i <= end; i++) {
4491
- const docInRange = this.documents[i];
4492
- const existsInRange = selectedDocumentIds.has(docInRange._id.toString());
4493
- if (!existsInRange) {
4494
- this.selectedDocuments.push(docInRange);
4495
- }
4816
+ this.handleDocumentSelection(document, event);
4817
+ } else {
4818
+ this.openDocument(document);
4819
+ }
4820
+ },
4821
+ handleDocumentContainerClick(document, event) {
4822
+ if (this.selectMultiple) {
4823
+ this.handleDocumentSelection(document, event);
4824
+ }
4825
+ },
4826
+ handleDocumentSelection(document, event) {
4827
+ const documentIndex = this.documents.findIndex(doc => doc._id.toString() == document._id.toString());
4828
+ if (event?.shiftKey && this.selectedDocuments.length > 0) {
4829
+ const anchorIndex = this.lastSelectedIndex;
4830
+ if (anchorIndex != null && anchorIndex !== -1 && documentIndex !== -1) {
4831
+ const start = Math.min(anchorIndex, documentIndex);
4832
+ const end = Math.max(anchorIndex, documentIndex);
4833
+ const selectedDocumentIds = new Set(this.selectedDocuments.map(doc => doc._id.toString()));
4834
+ for (let i = start; i <= end; i++) {
4835
+ const docInRange = this.documents[i];
4836
+ const existsInRange = selectedDocumentIds.has(docInRange._id.toString());
4837
+ if (!existsInRange) {
4838
+ this.selectedDocuments.push(docInRange);
4496
4839
  }
4497
- this.lastSelectedIndex = documentIndex;
4498
- return;
4499
4840
  }
4841
+ this.lastSelectedIndex = documentIndex;
4842
+ return;
4500
4843
  }
4501
- const index = this.selectedDocuments.findIndex(x => x._id.toString() == document._id.toString());
4502
- if (index !== -1) {
4503
- this.selectedDocuments.splice(index, 1);
4504
- if (this.selectedDocuments.length === 0) {
4505
- this.lastSelectedIndex = null;
4506
- } else {
4507
- const lastDoc = this.selectedDocuments[this.selectedDocuments.length - 1];
4508
- this.lastSelectedIndex = this.documents.findIndex(doc => doc._id.toString() == lastDoc._id.toString());
4509
- }
4844
+ }
4845
+ const index = this.selectedDocuments.findIndex(x => x._id.toString() == document._id.toString());
4846
+ if (index !== -1) {
4847
+ this.selectedDocuments.splice(index, 1);
4848
+ if (this.selectedDocuments.length === 0) {
4849
+ this.lastSelectedIndex = null;
4510
4850
  } else {
4511
- this.selectedDocuments.push(document);
4512
- this.lastSelectedIndex = documentIndex;
4851
+ const lastDoc = this.selectedDocuments[this.selectedDocuments.length - 1];
4852
+ this.lastSelectedIndex = this.documents.findIndex(doc => doc._id.toString() == lastDoc._id.toString());
4513
4853
  }
4514
4854
  } else {
4515
- this.$router.push('/model/' + this.currentModel + '/document/' + document._id);
4855
+ this.selectedDocuments.push(document);
4856
+ this.lastSelectedIndex = documentIndex;
4516
4857
  }
4517
4858
  },
4859
+ openDocument(document) {
4860
+ this.$router.push('/model/' + this.currentModel + '/document/' + document._id);
4861
+ },
4518
4862
  async deleteDocuments() {
4519
4863
  const documentIds = this.selectedDocuments.map(x => x._id);
4520
4864
  await api.Model.deleteDocuments({
@@ -15932,7 +16276,7 @@ module.exports = function stringToParts(str) {
15932
16276
  /***/ ((module) => {
15933
16277
 
15934
16278
  "use strict";
15935
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.136","description":"A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.","homepage":"https://studio.mongoosejs.io/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.1.0","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vanillatoasts":"^1.6.0","vue":"3.x","webpack":"5.x"},"peerDependencies":{"bson":"^5.5.1 || 6.x","express":"4.x","mongoose":"7.x || 8.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongoose":"8.x"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js"}}');
16279
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.138","description":"A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.","homepage":"https://studio.mongoosejs.io/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.1.0","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vanillatoasts":"^1.6.0","vue":"3.x","webpack":"5.x"},"peerDependencies":{"bson":"^5.5.1 || 6.x","express":"4.x","mongoose":"7.x || 8.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongoose":"8.x"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js"}}');
15936
16280
 
15937
16281
  /***/ })
15938
16282