@mongoosejs/studio 0.1.14 → 0.1.15
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/frontend/public/app.js +22 -6
- package/frontend/public/tw.css +12 -0
- package/frontend/src/document/document.html +2 -2
- package/frontend/src/document/document.js +15 -2
- package/frontend/src/document-details/document-details.html +1 -1
- package/frontend/src/models/models.js +4 -1
- package/package.json +1 -1
package/frontend/public/app.js
CHANGED
|
@@ -2278,7 +2278,7 @@ module.exports = ".document-details {\n width: 100%;\n}\n\n.document-details .v
|
|
|
2278
2278
|
/***/ ((module) => {
|
|
2279
2279
|
|
|
2280
2280
|
"use strict";
|
|
2281
|
-
module.exports = "<div class=\"document-details\">\n <!-- View Toggle and Search/Filter Bar -->\n <div class=\"mb-4 mt-4\">\n\n <!-- Search and Filter Bar (only show in fields view) -->\n <div v-if=\"viewMode === 'fields'\" class=\"flex md:gap-3\">\n <!-- Search Bar -->\n <div class=\"relative flex-1\">\n <input\n ref=\"searchInput\"\n v-model=\"searchQuery\"\n type=\"text\"\n placeholder=\"Search fields...\"\n class=\"w-full px-4 py-2 pl-10 pr-4 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n <div class=\"absolute inset-y-0 left-0 flex items-center pl-3\">\n <svg class=\"w-4 h-4 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"></path>\n </svg>\n </div>\n </div>\n\n <!-- Data Type Filter -->\n <div class=\"relative hidden md:block\">\n <select\n v-model=\"selectedType\"\n class=\"px-4 py-2 pr-8 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white min-w-[140px] appearance-none\"\n >\n <option value=\"\">All Types</option>\n <option v-for=\"type in availableTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <div class=\"absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none\">\n <svg class=\"w-4 h-4 text-gray-400\" 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 </div>\n </div>\n\n <!-- Add Field Button -->\n <button\n @click=\"openAddFieldModal\"\n class=\"hidden md:flex px-4 py-2 text-sm font-medium text-white bg-green-600 hover:bg-green-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 items-center gap-2\"\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=\"M12 4v16m8-8H4\"></path>\n </svg> Add Field\n </button>\n </div>\n </div>\n\n <!-- Fields View -->\n <div v-if=\"viewMode === 'fields'\">\n <!-- Schema Paths -->\n <div\n v-for=\"path in filteredSchemaPaths\"\n :key=\"path.path || path\"\n class=\"value\"\n >\n <document-property\n :path=\"path\"\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :highlight=\"isSchemaPathMatched(path)\"\n :invalid=\"invalid\"></document-property>\n </div>\n\n <!-- Virtual Fields -->\n <div\n v-for=\"path in filteredVirtuals\"\n :key=\"path.name\"\n class=\"border rounded-lg mb-2 transition-all duration-200 ease-in-out\"\n :class=\"[\n isVirtualMatched(path)\n ? 'border-amber-400 ring-2 ring-amber-200 ring-offset-1'\n : 'border-gray-200'\n ]\"\n >\n <!-- Virtual Field Header (Always Visible) -->\n <div\n @click=\"toggleVirtualField(path.name)\"\n class=\"p-3 bg-slate-50 hover:bg-slate-100 cursor-pointer flex items-center justify-between border-b border-gray-200\"\n >\n <div class=\"flex items-center\">\n <svg\n :class=\"isVirtualFieldCollapsed(path.name) ? '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.name}}</span>\n <span v-if=\"path.isVirtual\" class=\"ml-2 text-sm text-purple-600\">(virtual - {{getVirtualFieldType(path)}})</span>\n <span v-else class=\"ml-2 text-sm text-blue-600\">(user-added - {{getVirtualFieldType(path)}})</span>\n </div>\n </div>\n\n <!-- Virtual Field Content (Collapsible) -->\n <div v-if=\"!isVirtualFieldCollapsed(path.name)\" class=\"p-3\">\n <div v-if=\"path.value == null\" class=\"text-sky-800\">\n {{'' + path.value}}\n </div>\n <div v-else>\n {{path.value}}\n </div>\n </div>\n </div>\n\n <!-- No Results Message -->\n <div v-if=\"searchQuery.trim() && !hasSearchMatches\" class=\"text-center py-8 text-gray-500\">\n <svg class=\"w-12 h-12 mx-auto mb-4 text-gray-300\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9.172 16.172a4 4 0 015.656 0M9 12h6m-6-4h6m2 5.291A7.962 7.962 0 0112 15c-2.34 0-4.29-1.009-5.824-2.709M15 6.291A7.962 7.962 0 0012 5c-2.34 0-4.29 1.009-5.824 2.709\"></path>\n </svg>\n <p>No fields found matching \"{{searchQuery}}\"</p>\n </div>\n </div>\n\n <!-- JSON View -->\n <div v-if=\"viewMode === 'json'\" class=\"json-view\">\n <div class=\"border border-gray-300 rounded-lg bg-gray-50 p-4 overflow-auto\">\n <pre class=\"text-sm font-mono text-gray-800 whitespace-pre\">{{formattedJson}}</pre>\n </div>\n </div>\n\n <!-- Add Field Modal -->\n <modal v-if=\"showAddFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"closeAddFieldModal\">×</div>\n <div class=\"add-field-modal\">\n <div class=\"mb-6\">\n <h2 class=\"text-xl font-semibold text-gray-900 mb-2\">Add New Field</h2>\n <p class=\"text-sm text-gray-600\">Create a new field for this document</p>\n </div>\n\n <form @submit.prevent=\"handleAddFieldSubmit\" class=\"space-y-4\">\n <!-- Field Name -->\n <div>\n <label for=\"fieldName\" class=\"block text-sm font-medium text-gray-700 mb-1\">\n Field Name *\n </label>\n <input\n id=\"fieldName\"\n v-model=\"fieldData.name\"\n type=\"text\"\n required\n placeholder=\"Enter field name...\"\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 :class=\"{ 'border-red-500': fieldErrors.name }\"\n />\n <p v-if=\"fieldErrors.name\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.name}}</p>\n <p v-if=\"fieldData.name && getTransformedFieldName() !== fieldData.name.trim()\" class=\"mt-1 text-sm text-blue-600\">\n Field name will be: <code class=\"bg-blue-50 px-1 py-0.5 rounded text-xs\">{{getTransformedFieldName()}}</code>\n </p>\n </div>\n\n <!-- Field Type -->\n <div>\n <label for=\"fieldType\" class=\"block text-sm font-medium text-gray-700 mb-1\">\n Field Type *\n </label>\n <select\n id=\"fieldType\"\n v-model=\"fieldData.type\"\n required\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 :class=\"{ 'border-red-500': fieldErrors.type }\"\n >\n <option value=\"\">Select field type...</option>\n <option v-for=\"type in allFieldTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <p v-if=\"fieldErrors.type\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.type}}</p>\n </div>\n\n <!-- Field Value -->\n <div>\n <label for=\"fieldValue\" class=\"block text-sm font-medium text-gray-700 mb-1\">\n Initial Value\n </label>\n\n <!-- Date picker for Date type -->\n <input\n v-if=\"shouldUseDatePicker\"\n v-model=\"fieldData.value\"\n type=\"date\"\n id=\"fieldValue\"\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 :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <!-- Simple input for basic types -->\n <input\n v-else-if=\"!shouldUseCodeMirror\"\n v-model=\"fieldData.value\"\n type=\"text\"\n id=\"fieldValue\"\n placeholder=\"Enter initial value (optional)...\"\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 :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <!-- CodeMirror textarea for complex types -->\n <textarea\n v-else\n ref=\"fieldValueEditor\"\n id=\"fieldValue\"\n rows=\"3\"\n placeholder=\"Enter initial value (optional)...\"\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 :class=\"{ 'border-red-500': fieldErrors.value }\"\n ></textarea>\n\n <p v-if=\"fieldErrors.value\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.value}}</p>\n <p class=\"mt-1 text-xs text-gray-500\">\n <span v-if=\"shouldUseDatePicker\">Select a date or leave empty for null/undefined values.</span>\n <span v-else-if=\"shouldUseCodeMirror\">Leave empty for null/undefined values. Use valid JSON format.</span>\n <span v-else>Leave empty for null/undefined values.</span>\n </p>\n </div>\n\n\n <!-- Action Buttons -->\n <div class=\"flex justify-end gap-3 pt-4 border-t border-gray-200\">\n <button\n type=\"button\"\n @click=\"closeAddFieldModal\"\n class=\"px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n :disabled=\"isSubmittingField\"\n class=\"px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <span v-if=\"isSubmittingField\">Adding...</span>\n <span v-else>Add Field</span>\n </button>\n </div>\n </form>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
2281
|
+
module.exports = "<div class=\"document-details\">\n <!-- View Toggle and Search/Filter Bar -->\n <div class=\"sticky top-[60px] z-40 bg-white p-4 border-b border-gray-200 shadow-sm\">\n\n <!-- Search and Filter Bar (only show in fields view) -->\n <div v-if=\"viewMode === 'fields'\" class=\"flex md:gap-3\">\n <!-- Search Bar -->\n <div class=\"relative flex-1\">\n <input\n ref=\"searchInput\"\n v-model=\"searchQuery\"\n type=\"text\"\n placeholder=\"Search fields...\"\n class=\"w-full px-4 py-2 pl-10 pr-4 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n <div class=\"absolute inset-y-0 left-0 flex items-center pl-3\">\n <svg class=\"w-4 h-4 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"></path>\n </svg>\n </div>\n </div>\n\n <!-- Data Type Filter -->\n <div class=\"relative hidden md:block\">\n <select\n v-model=\"selectedType\"\n class=\"px-4 py-2 pr-8 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white min-w-[140px] appearance-none\"\n >\n <option value=\"\">All Types</option>\n <option v-for=\"type in availableTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <div class=\"absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none\">\n <svg class=\"w-4 h-4 text-gray-400\" 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 </div>\n </div>\n\n <!-- Add Field Button -->\n <button\n @click=\"openAddFieldModal\"\n class=\"hidden md:flex px-4 py-2 text-sm font-medium text-white bg-green-600 hover:bg-green-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 items-center gap-2\"\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=\"M12 4v16m8-8H4\"></path>\n </svg> Add Field\n </button>\n </div>\n </div>\n\n <!-- Fields View -->\n <div v-if=\"viewMode === 'fields'\">\n <!-- Schema Paths -->\n <div\n v-for=\"path in filteredSchemaPaths\"\n :key=\"path.path || path\"\n class=\"value\"\n >\n <document-property\n :path=\"path\"\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :highlight=\"isSchemaPathMatched(path)\"\n :invalid=\"invalid\"></document-property>\n </div>\n\n <!-- Virtual Fields -->\n <div\n v-for=\"path in filteredVirtuals\"\n :key=\"path.name\"\n class=\"border rounded-lg mb-2 transition-all duration-200 ease-in-out\"\n :class=\"[\n isVirtualMatched(path)\n ? 'border-amber-400 ring-2 ring-amber-200 ring-offset-1'\n : 'border-gray-200'\n ]\"\n >\n <!-- Virtual Field Header (Always Visible) -->\n <div\n @click=\"toggleVirtualField(path.name)\"\n class=\"p-3 bg-slate-50 hover:bg-slate-100 cursor-pointer flex items-center justify-between border-b border-gray-200\"\n >\n <div class=\"flex items-center\">\n <svg\n :class=\"isVirtualFieldCollapsed(path.name) ? '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.name}}</span>\n <span v-if=\"path.isVirtual\" class=\"ml-2 text-sm text-purple-600\">(virtual - {{getVirtualFieldType(path)}})</span>\n <span v-else class=\"ml-2 text-sm text-blue-600\">(user-added - {{getVirtualFieldType(path)}})</span>\n </div>\n </div>\n\n <!-- Virtual Field Content (Collapsible) -->\n <div v-if=\"!isVirtualFieldCollapsed(path.name)\" class=\"p-3\">\n <div v-if=\"path.value == null\" class=\"text-sky-800\">\n {{'' + path.value}}\n </div>\n <div v-else>\n {{path.value}}\n </div>\n </div>\n </div>\n\n <!-- No Results Message -->\n <div v-if=\"searchQuery.trim() && !hasSearchMatches\" class=\"text-center py-8 text-gray-500\">\n <svg class=\"w-12 h-12 mx-auto mb-4 text-gray-300\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9.172 16.172a4 4 0 015.656 0M9 12h6m-6-4h6m2 5.291A7.962 7.962 0 0112 15c-2.34 0-4.29-1.009-5.824-2.709M15 6.291A7.962 7.962 0 0012 5c-2.34 0-4.29 1.009-5.824 2.709\"></path>\n </svg>\n <p>No fields found matching \"{{searchQuery}}\"</p>\n </div>\n </div>\n\n <!-- JSON View -->\n <div v-if=\"viewMode === 'json'\" class=\"json-view\">\n <div class=\"border border-gray-300 rounded-lg bg-gray-50 p-4 overflow-auto\">\n <pre class=\"text-sm font-mono text-gray-800 whitespace-pre\">{{formattedJson}}</pre>\n </div>\n </div>\n\n <!-- Add Field Modal -->\n <modal v-if=\"showAddFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"closeAddFieldModal\">×</div>\n <div class=\"add-field-modal\">\n <div class=\"mb-6\">\n <h2 class=\"text-xl font-semibold text-gray-900 mb-2\">Add New Field</h2>\n <p class=\"text-sm text-gray-600\">Create a new field for this document</p>\n </div>\n\n <form @submit.prevent=\"handleAddFieldSubmit\" class=\"space-y-4\">\n <!-- Field Name -->\n <div>\n <label for=\"fieldName\" class=\"block text-sm font-medium text-gray-700 mb-1\">\n Field Name *\n </label>\n <input\n id=\"fieldName\"\n v-model=\"fieldData.name\"\n type=\"text\"\n required\n placeholder=\"Enter field name...\"\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 :class=\"{ 'border-red-500': fieldErrors.name }\"\n />\n <p v-if=\"fieldErrors.name\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.name}}</p>\n <p v-if=\"fieldData.name && getTransformedFieldName() !== fieldData.name.trim()\" class=\"mt-1 text-sm text-blue-600\">\n Field name will be: <code class=\"bg-blue-50 px-1 py-0.5 rounded text-xs\">{{getTransformedFieldName()}}</code>\n </p>\n </div>\n\n <!-- Field Type -->\n <div>\n <label for=\"fieldType\" class=\"block text-sm font-medium text-gray-700 mb-1\">\n Field Type *\n </label>\n <select\n id=\"fieldType\"\n v-model=\"fieldData.type\"\n required\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 :class=\"{ 'border-red-500': fieldErrors.type }\"\n >\n <option value=\"\">Select field type...</option>\n <option v-for=\"type in allFieldTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <p v-if=\"fieldErrors.type\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.type}}</p>\n </div>\n\n <!-- Field Value -->\n <div>\n <label for=\"fieldValue\" class=\"block text-sm font-medium text-gray-700 mb-1\">\n Initial Value\n </label>\n\n <!-- Date picker for Date type -->\n <input\n v-if=\"shouldUseDatePicker\"\n v-model=\"fieldData.value\"\n type=\"date\"\n id=\"fieldValue\"\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 :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <!-- Simple input for basic types -->\n <input\n v-else-if=\"!shouldUseCodeMirror\"\n v-model=\"fieldData.value\"\n type=\"text\"\n id=\"fieldValue\"\n placeholder=\"Enter initial value (optional)...\"\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 :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <!-- CodeMirror textarea for complex types -->\n <textarea\n v-else\n ref=\"fieldValueEditor\"\n id=\"fieldValue\"\n rows=\"3\"\n placeholder=\"Enter initial value (optional)...\"\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 :class=\"{ 'border-red-500': fieldErrors.value }\"\n ></textarea>\n\n <p v-if=\"fieldErrors.value\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.value}}</p>\n <p class=\"mt-1 text-xs text-gray-500\">\n <span v-if=\"shouldUseDatePicker\">Select a date or leave empty for null/undefined values.</span>\n <span v-else-if=\"shouldUseCodeMirror\">Leave empty for null/undefined values. Use valid JSON format.</span>\n <span v-else>Leave empty for null/undefined values.</span>\n </p>\n </div>\n\n\n <!-- Action Buttons -->\n <div class=\"flex justify-end gap-3 pt-4 border-t border-gray-200\">\n <button\n type=\"button\"\n @click=\"closeAddFieldModal\"\n class=\"px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n :disabled=\"isSubmittingField\"\n class=\"px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <span v-if=\"isSubmittingField\">Adding...</span>\n <span v-else>Add Field</span>\n </button>\n </div>\n </form>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
2282
2282
|
|
|
2283
2283
|
/***/ }),
|
|
2284
2284
|
|
|
@@ -3025,7 +3025,7 @@ module.exports = ".document {\n max-width: 1200px;\n margin-left: auto;\n mar
|
|
|
3025
3025
|
/***/ ((module) => {
|
|
3026
3026
|
|
|
3027
3027
|
"use strict";
|
|
3028
|
-
module.exports = "<div class=\"document px-1 md:px-0\">\n <div class=\"flex justify-between\">\n <div class=\"flex\">\n <button\n @click=\"
|
|
3028
|
+
module.exports = "<div class=\"document px-1 md:px-0\">\n <div class=\"flex justify-between sticky top-0 z-50 bg-white p-4 border-b border-gray-200 shadow-sm\">\n <div class=\"flex\">\n <button\n @click=\"goBack\"\n class=\"mr-2 rounded-md bg-gray-400 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n ‹ Back\n </button>\n <button\n @click=\"viewMode = 'fields'\"\n :class=\"viewMode === 'fields'\n ? 'bg-blue-600 text-white z-10'\n : 'bg-gray-200 text-gray-700 hover:bg-gray-300'\"\n class=\"px-4 py-2 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-gray-300 border-r-0 rounded-l-lg rounded-r-none\"\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=\"M4 6h16M4 10h16M4 14h16M4 18h16\"></path>\n </svg>\n Fields\n </button>\n <button\n @click=\"viewMode = 'json'\"\n :class=\"viewMode === 'json'\n ? 'bg-blue-600 text-white z-10'\n : 'bg-gray-200 text-gray-700 hover:bg-gray-300'\"\n class=\"px-4 py-2 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-gray-300 rounded-r-lg rounded-l-none\"\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=\"M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4\"></path>\n </svg>\n JSON\n </button>\n </div>\n\n <div class=\"gap-2 hidden md:flex\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true\"\n :disabled=\"!canEdit\"\n :class=\"{'cursor-not-allowed opacity-50': !canEdit}\"\n type=\"button\"\n 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\">\n <img src=\"images/edit.svg\" class=\"inline\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false\"\n type=\"button\"\n class=\"rounded-md bg-slate-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n × Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n @click=\"shouldShowConfirmModal=true;\"\n type=\"button\"\n 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\">\n <img src=\"images/save.svg\" class=\"inline\" /> Save\n </button>\n <button\n @click=\"shouldShowDeleteModal=true;\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n type=\"button\"\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\">\n <img src=\"images/delete.svg\" class=\"inline\" /> Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true;\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n type=\"button\"\n class=\"rounded-md bg-pink-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\">\n <img src=\"images/duplicate.svg\" class=\"inline\" /> Clone\n </button>\n </div>\n <div class=\"md:hidden flex items-center\">\n <div class=\"relative\">\n <button\n @click=\"mobileMenuOpen = !mobileMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"mobileMenuOpen\"\n aria-label=\"Open menu\"\n >\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\n d=\"M4 6h16M4 12h16M4 18h16\"></path>\n </svg>\n </button>\n <div\n v-show=\"mobileMenuOpen\"\n @click.away=\"mobileMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-52 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true; mobileMenuOpen = false\"\n :disabled=\"!canEdit\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canEdit ? 'cursor-not-allowed opacity-50' : 'hover:bg-ultramarine-100']\"\n type=\"button\"\n >\n <img src=\"images/edit.svg\" class=\"inline mr-2\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false; mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-slate-100\"\n >\n × Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-green-100']\"\n @click=\"shouldShowConfirmModal=true; mobileMenuOpen = false\"\n type=\"button\"\n >\n <img src=\"images/save.svg\" class=\"inline mr-2\" /> Save\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n <img src=\"images/delete.svg\" class=\"inline mr-2\" /> Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n <img src=\"images/duplicate.svg\" class=\"inline mr-2\" /> Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div v-if=\"status === 'loaded'\">\n <document-details\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :virtualPaths=\"virtualPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :invalid=\"invalid\"\n :viewMode=\"viewMode\"\n @add-field=\"addField\"\n @view-mode-change=\"updateViewMode\"></document-details>\n <modal v-if=\"shouldShowConfirmModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowConfirmModal = false;\">×</div>\n <confirm-changes @close=\"shouldShowConfirmModal = false;\" @save=\"save\" :value=\"changes\"></confirm-changes>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteModal = false;\">×</div>\n <confirm-delete @close=\"shouldShowDeleteModal = false;\" @remove=\"remove\" :value=\"document\"></confirm-delete>\n </template>\n </modal>\n <modal v-if=\"shouldShowCloneModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCloneModal = false;\">×</div>\n <clone-document :currentModel=\"model\" :doc=\"document\" :schemaPaths=\"schemaPaths\" @close=\"showClonedDocument\"></clone-document>\n </template>\n </modal>\n </div>\n</div>\n";
|
|
3029
3029
|
|
|
3030
3030
|
/***/ }),
|
|
3031
3031
|
|
|
@@ -3063,10 +3063,13 @@ module.exports = app => app.component('document', {
|
|
|
3063
3063
|
viewMode: 'fields',
|
|
3064
3064
|
shouldShowConfirmModal: false,
|
|
3065
3065
|
shouldShowDeleteModal: false,
|
|
3066
|
-
shouldShowCloneModal: false
|
|
3066
|
+
shouldShowCloneModal: false,
|
|
3067
|
+
previousQuery: null
|
|
3067
3068
|
}),
|
|
3068
3069
|
async mounted() {
|
|
3069
3070
|
window.pageState = this;
|
|
3071
|
+
// Store query parameters from the route (preserved from models page)
|
|
3072
|
+
this.previousQuery = Object.assign({}, this.$route.query);
|
|
3070
3073
|
const { doc, schemaPaths, virtualPaths } = await api.Model.getDocument({ model: this.model, documentId: this.documentId });
|
|
3071
3074
|
window.doc = doc;
|
|
3072
3075
|
this.document = doc;
|
|
@@ -3129,7 +3132,10 @@ module.exports = app => app.component('document', {
|
|
|
3129
3132
|
timeout: 3000,
|
|
3130
3133
|
positionClass: 'bottomRight'
|
|
3131
3134
|
});
|
|
3132
|
-
this.$router.push({
|
|
3135
|
+
this.$router.push({
|
|
3136
|
+
path: `/model/${this.model}`,
|
|
3137
|
+
query: this.previousQuery || {}
|
|
3138
|
+
});
|
|
3133
3139
|
}
|
|
3134
3140
|
},
|
|
3135
3141
|
showClonedDocument(doc) {
|
|
@@ -3160,6 +3166,13 @@ module.exports = app => app.component('document', {
|
|
|
3160
3166
|
this.editting = false;
|
|
3161
3167
|
this.changes = {};
|
|
3162
3168
|
}
|
|
3169
|
+
},
|
|
3170
|
+
goBack() {
|
|
3171
|
+
// Preserve query parameters when going back to models page
|
|
3172
|
+
this.$router.push({
|
|
3173
|
+
path: '/model/' + this.model,
|
|
3174
|
+
query: this.previousQuery || {}
|
|
3175
|
+
});
|
|
3163
3176
|
}
|
|
3164
3177
|
}
|
|
3165
3178
|
});
|
|
@@ -5453,7 +5466,10 @@ module.exports = app => app.component('models', {
|
|
|
5453
5466
|
}
|
|
5454
5467
|
},
|
|
5455
5468
|
openDocument(document) {
|
|
5456
|
-
this.$router.push(
|
|
5469
|
+
this.$router.push({
|
|
5470
|
+
path: '/model/' + this.currentModel + '/document/' + document._id,
|
|
5471
|
+
query: this.$route.query
|
|
5472
|
+
});
|
|
5457
5473
|
},
|
|
5458
5474
|
async deleteDocuments() {
|
|
5459
5475
|
const documentIds = this.selectedDocuments.map(x => x._id);
|
|
@@ -17034,7 +17050,7 @@ module.exports = function stringToParts(str) {
|
|
|
17034
17050
|
/***/ ((module) => {
|
|
17035
17051
|
|
|
17036
17052
|
"use strict";
|
|
17037
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.1.
|
|
17053
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.1.15","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":{"@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","@ai-sdk/anthropic":"2.x","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vanillatoasts":"^1.6.0","vue":"3.x","webpack":"5.x"},"peerDependencies":{"mongoose":"7.x || 8.x || ^9.0.0"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongoose":"9.x"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js"}}');
|
|
17038
17054
|
|
|
17039
17055
|
/***/ })
|
|
17040
17056
|
|
package/frontend/public/tw.css
CHANGED
|
@@ -610,6 +610,10 @@ video {
|
|
|
610
610
|
position: relative;
|
|
611
611
|
}
|
|
612
612
|
|
|
613
|
+
.sticky {
|
|
614
|
+
position: sticky;
|
|
615
|
+
}
|
|
616
|
+
|
|
613
617
|
.-inset-1 {
|
|
614
618
|
inset: -0.25rem;
|
|
615
619
|
}
|
|
@@ -647,6 +651,10 @@ video {
|
|
|
647
651
|
right: 1rem;
|
|
648
652
|
}
|
|
649
653
|
|
|
654
|
+
.top-0 {
|
|
655
|
+
top: 0px;
|
|
656
|
+
}
|
|
657
|
+
|
|
650
658
|
.top-1 {
|
|
651
659
|
top: 0.25rem;
|
|
652
660
|
}
|
|
@@ -655,6 +663,10 @@ video {
|
|
|
655
663
|
top: 0.5rem;
|
|
656
664
|
}
|
|
657
665
|
|
|
666
|
+
.top-\[60px\] {
|
|
667
|
+
top: 60px;
|
|
668
|
+
}
|
|
669
|
+
|
|
658
670
|
.top-\[65px\] {
|
|
659
671
|
top: 65px;
|
|
660
672
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<div class="document px-1 md:px-0">
|
|
2
|
-
<div class="flex justify-between">
|
|
2
|
+
<div class="flex justify-between sticky top-0 z-50 bg-white p-4 border-b border-gray-200 shadow-sm">
|
|
3
3
|
<div class="flex">
|
|
4
4
|
<button
|
|
5
|
-
@click="
|
|
5
|
+
@click="goBack"
|
|
6
6
|
class="mr-2 rounded-md bg-gray-400 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600">
|
|
7
7
|
‹ Back
|
|
8
8
|
</button>
|
|
@@ -25,10 +25,13 @@ module.exports = app => app.component('document', {
|
|
|
25
25
|
viewMode: 'fields',
|
|
26
26
|
shouldShowConfirmModal: false,
|
|
27
27
|
shouldShowDeleteModal: false,
|
|
28
|
-
shouldShowCloneModal: false
|
|
28
|
+
shouldShowCloneModal: false,
|
|
29
|
+
previousQuery: null
|
|
29
30
|
}),
|
|
30
31
|
async mounted() {
|
|
31
32
|
window.pageState = this;
|
|
33
|
+
// Store query parameters from the route (preserved from models page)
|
|
34
|
+
this.previousQuery = Object.assign({}, this.$route.query);
|
|
32
35
|
const { doc, schemaPaths, virtualPaths } = await api.Model.getDocument({ model: this.model, documentId: this.documentId });
|
|
33
36
|
window.doc = doc;
|
|
34
37
|
this.document = doc;
|
|
@@ -91,7 +94,10 @@ module.exports = app => app.component('document', {
|
|
|
91
94
|
timeout: 3000,
|
|
92
95
|
positionClass: 'bottomRight'
|
|
93
96
|
});
|
|
94
|
-
this.$router.push({
|
|
97
|
+
this.$router.push({
|
|
98
|
+
path: `/model/${this.model}`,
|
|
99
|
+
query: this.previousQuery || {}
|
|
100
|
+
});
|
|
95
101
|
}
|
|
96
102
|
},
|
|
97
103
|
showClonedDocument(doc) {
|
|
@@ -122,6 +128,13 @@ module.exports = app => app.component('document', {
|
|
|
122
128
|
this.editting = false;
|
|
123
129
|
this.changes = {};
|
|
124
130
|
}
|
|
131
|
+
},
|
|
132
|
+
goBack() {
|
|
133
|
+
// Preserve query parameters when going back to models page
|
|
134
|
+
this.$router.push({
|
|
135
|
+
path: '/model/' + this.model,
|
|
136
|
+
query: this.previousQuery || {}
|
|
137
|
+
});
|
|
125
138
|
}
|
|
126
139
|
}
|
|
127
140
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div class="document-details">
|
|
2
2
|
<!-- View Toggle and Search/Filter Bar -->
|
|
3
|
-
<div class="
|
|
3
|
+
<div class="sticky top-[60px] z-40 bg-white p-4 border-b border-gray-200 shadow-sm">
|
|
4
4
|
|
|
5
5
|
<!-- Search and Filter Bar (only show in fields view) -->
|
|
6
6
|
<div v-if="viewMode === 'fields'" class="flex md:gap-3">
|
|
@@ -457,7 +457,10 @@ module.exports = app => app.component('models', {
|
|
|
457
457
|
}
|
|
458
458
|
},
|
|
459
459
|
openDocument(document) {
|
|
460
|
-
this.$router.push(
|
|
460
|
+
this.$router.push({
|
|
461
|
+
path: '/model/' + this.currentModel + '/document/' + document._id,
|
|
462
|
+
query: this.$route.query
|
|
463
|
+
});
|
|
461
464
|
},
|
|
462
465
|
async deleteDocuments() {
|
|
463
466
|
const documentIds = this.selectedDocuments.map(x => x._id);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongoosejs/studio",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
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": {
|