@mongoosejs/studio 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -5
- package/backend/actions/Model/getDocument.js +3 -1
- package/backend/actions/Model/getDocuments.js +2 -1
- package/backend/actions/Model/getDocumentsStream.js +2 -1
- package/frontend/public/app.js +245 -3
- package/frontend/public/tw.css +5 -0
- package/frontend/src/document-details/document-property/document-property.html +13 -0
- package/frontend/src/document-details/document-property/document-property.js +70 -1
- package/frontend/src/edit-string/edit-string.html +32 -0
- package/frontend/src/edit-string/edit-string.js +148 -0
- package/next.js +11 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,11 +62,14 @@ Then, add `pages/api/studio.js` to your Next.js project to host the Mongoose Stu
|
|
|
62
62
|
import db from '../../src/db';
|
|
63
63
|
import studio from '@mongoosejs/studio/backend/next';
|
|
64
64
|
|
|
65
|
-
const handler = studio(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
const handler = studio(
|
|
66
|
+
db, // Mongoose connection or Mongoose global. Or null to use `import mongoose`.
|
|
67
|
+
{
|
|
68
|
+
apiKey: process.env.MONGOOSE_STUDIO_API_KEY, // optional
|
|
69
|
+
connection: db, // Optional: Connection or Mongoose global. If omitted, will use `import mongoose`
|
|
70
|
+
connectToDB: async () => { /* connection logic here */ }, // Optional: if you need to call a function to connect to the database put it here
|
|
71
|
+
}
|
|
72
|
+
);
|
|
70
73
|
|
|
71
74
|
export default handler;
|
|
72
75
|
```
|
|
@@ -37,7 +37,9 @@ module.exports = ({ db }) => async function getDocument(params) {
|
|
|
37
37
|
schemaPaths[path] = {
|
|
38
38
|
instance: Model.schema.paths[path].instance,
|
|
39
39
|
path,
|
|
40
|
-
ref: Model.schema.paths[path].options?.ref
|
|
40
|
+
ref: Model.schema.paths[path].options?.ref,
|
|
41
|
+
required: Model.schema.paths[path].options?.required,
|
|
42
|
+
enum: Model.schema.paths[path].options?.enum
|
|
41
43
|
};
|
|
42
44
|
}
|
|
43
45
|
removeSpecifiedPaths(schemaPaths, '.$*');
|
|
@@ -77,7 +77,8 @@ module.exports = ({ db }) => async function getDocuments(params) {
|
|
|
77
77
|
instance: Model.schema.paths[path].instance,
|
|
78
78
|
path,
|
|
79
79
|
ref: Model.schema.paths[path].options?.ref,
|
|
80
|
-
required: Model.schema.paths[path].options?.required
|
|
80
|
+
required: Model.schema.paths[path].options?.required,
|
|
81
|
+
enum: Model.schema.paths[path].options?.enum
|
|
81
82
|
};
|
|
82
83
|
}
|
|
83
84
|
removeSpecifiedPaths(schemaPaths, '.$*');
|
|
@@ -67,7 +67,8 @@ module.exports = ({ db }) => async function* getDocumentsStream(params) {
|
|
|
67
67
|
instance: Model.schema.paths[path].instance,
|
|
68
68
|
path,
|
|
69
69
|
ref: Model.schema.paths[path].options?.ref,
|
|
70
|
-
required: Model.schema.paths[path].options?.required
|
|
70
|
+
required: Model.schema.paths[path].options?.required,
|
|
71
|
+
enum: Model.schema.paths[path].options?.enum
|
|
71
72
|
};
|
|
72
73
|
}
|
|
73
74
|
removeSpecifiedPaths(schemaPaths, '.$*');
|
package/frontend/public/app.js
CHANGED
|
@@ -106,6 +106,9 @@ var map = {
|
|
|
106
106
|
"./edit-number/edit-number": "./frontend/src/edit-number/edit-number.js",
|
|
107
107
|
"./edit-number/edit-number.html": "./frontend/src/edit-number/edit-number.html",
|
|
108
108
|
"./edit-number/edit-number.js": "./frontend/src/edit-number/edit-number.js",
|
|
109
|
+
"./edit-string/edit-string": "./frontend/src/edit-string/edit-string.js",
|
|
110
|
+
"./edit-string/edit-string.html": "./frontend/src/edit-string/edit-string.html",
|
|
111
|
+
"./edit-string/edit-string.js": "./frontend/src/edit-string/edit-string.js",
|
|
109
112
|
"./edit-subdocument/edit-subdocument": "./frontend/src/edit-subdocument/edit-subdocument.js",
|
|
110
113
|
"./edit-subdocument/edit-subdocument.html": "./frontend/src/edit-subdocument/edit-subdocument.html",
|
|
111
114
|
"./edit-subdocument/edit-subdocument.js": "./frontend/src/edit-subdocument/edit-subdocument.js",
|
|
@@ -2517,7 +2520,7 @@ module.exports = ".document-details {\n width: 100%;\n }\n \n .document-de
|
|
|
2517
2520
|
/***/ ((module) => {
|
|
2518
2521
|
|
|
2519
2522
|
"use strict";
|
|
2520
|
-
module.exports = "<div class=\"border border-gray-200 rounded-lg mb-2\">\n <!-- Collapsible Header -->\n <div\n @click=\"toggleCollapse\"\n class=\"p-3 hover:bg-gray-100 cursor-pointer flex items-center justify-between border-b border-gray-200 transition-colors duration-200 ease-in-out\"\n :class=\"{ 'bg-amber-100': highlight, 'bg-gray-50': !highlight }\"\n >\n <div class=\"flex items-center\" >\n <svg\n :class=\"isCollapsed ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-gray-500 mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-gray-900\">{{path.path}}</span>\n <span class=\"ml-2 text-sm text-gray-500\">({{(path.instance || 'unknown').toLowerCase()}})</span>\n </div>\n <div class=\"flex items-center gap-2\">\n <router-link\n v-if=\"path.ref && getValueForPath(path.path)\"\n :to=\"`/model/${path.ref}/document/${getValueForPath(path.path)}`\"\n class=\"bg-ultramarine-600 hover:bg-ultramarine-500 text-white px-2 py-1 text-sm rounded-md\"\n @click.stop\n >View Document\n </router-link>\n </div>\n </div>\n\n <!-- Collapsible Content -->\n <div v-if=\"!isCollapsed\" class=\"p-3\">\n <!-- Date Type Selector (when editing dates) -->\n <div v-if=\"editting && path.instance === 'Date'\" class=\"mb-3 flex gap-1.5\">\n <div\n @click=\"dateType = 'picker'\"\n :class=\"dateType === 'picker' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'picker' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n Date Picker\n </div>\n </div>\n <div\n @click=\"dateType = 'iso'\"\n :class=\"dateType === 'iso' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'iso' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n ISO String\n </div>\n </div>\n </div>\n\n <!-- Field Content -->\n <div v-if=\"editting && path.path !== '_id'\">\n <component\n :is=\"getEditComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :format=\"dateType\"\n @input=\"changes[path.path] = $event; delete invalid[path.path];\"\n @error=\"invalid[path.path] = $event;\"\n >\n </component>\n </div>\n <div v-else>\n <!-- Show truncated or full value based on needsTruncation and isValueExpanded -->\n <div v-if=\"needsTruncation && !isValueExpanded\" class=\"relative\">\n <div class=\"text-gray-700 whitespace-pre-wrap break-words font-mono text-sm\">{{truncatedString}}</div>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show more ({{valueAsString.length}} characters)\n </button>\n </div>\n <div v-else-if=\"needsTruncation && isValueExpanded\" class=\"relative\">\n <component :is=\"getComponentForPath(path)\" :value=\"getValueForPath(path.path)\"></component>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4 rotate-180\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show less\n </button>\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";
|
|
2523
|
+
module.exports = "<div class=\"border border-gray-200 rounded-lg mb-2\">\n <!-- Collapsible Header -->\n <div\n @click=\"toggleCollapse\"\n class=\"p-3 hover:bg-gray-100 cursor-pointer flex items-center justify-between border-b border-gray-200 transition-colors duration-200 ease-in-out\"\n :class=\"{ 'bg-amber-100': highlight, 'bg-gray-50': !highlight }\"\n >\n <div class=\"flex items-center\" >\n <svg\n :class=\"isCollapsed ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-gray-500 mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-gray-900\">{{path.path}}</span>\n <span class=\"ml-2 text-sm text-gray-500\">({{(path.instance || 'unknown').toLowerCase()}})</span>\n </div>\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"flex items-center gap-1 text-sm text-gray-600 hover:text-gray-800 px-2 py-1 rounded-md border border-transparent hover:border-gray-300 bg-white\"\n @click.stop.prevent=\"copyPropertyValue\"\n title=\"Copy value\"\n aria-label=\"Copy property value\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7h8m-8 4h8m-8 4h5m-7-9a2 2 0 012-2h7a2 2 0 012 2v10a2 2 0 01-2 2H8l-4-4V7a2 2 0 012-2z\" />\n </svg>\n {{copyButtonLabel}}\n </button>\n <router-link\n v-if=\"path.ref && getValueForPath(path.path)\"\n :to=\"`/model/${path.ref}/document/${getValueForPath(path.path)}`\"\n class=\"bg-ultramarine-600 hover:bg-ultramarine-500 text-white px-2 py-1 text-sm rounded-md\"\n @click.stop\n >View Document\n </router-link>\n </div>\n </div>\n\n <!-- Collapsible Content -->\n <div v-if=\"!isCollapsed\" class=\"p-3\">\n <!-- Date Type Selector (when editing dates) -->\n <div v-if=\"editting && path.instance === 'Date'\" class=\"mb-3 flex gap-1.5\">\n <div\n @click=\"dateType = 'picker'\"\n :class=\"dateType === 'picker' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'picker' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n Date Picker\n </div>\n </div>\n <div\n @click=\"dateType = 'iso'\"\n :class=\"dateType === 'iso' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'iso' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n ISO String\n </div>\n </div>\n </div>\n\n <!-- Field Content -->\n <div v-if=\"editting && path.path !== '_id'\">\n <component\n :is=\"getEditComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :format=\"dateType\"\n v-bind=\"getEditComponentProps(path)\"\n @input=\"changes[path.path] = $event; delete invalid[path.path];\"\n @error=\"invalid[path.path] = $event;\"\n >\n </component>\n </div>\n <div v-else>\n <!-- Show truncated or full value based on needsTruncation and isValueExpanded -->\n <div v-if=\"needsTruncation && !isValueExpanded\" class=\"relative\">\n <div class=\"text-gray-700 whitespace-pre-wrap break-words font-mono text-sm\">{{truncatedString}}</div>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show more ({{valueAsString.length}} characters)\n </button>\n </div>\n <div v-else-if=\"needsTruncation && isValueExpanded\" class=\"relative\">\n <component :is=\"getComponentForPath(path)\" :value=\"getValueForPath(path.path)\"></component>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4 rotate-180\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show less\n </button>\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";
|
|
2521
2524
|
|
|
2522
2525
|
/***/ }),
|
|
2523
2526
|
|
|
@@ -2528,6 +2531,8 @@ module.exports = "<div class=\"border border-gray-200 rounded-lg mb-2\">\n <!--
|
|
|
2528
2531
|
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
2529
2532
|
|
|
2530
2533
|
"use strict";
|
|
2534
|
+
/* global clearTimeout setTimeout */
|
|
2535
|
+
|
|
2531
2536
|
|
|
2532
2537
|
|
|
2533
2538
|
const mpath = __webpack_require__(/*! mpath */ "./node_modules/mpath/index.js");
|
|
@@ -2543,9 +2548,17 @@ module.exports = app => app.component('document-property', {
|
|
|
2543
2548
|
return {
|
|
2544
2549
|
dateType: 'picker', // picker, iso
|
|
2545
2550
|
isCollapsed: false, // Start uncollapsed by default
|
|
2546
|
-
isValueExpanded: false // Track if the value is expanded
|
|
2551
|
+
isValueExpanded: false, // Track if the value is expanded
|
|
2552
|
+
copyButtonLabel: 'Copy',
|
|
2553
|
+
copyResetTimeoutId: null
|
|
2547
2554
|
};
|
|
2548
2555
|
},
|
|
2556
|
+
beforeDestroy() {
|
|
2557
|
+
if (this.copyResetTimeoutId) {
|
|
2558
|
+
clearTimeout(this.copyResetTimeoutId);
|
|
2559
|
+
this.copyResetTimeoutId = null;
|
|
2560
|
+
}
|
|
2561
|
+
},
|
|
2549
2562
|
props: ['path', 'document', 'schemaPaths', 'editting', 'changes', 'invalid', 'highlight'],
|
|
2550
2563
|
computed: {
|
|
2551
2564
|
valueAsString() {
|
|
@@ -2584,6 +2597,9 @@ module.exports = app => app.component('document-property', {
|
|
|
2584
2597
|
return 'detail-default';
|
|
2585
2598
|
},
|
|
2586
2599
|
getEditComponentForPath(path) {
|
|
2600
|
+
if (path.instance === 'String') {
|
|
2601
|
+
return 'edit-string';
|
|
2602
|
+
}
|
|
2587
2603
|
if (path.instance == 'Date') {
|
|
2588
2604
|
return 'edit-date';
|
|
2589
2605
|
}
|
|
@@ -2601,6 +2617,15 @@ module.exports = app => app.component('document-property', {
|
|
|
2601
2617
|
}
|
|
2602
2618
|
return 'edit-default';
|
|
2603
2619
|
},
|
|
2620
|
+
getEditComponentProps(path) {
|
|
2621
|
+
const props = {};
|
|
2622
|
+
if (path.instance === 'String') {
|
|
2623
|
+
if (path.enum?.length > 0) {
|
|
2624
|
+
props.enumValues = path.enum;
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
return props;
|
|
2628
|
+
},
|
|
2604
2629
|
getValueForPath(path) {
|
|
2605
2630
|
if (this.document == null) {
|
|
2606
2631
|
return undefined;
|
|
@@ -2622,6 +2647,53 @@ module.exports = app => app.component('document-property', {
|
|
|
2622
2647
|
},
|
|
2623
2648
|
toggleValueExpansion() {
|
|
2624
2649
|
this.isValueExpanded = !this.isValueExpanded;
|
|
2650
|
+
},
|
|
2651
|
+
setCopyFeedback() {
|
|
2652
|
+
this.copyButtonLabel = 'Copied';
|
|
2653
|
+
if (this.copyResetTimeoutId) {
|
|
2654
|
+
clearTimeout(this.copyResetTimeoutId);
|
|
2655
|
+
}
|
|
2656
|
+
this.copyResetTimeoutId = setTimeout(() => {
|
|
2657
|
+
this.copyButtonLabel = 'Copy';
|
|
2658
|
+
this.copyResetTimeoutId = null;
|
|
2659
|
+
}, 5000);
|
|
2660
|
+
},
|
|
2661
|
+
copyPropertyValue() {
|
|
2662
|
+
const textToCopy = this.valueAsString;
|
|
2663
|
+
if (textToCopy == null) {
|
|
2664
|
+
return;
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
const fallbackCopy = () => {
|
|
2668
|
+
if (typeof document === 'undefined') {
|
|
2669
|
+
return;
|
|
2670
|
+
}
|
|
2671
|
+
const textArea = document.createElement('textarea');
|
|
2672
|
+
textArea.value = textToCopy;
|
|
2673
|
+
textArea.setAttribute('readonly', '');
|
|
2674
|
+
textArea.style.position = 'absolute';
|
|
2675
|
+
textArea.style.left = '-9999px';
|
|
2676
|
+
document.body.appendChild(textArea);
|
|
2677
|
+
textArea.select();
|
|
2678
|
+
try {
|
|
2679
|
+
document.execCommand('copy');
|
|
2680
|
+
} finally {
|
|
2681
|
+
document.body.removeChild(textArea);
|
|
2682
|
+
}
|
|
2683
|
+
this.setCopyFeedback();
|
|
2684
|
+
};
|
|
2685
|
+
|
|
2686
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard && navigator.clipboard.writeText) {
|
|
2687
|
+
navigator.clipboard.writeText(textToCopy)
|
|
2688
|
+
.then(() => {
|
|
2689
|
+
this.setCopyFeedback();
|
|
2690
|
+
})
|
|
2691
|
+
.catch(() => {
|
|
2692
|
+
fallbackCopy();
|
|
2693
|
+
});
|
|
2694
|
+
} else {
|
|
2695
|
+
fallbackCopy();
|
|
2696
|
+
}
|
|
2625
2697
|
}
|
|
2626
2698
|
}
|
|
2627
2699
|
});
|
|
@@ -3186,6 +3258,176 @@ module.exports = app => app.component('edit-number', {
|
|
|
3186
3258
|
}
|
|
3187
3259
|
});
|
|
3188
3260
|
|
|
3261
|
+
/***/ }),
|
|
3262
|
+
|
|
3263
|
+
/***/ "./frontend/src/edit-string/edit-string.html":
|
|
3264
|
+
/*!***************************************************!*\
|
|
3265
|
+
!*** ./frontend/src/edit-string/edit-string.html ***!
|
|
3266
|
+
\***************************************************/
|
|
3267
|
+
/***/ ((module) => {
|
|
3268
|
+
|
|
3269
|
+
"use strict";
|
|
3270
|
+
module.exports = "<div>\n <div v-if=\"hasEnumValues\" class=\"space-y-2\">\n <select\n class=\"w-full px-3 py-2 border border-gray-300 bg-white rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :value=\"selectedOption\"\n @change=\"onSelectChange\"\n >\n <option v-for=\"option in normalizedEnums\" :key=\"`enum-${option}`\" :value=\"option\">\n {{ option }}\n </option>\n <option :value=\"'__null'\">null</option>\n <option :value=\"'__other'\">Other</option>\n </select>\n <input\n v-if=\"selectedOption === '__other'\"\n type=\"text\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :value=\"otherValue\"\n @input=\"onOtherInput\"\n placeholder=\"Enter a value\"\n />\n </div>\n <div v-else>\n <input\n type=\"text\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :value=\"value != null ? value : ''\"\n @input=\"onTextInput\"\n placeholder=\"Enter a value\"\n />\n </div>\n</div>\n";
|
|
3271
|
+
|
|
3272
|
+
/***/ }),
|
|
3273
|
+
|
|
3274
|
+
/***/ "./frontend/src/edit-string/edit-string.js":
|
|
3275
|
+
/*!*************************************************!*\
|
|
3276
|
+
!*** ./frontend/src/edit-string/edit-string.js ***!
|
|
3277
|
+
\*************************************************/
|
|
3278
|
+
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
3279
|
+
|
|
3280
|
+
"use strict";
|
|
3281
|
+
|
|
3282
|
+
|
|
3283
|
+
const template = __webpack_require__(/*! ./edit-string.html */ "./frontend/src/edit-string/edit-string.html");
|
|
3284
|
+
|
|
3285
|
+
const OTHER_OPTION = '__other';
|
|
3286
|
+
const NULL_OPTION = '__null';
|
|
3287
|
+
|
|
3288
|
+
function normalizeEnumValues(enumValues) {
|
|
3289
|
+
if (!Array.isArray(enumValues)) {
|
|
3290
|
+
return [];
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3293
|
+
const deduped = [];
|
|
3294
|
+
enumValues.forEach(value => {
|
|
3295
|
+
if (value == null) {
|
|
3296
|
+
return;
|
|
3297
|
+
}
|
|
3298
|
+
if (deduped.indexOf(value) === -1) {
|
|
3299
|
+
deduped.push(value);
|
|
3300
|
+
}
|
|
3301
|
+
});
|
|
3302
|
+
|
|
3303
|
+
return deduped;
|
|
3304
|
+
}
|
|
3305
|
+
|
|
3306
|
+
function getInitialSelection(value, normalizedEnumValues) {
|
|
3307
|
+
if (value == null) {
|
|
3308
|
+
return NULL_OPTION;
|
|
3309
|
+
}
|
|
3310
|
+
if (normalizedEnumValues.indexOf(value) !== -1) {
|
|
3311
|
+
return value;
|
|
3312
|
+
}
|
|
3313
|
+
if (typeof value === 'string' && value === '') {
|
|
3314
|
+
return OTHER_OPTION;
|
|
3315
|
+
}
|
|
3316
|
+
// For any other non-null, non-enum, non-empty string value, return OTHER_OPTION
|
|
3317
|
+
return OTHER_OPTION;
|
|
3318
|
+
}
|
|
3319
|
+
|
|
3320
|
+
module.exports = app => app.component('edit-string', {
|
|
3321
|
+
template,
|
|
3322
|
+
props: {
|
|
3323
|
+
value: {
|
|
3324
|
+
type: null,
|
|
3325
|
+
default: undefined
|
|
3326
|
+
},
|
|
3327
|
+
enumValues: {
|
|
3328
|
+
type: Array,
|
|
3329
|
+
default: () => []
|
|
3330
|
+
}
|
|
3331
|
+
},
|
|
3332
|
+
emits: ['input'],
|
|
3333
|
+
data() {
|
|
3334
|
+
const normalizedEnums = normalizeEnumValues(this.enumValues);
|
|
3335
|
+
const initialSelection = normalizedEnums.length > 0 ? getInitialSelection(this.value, normalizedEnums) : null;
|
|
3336
|
+
|
|
3337
|
+
return {
|
|
3338
|
+
normalizedEnums,
|
|
3339
|
+
selectedOption: initialSelection,
|
|
3340
|
+
otherValue: initialSelection === OTHER_OPTION && typeof this.value === 'string' ? this.value : ''
|
|
3341
|
+
};
|
|
3342
|
+
},
|
|
3343
|
+
computed: {
|
|
3344
|
+
hasEnumValues() {
|
|
3345
|
+
return this.normalizedEnums.length > 0;
|
|
3346
|
+
}
|
|
3347
|
+
},
|
|
3348
|
+
watch: {
|
|
3349
|
+
value(newVal) {
|
|
3350
|
+
if (!this.hasEnumValues) {
|
|
3351
|
+
return;
|
|
3352
|
+
}
|
|
3353
|
+
const newSelection = getInitialSelection(newVal, this.normalizedEnums);
|
|
3354
|
+
const selectionChanged = newSelection !== this.selectedOption;
|
|
3355
|
+
|
|
3356
|
+
if (selectionChanged) {
|
|
3357
|
+
this.selectedOption = newSelection;
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
if (newSelection === OTHER_OPTION) {
|
|
3361
|
+
const nextOtherValue = typeof newVal === 'string' ? newVal : '';
|
|
3362
|
+
if (this.otherValue !== nextOtherValue) {
|
|
3363
|
+
this.otherValue = nextOtherValue;
|
|
3364
|
+
}
|
|
3365
|
+
} else if (selectionChanged && this.otherValue !== '') {
|
|
3366
|
+
this.otherValue = '';
|
|
3367
|
+
}
|
|
3368
|
+
},
|
|
3369
|
+
enumValues(newValues) {
|
|
3370
|
+
const normalized = normalizeEnumValues(newValues);
|
|
3371
|
+
this.normalizedEnums = normalized;
|
|
3372
|
+
|
|
3373
|
+
if (!normalized.length) {
|
|
3374
|
+
this.selectedOption = null;
|
|
3375
|
+
this.otherValue = '';
|
|
3376
|
+
return;
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
const newSelection = getInitialSelection(this.value, normalized);
|
|
3380
|
+
const selectionChanged = newSelection !== this.selectedOption;
|
|
3381
|
+
|
|
3382
|
+
if (selectionChanged) {
|
|
3383
|
+
this.selectedOption = newSelection;
|
|
3384
|
+
}
|
|
3385
|
+
|
|
3386
|
+
if (newSelection === OTHER_OPTION) {
|
|
3387
|
+
const sourceValue = typeof this.value === 'string' ? this.value : '';
|
|
3388
|
+
if (this.otherValue !== sourceValue) {
|
|
3389
|
+
this.otherValue = sourceValue;
|
|
3390
|
+
}
|
|
3391
|
+
} else if (this.otherValue !== '') {
|
|
3392
|
+
this.otherValue = '';
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
},
|
|
3396
|
+
methods: {
|
|
3397
|
+
onSelectChange(event) {
|
|
3398
|
+
if (!this.hasEnumValues) {
|
|
3399
|
+
return;
|
|
3400
|
+
}
|
|
3401
|
+
|
|
3402
|
+
const selected = event.target.value;
|
|
3403
|
+
this.selectedOption = selected;
|
|
3404
|
+
|
|
3405
|
+
if (selected === NULL_OPTION) {
|
|
3406
|
+
this.otherValue = '';
|
|
3407
|
+
this.$emit('input', null);
|
|
3408
|
+
return;
|
|
3409
|
+
}
|
|
3410
|
+
if (selected === OTHER_OPTION) {
|
|
3411
|
+
if (this.otherValue === '' && typeof this.value === 'string' && this.normalizedEnums.indexOf(this.value) === -1) {
|
|
3412
|
+
this.otherValue = this.value;
|
|
3413
|
+
}
|
|
3414
|
+
return;
|
|
3415
|
+
}
|
|
3416
|
+
|
|
3417
|
+
this.otherValue = '';
|
|
3418
|
+
this.$emit('input', selected);
|
|
3419
|
+
},
|
|
3420
|
+
onOtherInput(event) {
|
|
3421
|
+
this.otherValue = event.target.value;
|
|
3422
|
+
this.$emit('input', this.otherValue);
|
|
3423
|
+
},
|
|
3424
|
+
onTextInput(event) {
|
|
3425
|
+
this.$emit('input', event.target.value);
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
});
|
|
3429
|
+
|
|
3430
|
+
|
|
3189
3431
|
/***/ }),
|
|
3190
3432
|
|
|
3191
3433
|
/***/ "./frontend/src/edit-subdocument/edit-subdocument.html":
|
|
@@ -16367,7 +16609,7 @@ module.exports = function stringToParts(str) {
|
|
|
16367
16609
|
/***/ ((module) => {
|
|
16368
16610
|
|
|
16369
16611
|
"use strict";
|
|
16370
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.1.
|
|
16612
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.1.4","description":"A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.","homepage":"https://studio.mongoosejs.io/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.1.0","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vanillatoasts":"^1.6.0","vue":"3.x","webpack":"5.x"},"peerDependencies":{"bson":"^5.5.1 || 6.x","express":"4.x","mongoose":"7.x || 8.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongoose":"8.x"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js"}}');
|
|
16371
16613
|
|
|
16372
16614
|
/***/ })
|
|
16373
16615
|
|
package/frontend/public/tw.css
CHANGED
|
@@ -2446,6 +2446,11 @@ video {
|
|
|
2446
2446
|
color: rgb(55 65 81 / var(--tw-text-opacity));
|
|
2447
2447
|
}
|
|
2448
2448
|
|
|
2449
|
+
.hover\:text-gray-800:hover {
|
|
2450
|
+
--tw-text-opacity: 1;
|
|
2451
|
+
color: rgb(31 41 55 / var(--tw-text-opacity));
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2449
2454
|
.hover\:text-slate-700:hover {
|
|
2450
2455
|
--tw-text-opacity: 1;
|
|
2451
2456
|
color: rgb(51 65 85 / var(--tw-text-opacity));
|
|
@@ -19,6 +19,18 @@
|
|
|
19
19
|
<span class="ml-2 text-sm text-gray-500">({{(path.instance || 'unknown').toLowerCase()}})</span>
|
|
20
20
|
</div>
|
|
21
21
|
<div class="flex items-center gap-2">
|
|
22
|
+
<button
|
|
23
|
+
type="button"
|
|
24
|
+
class="flex items-center gap-1 text-sm text-gray-600 hover:text-gray-800 px-2 py-1 rounded-md border border-transparent hover:border-gray-300 bg-white"
|
|
25
|
+
@click.stop.prevent="copyPropertyValue"
|
|
26
|
+
title="Copy value"
|
|
27
|
+
aria-label="Copy property value"
|
|
28
|
+
>
|
|
29
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
30
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h8m-8 4h8m-8 4h5m-7-9a2 2 0 012-2h7a2 2 0 012 2v10a2 2 0 01-2 2H8l-4-4V7a2 2 0 012-2z" />
|
|
31
|
+
</svg>
|
|
32
|
+
{{copyButtonLabel}}
|
|
33
|
+
</button>
|
|
22
34
|
<router-link
|
|
23
35
|
v-if="path.ref && getValueForPath(path.path)"
|
|
24
36
|
:to="`/model/${path.ref}/document/${getValueForPath(path.path)}`"
|
|
@@ -61,6 +73,7 @@
|
|
|
61
73
|
:is="getEditComponentForPath(path)"
|
|
62
74
|
:value="getEditValueForPath(path)"
|
|
63
75
|
:format="dateType"
|
|
76
|
+
v-bind="getEditComponentProps(path)"
|
|
64
77
|
@input="changes[path.path] = $event; delete invalid[path.path];"
|
|
65
78
|
@error="invalid[path.path] = $event;"
|
|
66
79
|
>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* global clearTimeout setTimeout */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
const mpath = require('mpath');
|
|
@@ -13,9 +15,17 @@ module.exports = app => app.component('document-property', {
|
|
|
13
15
|
return {
|
|
14
16
|
dateType: 'picker', // picker, iso
|
|
15
17
|
isCollapsed: false, // Start uncollapsed by default
|
|
16
|
-
isValueExpanded: false // Track if the value is expanded
|
|
18
|
+
isValueExpanded: false, // Track if the value is expanded
|
|
19
|
+
copyButtonLabel: 'Copy',
|
|
20
|
+
copyResetTimeoutId: null
|
|
17
21
|
};
|
|
18
22
|
},
|
|
23
|
+
beforeDestroy() {
|
|
24
|
+
if (this.copyResetTimeoutId) {
|
|
25
|
+
clearTimeout(this.copyResetTimeoutId);
|
|
26
|
+
this.copyResetTimeoutId = null;
|
|
27
|
+
}
|
|
28
|
+
},
|
|
19
29
|
props: ['path', 'document', 'schemaPaths', 'editting', 'changes', 'invalid', 'highlight'],
|
|
20
30
|
computed: {
|
|
21
31
|
valueAsString() {
|
|
@@ -54,6 +64,9 @@ module.exports = app => app.component('document-property', {
|
|
|
54
64
|
return 'detail-default';
|
|
55
65
|
},
|
|
56
66
|
getEditComponentForPath(path) {
|
|
67
|
+
if (path.instance === 'String') {
|
|
68
|
+
return 'edit-string';
|
|
69
|
+
}
|
|
57
70
|
if (path.instance == 'Date') {
|
|
58
71
|
return 'edit-date';
|
|
59
72
|
}
|
|
@@ -71,6 +84,15 @@ module.exports = app => app.component('document-property', {
|
|
|
71
84
|
}
|
|
72
85
|
return 'edit-default';
|
|
73
86
|
},
|
|
87
|
+
getEditComponentProps(path) {
|
|
88
|
+
const props = {};
|
|
89
|
+
if (path.instance === 'String') {
|
|
90
|
+
if (path.enum?.length > 0) {
|
|
91
|
+
props.enumValues = path.enum;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return props;
|
|
95
|
+
},
|
|
74
96
|
getValueForPath(path) {
|
|
75
97
|
if (this.document == null) {
|
|
76
98
|
return undefined;
|
|
@@ -92,6 +114,53 @@ module.exports = app => app.component('document-property', {
|
|
|
92
114
|
},
|
|
93
115
|
toggleValueExpansion() {
|
|
94
116
|
this.isValueExpanded = !this.isValueExpanded;
|
|
117
|
+
},
|
|
118
|
+
setCopyFeedback() {
|
|
119
|
+
this.copyButtonLabel = 'Copied';
|
|
120
|
+
if (this.copyResetTimeoutId) {
|
|
121
|
+
clearTimeout(this.copyResetTimeoutId);
|
|
122
|
+
}
|
|
123
|
+
this.copyResetTimeoutId = setTimeout(() => {
|
|
124
|
+
this.copyButtonLabel = 'Copy';
|
|
125
|
+
this.copyResetTimeoutId = null;
|
|
126
|
+
}, 5000);
|
|
127
|
+
},
|
|
128
|
+
copyPropertyValue() {
|
|
129
|
+
const textToCopy = this.valueAsString;
|
|
130
|
+
if (textToCopy == null) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const fallbackCopy = () => {
|
|
135
|
+
if (typeof document === 'undefined') {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const textArea = document.createElement('textarea');
|
|
139
|
+
textArea.value = textToCopy;
|
|
140
|
+
textArea.setAttribute('readonly', '');
|
|
141
|
+
textArea.style.position = 'absolute';
|
|
142
|
+
textArea.style.left = '-9999px';
|
|
143
|
+
document.body.appendChild(textArea);
|
|
144
|
+
textArea.select();
|
|
145
|
+
try {
|
|
146
|
+
document.execCommand('copy');
|
|
147
|
+
} finally {
|
|
148
|
+
document.body.removeChild(textArea);
|
|
149
|
+
}
|
|
150
|
+
this.setCopyFeedback();
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard && navigator.clipboard.writeText) {
|
|
154
|
+
navigator.clipboard.writeText(textToCopy)
|
|
155
|
+
.then(() => {
|
|
156
|
+
this.setCopyFeedback();
|
|
157
|
+
})
|
|
158
|
+
.catch(() => {
|
|
159
|
+
fallbackCopy();
|
|
160
|
+
});
|
|
161
|
+
} else {
|
|
162
|
+
fallbackCopy();
|
|
163
|
+
}
|
|
95
164
|
}
|
|
96
165
|
}
|
|
97
166
|
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<div>
|
|
2
|
+
<div v-if="hasEnumValues" class="space-y-2">
|
|
3
|
+
<select
|
|
4
|
+
class="w-full px-3 py-2 border border-gray-300 bg-white rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
5
|
+
:value="selectedOption"
|
|
6
|
+
@change="onSelectChange"
|
|
7
|
+
>
|
|
8
|
+
<option v-for="option in normalizedEnums" :key="`enum-${option}`" :value="option">
|
|
9
|
+
{{ option }}
|
|
10
|
+
</option>
|
|
11
|
+
<option :value="'__null'">null</option>
|
|
12
|
+
<option :value="'__other'">Other</option>
|
|
13
|
+
</select>
|
|
14
|
+
<input
|
|
15
|
+
v-if="selectedOption === '__other'"
|
|
16
|
+
type="text"
|
|
17
|
+
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
18
|
+
:value="otherValue"
|
|
19
|
+
@input="onOtherInput"
|
|
20
|
+
placeholder="Enter a value"
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
<div v-else>
|
|
24
|
+
<input
|
|
25
|
+
type="text"
|
|
26
|
+
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
27
|
+
:value="value != null ? value : ''"
|
|
28
|
+
@input="onTextInput"
|
|
29
|
+
placeholder="Enter a value"
|
|
30
|
+
/>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const template = require('./edit-string.html');
|
|
4
|
+
|
|
5
|
+
const OTHER_OPTION = '__other';
|
|
6
|
+
const NULL_OPTION = '__null';
|
|
7
|
+
|
|
8
|
+
function normalizeEnumValues(enumValues) {
|
|
9
|
+
if (!Array.isArray(enumValues)) {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const deduped = [];
|
|
14
|
+
enumValues.forEach(value => {
|
|
15
|
+
if (value == null) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (deduped.indexOf(value) === -1) {
|
|
19
|
+
deduped.push(value);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return deduped;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getInitialSelection(value, normalizedEnumValues) {
|
|
27
|
+
if (value == null) {
|
|
28
|
+
return NULL_OPTION;
|
|
29
|
+
}
|
|
30
|
+
if (normalizedEnumValues.indexOf(value) !== -1) {
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
if (typeof value === 'string' && value === '') {
|
|
34
|
+
return OTHER_OPTION;
|
|
35
|
+
}
|
|
36
|
+
// For any other non-null, non-enum, non-empty string value, return OTHER_OPTION
|
|
37
|
+
return OTHER_OPTION;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = app => app.component('edit-string', {
|
|
41
|
+
template,
|
|
42
|
+
props: {
|
|
43
|
+
value: {
|
|
44
|
+
type: null,
|
|
45
|
+
default: undefined
|
|
46
|
+
},
|
|
47
|
+
enumValues: {
|
|
48
|
+
type: Array,
|
|
49
|
+
default: () => []
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
emits: ['input'],
|
|
53
|
+
data() {
|
|
54
|
+
const normalizedEnums = normalizeEnumValues(this.enumValues);
|
|
55
|
+
const initialSelection = normalizedEnums.length > 0 ? getInitialSelection(this.value, normalizedEnums) : null;
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
normalizedEnums,
|
|
59
|
+
selectedOption: initialSelection,
|
|
60
|
+
otherValue: initialSelection === OTHER_OPTION && typeof this.value === 'string' ? this.value : ''
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
computed: {
|
|
64
|
+
hasEnumValues() {
|
|
65
|
+
return this.normalizedEnums.length > 0;
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
watch: {
|
|
69
|
+
value(newVal) {
|
|
70
|
+
if (!this.hasEnumValues) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const newSelection = getInitialSelection(newVal, this.normalizedEnums);
|
|
74
|
+
const selectionChanged = newSelection !== this.selectedOption;
|
|
75
|
+
|
|
76
|
+
if (selectionChanged) {
|
|
77
|
+
this.selectedOption = newSelection;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (newSelection === OTHER_OPTION) {
|
|
81
|
+
const nextOtherValue = typeof newVal === 'string' ? newVal : '';
|
|
82
|
+
if (this.otherValue !== nextOtherValue) {
|
|
83
|
+
this.otherValue = nextOtherValue;
|
|
84
|
+
}
|
|
85
|
+
} else if (selectionChanged && this.otherValue !== '') {
|
|
86
|
+
this.otherValue = '';
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
enumValues(newValues) {
|
|
90
|
+
const normalized = normalizeEnumValues(newValues);
|
|
91
|
+
this.normalizedEnums = normalized;
|
|
92
|
+
|
|
93
|
+
if (!normalized.length) {
|
|
94
|
+
this.selectedOption = null;
|
|
95
|
+
this.otherValue = '';
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const newSelection = getInitialSelection(this.value, normalized);
|
|
100
|
+
const selectionChanged = newSelection !== this.selectedOption;
|
|
101
|
+
|
|
102
|
+
if (selectionChanged) {
|
|
103
|
+
this.selectedOption = newSelection;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (newSelection === OTHER_OPTION) {
|
|
107
|
+
const sourceValue = typeof this.value === 'string' ? this.value : '';
|
|
108
|
+
if (this.otherValue !== sourceValue) {
|
|
109
|
+
this.otherValue = sourceValue;
|
|
110
|
+
}
|
|
111
|
+
} else if (this.otherValue !== '') {
|
|
112
|
+
this.otherValue = '';
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
methods: {
|
|
117
|
+
onSelectChange(event) {
|
|
118
|
+
if (!this.hasEnumValues) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const selected = event.target.value;
|
|
123
|
+
this.selectedOption = selected;
|
|
124
|
+
|
|
125
|
+
if (selected === NULL_OPTION) {
|
|
126
|
+
this.otherValue = '';
|
|
127
|
+
this.$emit('input', null);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (selected === OTHER_OPTION) {
|
|
131
|
+
if (this.otherValue === '' && typeof this.value === 'string' && this.normalizedEnums.indexOf(this.value) === -1) {
|
|
132
|
+
this.otherValue = this.value;
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.otherValue = '';
|
|
138
|
+
this.$emit('input', selected);
|
|
139
|
+
},
|
|
140
|
+
onOtherInput(event) {
|
|
141
|
+
this.otherValue = event.target.value;
|
|
142
|
+
this.$emit('input', this.otherValue);
|
|
143
|
+
},
|
|
144
|
+
onTextInput(event) {
|
|
145
|
+
this.$emit('input', event.target.value);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
});
|
package/next.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
+
const frontend = require('./frontend');
|
|
4
5
|
const path = require('path');
|
|
5
6
|
|
|
6
7
|
module.exports = withMongooseStudio;
|
|
@@ -15,6 +16,15 @@ module.exports = withMongooseStudio;
|
|
|
15
16
|
function withMongooseStudio(nextConfig = {}) {
|
|
16
17
|
const studioPath = normalizeBasePath(nextConfig.studioPath || '/studio');
|
|
17
18
|
|
|
19
|
+
try {
|
|
20
|
+
copyStudioFrontend(studioPath);
|
|
21
|
+
frontend('/api/studio', true)
|
|
22
|
+
.then(() => console.log(`✅ Mongoose Studio: copied frontend+config to public${studioPath}`))
|
|
23
|
+
.catch(err => console.error(`❌ Mongoose Studio: failed to copy frontend`, err));
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error('❌ Mongoose Studio: failed to copy frontend', err);
|
|
26
|
+
}
|
|
27
|
+
|
|
18
28
|
return {
|
|
19
29
|
...nextConfig,
|
|
20
30
|
|
|
@@ -32,24 +42,7 @@ function withMongooseStudio(nextConfig = {}) {
|
|
|
32
42
|
};
|
|
33
43
|
|
|
34
44
|
return [...userRedirects, studioRedirect];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
webpack(config, { isServer }) {
|
|
38
|
-
if (isServer) {
|
|
39
|
-
try {
|
|
40
|
-
copyStudioFrontend(studioPath);
|
|
41
|
-
console.log(`✅ Mongoose Studio: copied frontend to public${studioPath}`);
|
|
42
|
-
} catch (err) {
|
|
43
|
-
console.error('❌ Mongoose Studio: failed to copy frontend', err);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Preserve user’s webpack config
|
|
48
|
-
if (typeof nextConfig.webpack === 'function') {
|
|
49
|
-
return nextConfig.webpack(config, { isServer });
|
|
50
|
-
}
|
|
51
|
-
return config;
|
|
52
|
-
},
|
|
45
|
+
}
|
|
53
46
|
};
|
|
54
47
|
}
|
|
55
48
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongoosejs/studio",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.",
|
|
5
5
|
"homepage": "https://studio.mongoosejs.io/",
|
|
6
6
|
"repository": {
|