@mongoosejs/studio 0.5.3 → 0.5.5
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/backend/actions/Model/analyzeSchema.js +202 -0
- package/backend/actions/Model/index.js +2 -0
- package/backend/actions/Model/validateDocument.js +35 -0
- package/backend/authorize.js +2 -0
- package/backend/helpers/validateDocumentWithTimeout.js +39 -0
- package/frontend/index.js +1 -2
- package/frontend/public/app.js +689 -114
- package/frontend/public/tw.css +221 -28
- package/frontend/src/api.js +12 -0
- package/frontend/src/array-utils.js +60 -2
- package/frontend/src/chat/chat.js +2 -2
- package/frontend/src/detail-array/detail-array.html +98 -14
- package/frontend/src/detail-array/detail-array.js +73 -3
- package/frontend/src/document/document.html +51 -0
- package/frontend/src/document/document.js +13 -0
- package/frontend/src/document-details/document-details.html +10 -3
- package/frontend/src/document-details/document-details.js +28 -0
- package/frontend/src/document-details/document-property/document-property.html +36 -44
- package/frontend/src/document-details/document-property/document-property.js +78 -14
- package/frontend/src/edit-array/edit-array.html +143 -8
- package/frontend/src/edit-array/edit-array.js +104 -2
- package/frontend/src/index.js +45 -9
- package/frontend/src/models/analyze-schema-modal/analyze-schema-modal.html +169 -0
- package/frontend/src/models/analyze-schema-modal/analyze-schema-modal.js +149 -0
- package/frontend/src/models/models.html +25 -13
- package/frontend/src/models/models.js +5 -13
- package/frontend/src/navbar/navbar.html +4 -4
- package/frontend/src/navbar/navbar.js +2 -3
- package/package.json +2 -1
package/frontend/public/app.js
CHANGED
|
@@ -949,6 +949,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
949
949
|
addField(params) {
|
|
950
950
|
return client.post('', { action: 'Model.addField', ...params }).then(res => res.data);
|
|
951
951
|
},
|
|
952
|
+
analyzeSchema(params) {
|
|
953
|
+
return client.post('', { action: 'Model.analyzeSchema', ...params }).then(res => res.data);
|
|
954
|
+
},
|
|
952
955
|
createChart(params) {
|
|
953
956
|
return client.post('', { action: 'Model.createChart', ...params }).then(res => res.data);
|
|
954
957
|
},
|
|
@@ -964,6 +967,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
964
967
|
executeDocumentScript(params) {
|
|
965
968
|
return client.post('', { action: 'Model.executeDocumentScript', ...params }).then(res => res.data);
|
|
966
969
|
},
|
|
970
|
+
validateDocument(params) {
|
|
971
|
+
return client.post('', { action: 'Model.validateDocument', ...params }).then(res => res.data);
|
|
972
|
+
},
|
|
967
973
|
exportQueryResults(params) {
|
|
968
974
|
const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
|
|
969
975
|
|
|
@@ -1220,6 +1226,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
1220
1226
|
getCollectionInfo: function getCollectionInfo(params) {
|
|
1221
1227
|
return client.post('/Model/getCollectionInfo', params).then(res => res.data);
|
|
1222
1228
|
},
|
|
1229
|
+
analyzeSchema: function analyzeSchema(params) {
|
|
1230
|
+
return client.post('/Model/analyzeSchema', params).then(res => res.data);
|
|
1231
|
+
},
|
|
1223
1232
|
getIndexes: function getIndexes(params) {
|
|
1224
1233
|
return client.post('/Model/getIndexes', params).then(res => res.data);
|
|
1225
1234
|
},
|
|
@@ -1232,6 +1241,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
1232
1241
|
updateDocument: function updateDocument(params) {
|
|
1233
1242
|
return client.post('/Model/updateDocument', params).then(res => res.data);
|
|
1234
1243
|
},
|
|
1244
|
+
validateDocument: function validateDocument(params) {
|
|
1245
|
+
return client.post('/Model/validateDocument', params).then(res => res.data);
|
|
1246
|
+
},
|
|
1235
1247
|
streamChatMessage: async function* streamChatMessage(params) {
|
|
1236
1248
|
const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
|
|
1237
1249
|
const url = window.MONGOOSE_STUDIO_CONFIG.baseURL + '/Model/streamChatMessage?' + new URLSearchParams(params).toString();
|
|
@@ -1305,7 +1317,6 @@ module.exports = function appendCSS(css) {
|
|
|
1305
1317
|
|
|
1306
1318
|
|
|
1307
1319
|
const { inspect } = __webpack_require__(/*! node-inspect-extracted */ "./node_modules/node-inspect-extracted/dist/inspect.js");
|
|
1308
|
-
const deepEqual = __webpack_require__(/*! ./_util/deepEqual */ "./frontend/src/_util/deepEqual.js");
|
|
1309
1320
|
|
|
1310
1321
|
/**
|
|
1311
1322
|
* Format a value for display in array views
|
|
@@ -1363,11 +1374,70 @@ function formatItemValue(item, key) {
|
|
|
1363
1374
|
return String(value);
|
|
1364
1375
|
}
|
|
1365
1376
|
|
|
1377
|
+
/**
|
|
1378
|
+
* True when every element is a plain object (empty array counts as eligible).
|
|
1379
|
+
*/
|
|
1380
|
+
function isArrayOfObjects(arr) {
|
|
1381
|
+
return Array.isArray(arr) && arr.every(item => isObjectItem(item));
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
/**
|
|
1385
|
+
* Sorted union of keys across plain object elements.
|
|
1386
|
+
*/
|
|
1387
|
+
function unionKeysForArrayOfObjects(arr) {
|
|
1388
|
+
if (!Array.isArray(arr)) {
|
|
1389
|
+
return [];
|
|
1390
|
+
}
|
|
1391
|
+
const set = new Set();
|
|
1392
|
+
for (const item of arr) {
|
|
1393
|
+
if (!isObjectItem(item)) {
|
|
1394
|
+
continue;
|
|
1395
|
+
}
|
|
1396
|
+
for (const k of Object.keys(item)) {
|
|
1397
|
+
set.add(k);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
return Array.from(set).sort((a, b) => a.localeCompare(b));
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
/**
|
|
1404
|
+
* Flat text for substring search over one array element (field names + values, or whole item).
|
|
1405
|
+
* @param {*} item
|
|
1406
|
+
* @returns {string}
|
|
1407
|
+
*/
|
|
1408
|
+
function searchTextForArrayItem(item) {
|
|
1409
|
+
if (item == null) {
|
|
1410
|
+
return String(item);
|
|
1411
|
+
}
|
|
1412
|
+
if (isObjectItem(item)) {
|
|
1413
|
+
return getItemKeys(item)
|
|
1414
|
+
.map(k => `${k} ${formatItemValue(item, k)}`)
|
|
1415
|
+
.join(' \n ');
|
|
1416
|
+
}
|
|
1417
|
+
return formatValue(item);
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
/**
|
|
1421
|
+
* Case-insensitive substring match against an array element.
|
|
1422
|
+
* @param {*} item
|
|
1423
|
+
* @param {string} normalizedQuery lowercased trimmed query; empty matches all
|
|
1424
|
+
*/
|
|
1425
|
+
function arrayItemMatchesSearch(item, normalizedQuery) {
|
|
1426
|
+
if (normalizedQuery == null || normalizedQuery === '') {
|
|
1427
|
+
return true;
|
|
1428
|
+
}
|
|
1429
|
+
return searchTextForArrayItem(item).toLowerCase().includes(normalizedQuery);
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1366
1432
|
module.exports = {
|
|
1367
1433
|
formatValue,
|
|
1368
1434
|
isObjectItem,
|
|
1369
1435
|
getItemKeys,
|
|
1370
|
-
formatItemValue
|
|
1436
|
+
formatItemValue,
|
|
1437
|
+
isArrayOfObjects,
|
|
1438
|
+
unionKeysForArrayOfObjects,
|
|
1439
|
+
searchTextForArrayItem,
|
|
1440
|
+
arrayItemMatchesSearch
|
|
1371
1441
|
};
|
|
1372
1442
|
|
|
1373
1443
|
|
|
@@ -2118,9 +2188,9 @@ module.exports = {
|
|
|
2118
2188
|
|
|
2119
2189
|
this.draftAgentMode = this.getAgentModePreference();
|
|
2120
2190
|
this.chatThreadId = this.threadId;
|
|
2121
|
-
const [{ chatThreads },
|
|
2191
|
+
const [{ chatThreads }, capabilities] = await Promise.all([
|
|
2122
2192
|
api.ChatThread.listChatThreads(),
|
|
2123
|
-
api.getCapabilities()
|
|
2193
|
+
api.getCapabilities().catch(() => this.capabilities)
|
|
2124
2194
|
]);
|
|
2125
2195
|
this.chatThreads = chatThreads;
|
|
2126
2196
|
this.capabilities = capabilities;
|
|
@@ -3259,16 +3329,83 @@ module.exports = app => app.component('dashboards', {
|
|
|
3259
3329
|
|
|
3260
3330
|
|
|
3261
3331
|
const template = __webpack_require__(/*! ./detail-array.html */ "./frontend/src/detail-array/detail-array.html");
|
|
3332
|
+
const { isArrayOfObjects, unionKeysForArrayOfObjects, arrayItemMatchesSearch } = __webpack_require__(/*! ../array-utils */ "./frontend/src/array-utils.js");
|
|
3262
3333
|
|
|
3263
3334
|
module.exports = app => app.component('detail-array', {
|
|
3264
3335
|
template: template,
|
|
3265
|
-
props:
|
|
3336
|
+
props: {
|
|
3337
|
+
value: {},
|
|
3338
|
+
/** 'list' | 'table' — table applies to arrays of plain objects */
|
|
3339
|
+
viewMode: {
|
|
3340
|
+
type: String,
|
|
3341
|
+
default: 'list'
|
|
3342
|
+
},
|
|
3343
|
+
truncate: {
|
|
3344
|
+
type: Boolean,
|
|
3345
|
+
default: false
|
|
3346
|
+
},
|
|
3347
|
+
initialLimit: {
|
|
3348
|
+
type: Number,
|
|
3349
|
+
default: 2
|
|
3350
|
+
}
|
|
3351
|
+
},
|
|
3266
3352
|
data() {
|
|
3267
3353
|
return {
|
|
3268
|
-
arrayValue: []
|
|
3354
|
+
arrayValue: [],
|
|
3355
|
+
arraySearchQuery: '',
|
|
3356
|
+
isExpanded: false
|
|
3269
3357
|
};
|
|
3270
3358
|
},
|
|
3359
|
+
computed: {
|
|
3360
|
+
effectiveViewMode() {
|
|
3361
|
+
return this.viewMode === 'table' ? 'table' : 'list';
|
|
3362
|
+
},
|
|
3363
|
+
showAsTable() {
|
|
3364
|
+
return this.effectiveViewMode === 'table' && isArrayOfObjects(this.arrayValue);
|
|
3365
|
+
},
|
|
3366
|
+
tableColumnKeys() {
|
|
3367
|
+
return unionKeysForArrayOfObjects(this.arrayValue);
|
|
3368
|
+
},
|
|
3369
|
+
arraySearchNormalized() {
|
|
3370
|
+
return (this.arraySearchQuery || '').trim().toLowerCase();
|
|
3371
|
+
},
|
|
3372
|
+
filteredArrayRows() {
|
|
3373
|
+
if (!Array.isArray(this.arrayValue)) {
|
|
3374
|
+
return [];
|
|
3375
|
+
}
|
|
3376
|
+
return this.arrayValue
|
|
3377
|
+
.map((item, index) => ({ item, index }))
|
|
3378
|
+
.filter(({ item }) => arrayItemMatchesSearch(item, this.arraySearchNormalized));
|
|
3379
|
+
},
|
|
3380
|
+
displayedArrayRows() {
|
|
3381
|
+
if (!this.shouldLimitRows) {
|
|
3382
|
+
return this.filteredArrayRows;
|
|
3383
|
+
}
|
|
3384
|
+
return this.filteredArrayRows.slice(0, this.initialLimit);
|
|
3385
|
+
},
|
|
3386
|
+
shouldLimitRows() {
|
|
3387
|
+
return this.truncate
|
|
3388
|
+
&& !this.isExpanded
|
|
3389
|
+
&& !this.arraySearchNormalized
|
|
3390
|
+
&& this.filteredArrayRows.length > this.initialLimit;
|
|
3391
|
+
},
|
|
3392
|
+
canToggleRows() {
|
|
3393
|
+
return this.truncate && !this.arraySearchNormalized && this.arrayValue.length > this.initialLimit;
|
|
3394
|
+
},
|
|
3395
|
+
expansionToggleLabel() {
|
|
3396
|
+
return this.isExpanded ? 'Show less' : `Show all ${this.arrayValue.length} items`;
|
|
3397
|
+
},
|
|
3398
|
+
remainingArrayCount() {
|
|
3399
|
+
if (!this.shouldLimitRows) {
|
|
3400
|
+
return 0;
|
|
3401
|
+
}
|
|
3402
|
+
return this.filteredArrayRows.length - this.displayedArrayRows.length;
|
|
3403
|
+
}
|
|
3404
|
+
},
|
|
3271
3405
|
methods: {
|
|
3406
|
+
toggleExpansion() {
|
|
3407
|
+
this.isExpanded = !this.isExpanded;
|
|
3408
|
+
},
|
|
3272
3409
|
initializeArray() {
|
|
3273
3410
|
if (this.value == null) {
|
|
3274
3411
|
this.arrayValue = [];
|
|
@@ -3286,6 +3423,9 @@ module.exports = app => app.component('detail-array', {
|
|
|
3286
3423
|
value: {
|
|
3287
3424
|
handler(newValue) {
|
|
3288
3425
|
this.initializeArray();
|
|
3426
|
+
if (this.arrayValue.length <= this.initialLimit) {
|
|
3427
|
+
this.isExpanded = false;
|
|
3428
|
+
}
|
|
3289
3429
|
},
|
|
3290
3430
|
deep: true,
|
|
3291
3431
|
immediate: true
|
|
@@ -3293,6 +3433,7 @@ module.exports = app => app.component('detail-array', {
|
|
|
3293
3433
|
}
|
|
3294
3434
|
});
|
|
3295
3435
|
|
|
3436
|
+
|
|
3296
3437
|
/***/ },
|
|
3297
3438
|
|
|
3298
3439
|
/***/ "./frontend/src/detail-date/detail-date.js"
|
|
@@ -4978,6 +5119,34 @@ module.exports = app => app.component('document-details', {
|
|
|
4978
5119
|
|
|
4979
5120
|
return this.virtualSearchMatchSet.has(virtual.name);
|
|
4980
5121
|
},
|
|
5122
|
+
getSearchMatchParts(text) {
|
|
5123
|
+
const normalizedQuery = typeof this.searchQuery === 'string' ? this.searchQuery.trim() : '';
|
|
5124
|
+
if (!normalizedQuery) {
|
|
5125
|
+
return [{ text, matched: false }];
|
|
5126
|
+
}
|
|
5127
|
+
|
|
5128
|
+
const lowerText = String(text).toLowerCase();
|
|
5129
|
+
const lowerQuery = normalizedQuery.toLowerCase();
|
|
5130
|
+
const parts = [];
|
|
5131
|
+
let position = 0;
|
|
5132
|
+
let matchIndex = lowerText.indexOf(lowerQuery, position);
|
|
5133
|
+
|
|
5134
|
+
while (matchIndex !== -1) {
|
|
5135
|
+
if (matchIndex > position) {
|
|
5136
|
+
parts.push({ text: text.slice(position, matchIndex), matched: false });
|
|
5137
|
+
}
|
|
5138
|
+
const matchEnd = matchIndex + normalizedQuery.length;
|
|
5139
|
+
parts.push({ text: text.slice(matchIndex, matchEnd), matched: true });
|
|
5140
|
+
position = matchEnd;
|
|
5141
|
+
matchIndex = lowerText.indexOf(lowerQuery, position);
|
|
5142
|
+
}
|
|
5143
|
+
|
|
5144
|
+
if (position < text.length) {
|
|
5145
|
+
parts.push({ text: text.slice(position), matched: false });
|
|
5146
|
+
}
|
|
5147
|
+
|
|
5148
|
+
return parts.length > 0 ? parts : [{ text, matched: false }];
|
|
5149
|
+
},
|
|
4981
5150
|
isVirtualFieldCollapsed(fieldName) {
|
|
4982
5151
|
return this.collapsedVirtuals.has(fieldName);
|
|
4983
5152
|
},
|
|
@@ -5146,6 +5315,7 @@ const appendCSS = __webpack_require__(/*! ../../appendCSS */ "./frontend/src/app
|
|
|
5146
5315
|
appendCSS(__webpack_require__(/*! ./document-property.css */ "./frontend/src/document-details/document-property/document-property.css"));
|
|
5147
5316
|
|
|
5148
5317
|
const UNSET = Symbol('unset');
|
|
5318
|
+
const { isArrayOfObjects } = __webpack_require__(/*! ../../array-utils */ "./frontend/src/array-utils.js");
|
|
5149
5319
|
|
|
5150
5320
|
module.exports = app => app.component('document-property', {
|
|
5151
5321
|
template,
|
|
@@ -5157,6 +5327,8 @@ module.exports = app => app.component('document-property', {
|
|
|
5157
5327
|
renderedValue: UNSET,
|
|
5158
5328
|
isCollapsed: false, // Start uncollapsed by default
|
|
5159
5329
|
isValueExpanded: false, // Track if the value is expanded
|
|
5330
|
+
/** 'list' | 'table' for array-of-objects display and editing */
|
|
5331
|
+
arrayDetailViewMode: 'list',
|
|
5160
5332
|
detailViewMode: 'text',
|
|
5161
5333
|
copyButtonLabel: 'Copy',
|
|
5162
5334
|
copyResetTimeoutId: null,
|
|
@@ -5169,7 +5341,7 @@ module.exports = app => app.component('document-property', {
|
|
|
5169
5341
|
this.copyResetTimeoutId = null;
|
|
5170
5342
|
}
|
|
5171
5343
|
},
|
|
5172
|
-
props: ['path', 'document', 'schemaPaths', 'editting', 'changes', 'invalid', 'highlight'],
|
|
5344
|
+
props: ['path', 'document', 'schemaPaths', 'editting', 'changes', 'invalid', 'highlight', 'searchQuery'],
|
|
5173
5345
|
computed: {
|
|
5174
5346
|
isDatePath() {
|
|
5175
5347
|
return this.path?.instance === 'Date';
|
|
@@ -5232,18 +5404,6 @@ module.exports = app => app.component('document-property', {
|
|
|
5232
5404
|
}
|
|
5233
5405
|
return this.valueAsString;
|
|
5234
5406
|
},
|
|
5235
|
-
truncatedArrayItems() {
|
|
5236
|
-
if (this.isArray && this.needsTruncation && !this.isValueExpanded) {
|
|
5237
|
-
return this.arrayValue.slice(0, 2);
|
|
5238
|
-
}
|
|
5239
|
-
return [];
|
|
5240
|
-
},
|
|
5241
|
-
remainingArrayCount() {
|
|
5242
|
-
if (this.isArray && this.needsTruncation && !this.isValueExpanded) {
|
|
5243
|
-
return this.arrayValue.length - 2;
|
|
5244
|
-
}
|
|
5245
|
-
return 0;
|
|
5246
|
-
},
|
|
5247
5407
|
isGeoJsonGeometry() {
|
|
5248
5408
|
const value = this.getValueForPath(this.path.path);
|
|
5249
5409
|
return value != null
|
|
@@ -5263,6 +5423,34 @@ module.exports = app => app.component('document-property', {
|
|
|
5263
5423
|
isMultiPolygon() {
|
|
5264
5424
|
const value = this.getValueForPath(this.path.path);
|
|
5265
5425
|
return this.isGeoJsonGeometry && value.type === 'MultiPolygon';
|
|
5426
|
+
},
|
|
5427
|
+
arrayPathCurrentValue() {
|
|
5428
|
+
if (!this.path || this.path.instance !== 'Array') {
|
|
5429
|
+
return null;
|
|
5430
|
+
}
|
|
5431
|
+
if (this.editting) {
|
|
5432
|
+
return this.getEditValueForPath(this.path);
|
|
5433
|
+
}
|
|
5434
|
+
return this.getValueForPath(this.path.path);
|
|
5435
|
+
},
|
|
5436
|
+
canUseArrayTableView() {
|
|
5437
|
+
const v = this.arrayPathCurrentValue;
|
|
5438
|
+
return Array.isArray(v) && isArrayOfObjects(v);
|
|
5439
|
+
},
|
|
5440
|
+
propertyDetailViewMode() {
|
|
5441
|
+
if (this.isDatePath) {
|
|
5442
|
+
return this.dateViewMode;
|
|
5443
|
+
}
|
|
5444
|
+
if (this.isObjectIdPath) {
|
|
5445
|
+
return this.objectIdViewMode;
|
|
5446
|
+
}
|
|
5447
|
+
if (this.path?.instance === 'Array') {
|
|
5448
|
+
return this.arrayDetailViewMode;
|
|
5449
|
+
}
|
|
5450
|
+
return this.detailViewMode;
|
|
5451
|
+
},
|
|
5452
|
+
pathNameParts() {
|
|
5453
|
+
return this.getSearchMatchParts(this.path?.path || '', this.searchQuery);
|
|
5266
5454
|
}
|
|
5267
5455
|
},
|
|
5268
5456
|
watch: {
|
|
@@ -5279,12 +5467,56 @@ module.exports = app => app.component('document-property', {
|
|
|
5279
5467
|
if (newValue && this.isGeoJsonGeometry) {
|
|
5280
5468
|
this.detailViewMode = 'map';
|
|
5281
5469
|
}
|
|
5470
|
+
},
|
|
5471
|
+
canUseArrayTableView(can) {
|
|
5472
|
+
if (!can && this.arrayDetailViewMode === 'table') {
|
|
5473
|
+
this.arrayDetailViewMode = 'list';
|
|
5474
|
+
}
|
|
5475
|
+
},
|
|
5476
|
+
'path.path'() {
|
|
5477
|
+
this.arrayDetailViewMode = 'list';
|
|
5282
5478
|
}
|
|
5283
5479
|
},
|
|
5284
5480
|
methods: {
|
|
5481
|
+
getSearchMatchParts(text, query) {
|
|
5482
|
+
const normalizedQuery = typeof query === 'string' ? query.trim() : '';
|
|
5483
|
+
if (!normalizedQuery) {
|
|
5484
|
+
return [{ text, matched: false }];
|
|
5485
|
+
}
|
|
5486
|
+
|
|
5487
|
+
const lowerText = String(text).toLowerCase();
|
|
5488
|
+
const lowerQuery = normalizedQuery.toLowerCase();
|
|
5489
|
+
const parts = [];
|
|
5490
|
+
let position = 0;
|
|
5491
|
+
let matchIndex = lowerText.indexOf(lowerQuery, position);
|
|
5492
|
+
|
|
5493
|
+
while (matchIndex !== -1) {
|
|
5494
|
+
if (matchIndex > position) {
|
|
5495
|
+
parts.push({ text: text.slice(position, matchIndex), matched: false });
|
|
5496
|
+
}
|
|
5497
|
+
const matchEnd = matchIndex + normalizedQuery.length;
|
|
5498
|
+
parts.push({ text: text.slice(matchIndex, matchEnd), matched: true });
|
|
5499
|
+
position = matchEnd;
|
|
5500
|
+
matchIndex = lowerText.indexOf(lowerQuery, position);
|
|
5501
|
+
}
|
|
5502
|
+
|
|
5503
|
+
if (position < text.length) {
|
|
5504
|
+
parts.push({ text: text.slice(position), matched: false });
|
|
5505
|
+
}
|
|
5506
|
+
|
|
5507
|
+
return parts.length > 0 ? parts : [{ text, matched: false }];
|
|
5508
|
+
},
|
|
5509
|
+
setArrayDetailViewMode(mode) {
|
|
5510
|
+
this.arrayDetailViewMode = mode;
|
|
5511
|
+
if (mode === 'table') {
|
|
5512
|
+
if (this.isCollapsed) {
|
|
5513
|
+
this.isCollapsed = false;
|
|
5514
|
+
}
|
|
5515
|
+
}
|
|
5516
|
+
},
|
|
5285
5517
|
setDetailViewMode(mode) {
|
|
5286
5518
|
this.detailViewMode = mode;
|
|
5287
|
-
|
|
5519
|
+
|
|
5288
5520
|
// When switching to map view, expand the container and value so the map is visible
|
|
5289
5521
|
if (mode === 'map' && this.isGeoJsonGeometry) {
|
|
5290
5522
|
if (this.isCollapsed) {
|
|
@@ -5355,6 +5587,7 @@ module.exports = app => app.component('document-property', {
|
|
|
5355
5587
|
if (path.instance === 'Array') {
|
|
5356
5588
|
props.path = path;
|
|
5357
5589
|
props.schemaPaths = this.schemaPaths;
|
|
5590
|
+
props.viewMode = this.arrayDetailViewMode === 'table' ? 'table' : 'json';
|
|
5358
5591
|
}
|
|
5359
5592
|
return props;
|
|
5360
5593
|
},
|
|
@@ -5612,6 +5845,8 @@ module.exports = app => app.component('document', {
|
|
|
5612
5845
|
shouldShowConfirmModal: false,
|
|
5613
5846
|
shouldShowDeleteModal: false,
|
|
5614
5847
|
shouldShowCloneModal: false,
|
|
5848
|
+
shouldShowValidationModal: false,
|
|
5849
|
+
validationResult: null,
|
|
5615
5850
|
previousQuery: null,
|
|
5616
5851
|
lastUpdatedAt: null,
|
|
5617
5852
|
isRefreshing: false,
|
|
@@ -5937,6 +6172,17 @@ module.exports = app => app.component('document', {
|
|
|
5937
6172
|
this.desktopMenuOpen = false;
|
|
5938
6173
|
this.mobileMenuOpen = false;
|
|
5939
6174
|
},
|
|
6175
|
+
async openValidationModal() {
|
|
6176
|
+
this.desktopMenuOpen = false;
|
|
6177
|
+
this.mobileMenuOpen = false;
|
|
6178
|
+
this.shouldShowValidationModal = true;
|
|
6179
|
+
this.validationResult = null;
|
|
6180
|
+
const { result } = await api.Model.validateDocument({
|
|
6181
|
+
model: this.model,
|
|
6182
|
+
documentId: this.documentId
|
|
6183
|
+
});
|
|
6184
|
+
this.validationResult = result;
|
|
6185
|
+
},
|
|
5940
6186
|
closeScriptDrawer() {
|
|
5941
6187
|
this.scriptDrawerOpen = false;
|
|
5942
6188
|
},
|
|
@@ -6071,6 +6317,7 @@ module.exports = app => app.component('execute-script', {
|
|
|
6071
6317
|
|
|
6072
6318
|
const template = __webpack_require__(/*! ./edit-array.html */ "./frontend/src/edit-array/edit-array.html");
|
|
6073
6319
|
const { BSON } = __webpack_require__(/*! mongodb/lib/bson */ "./node_modules/mongodb/lib/bson.js");
|
|
6320
|
+
const { isArrayOfObjects, unionKeysForArrayOfObjects, arrayItemMatchesSearch } = __webpack_require__(/*! ../array-utils */ "./frontend/src/array-utils.js");
|
|
6074
6321
|
|
|
6075
6322
|
const ObjectId = new Proxy(BSON.ObjectId, {
|
|
6076
6323
|
apply(target, thisArg, argumentsList) {
|
|
@@ -6081,15 +6328,44 @@ const ObjectId = new Proxy(BSON.ObjectId, {
|
|
|
6081
6328
|
|
|
6082
6329
|
module.exports = app => app.component('edit-array', {
|
|
6083
6330
|
template: template,
|
|
6084
|
-
props:
|
|
6331
|
+
props: {
|
|
6332
|
+
value: {},
|
|
6333
|
+
path: {},
|
|
6334
|
+
schemaPaths: {},
|
|
6335
|
+
/** 'json' (ACE) or 'table' for arrays of plain objects */
|
|
6336
|
+
viewMode: {
|
|
6337
|
+
type: String,
|
|
6338
|
+
default: 'json'
|
|
6339
|
+
}
|
|
6340
|
+
},
|
|
6085
6341
|
data() {
|
|
6086
6342
|
return {
|
|
6087
|
-
arrayValue: []
|
|
6343
|
+
arrayValue: [],
|
|
6344
|
+
newColumnKeyInput: '',
|
|
6345
|
+
newColumnKeyError: '',
|
|
6346
|
+
arraySearchQuery: ''
|
|
6088
6347
|
};
|
|
6089
6348
|
},
|
|
6090
6349
|
computed: {
|
|
6091
6350
|
arrayStr() {
|
|
6092
6351
|
return JSON.stringify(this.arrayValue, null, 2);
|
|
6352
|
+
},
|
|
6353
|
+
useTableEditor() {
|
|
6354
|
+
return this.viewMode === 'table' && isArrayOfObjects(this.arrayValue);
|
|
6355
|
+
},
|
|
6356
|
+
tableColumnKeys() {
|
|
6357
|
+
return unionKeysForArrayOfObjects(this.arrayValue);
|
|
6358
|
+
},
|
|
6359
|
+
arraySearchNormalized() {
|
|
6360
|
+
return (this.arraySearchQuery || '').trim().toLowerCase();
|
|
6361
|
+
},
|
|
6362
|
+
filteredTableRows() {
|
|
6363
|
+
if (!Array.isArray(this.arrayValue)) {
|
|
6364
|
+
return [];
|
|
6365
|
+
}
|
|
6366
|
+
return this.arrayValue
|
|
6367
|
+
.map((item, index) => ({ item, index }))
|
|
6368
|
+
.filter(({ item }) => arrayItemMatchesSearch(item, this.arraySearchNormalized));
|
|
6093
6369
|
}
|
|
6094
6370
|
},
|
|
6095
6371
|
methods: {
|
|
@@ -6120,6 +6396,78 @@ module.exports = app => app.component('edit-array', {
|
|
|
6120
6396
|
} catch (err) {
|
|
6121
6397
|
this.$emit('error', err);
|
|
6122
6398
|
}
|
|
6399
|
+
},
|
|
6400
|
+
serializeCell(value) {
|
|
6401
|
+
if (value === undefined) {
|
|
6402
|
+
return '';
|
|
6403
|
+
}
|
|
6404
|
+
return JSON.stringify(value);
|
|
6405
|
+
},
|
|
6406
|
+
onCellChange(rowIndex, key, raw) {
|
|
6407
|
+
const row = this.arrayValue[rowIndex];
|
|
6408
|
+
if (row == null || typeof row !== 'object') {
|
|
6409
|
+
return;
|
|
6410
|
+
}
|
|
6411
|
+
const trimmed = raw.trim();
|
|
6412
|
+
if (trimmed === '') {
|
|
6413
|
+
delete row[key];
|
|
6414
|
+
this.emitUpdate();
|
|
6415
|
+
return;
|
|
6416
|
+
}
|
|
6417
|
+
try {
|
|
6418
|
+
row[key] = JSON.parse(trimmed);
|
|
6419
|
+
this.emitUpdate();
|
|
6420
|
+
} catch (err) {
|
|
6421
|
+
this.$emit('error', err);
|
|
6422
|
+
}
|
|
6423
|
+
},
|
|
6424
|
+
addRow() {
|
|
6425
|
+
const row = {};
|
|
6426
|
+
for (const k of this.tableColumnKeys) {
|
|
6427
|
+
row[k] = null;
|
|
6428
|
+
}
|
|
6429
|
+
this.arrayValue.push(row);
|
|
6430
|
+
this.emitUpdate();
|
|
6431
|
+
},
|
|
6432
|
+
addColumn() {
|
|
6433
|
+
const key = (this.newColumnKeyInput || '').trim();
|
|
6434
|
+
this.newColumnKeyError = '';
|
|
6435
|
+
if (!key) {
|
|
6436
|
+
this.newColumnKeyError = 'Enter a column name.';
|
|
6437
|
+
return;
|
|
6438
|
+
}
|
|
6439
|
+
if (this.tableColumnKeys.includes(key)) {
|
|
6440
|
+
this.newColumnKeyError = 'That column already exists.';
|
|
6441
|
+
return;
|
|
6442
|
+
}
|
|
6443
|
+
if (!Array.isArray(this.arrayValue)) {
|
|
6444
|
+
this.arrayValue = [];
|
|
6445
|
+
}
|
|
6446
|
+
if (this.arrayValue.length === 0) {
|
|
6447
|
+
this.arrayValue.push({ [key]: null });
|
|
6448
|
+
} else {
|
|
6449
|
+
for (const row of this.arrayValue) {
|
|
6450
|
+
if (row != null && typeof row === 'object' && !Array.isArray(row) && row.constructor === Object) {
|
|
6451
|
+
row[key] = null;
|
|
6452
|
+
}
|
|
6453
|
+
}
|
|
6454
|
+
}
|
|
6455
|
+
this.newColumnKeyInput = '';
|
|
6456
|
+
this.emitUpdate();
|
|
6457
|
+
},
|
|
6458
|
+
removeRow(index) {
|
|
6459
|
+
this.arrayValue.splice(index, 1);
|
|
6460
|
+
this.emitUpdate();
|
|
6461
|
+
},
|
|
6462
|
+
moveRow(index, delta) {
|
|
6463
|
+
const j = index + delta;
|
|
6464
|
+
if (j < 0 || j >= this.arrayValue.length) {
|
|
6465
|
+
return;
|
|
6466
|
+
}
|
|
6467
|
+
const copy = this.arrayValue[index];
|
|
6468
|
+
this.arrayValue.splice(index, 1);
|
|
6469
|
+
this.arrayValue.splice(j, 0, copy);
|
|
6470
|
+
this.emitUpdate();
|
|
6123
6471
|
}
|
|
6124
6472
|
},
|
|
6125
6473
|
mounted() {
|
|
@@ -6646,6 +6994,7 @@ const {
|
|
|
6646
6994
|
appendCSS(__webpack_require__(/*! vue-toastification/dist/index.css */ "./node_modules/vue-toastification/dist/index.css"));
|
|
6647
6995
|
|
|
6648
6996
|
const TRACKED_RECENT_PAGE_ROUTE_NAMES = new Set(['model', 'document', 'dashboard', 'chat']);
|
|
6997
|
+
const CHAT_ROUTE_NAMES = new Set(['chat index', 'chat']);
|
|
6649
6998
|
|
|
6650
6999
|
function formatHistoryLabel(route) {
|
|
6651
7000
|
if (!route || typeof route.path !== 'string') {
|
|
@@ -6858,8 +7207,12 @@ app.component('app-component', {
|
|
|
6858
7207
|
window.localStorage.setItem('_mongooseStudioAccessToken', accessToken._id);
|
|
6859
7208
|
|
|
6860
7209
|
try {
|
|
6861
|
-
const { nodeEnv } = await
|
|
7210
|
+
const [{ nodeEnv }, capabilities] = await Promise.all([
|
|
7211
|
+
api.status(),
|
|
7212
|
+
loadStudioCapabilities()
|
|
7213
|
+
]);
|
|
6862
7214
|
this.nodeEnv = nodeEnv;
|
|
7215
|
+
this.capabilities = capabilities;
|
|
6863
7216
|
} catch (err) {
|
|
6864
7217
|
this.authError = 'Error connecting to Mongoose Studio API: ' + err.response?.data?.message ?? err.message;
|
|
6865
7218
|
this.status = 'loaded';
|
|
@@ -6879,12 +7232,14 @@ app.component('app-component', {
|
|
|
6879
7232
|
const { user, roles } = await mothership.me();
|
|
6880
7233
|
|
|
6881
7234
|
try {
|
|
6882
|
-
const [{ nodeEnv }, { modelSchemaPaths }] = await Promise.all([
|
|
7235
|
+
const [{ nodeEnv }, { modelSchemaPaths }, capabilities] = await Promise.all([
|
|
6883
7236
|
api.status(),
|
|
6884
|
-
api.Model.listModels()
|
|
7237
|
+
api.Model.listModels(),
|
|
7238
|
+
loadStudioCapabilities()
|
|
6885
7239
|
]);
|
|
6886
7240
|
this.nodeEnv = nodeEnv;
|
|
6887
7241
|
this.modelSchemaPaths = modelSchemaPaths;
|
|
7242
|
+
this.capabilities = capabilities;
|
|
6888
7243
|
} catch (err) {
|
|
6889
7244
|
this.authError = 'Error connecting to Mongoose Studio API: ' + (err.response?.data?.message ?? err.message);
|
|
6890
7245
|
this.status = 'loaded';
|
|
@@ -6897,12 +7252,14 @@ app.component('app-component', {
|
|
|
6897
7252
|
}
|
|
6898
7253
|
} else {
|
|
6899
7254
|
try {
|
|
6900
|
-
const [{ nodeEnv }, { modelSchemaPaths }] = await Promise.all([
|
|
7255
|
+
const [{ nodeEnv }, { modelSchemaPaths }, capabilities] = await Promise.all([
|
|
6901
7256
|
api.status(),
|
|
6902
|
-
api.Model.listModels()
|
|
7257
|
+
api.Model.listModels(),
|
|
7258
|
+
loadStudioCapabilities()
|
|
6903
7259
|
]);
|
|
6904
7260
|
this.nodeEnv = nodeEnv;
|
|
6905
7261
|
this.modelSchemaPaths = modelSchemaPaths;
|
|
7262
|
+
this.capabilities = capabilities;
|
|
6906
7263
|
} catch (err) {
|
|
6907
7264
|
this.authError = 'Error connecting to Mongoose Studio API: ' + (err.response?.data?.message ?? err.message);
|
|
6908
7265
|
}
|
|
@@ -6917,6 +7274,7 @@ app.component('app-component', {
|
|
|
6917
7274
|
const nodeEnv = Vue.ref(null);
|
|
6918
7275
|
const authError = Vue.ref(null);
|
|
6919
7276
|
const modelSchemaPaths = Vue.ref(null);
|
|
7277
|
+
const capabilities = Vue.ref(null);
|
|
6920
7278
|
|
|
6921
7279
|
const state = Vue.reactive({
|
|
6922
7280
|
user,
|
|
@@ -6924,7 +7282,8 @@ app.component('app-component', {
|
|
|
6924
7282
|
status,
|
|
6925
7283
|
nodeEnv,
|
|
6926
7284
|
authError,
|
|
6927
|
-
modelSchemaPaths
|
|
7285
|
+
modelSchemaPaths,
|
|
7286
|
+
capabilities
|
|
6928
7287
|
});
|
|
6929
7288
|
Vue.provide('state', state);
|
|
6930
7289
|
|
|
@@ -6976,13 +7335,13 @@ router.beforeEach((to, from, next) => {
|
|
|
6976
7335
|
next();
|
|
6977
7336
|
});
|
|
6978
7337
|
|
|
6979
|
-
router.beforeEach((to, from, next) => {
|
|
6980
|
-
if (
|
|
7338
|
+
router.beforeEach(async(to, from, next) => {
|
|
7339
|
+
if (CHAT_ROUTE_NAMES.has(to.name) && !(await supportsAI())) {
|
|
6981
7340
|
next({ name: 'root' });
|
|
6982
7341
|
return;
|
|
6983
7342
|
}
|
|
6984
7343
|
|
|
6985
|
-
if (to.name === 'root' && window.state
|
|
7344
|
+
if (to.name === 'root' && window.state?.roles && window.state.roles[0] === 'dashboards') {
|
|
6986
7345
|
return next({ name: 'dashboards' });
|
|
6987
7346
|
} else {
|
|
6988
7347
|
next();
|
|
@@ -6998,6 +7357,31 @@ app.use(router);
|
|
|
6998
7357
|
|
|
6999
7358
|
app.mount('#content');
|
|
7000
7359
|
|
|
7360
|
+
async function getStudioCapabilities() {
|
|
7361
|
+
if (window.__mongooseStudioCapabilitiesPromise == null) {
|
|
7362
|
+
window.__mongooseStudioCapabilitiesPromise = api.getCapabilities().catch(err => {
|
|
7363
|
+
window.__mongooseStudioCapabilitiesPromise = null;
|
|
7364
|
+
throw err;
|
|
7365
|
+
});
|
|
7366
|
+
}
|
|
7367
|
+
return window.__mongooseStudioCapabilitiesPromise;
|
|
7368
|
+
}
|
|
7369
|
+
|
|
7370
|
+
async function loadStudioCapabilities() {
|
|
7371
|
+
try {
|
|
7372
|
+
return await getStudioCapabilities();
|
|
7373
|
+
} catch (err) {
|
|
7374
|
+
return null;
|
|
7375
|
+
}
|
|
7376
|
+
}
|
|
7377
|
+
|
|
7378
|
+
async function supportsAI() {
|
|
7379
|
+
if (window.state?.capabilities == null) {
|
|
7380
|
+
return true;
|
|
7381
|
+
}
|
|
7382
|
+
return window.state.capabilities.supportsAI !== false;
|
|
7383
|
+
}
|
|
7384
|
+
|
|
7001
7385
|
|
|
7002
7386
|
/***/ },
|
|
7003
7387
|
|
|
@@ -7696,6 +8080,166 @@ module.exports = app => app.component('modal', {
|
|
|
7696
8080
|
});
|
|
7697
8081
|
|
|
7698
8082
|
|
|
8083
|
+
/***/ },
|
|
8084
|
+
|
|
8085
|
+
/***/ "./frontend/src/models/analyze-schema-modal/analyze-schema-modal.js"
|
|
8086
|
+
/*!**************************************************************************!*\
|
|
8087
|
+
!*** ./frontend/src/models/analyze-schema-modal/analyze-schema-modal.js ***!
|
|
8088
|
+
\**************************************************************************/
|
|
8089
|
+
(module, __unused_webpack_exports, __webpack_require__) {
|
|
8090
|
+
|
|
8091
|
+
"use strict";
|
|
8092
|
+
|
|
8093
|
+
|
|
8094
|
+
const api = __webpack_require__(/*! ../../api */ "./frontend/src/api.js");
|
|
8095
|
+
const template = __webpack_require__(/*! ./analyze-schema-modal.html */ "./frontend/src/models/analyze-schema-modal/analyze-schema-modal.html");
|
|
8096
|
+
const xss = __webpack_require__(/*! xss */ "./node_modules/xss/lib/index.js");
|
|
8097
|
+
|
|
8098
|
+
const TYPE_COLORS = {
|
|
8099
|
+
string: '#315f5b',
|
|
8100
|
+
number: '#465b86',
|
|
8101
|
+
objectid: '#2f6381',
|
|
8102
|
+
boolean: '#5b6540',
|
|
8103
|
+
date: '#5a5278',
|
|
8104
|
+
array: '#6b573d',
|
|
8105
|
+
object: '#3f6648',
|
|
8106
|
+
mixed: '#62546a',
|
|
8107
|
+
buffer: '#5a5f50',
|
|
8108
|
+
decimal128: '#4a6474',
|
|
8109
|
+
map: '#4e665d',
|
|
8110
|
+
null: '#3f4650',
|
|
8111
|
+
undefined: '#5b626b'
|
|
8112
|
+
};
|
|
8113
|
+
|
|
8114
|
+
const FALLBACK_TYPE_COLORS = [
|
|
8115
|
+
'#315f5b',
|
|
8116
|
+
'#465b86',
|
|
8117
|
+
'#2f6381',
|
|
8118
|
+
'#5b6540',
|
|
8119
|
+
'#5a5278',
|
|
8120
|
+
'#6b573d',
|
|
8121
|
+
'#3f6648',
|
|
8122
|
+
'#62546a'
|
|
8123
|
+
];
|
|
8124
|
+
|
|
8125
|
+
module.exports = app => app.component('analyze-schema-modal', {
|
|
8126
|
+
template,
|
|
8127
|
+
props: ['currentModel'],
|
|
8128
|
+
emits: ['close'],
|
|
8129
|
+
data: () => ({
|
|
8130
|
+
schemaAnalysis: null,
|
|
8131
|
+
fieldSearch: '',
|
|
8132
|
+
showInvalidDocuments: false,
|
|
8133
|
+
typeDisplayMode: 'bars'
|
|
8134
|
+
}),
|
|
8135
|
+
computed: {
|
|
8136
|
+
filteredPaths() {
|
|
8137
|
+
if (!this.schemaAnalysis) {
|
|
8138
|
+
return [];
|
|
8139
|
+
}
|
|
8140
|
+
const search = this.fieldSearch.trim().toLowerCase();
|
|
8141
|
+
if (!search) {
|
|
8142
|
+
return this.schemaAnalysis.paths;
|
|
8143
|
+
}
|
|
8144
|
+
return this.schemaAnalysis.paths.filter(path => path.path.toLowerCase().includes(search));
|
|
8145
|
+
}
|
|
8146
|
+
},
|
|
8147
|
+
async mounted() {
|
|
8148
|
+
window.analyzeSchemaModal = this;
|
|
8149
|
+
const { analysis } = await api.Model.analyzeSchema({ model: this.currentModel });
|
|
8150
|
+
this.schemaAnalysis = analysis;
|
|
8151
|
+
this.$nextTick(() => {
|
|
8152
|
+
this.$refs.fieldSearch?.focus();
|
|
8153
|
+
});
|
|
8154
|
+
},
|
|
8155
|
+
unmounted() {
|
|
8156
|
+
window.analyzeSchemaModal = null;
|
|
8157
|
+
},
|
|
8158
|
+
methods: {
|
|
8159
|
+
close() {
|
|
8160
|
+
this.$emit('close');
|
|
8161
|
+
},
|
|
8162
|
+
formatNumber(value) {
|
|
8163
|
+
if (typeof value !== 'number') {
|
|
8164
|
+
return 'Unknown';
|
|
8165
|
+
}
|
|
8166
|
+
|
|
8167
|
+
return value.toLocaleString();
|
|
8168
|
+
},
|
|
8169
|
+
formatPercent(value, total) {
|
|
8170
|
+
if (!total) {
|
|
8171
|
+
return '0%';
|
|
8172
|
+
}
|
|
8173
|
+
return `${Math.round((value / total) * 1000) / 10}%`;
|
|
8174
|
+
},
|
|
8175
|
+
typePercentValue(value, total) {
|
|
8176
|
+
if (!total) {
|
|
8177
|
+
return 0;
|
|
8178
|
+
}
|
|
8179
|
+
return Math.round((value / total) * 1000) / 10;
|
|
8180
|
+
},
|
|
8181
|
+
pathValueCount(path) {
|
|
8182
|
+
return typeof path?.valueCount === 'number' && path.valueCount > 0 ?
|
|
8183
|
+
path.valueCount :
|
|
8184
|
+
this.schemaAnalysis.sampleSize;
|
|
8185
|
+
},
|
|
8186
|
+
primaryType(path) {
|
|
8187
|
+
if (!path || !Array.isArray(path.types) || path.types.length === 0) {
|
|
8188
|
+
return null;
|
|
8189
|
+
}
|
|
8190
|
+
|
|
8191
|
+
return path.types.find(typeInfo => typeInfo.type !== 'null' && typeInfo.type !== 'undefined') || path.types[0];
|
|
8192
|
+
},
|
|
8193
|
+
typeColor(type) {
|
|
8194
|
+
const normalizedType = String(type || '').toLowerCase();
|
|
8195
|
+
if (TYPE_COLORS[normalizedType]) {
|
|
8196
|
+
return TYPE_COLORS[normalizedType];
|
|
8197
|
+
}
|
|
8198
|
+
|
|
8199
|
+
let hash = 0;
|
|
8200
|
+
for (let i = 0; i < normalizedType.length; ++i) {
|
|
8201
|
+
hash = ((hash << 5) - hash) + normalizedType.charCodeAt(i);
|
|
8202
|
+
hash |= 0;
|
|
8203
|
+
}
|
|
8204
|
+
const index = Math.abs(hash) % FALLBACK_TYPE_COLORS.length;
|
|
8205
|
+
return FALLBACK_TYPE_COLORS[index % FALLBACK_TYPE_COLORS.length];
|
|
8206
|
+
},
|
|
8207
|
+
typeBadgeStyle(type) {
|
|
8208
|
+
return {
|
|
8209
|
+
backgroundColor: this.typeColor(type),
|
|
8210
|
+
color: '#fff'
|
|
8211
|
+
};
|
|
8212
|
+
},
|
|
8213
|
+
typeSegmentStyle(type, count, total) {
|
|
8214
|
+
return {
|
|
8215
|
+
width: `${this.typePercentValue(count, total)}%`,
|
|
8216
|
+
backgroundColor: this.typeColor(type),
|
|
8217
|
+
color: '#fff'
|
|
8218
|
+
};
|
|
8219
|
+
},
|
|
8220
|
+
documentRoute(documentId) {
|
|
8221
|
+
return `/model/${this.currentModel}/document/${documentId}`;
|
|
8222
|
+
},
|
|
8223
|
+
highlightPath(path) {
|
|
8224
|
+
const search = this.fieldSearch.trim();
|
|
8225
|
+
if (!search) {
|
|
8226
|
+
return xss(path);
|
|
8227
|
+
}
|
|
8228
|
+
|
|
8229
|
+
const index = path.toLowerCase().indexOf(search.toLowerCase());
|
|
8230
|
+
if (index === -1) {
|
|
8231
|
+
return xss(path);
|
|
8232
|
+
}
|
|
8233
|
+
|
|
8234
|
+
const before = path.slice(0, index);
|
|
8235
|
+
const match = path.slice(index, index + search.length);
|
|
8236
|
+
const after = path.slice(index + search.length);
|
|
8237
|
+
return `${xss(before)}<strong>${xss(match)}</strong>${xss(after)}`;
|
|
8238
|
+
}
|
|
8239
|
+
}
|
|
8240
|
+
});
|
|
8241
|
+
|
|
8242
|
+
|
|
7699
8243
|
/***/ },
|
|
7700
8244
|
|
|
7701
8245
|
/***/ "./frontend/src/models/document-search/document-search.js"
|
|
@@ -8161,6 +8705,7 @@ module.exports = app => app.component('models', {
|
|
|
8161
8705
|
addFieldFilterText: '',
|
|
8162
8706
|
showAddFieldDropdown: false,
|
|
8163
8707
|
shouldShowIndexModal: false,
|
|
8708
|
+
shouldShowAnalyzeSchemaModal: false,
|
|
8164
8709
|
shouldShowCollectionInfoModal: false,
|
|
8165
8710
|
shouldShowDropCollectionModal: false,
|
|
8166
8711
|
dropCollectionConfirmName: '',
|
|
@@ -8883,6 +9428,10 @@ module.exports = app => app.component('models', {
|
|
|
8883
9428
|
this.mongoDBIndexes = mongoDBIndexes;
|
|
8884
9429
|
this.schemaIndexes = schemaIndexes;
|
|
8885
9430
|
},
|
|
9431
|
+
openAnalyzeSchemaModal() {
|
|
9432
|
+
this.closeActionsMenu();
|
|
9433
|
+
this.shouldShowAnalyzeSchemaModal = true;
|
|
9434
|
+
},
|
|
8886
9435
|
toggleActionsMenu() {
|
|
8887
9436
|
this.showActionsMenu = !this.showActionsMenu;
|
|
8888
9437
|
},
|
|
@@ -8943,19 +9492,6 @@ module.exports = app => app.component('models', {
|
|
|
8943
9492
|
this.$toast.success(`Collection ${this.currentModel} dropped.`);
|
|
8944
9493
|
await this.getDocuments();
|
|
8945
9494
|
},
|
|
8946
|
-
async findOldestDocument() {
|
|
8947
|
-
this.closeActionsMenu();
|
|
8948
|
-
const { docs } = await api.Model.getDocuments({
|
|
8949
|
-
model: this.currentModel,
|
|
8950
|
-
limit: 1,
|
|
8951
|
-
sortKey: '_id',
|
|
8952
|
-
sortDirection: 1
|
|
8953
|
-
});
|
|
8954
|
-
if (!Array.isArray(docs) || docs.length === 0) {
|
|
8955
|
-
throw new Error('No documents found');
|
|
8956
|
-
}
|
|
8957
|
-
this.openDocument(docs[0]);
|
|
8958
|
-
},
|
|
8959
9495
|
isTTLIndex(index) {
|
|
8960
9496
|
return index != null && index.expireAfterSeconds != null;
|
|
8961
9497
|
},
|
|
@@ -10100,7 +10636,6 @@ exports.hasAPIKey = client.hasAPIKey;
|
|
|
10100
10636
|
"use strict";
|
|
10101
10637
|
|
|
10102
10638
|
|
|
10103
|
-
const api = __webpack_require__(/*! ../api */ "./frontend/src/api.js");
|
|
10104
10639
|
const mothership = __webpack_require__(/*! ../mothership */ "./frontend/src/mothership.js");
|
|
10105
10640
|
const template = __webpack_require__(/*! ./navbar.html */ "./frontend/src/navbar/navbar.html");
|
|
10106
10641
|
const { routes, hasAccess } = __webpack_require__(/*! ../routes */ "./frontend/src/routes.js");
|
|
@@ -10207,8 +10742,8 @@ module.exports = app => app.component('navbar', {
|
|
|
10207
10742
|
recentPagesList() {
|
|
10208
10743
|
return this.localRecentPages;
|
|
10209
10744
|
},
|
|
10210
|
-
|
|
10211
|
-
return
|
|
10745
|
+
supportsAI() {
|
|
10746
|
+
return this.state.capabilities == null || this.state.capabilities.supportsAI !== false;
|
|
10212
10747
|
},
|
|
10213
10748
|
chatDisabledReason() {
|
|
10214
10749
|
return 'Chat requires an Anthropic, Gemini, or OpenAI API key.';
|
|
@@ -11928,6 +12463,9 @@ var map = {
|
|
|
11928
12463
|
"./modal/modal.css": "./frontend/src/modal/modal.css",
|
|
11929
12464
|
"./modal/modal.html": "./frontend/src/modal/modal.html",
|
|
11930
12465
|
"./modal/modal.js": "./frontend/src/modal/modal.js",
|
|
12466
|
+
"./models/analyze-schema-modal/analyze-schema-modal": "./frontend/src/models/analyze-schema-modal/analyze-schema-modal.js",
|
|
12467
|
+
"./models/analyze-schema-modal/analyze-schema-modal.html": "./frontend/src/models/analyze-schema-modal/analyze-schema-modal.html",
|
|
12468
|
+
"./models/analyze-schema-modal/analyze-schema-modal.js": "./frontend/src/models/analyze-schema-modal/analyze-schema-modal.js",
|
|
11931
12469
|
"./models/document-search/document-search": "./frontend/src/models/document-search/document-search.js",
|
|
11932
12470
|
"./models/document-search/document-search.html": "./frontend/src/models/document-search/document-search.html",
|
|
11933
12471
|
"./models/document-search/document-search.js": "./frontend/src/models/document-search/document-search.js",
|
|
@@ -12066,7 +12604,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
12066
12604
|
/* harmony export */ });
|
|
12067
12605
|
/* harmony import */ var _vue_shared__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
|
|
12068
12606
|
/**
|
|
12069
|
-
* @vue/reactivity v3.5.
|
|
12607
|
+
* @vue/reactivity v3.5.35
|
|
12070
12608
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
12071
12609
|
* @license MIT
|
|
12072
12610
|
**/
|
|
@@ -13449,9 +13987,6 @@ function targetTypeMap(rawType) {
|
|
|
13449
13987
|
return 0 /* INVALID */;
|
|
13450
13988
|
}
|
|
13451
13989
|
}
|
|
13452
|
-
function getTargetType(value) {
|
|
13453
|
-
return value["__v_skip"] || !Object.isExtensible(value) ? 0 /* INVALID */ : targetTypeMap((0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.toRawType)(value));
|
|
13454
|
-
}
|
|
13455
13990
|
// @__NO_SIDE_EFFECTS__
|
|
13456
13991
|
function reactive(target) {
|
|
13457
13992
|
if (/* @__PURE__ */ isReadonly(target)) {
|
|
@@ -13509,14 +14044,17 @@ function createReactiveObject(target, isReadonly2, baseHandlers, collectionHandl
|
|
|
13509
14044
|
if (target["__v_raw"] && !(isReadonly2 && target["__v_isReactive"])) {
|
|
13510
14045
|
return target;
|
|
13511
14046
|
}
|
|
13512
|
-
|
|
13513
|
-
if (targetType === 0 /* INVALID */) {
|
|
14047
|
+
if (target["__v_skip"] || !Object.isExtensible(target)) {
|
|
13514
14048
|
return target;
|
|
13515
14049
|
}
|
|
13516
14050
|
const existingProxy = proxyMap.get(target);
|
|
13517
14051
|
if (existingProxy) {
|
|
13518
14052
|
return existingProxy;
|
|
13519
14053
|
}
|
|
14054
|
+
const targetType = targetTypeMap((0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.toRawType)(target));
|
|
14055
|
+
if (targetType === 0 /* INVALID */) {
|
|
14056
|
+
return target;
|
|
14057
|
+
}
|
|
13520
14058
|
const proxy = new Proxy(
|
|
13521
14059
|
target,
|
|
13522
14060
|
targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers
|
|
@@ -14234,7 +14772,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
14234
14772
|
/* harmony import */ var _vue_reactivity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/reactivity */ "./node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js");
|
|
14235
14773
|
/* harmony import */ var _vue_shared__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
|
|
14236
14774
|
/**
|
|
14237
|
-
* @vue/runtime-core v3.5.
|
|
14775
|
+
* @vue/runtime-core v3.5.35
|
|
14238
14776
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
14239
14777
|
* @license MIT
|
|
14240
14778
|
**/
|
|
@@ -15394,19 +15932,18 @@ const TeleportImpl = {
|
|
|
15394
15932
|
target,
|
|
15395
15933
|
props
|
|
15396
15934
|
} = vnode;
|
|
15397
|
-
|
|
15935
|
+
const shouldRemove = doRemove || !isTeleportDisabled(props);
|
|
15398
15936
|
const pendingMount = pendingMounts.get(vnode);
|
|
15399
15937
|
if (pendingMount) {
|
|
15400
15938
|
pendingMount.flags |= 8;
|
|
15401
15939
|
pendingMounts.delete(vnode);
|
|
15402
|
-
shouldRemove = false;
|
|
15403
15940
|
}
|
|
15404
15941
|
if (target) {
|
|
15405
15942
|
hostRemove(targetStart);
|
|
15406
15943
|
hostRemove(targetAnchor);
|
|
15407
15944
|
}
|
|
15408
15945
|
doRemove && hostRemove(anchor);
|
|
15409
|
-
if (shapeFlag & 16) {
|
|
15946
|
+
if (!pendingMount && shapeFlag & 16) {
|
|
15410
15947
|
for (let i = 0; i < children.length; i++) {
|
|
15411
15948
|
const child = children[i];
|
|
15412
15949
|
unmount(
|
|
@@ -16376,20 +16913,16 @@ function createHydrationFunctions(rendererInternals) {
|
|
|
16376
16913
|
slotScopeIds,
|
|
16377
16914
|
optimized
|
|
16378
16915
|
);
|
|
16379
|
-
|
|
16380
|
-
|
|
16381
|
-
|
|
16382
|
-
|
|
16383
|
-
|
|
16384
|
-
`Hydration children mismatch on`,
|
|
16385
|
-
el,
|
|
16386
|
-
`
|
|
16916
|
+
if (next && !isMismatchAllowed(el, 1 /* CHILDREN */)) {
|
|
16917
|
+
( true) && warn$1(
|
|
16918
|
+
`Hydration children mismatch on`,
|
|
16919
|
+
el,
|
|
16920
|
+
`
|
|
16387
16921
|
Server rendered element contains more child nodes than client vdom.`
|
|
16388
|
-
|
|
16389
|
-
|
|
16390
|
-
|
|
16391
|
-
|
|
16392
|
-
}
|
|
16922
|
+
);
|
|
16923
|
+
logMismatchError();
|
|
16924
|
+
}
|
|
16925
|
+
while (next) {
|
|
16393
16926
|
const cur = next;
|
|
16394
16927
|
next = next.nextSibling;
|
|
16395
16928
|
remove(cur);
|
|
@@ -16453,7 +16986,7 @@ Server rendered element contains more child nodes than client vdom.`
|
|
|
16453
16986
|
optimized = optimized || !!parentVNode.dynamicChildren;
|
|
16454
16987
|
const children = parentVNode.children;
|
|
16455
16988
|
const l = children.length;
|
|
16456
|
-
let
|
|
16989
|
+
let hasCheckedMismatch = false;
|
|
16457
16990
|
for (let i = 0; i < l; i++) {
|
|
16458
16991
|
const vnode = optimized ? children[i] : children[i] = normalizeVNode(children[i]);
|
|
16459
16992
|
const isText = vnode.type === Text;
|
|
@@ -16481,17 +17014,17 @@ Server rendered element contains more child nodes than client vdom.`
|
|
|
16481
17014
|
} else if (isText && !vnode.children) {
|
|
16482
17015
|
insert(vnode.el = createText(""), container);
|
|
16483
17016
|
} else {
|
|
16484
|
-
if (!
|
|
16485
|
-
|
|
16486
|
-
|
|
17017
|
+
if (!hasCheckedMismatch) {
|
|
17018
|
+
hasCheckedMismatch = true;
|
|
17019
|
+
if (!isMismatchAllowed(container, 1 /* CHILDREN */)) {
|
|
17020
|
+
( true) && warn$1(
|
|
16487
17021
|
`Hydration children mismatch on`,
|
|
16488
17022
|
container,
|
|
16489
17023
|
`
|
|
16490
17024
|
Server rendered element contains fewer child nodes than client vdom.`
|
|
16491
17025
|
);
|
|
16492
|
-
|
|
17026
|
+
logMismatchError();
|
|
16493
17027
|
}
|
|
16494
|
-
logMismatchError();
|
|
16495
17028
|
}
|
|
16496
17029
|
patch(
|
|
16497
17030
|
null,
|
|
@@ -20802,9 +21335,13 @@ function baseCreateRenderer(options, createHydrationFns) {
|
|
|
20802
21335
|
const needTransition2 = moveType !== 2 && shapeFlag & 1 && transition;
|
|
20803
21336
|
if (needTransition2) {
|
|
20804
21337
|
if (moveType === 0) {
|
|
20805
|
-
transition.
|
|
20806
|
-
|
|
20807
|
-
|
|
21338
|
+
if (transition.persisted && !el[leaveCbKey]) {
|
|
21339
|
+
hostInsert(el, container, anchor);
|
|
21340
|
+
} else {
|
|
21341
|
+
transition.beforeEnter(el);
|
|
21342
|
+
hostInsert(el, container, anchor);
|
|
21343
|
+
queuePostRenderEffect(() => transition.enter(el), parentSuspense);
|
|
21344
|
+
}
|
|
20808
21345
|
} else {
|
|
20809
21346
|
const { leave, delayLeave, afterLeave } = transition;
|
|
20810
21347
|
const remove2 = () => {
|
|
@@ -20815,16 +21352,21 @@ function baseCreateRenderer(options, createHydrationFns) {
|
|
|
20815
21352
|
}
|
|
20816
21353
|
};
|
|
20817
21354
|
const performLeave = () => {
|
|
21355
|
+
const wasLeaving = el._isLeaving || !!el[leaveCbKey];
|
|
20818
21356
|
if (el._isLeaving) {
|
|
20819
21357
|
el[leaveCbKey](
|
|
20820
21358
|
true
|
|
20821
21359
|
/* cancelled */
|
|
20822
21360
|
);
|
|
20823
21361
|
}
|
|
20824
|
-
|
|
21362
|
+
if (transition.persisted && !wasLeaving) {
|
|
20825
21363
|
remove2();
|
|
20826
|
-
|
|
20827
|
-
|
|
21364
|
+
} else {
|
|
21365
|
+
leave(el, () => {
|
|
21366
|
+
remove2();
|
|
21367
|
+
afterLeave && afterLeave();
|
|
21368
|
+
});
|
|
21369
|
+
}
|
|
20828
21370
|
};
|
|
20829
21371
|
if (delayLeave) {
|
|
20830
21372
|
delayLeave(el, remove2, performLeave);
|
|
@@ -22833,7 +23375,7 @@ function isMemoSame(cached, memo) {
|
|
|
22833
23375
|
return true;
|
|
22834
23376
|
}
|
|
22835
23377
|
|
|
22836
|
-
const version = "3.5.
|
|
23378
|
+
const version = "3.5.35";
|
|
22837
23379
|
const warn = true ? warn$1 : 0;
|
|
22838
23380
|
const ErrorTypeStrings = ErrorTypeStrings$1 ;
|
|
22839
23381
|
const devtools = true ? devtools$1 : 0;
|
|
@@ -23044,7 +23586,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
23044
23586
|
/* harmony import */ var _vue_runtime_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/runtime-core */ "./node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js");
|
|
23045
23587
|
/* harmony import */ var _vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
|
|
23046
23588
|
/**
|
|
23047
|
-
* @vue/runtime-dom v3.5.
|
|
23589
|
+
* @vue/runtime-dom v3.5.35
|
|
23048
23590
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
23049
23591
|
* @license MIT
|
|
23050
23592
|
**/
|
|
@@ -23787,12 +24329,37 @@ function createInvoker(initialValue, instance) {
|
|
|
23787
24329
|
} else if (e._vts <= invoker.attached) {
|
|
23788
24330
|
return;
|
|
23789
24331
|
}
|
|
23790
|
-
|
|
23791
|
-
|
|
23792
|
-
|
|
23793
|
-
|
|
23794
|
-
|
|
23795
|
-
|
|
24332
|
+
const value = invoker.value;
|
|
24333
|
+
if ((0,_vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__.isArray)(value)) {
|
|
24334
|
+
const originalStop = e.stopImmediatePropagation;
|
|
24335
|
+
e.stopImmediatePropagation = () => {
|
|
24336
|
+
originalStop.call(e);
|
|
24337
|
+
e._stopped = true;
|
|
24338
|
+
};
|
|
24339
|
+
const handlers = value.slice();
|
|
24340
|
+
const args = [e];
|
|
24341
|
+
for (let i = 0; i < handlers.length; i++) {
|
|
24342
|
+
if (e._stopped) {
|
|
24343
|
+
break;
|
|
24344
|
+
}
|
|
24345
|
+
const handler = handlers[i];
|
|
24346
|
+
if (handler) {
|
|
24347
|
+
(0,_vue_runtime_core__WEBPACK_IMPORTED_MODULE_0__.callWithAsyncErrorHandling)(
|
|
24348
|
+
handler,
|
|
24349
|
+
instance,
|
|
24350
|
+
5,
|
|
24351
|
+
args
|
|
24352
|
+
);
|
|
24353
|
+
}
|
|
24354
|
+
}
|
|
24355
|
+
} else {
|
|
24356
|
+
(0,_vue_runtime_core__WEBPACK_IMPORTED_MODULE_0__.callWithAsyncErrorHandling)(
|
|
24357
|
+
value,
|
|
24358
|
+
instance,
|
|
24359
|
+
5,
|
|
24360
|
+
[e]
|
|
24361
|
+
);
|
|
24362
|
+
}
|
|
23796
24363
|
};
|
|
23797
24364
|
invoker.value = initialValue;
|
|
23798
24365
|
invoker.attached = getNow();
|
|
@@ -23808,20 +24375,6 @@ Expected function or array of functions, received type ${typeof value}.`
|
|
|
23808
24375
|
);
|
|
23809
24376
|
return _vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__.NOOP;
|
|
23810
24377
|
}
|
|
23811
|
-
function patchStopImmediatePropagation(e, value) {
|
|
23812
|
-
if ((0,_vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__.isArray)(value)) {
|
|
23813
|
-
const originalStop = e.stopImmediatePropagation;
|
|
23814
|
-
e.stopImmediatePropagation = () => {
|
|
23815
|
-
originalStop.call(e);
|
|
23816
|
-
e._stopped = true;
|
|
23817
|
-
};
|
|
23818
|
-
return value.map(
|
|
23819
|
-
(fn) => (e2) => !e2._stopped && fn && fn(e2)
|
|
23820
|
-
);
|
|
23821
|
-
} else {
|
|
23822
|
-
return value;
|
|
23823
|
-
}
|
|
23824
|
-
}
|
|
23825
24378
|
|
|
23826
24379
|
const isNativeOn = (key) => key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && // lowercase letter
|
|
23827
24380
|
key.charCodeAt(2) > 96 && key.charCodeAt(2) < 123;
|
|
@@ -25131,7 +25684,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
25131
25684
|
/* harmony export */ toTypeString: () => (/* binding */ toTypeString)
|
|
25132
25685
|
/* harmony export */ });
|
|
25133
25686
|
/**
|
|
25134
|
-
* @vue/shared v3.5.
|
|
25687
|
+
* @vue/shared v3.5.35
|
|
25135
25688
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
25136
25689
|
* @license MIT
|
|
25137
25690
|
**/
|
|
@@ -50402,6 +50955,16 @@ exports.set = function(path, val, o, special, map, _copying) {
|
|
|
50402
50955
|
}
|
|
50403
50956
|
};
|
|
50404
50957
|
|
|
50958
|
+
/*!
|
|
50959
|
+
* Split a string path into components delimited by '.' or
|
|
50960
|
+
* '[\d+]'
|
|
50961
|
+
*
|
|
50962
|
+
* #### Example:
|
|
50963
|
+
* stringToParts('foo[0].bar.1'); // ['foo', '0', 'bar', '1']
|
|
50964
|
+
*/
|
|
50965
|
+
|
|
50966
|
+
exports.stringToParts = stringToParts;
|
|
50967
|
+
|
|
50405
50968
|
/*!
|
|
50406
50969
|
* Recursively set nested arrays
|
|
50407
50970
|
*/
|
|
@@ -50430,6 +50993,7 @@ function K(v) {
|
|
|
50430
50993
|
return v;
|
|
50431
50994
|
}
|
|
50432
50995
|
|
|
50996
|
+
|
|
50433
50997
|
/***/ },
|
|
50434
50998
|
|
|
50435
50999
|
/***/ "./node_modules/mpath/lib/stringToParts.js"
|
|
@@ -50829,7 +51393,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
50829
51393
|
/* harmony import */ var _vue_runtime_dom__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/runtime-dom */ "./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js");
|
|
50830
51394
|
/* harmony import */ var _vue_runtime_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/runtime-dom */ "./node_modules/@vue/runtime-dom/dist/runtime-dom.esm-bundler.js");
|
|
50831
51395
|
/**
|
|
50832
|
-
* vue v3.5.
|
|
51396
|
+
* vue v3.5.35
|
|
50833
51397
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
50834
51398
|
* @license MIT
|
|
50835
51399
|
**/
|
|
@@ -52192,7 +52756,7 @@ module.exports = "<div class=\"dashboards bg-page min-h-[calc(100vh-55px)]\">\n
|
|
|
52192
52756
|
(module) {
|
|
52193
52757
|
|
|
52194
52758
|
"use strict";
|
|
52195
|
-
module.exports = "<div class=\"w-full\">\n <div v-if=\"!arrayValue || arrayValue.length === 0\" class=\"text-gray-400 text-xs py-2 text-center\">\n Empty array\n </div>\n\n <div v-else class=\"
|
|
52759
|
+
module.exports = "<div class=\"w-full\">\n <div v-if=\"!arrayValue || arrayValue.length === 0\" class=\"text-gray-400 text-xs py-2 text-center\">\n Empty array\n </div>\n\n <div v-else>\n <div class=\"mb-2 flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3\">\n <div class=\"relative min-w-0 flex-1\">\n <input\n v-model=\"arraySearchQuery\"\n type=\"text\"\n autocomplete=\"off\"\n aria-label=\"Search in this array\"\n placeholder=\"Search in this array…\"\n class=\"w-full rounded-md border border-edge bg-surface py-1.5 pl-2 pr-8 text-xs text-content placeholder:text-content-tertiary focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\n />\n <button\n v-if=\"arraySearchNormalized\"\n type=\"button\"\n class=\"absolute right-1 top-1/2 -translate-y-1/2 rounded px-1.5 py-0.5 text-[11px] text-content-tertiary hover:bg-slate-100 hover:text-content\"\n title=\"Clear search\"\n @click=\"arraySearchQuery = ''\">\n Clear\n </button>\n </div>\n <span v-if=\"arraySearchNormalized\" class=\"shrink-0 text-xs text-content-tertiary\">\n {{ filteredArrayRows.length }} of {{ arrayValue.length }} shown\n </span>\n </div>\n\n <div\n v-if=\"filteredArrayRows.length === 0\"\n class=\"rounded-md border border-dashed border-edge py-6 text-center text-xs text-content-tertiary\">\n No items match \"{{ arraySearchQuery.trim() }}\".\n </div>\n\n <div v-else-if=\"showAsTable\" class=\"mt-2 overflow-x-auto rounded-md border border-edge\">\n <table class=\"min-w-full text-left text-xs font-mono text-content\">\n <thead class=\"bg-slate-100 text-content-secondary border-b border-edge\">\n <tr>\n <th scope=\"col\" class=\"sticky left-0 z-[1] bg-slate-100 px-2 py-2 font-semibold border-r border-edge w-10 text-center\">#</th>\n <th\n v-for=\"key in tableColumnKeys\"\n :key=\"key\"\n scope=\"col\"\n class=\"px-3 py-2 font-semibold whitespace-nowrap border-r border-edge last:border-r-0\">\n {{ key }}\n </th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-edge bg-surface\">\n <tr v-for=\"row in displayedArrayRows\" :key=\"row.index\" class=\"hover:bg-slate-50/80\">\n <td class=\"sticky left-0 z-[1] bg-inherit px-2 py-2 text-center text-content-tertiary border-r border-edge\">{{ row.index }}</td>\n <td\n v-for=\"key in tableColumnKeys\"\n :key=\"key\"\n class=\"px-3 py-2 align-top break-words whitespace-pre-wrap max-w-md border-r border-edge last:border-r-0\">\n {{ Object.prototype.hasOwnProperty.call(row.item, key) ? arrayUtils.formatItemValue(row.item, key) : '' }}\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <div v-else class=\"mt-2\">\n <div\n v-for=\"row in displayedArrayRows\"\n :key=\"row.index\"\n :title=\"'Index: ' + row.index\"\n class=\"mb-1.5 last:mb-0 py-2.5 px-3 pl-4 bg-transparent border-l-[3px] border-l-slate-100 rounded-none transition-all duration-200 cursor-pointer relative hover:bg-slate-50 hover:border-l-slate-100 hover:shadow-lg hover:-translate-y-0.5\">\n <div class=\"absolute -left-3 top-1/2 -translate-y-1/2 w-5 h-5 bg-slate-100 text-content rounded-full flex items-center justify-center text-[10px] font-semibold font-mono z-10 hover:bg-slate-100\">{{ row.index >= 1000 ? '1k+' : row.index }}</div>\n <div v-if=\"arrayUtils.isObjectItem(row.item)\" class=\"flex flex-col gap-1 mt-1 px-2\">\n <div\n v-for=\"key in arrayUtils.getItemKeys(row.item)\"\n :key=\"key\"\n class=\"flex items-start gap-2 text-xs font-mono\">\n <span class=\"font-semibold text-gray-600 flex-shrink-0 min-w-[80px]\">{{ key }}:</span>\n <span class=\"text-gray-800 break-words whitespace-pre-wrap flex-1\">{{ arrayUtils.formatItemValue(row.item, key) }}</span>\n </div>\n </div>\n <div v-else class=\"text-xs py-1.5 px-2 font-mono text-gray-800 break-words whitespace-pre-wrap mt-1\">{{ arrayUtils.formatValue(row.item) }}</div>\n </div>\n </div>\n\n <div\n v-if=\"remainingArrayCount > 0\"\n class=\"mb-1.5 py-2.5 px-3 pl-4 bg-transparent border-none border-l-[3px] border-l-slate-100 rounded-none transition-all duration-200 cursor-pointer relative opacity-70 hover:opacity-100\">\n <div class=\"text-xs py-1.5 px-2 font-mono text-content-tertiary italic break-words whitespace-pre-wrap mt-1\">\n ... and {{ remainingArrayCount }} more item{{ remainingArrayCount !== 1 ? 's' : '' }}\n </div>\n </div>\n\n <button\n v-if=\"canToggleRows\"\n @click=\"toggleExpansion\"\n class=\"mt-2 text-merlot-700 hover:text-merlot-900 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\n class=\"w-4 h-4\"\n :class=\"{ 'rotate-180': isExpanded }\"\n fill=\"none\"\n stroke=\"currentColor\"\n 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 {{ expansionToggleLabel }}\n </button>\n </div>\n</div>\n";
|
|
52196
52760
|
|
|
52197
52761
|
/***/ },
|
|
52198
52762
|
|
|
@@ -52258,7 +52822,7 @@ module.exports = ".document-details {\n width: 100%;\n}\n\n.document-details .v
|
|
|
52258
52822
|
(module) {
|
|
52259
52823
|
|
|
52260
52824
|
"use strict";
|
|
52261
|
-
module.exports = "<div class=\"document-details\">\n <!-- View Toggle and Search/Filter Bar -->\n <div class=\"sticky top-[60px] z-40 bg-surface rounded-md p-4 border-b border-edge 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-edge-strong 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-edge-strong rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-surface 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 </div>\n </div>\n\n <!-- Fields View -->\n <div v-if=\"viewMode === 'fields'\">\n <!-- Matched Schema Paths (shown first when searching) -->\n <div\n v-for=\"path in matchedSchemaPaths\"\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=\"true\"\n :invalid=\"invalid\"></document-property>\n </div>\n\n <!-- Matched Virtual Fields (shown after matched schema paths when searching) -->\n <div\n v-for=\"path in matchedVirtuals\"\n :key=\"path.name\"\n class=\"border border-edge rounded-lg mb-2 transition-all duration-200 ease-in-out mt-4\"\n >\n <!-- Virtual Field Header (Always Visible) -->\n <div\n @click=\"toggleVirtualField(path.name)\"\n class=\"p-3 bg-amber-100 hover:bg-amber-200 cursor-pointer flex items-center justify-between border-b border-edge transition-colors duration-200 ease-in-out\"\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-content-tertiary 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-content\">{{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 <!-- Unmatched Schema Paths (shown after matched items when searching, or all when not searching) -->\n <div\n v-for=\"path in unmatchedSchemaPaths\"\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=\"false\"\n :invalid=\"invalid\"></document-property>\n </div>\n\n <!-- Unmatched Virtual Fields (shown last) -->\n <div\n v-for=\"path in unmatchedVirtuals\"\n :key=\"path.name\"\n class=\"border border-edge rounded-lg mb-2 transition-all duration-200 ease-in-out\"\n >\n <!-- Virtual Field Header (Always Visible) -->\n <div\n @click=\"toggleVirtualField(path.name)\"\n class=\"p-3 bg-page hover:bg-muted cursor-pointer flex items-center justify-between border-b border-edge transition-colors duration-200 ease-in-out\"\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-content-tertiary 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-content\">{{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-content-tertiary\">\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-edge-strong rounded-lg bg-page 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-content 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-content-secondary 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-edge-strong 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-content-secondary 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-edge-strong 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-content-secondary 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-edge-strong 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=\"!shouldUseAce\"\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-edge-strong 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 <!-- Ace editor for complex types (Array/Object/Embedded) -->\n <ace-editor\n v-else\n id=\"fieldValue\"\n v-model=\"fieldData.value\"\n mode=\"json\"\n :line-numbers=\"true\"\n :min-lines=\"3\"\n class=\"add-field-ace-editor w-full min-h-[100px]\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\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-content-tertiary\">\n <span v-if=\"shouldUseDatePicker\">Select a date or leave empty for null/undefined values.</span>\n <span v-else-if=\"shouldUseAce\">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-edge\">\n <button\n type=\"button\"\n @click=\"closeAddFieldModal\"\n class=\"px-4 py-2 text-sm font-medium text-content-secondary bg-surface border border-edge-strong rounded-md hover:bg-page 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";
|
|
52825
|
+
module.exports = "<div class=\"document-details\">\n <!-- View Toggle and Search/Filter Bar -->\n <div class=\"sticky top-[60px] z-40 bg-surface rounded-md p-4 border-b border-edge 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-edge-strong 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-edge-strong rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-surface 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 </div>\n </div>\n\n <!-- Fields View -->\n <div v-if=\"viewMode === 'fields'\">\n <!-- Matched Schema Paths (shown first when searching) -->\n <div\n v-for=\"path in matchedSchemaPaths\"\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=\"true\"\n :searchQuery=\"searchQuery\"\n :invalid=\"invalid\"></document-property>\n </div>\n\n <!-- Matched Virtual Fields (shown after matched schema paths when searching) -->\n <div\n v-for=\"path in matchedVirtuals\"\n :key=\"path.name\"\n class=\"border border-edge rounded-lg mb-2 transition-all duration-200 ease-in-out mt-4\"\n >\n <!-- Virtual Field Header (Always Visible) -->\n <div\n @click=\"toggleVirtualField(path.name)\"\n class=\"p-3 bg-slate-100 hover:bg-muted cursor-pointer flex items-center justify-between border-b border-edge transition-colors duration-200 ease-in-out\"\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-content-tertiary 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-content\">\n <span\n v-for=\"(part, index) in getSearchMatchParts(path.name)\"\n :key=\"index\"\n :class=\"{ 'font-bold': part.matched }\">{{ part.text }}</span>\n </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 <!-- Unmatched Schema Paths (shown after matched items when searching, or all when not searching) -->\n <div\n v-for=\"path in unmatchedSchemaPaths\"\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=\"false\"\n :searchQuery=\"searchQuery\"\n :invalid=\"invalid\"></document-property>\n </div>\n\n <!-- Unmatched Virtual Fields (shown last) -->\n <div\n v-for=\"path in unmatchedVirtuals\"\n :key=\"path.name\"\n class=\"border border-edge rounded-lg mb-2 transition-all duration-200 ease-in-out\"\n >\n <!-- Virtual Field Header (Always Visible) -->\n <div\n @click=\"toggleVirtualField(path.name)\"\n class=\"p-3 bg-slate-100 hover:bg-muted cursor-pointer flex items-center justify-between border-b border-edge transition-colors duration-200 ease-in-out\"\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-content-tertiary 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-content\">{{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-content-tertiary\">\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-edge-strong rounded-lg bg-page 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-content 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-content-secondary 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-edge-strong 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-content-secondary 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-edge-strong 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-content-secondary 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-edge-strong 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=\"!shouldUseAce\"\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-edge-strong 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 <!-- Ace editor for complex types (Array/Object/Embedded) -->\n <ace-editor\n v-else\n id=\"fieldValue\"\n v-model=\"fieldData.value\"\n mode=\"json\"\n :line-numbers=\"true\"\n :min-lines=\"3\"\n class=\"add-field-ace-editor w-full min-h-[100px]\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\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-content-tertiary\">\n <span v-if=\"shouldUseDatePicker\">Select a date or leave empty for null/undefined values.</span>\n <span v-else-if=\"shouldUseAce\">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-edge\">\n <button\n type=\"button\"\n @click=\"closeAddFieldModal\"\n class=\"px-4 py-2 text-sm font-medium text-content-secondary bg-surface border border-edge-strong rounded-md hover:bg-page 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";
|
|
52262
52826
|
|
|
52263
52827
|
/***/ },
|
|
52264
52828
|
|
|
@@ -52280,7 +52844,7 @@ module.exports = ".document-details {\n width: 100%;\n }\n \n .document-de
|
|
|
52280
52844
|
(module) {
|
|
52281
52845
|
|
|
52282
52846
|
"use strict";
|
|
52283
|
-
module.exports = "<div class=\"border border-edge bg-surface rounded-lg mb-2\" style=\"overflow: visible;\">\n <!-- Collapsible Header -->\n <div\n @click=\"toggleCollapse\"\n class=\"p-1 cursor-pointer flex items-center justify-between border-b border-edge transition-colors duration-200 ease-in-out\"\n :class=\"{ 'bg-amber-100 hover:bg-amber-200': highlight, 'bg-slate-100 hover:bg-muted': !highlight }\"\n style=\"overflow: visible; position: relative;\"\n >\n <div class=\"flex items-center\" >\n <svg\n :class=\"isCollapsed ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-content-tertiary 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-content\">{{path.path}}</span>\n <span class=\"ml-2 text-sm text-content-tertiary\">({{(path.instance || 'unknown').toLowerCase()}})</span>\n <div v-if=\"isGeoJsonGeometry\" class=\"ml-3 inline-flex items-center gap-2\">\n <div class=\"inline-flex items-center rounded-full bg-gray-200 p-0.5 text-xs font-semibold\">\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"detailViewMode === 'text' ? 'bg-blue-600 text-white shadow' : 'text-content-secondary hover:text-content'\"\n :style=\"detailViewMode === 'text' ? 'color: white !important; background-color: #2563eb !important;' : ''\"\n @click.stop=\"setDetailViewMode('text')\">\n Text\n </button>\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"detailViewMode === 'map' ? 'bg-blue-600 text-white shadow' : 'text-content-secondary hover:text-content'\"\n :style=\"detailViewMode === 'map' ? 'color: white !important; background-color: #2563eb !important;' : ''\"\n @click.stop=\"setDetailViewMode('map')\">\n Map\n </button>\n </div>\n <!-- Info icon with tooltip -->\n <div v-if=\"editting\" class=\"relative inline-block\" style=\"z-index: 10002;\" @mouseenter=\"showTooltip = true\" @mouseleave=\"showTooltip = false\" @click.stop>\n <svg\n ref=\"infoIcon\"\n class=\"w-6 h-6 text-gray-400 hover:text-gray-600 cursor-help\"\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=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"></path>\n </svg>\n <div\n v-show=\"showTooltip\"\n ref=\"tooltip\"\n class=\"absolute left-full top-0 ml-2 w-64 p-3 text-white text-xs rounded-lg shadow-xl\"\n style=\"z-index: 99999; pointer-events: none; white-space: normal; position: fixed; background-color: #111827;\"\n :style=\"getTooltipStyle()\"\n >\n <div class=\"font-semibold mb-2\">Map Controls:</div>\n <div v-if=\"isGeoJsonPoint\" class=\"space-y-1\">\n <div>• Drag pin to move location</div>\n </div>\n <div v-else-if=\"isGeoJsonPolygon\" class=\"space-y-1\">\n <div>• Drag vertices to reshape polygon</div>\n <div v-if=\"isMultiPolygon\">• Right-click edge to add new vertex</div>\n <div>• Right-click vertex to delete</div>\n </div>\n <div class=\"absolute top-2 -left-1 w-0 h-0 border-t-4 border-b-4 border-r-4 border-transparent border-r-gray-900\"></div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"flex items-center gap-2\">\n <date-view-mode-picker\n v-if=\"isDatePath\"\n :viewMode=\"dateViewMode\"\n :path=\"path\"\n @update:viewMode=\"dateViewMode = $event\"\n ></date-view-mode-picker>\n <objectid-view-mode-picker\n v-if=\"isObjectIdPath\"\n :viewMode=\"objectIdViewMode\"\n @update:viewMode=\"objectIdViewMode = $event\"\n ></objectid-view-mode-picker>\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-edge-strong bg-surface\"\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-primary hover:bg-primary-hover text-primary-text 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-2\">\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 <!-- Use detail-default with map editing for GeoJSON geometries -->\n <component\n v-if=\"isGeoJsonGeometry\"\n :is=\"getComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :viewMode=\"detailViewMode\"\n :on-change=\"handleInputChange\"\n >\n </component>\n <!-- Use standard edit components for other types -->\n <component\n v-else\n :is=\"getEditComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :format=\"dateType\"\n v-bind=\"getEditComponentProps(path)\"\n @input=\"handleInputChange($event)\"\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 <!-- Special handling for truncated arrays -->\n <div v-if=\"isArray && shouldShowTruncated\" class=\"w-full\">\n <div class=\"mt-2\">\n <div\n v-for=\"(item, index) in truncatedArrayItems\"\n :key=\"index\"\n class=\"mb-1.5 py-2.5 px-3 pl-4 bg-transparent border-l-[3px] border-l-blue-500 rounded-none transition-all duration-200 cursor-pointer relative hover:bg-slate-50 hover:border-l-blue-600\">\n <div class=\"absolute -left-2 top-1/2 -translate-y-1/2 w-5 h-5 bg-blue-500 text-white rounded-full flex items-center justify-center text-[10px] font-semibold font-mono z-10 hover:bg-blue-600\">{{ index }}</div>\n <div v-if=\"arrayUtils.isObjectItem(item)\" class=\"flex flex-col gap-1 mt-1 px-2\">\n <div\n v-for=\"key in arrayUtils.getItemKeys(item)\"\n :key=\"key\"\n class=\"flex items-start gap-2 text-xs font-mono\">\n <span class=\"font-semibold text-gray-600 flex-shrink-0 min-w-[80px]\">{{ key }}:</span>\n <span class=\"text-gray-800 break-words whitespace-pre-wrap flex-1\">{{ arrayUtils.formatItemValue(item, key) }}</span>\n </div>\n </div>\n <div v-else class=\"text-xs py-1.5 px-2 font-mono text-gray-800 break-words whitespace-pre-wrap mt-1\">{{ arrayUtils.formatValue(item) }}</div>\n </div>\n <div class=\"mb-1.5 py-2.5 px-3 pl-4 bg-transparent border-none border-l-[3px] border-l-blue-500 rounded-none transition-all duration-200 cursor-pointer relative opacity-70 hover:opacity-100\">\n <div class=\"text-xs py-1.5 px-2 font-mono text-content-tertiary italic break-words whitespace-pre-wrap mt-1\">\n ... and {{ remainingArrayCount }} more item{{ remainingArrayCount !== 1 ? 's' : '' }}\n </div>\n </div>\n </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 all {{ arrayValue.length }} items\n </button>\n </div>\n <!-- Non-array truncated view -->\n <div v-else-if=\"shouldShowTruncated && !isArray\" class=\"relative\">\n <div class=\"text-content-secondary 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 <!-- Expanded view -->\n <div v-else-if=\"needsTruncation && isValueExpanded\" class=\"relative\">\n <component\n :is=\"getComponentForPath(path)\"\n :value=\"rawValue\"\n :viewMode=\"isDatePath ? dateViewMode : (isObjectIdPath ? objectIdViewMode : detailViewMode)\"\n @updated=\"renderedValue = $event\"></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 <!-- Full view (no truncation needed) -->\n <div v-else>\n <component\n :is=\"getComponentForPath(path)\"\n :value=\"rawValue\"\n :viewMode=\"isDatePath ? dateViewMode : (isObjectIdPath ? objectIdViewMode : detailViewMode)\"\n @updated=\"renderedValue = $event\"></component>\n </div>\n </div>\n </div>\n</div>\n";
|
|
52847
|
+
module.exports = "<div class=\"border border-edge bg-surface rounded-lg mb-2\" style=\"overflow: visible;\">\n <!-- Collapsible Header -->\n <div\n @click=\"toggleCollapse\"\n class=\"p-1 cursor-pointer flex items-center justify-between border-b border-edge bg-slate-100 hover:bg-muted transition-colors duration-200 ease-in-out\"\n style=\"overflow: visible; position: relative;\"\n >\n <div class=\"flex items-center\" >\n <svg\n :class=\"isCollapsed ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-content-tertiary 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-content\">\n <span\n v-for=\"(part, index) in pathNameParts\"\n :key=\"index\"\n :class=\"{ 'font-bold': part.matched }\">{{ part.text }}</span>\n </span>\n <span class=\"ml-2 text-sm text-content-tertiary\">({{(path.instance || 'unknown').toLowerCase()}})</span>\n <div v-if=\"path.instance === 'Array' && canUseArrayTableView\" class=\"ml-3 inline-flex items-center rounded-full bg-gray-200 p-0.5 text-xs font-semibold\">\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"arrayDetailViewMode === 'list' ? 'bg-merlot-700 text-white shadow' : 'text-content-secondary hover:text-content'\"\n @click.stop=\"setArrayDetailViewMode('list')\">\n List\n </button>\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"arrayDetailViewMode === 'table' ? 'bg-merlot-700 text-white shadow' : 'text-content-secondary hover:text-content'\"\n @click.stop=\"setArrayDetailViewMode('table')\">\n Table\n </button>\n </div>\n <div v-if=\"isGeoJsonGeometry\" class=\"ml-3 inline-flex items-center gap-2\">\n <div class=\"inline-flex items-center rounded-full bg-gray-200 p-0.5 text-xs font-semibold\">\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"detailViewMode === 'text' ? 'bg-blue-600 text-white shadow' : 'text-content-secondary hover:text-content'\"\n :style=\"detailViewMode === 'text' ? 'color: white !important; background-color: #2563eb !important;' : ''\"\n @click.stop=\"setDetailViewMode('text')\">\n Text\n </button>\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"detailViewMode === 'map' ? 'bg-blue-600 text-white shadow' : 'text-content-secondary hover:text-content'\"\n :style=\"detailViewMode === 'map' ? 'color: white !important; background-color: #2563eb !important;' : ''\"\n @click.stop=\"setDetailViewMode('map')\">\n Map\n </button>\n </div>\n <!-- Info icon with tooltip -->\n <div v-if=\"editting\" class=\"relative inline-block\" style=\"z-index: 10002;\" @mouseenter=\"showTooltip = true\" @mouseleave=\"showTooltip = false\" @click.stop>\n <svg\n ref=\"infoIcon\"\n class=\"w-6 h-6 text-gray-400 hover:text-gray-600 cursor-help\"\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=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"></path>\n </svg>\n <div\n v-show=\"showTooltip\"\n ref=\"tooltip\"\n class=\"absolute left-full top-0 ml-2 w-64 p-3 text-white text-xs rounded-lg shadow-xl\"\n style=\"z-index: 99999; pointer-events: none; white-space: normal; position: fixed; background-color: #111827;\"\n :style=\"getTooltipStyle()\"\n >\n <div class=\"font-semibold mb-2\">Map Controls:</div>\n <div v-if=\"isGeoJsonPoint\" class=\"space-y-1\">\n <div>• Drag pin to move location</div>\n </div>\n <div v-else-if=\"isGeoJsonPolygon\" class=\"space-y-1\">\n <div>• Drag vertices to reshape polygon</div>\n <div v-if=\"isMultiPolygon\">• Right-click edge to add new vertex</div>\n <div>• Right-click vertex to delete</div>\n </div>\n <div class=\"absolute top-2 -left-1 w-0 h-0 border-t-4 border-b-4 border-r-4 border-transparent border-r-gray-900\"></div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"flex items-center gap-2\">\n <date-view-mode-picker\n v-if=\"isDatePath\"\n :viewMode=\"dateViewMode\"\n :path=\"path\"\n @update:viewMode=\"dateViewMode = $event\"\n ></date-view-mode-picker>\n <objectid-view-mode-picker\n v-if=\"isObjectIdPath\"\n :viewMode=\"objectIdViewMode\"\n @update:viewMode=\"objectIdViewMode = $event\"\n ></objectid-view-mode-picker>\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-edge-strong bg-surface\"\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-primary hover:bg-primary-hover text-primary-text 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-2\">\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 <!-- Use detail-default with map editing for GeoJSON geometries -->\n <component\n v-if=\"isGeoJsonGeometry\"\n :is=\"getComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :viewMode=\"detailViewMode\"\n :on-change=\"handleInputChange\"\n >\n </component>\n <!-- Use standard edit components for other types -->\n <component\n v-else\n :is=\"getEditComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :format=\"dateType\"\n v-bind=\"getEditComponentProps(path)\"\n @input=\"handleInputChange($event)\"\n @error=\"invalid[path.path] = $event;\"\n >\n </component>\n </div>\n <div v-else>\n <!-- Arrays own their truncation and array search behavior in detail-array. -->\n <component\n v-if=\"isArray\"\n :is=\"getComponentForPath(path)\"\n :value=\"rawValue\"\n :viewMode=\"propertyDetailViewMode\"\n :truncate=\"needsTruncation\"\n @updated=\"renderedValue = $event\"></component>\n <!-- Non-array truncated view -->\n <div v-else-if=\"shouldShowTruncated && !isArray\" class=\"relative\">\n <div class=\"text-content-secondary whitespace-pre-wrap break-words font-mono text-sm\">{{truncatedString}}</div>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-merlot-700 hover:text-merlot-900 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 <!-- Expanded view -->\n <div v-else-if=\"needsTruncation && isValueExpanded\" class=\"relative\">\n <component\n :is=\"getComponentForPath(path)\"\n :value=\"rawValue\"\n :viewMode=\"propertyDetailViewMode\"\n @updated=\"renderedValue = $event\"></component>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-merlot-700 hover:text-merlot-900 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 <!-- Full view (no truncation needed) -->\n <div v-else>\n <component\n :is=\"getComponentForPath(path)\"\n :value=\"rawValue\"\n :viewMode=\"propertyDetailViewMode\"\n @updated=\"renderedValue = $event\"></component>\n </div>\n </div>\n </div>\n</div>\n";
|
|
52284
52848
|
|
|
52285
52849
|
/***/ },
|
|
52286
52850
|
|
|
@@ -52335,7 +52899,7 @@ module.exports = ".document .document-menu {\n display: flex;\n position: stic
|
|
|
52335
52899
|
(module) {
|
|
52336
52900
|
|
|
52337
52901
|
"use strict";
|
|
52338
|
-
module.exports = "<div class=\"document px-1 pt-4 pb-16 md:px-0 bg-slate-50 w-full\">\n <div class=\"max-w-7xl mx-auto\">\n <div class=\"flex gap-4 items-center sticky top-0 z-50 bg-surface p-4 border-b border-edge shadow-sm\">\n <div class=\"font-bold overflow-hidden text-ellipsis\">{{model}}: {{documentId}}</div>\n <div class=\"flex grow\">\n <button\n @click=\"viewMode = 'fields'\"\n :class=\"viewMode === 'fields'\n ? 'bg-primary text-white z-10'\n : 'bg-gray-200 text-content-secondary hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-edge-strong 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-primary text-white z-10'\n : 'bg-gray-200 text-content-secondary hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-edge-strong 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=\"hidden md:flex items-center gap-3 text-sm text-slate-600\">\n <div class=\"text-right leading-tight\">\n <div class=\"text-xs uppercase tracking-wide text-slate-400 flex items-center gap-2 justify-end\">\n <span>Loaded at</span>\n <span class=\"inline-flex items-center gap-1 text-[10px] font-semibold\" :class=\"autoRefreshEnabled ? 'text-forest-green-600' : 'text-slate-400'\">\n <span class=\"inline-block h-1.5 w-1.5 rounded-full\" :class=\"autoRefreshEnabled ? 'bg-forest-green-500' : 'bg-slate-300'\"></span>\n </span>\n </div>\n <div class=\"font-medium text-slate-700\">{{lastUpdatedLabel}}</div>\n </div>\n </div>\n\n <div class=\"gap-2 hidden md:flex items-center\">\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-primary px-2.5 py-1.5 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover 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\n <!-- 3-dot menu -->\n <div class=\"relative\">\n <button\n @click=\"desktopMenuOpen = !desktopMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-2.5 py-1.5 text-sm font-medium text-content-secondary hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"desktopMenuOpen\"\n aria-label=\"More options\"\n >\n <svg class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"5\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"12\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"19\" r=\"2\"></circle>\n </svg>\n </button>\n <div\n v-show=\"desktopMenuOpen\"\n @click.away=\"desktopMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-surface ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n @click=\"refreshDocument({ force: true, source: 'manual' }); desktopMenuOpen = false\"\n :disabled=\"isRefreshing\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', isRefreshing ? 'cursor-not-allowed opacity-50' : 'hover:bg-slate-100']\"\n type=\"button\"\n >\n {{isRefreshing ? 'Refreshing...' : 'Refresh'}}\n </button>\n <button\n @click=\"toggleAutoRefresh(); desktopMenuOpen = false\"\n :disabled=\"isLambda\"\n type=\"button\"\n :class=\"['flex items-center px-4 py-2 text-sm', isLambda ? 'text-gray-400 cursor-not-allowed' : 'text-content-secondary hover:bg-slate-100']\"\n :title=\"isLambda ? 'Auto-refresh only available on Express deployments' : ''\"\n >\n {{autoRefreshEnabled ? 'Disable Auto-Refresh' : 'Enable Auto-Refresh'}}\n </button>\n <button\n @click=\"addField(); desktopMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"copyDocument(); desktopMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-blue-100\"\n >\n Copy Document\n </button>\n <button\n @click=\"openScriptDrawer()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-indigo-100\"\n >\n Run Script\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\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-content-secondary 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-surface 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-content-secondary', !canEdit ? 'cursor-not-allowed opacity-50' : 'hover:bg-primary-subtle']\"\n type=\"button\"\n >\n 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-content-secondary 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-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-green-100']\"\n @click=\"shouldShowConfirmModal=true; mobileMenuOpen = false\"\n type=\"button\"\n >\n Save\n </button>\n <button\n @click=\"refreshDocument({ force: true, source: 'manual' }); mobileMenuOpen = false\"\n :disabled=\"isRefreshing\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', isRefreshing ? 'cursor-not-allowed opacity-50' : 'hover:bg-slate-100']\"\n type=\"button\"\n >\n {{isRefreshing ? 'Refreshing...' : 'Refresh'}}\n </button>\n <button\n @click=\"toggleAutoRefresh(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-slate-100\"\n >\n Auto-Refresh {{autoRefreshEnabled ? 'ON' : 'OFF'}}\n </button>\n <button\n @click=\"addField(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"copyDocument(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-blue-100\"\n >\n Copy Document\n </button>\n <button\n @click=\"openScriptDrawer()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-indigo-100\"\n >\n Run Script\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-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n 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-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n 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 :model=\"model\"\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 <execute-script\n :visible=\"scriptDrawerOpen\"\n :model=\"model\"\n :document-id=\"documentId\"\n :editting=\"editting\"\n @close=\"closeScriptDrawer\"\n @refresh=\"handleScriptRefresh\"\n ></execute-script>\n <div\n v-if=\"keyboardShortcuts.length > 0\"\n class=\"fixed bottom-4 left-4 z-40 group\"\n >\n <button\n type=\"button\"\n aria-label=\"Keyboard shortcuts\"\n title=\"Keyboard shortcuts\"\n class=\"rounded-full border border-slate-300 bg-surface p-2 text-slate-700 shadow-sm transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\">\n <rect x=\"2.5\" y=\"6.5\" width=\"19\" height=\"11\" rx=\"2\" stroke-width=\"1.5\"></rect>\n <path d=\"M6 10.5h1.5M9 10.5h1.5M12 10.5h1.5M15 10.5h1.5M18 10.5h.01M6 13.5h1.5M9 13.5h6M16.5 13.5h1.5\" stroke-width=\"1.5\" stroke-linecap=\"round\"></path>\n </svg>\n </button>\n <div class=\"pointer-events-none absolute left-0 bottom-full mb-2 min-w-[220px] translate-y-1 rounded-lg bg-slate-900 px-3 py-2.5 text-slate-200 opacity-0 shadow-xl transition-all duration-150 group-hover:pointer-events-auto group-hover:translate-y-0 group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:translate-y-0 group-focus-within:opacity-100\">\n <div\n v-for=\"shortcut in keyboardShortcuts\"\n :key=\"shortcut.command + shortcut.description\"\n class=\"flex items-center justify-between gap-3 text-xs\"\n >\n <span class=\"font-mono font-semibold text-slate-50\">{{shortcut.command}}</span>\n <span class=\"text-slate-300\">{{shortcut.description}}</span>\n </div>\n </div>\n </div>\n </div>\n</div>\n";
|
|
52902
|
+
module.exports = "<div class=\"document px-1 pt-4 pb-16 md:px-0 bg-slate-50 w-full\">\n <div class=\"max-w-7xl mx-auto\">\n <div class=\"flex gap-4 items-center sticky top-0 z-50 bg-surface p-4 border-b border-edge shadow-sm\">\n <div class=\"font-bold overflow-hidden text-ellipsis\">{{model}}: {{documentId}}</div>\n <div class=\"flex grow\">\n <button\n @click=\"viewMode = 'fields'\"\n :class=\"viewMode === 'fields'\n ? 'bg-primary text-white z-10'\n : 'bg-gray-200 text-content-secondary hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-edge-strong 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-primary text-white z-10'\n : 'bg-gray-200 text-content-secondary hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-edge-strong 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=\"hidden md:flex items-center gap-3 text-sm text-slate-600\">\n <div class=\"text-right leading-tight\">\n <div class=\"text-xs uppercase tracking-wide text-slate-400 flex items-center gap-2 justify-end\">\n <span>Loaded at</span>\n <span class=\"inline-flex items-center gap-1 text-[10px] font-semibold\" :class=\"autoRefreshEnabled ? 'text-forest-green-600' : 'text-slate-400'\">\n <span class=\"inline-block h-1.5 w-1.5 rounded-full\" :class=\"autoRefreshEnabled ? 'bg-forest-green-500' : 'bg-slate-300'\"></span>\n </span>\n </div>\n <div class=\"font-medium text-slate-700\">{{lastUpdatedLabel}}</div>\n </div>\n </div>\n\n <div class=\"gap-2 hidden md:flex items-center\">\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-primary px-2.5 py-1.5 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover 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\n <!-- 3-dot menu -->\n <div class=\"relative\">\n <button\n @click=\"desktopMenuOpen = !desktopMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-2.5 py-1.5 text-sm font-medium text-content-secondary hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"desktopMenuOpen\"\n aria-label=\"More options\"\n >\n <svg class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"5\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"12\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"19\" r=\"2\"></circle>\n </svg>\n </button>\n <div\n v-show=\"desktopMenuOpen\"\n @click.away=\"desktopMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-surface ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n @click=\"refreshDocument({ force: true, source: 'manual' }); desktopMenuOpen = false\"\n :disabled=\"isRefreshing\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', isRefreshing ? 'cursor-not-allowed opacity-50' : 'hover:bg-slate-100']\"\n type=\"button\"\n >\n {{isRefreshing ? 'Refreshing...' : 'Refresh'}}\n </button>\n <button\n @click=\"toggleAutoRefresh(); desktopMenuOpen = false\"\n :disabled=\"isLambda\"\n type=\"button\"\n :class=\"['flex items-center px-4 py-2 text-sm', isLambda ? 'text-gray-400 cursor-not-allowed' : 'text-content-secondary hover:bg-slate-100']\"\n :title=\"isLambda ? 'Auto-refresh only available on Express deployments' : ''\"\n >\n {{autoRefreshEnabled ? 'Disable Auto-Refresh' : 'Enable Auto-Refresh'}}\n </button>\n <button\n @click=\"addField(); desktopMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"copyDocument(); desktopMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-blue-100\"\n >\n Copy Document\n </button>\n <button\n @click=\"openValidationModal()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Validate\n </button>\n <button\n @click=\"openScriptDrawer()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-indigo-100\"\n >\n Run Script\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\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-content-secondary 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-surface 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-content-secondary', !canEdit ? 'cursor-not-allowed opacity-50' : 'hover:bg-primary-subtle']\"\n type=\"button\"\n >\n 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-content-secondary 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-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-green-100']\"\n @click=\"shouldShowConfirmModal=true; mobileMenuOpen = false\"\n type=\"button\"\n >\n Save\n </button>\n <button\n @click=\"refreshDocument({ force: true, source: 'manual' }); mobileMenuOpen = false\"\n :disabled=\"isRefreshing\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', isRefreshing ? 'cursor-not-allowed opacity-50' : 'hover:bg-slate-100']\"\n type=\"button\"\n >\n {{isRefreshing ? 'Refreshing...' : 'Refresh'}}\n </button>\n <button\n @click=\"toggleAutoRefresh(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-slate-100\"\n >\n Auto-Refresh {{autoRefreshEnabled ? 'ON' : 'OFF'}}\n </button>\n <button\n @click=\"addField(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"copyDocument(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-blue-100\"\n >\n Copy Document\n </button>\n <button\n @click=\"openValidationModal()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Validate\n </button>\n <button\n @click=\"openScriptDrawer()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-indigo-100\"\n >\n Run Script\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-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n 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-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n 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 :model=\"model\"\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 <modal v-if=\"shouldShowValidationModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowValidationModal = false;\">×</div>\n <div class=\"text-xl font-bold mb-2\">Validate Document</div>\n <div v-if=\"!validationResult\" class=\"space-y-3\">\n <div class=\"h-4 w-32 animate-pulse rounded bg-muted\"></div>\n <div class=\"h-20 w-full animate-pulse rounded bg-muted\"></div>\n </div>\n <div v-else class=\"space-y-3\">\n <div\n class=\"rounded px-3 py-2 text-sm font-semibold\"\n :class=\"validationResult.valid ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-700'\"\n >\n {{ validationResult.valid ? 'Document is valid' : 'Document is invalid' }}\n </div>\n <div v-if=\"!validationResult.valid\" class=\"rounded border border-edge bg-muted p-3 text-sm text-content-secondary\">\n {{ validationResult.error }}\n </div>\n <div v-if=\"validationResult?.errors && Object.keys(validationResult.errors).length > 0\" class=\"rounded border border-edge\">\n <div class=\"border-b border-edge px-3 py-2 text-sm font-semibold text-content-secondary\">Invalid Paths</div>\n <div class=\"max-h-72 overflow-auto\">\n <div\n v-for=\"[path, pathError] in Object.entries(validationResult.errors)\"\n :key=\"path\"\n class=\"grid gap-2 border-b border-edge px-3 py-2 text-sm last:border-b-0 md:grid-cols-[minmax(160px,220px)_1fr]\"\n >\n <div class=\"font-mono text-content\">{{ path }}</div>\n <div class=\"text-content-secondary\">\n <div>{{ pathError.message }}</div>\n <div v-if=\"pathError.kind\" class=\"mt-1 text-xs text-content-tertiary\">Kind: {{ pathError.kind }}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n </div>\n <execute-script\n :visible=\"scriptDrawerOpen\"\n :model=\"model\"\n :document-id=\"documentId\"\n :editting=\"editting\"\n @close=\"closeScriptDrawer\"\n @refresh=\"handleScriptRefresh\"\n ></execute-script>\n <div\n v-if=\"keyboardShortcuts.length > 0\"\n class=\"fixed bottom-4 left-4 z-40 group\"\n >\n <button\n type=\"button\"\n aria-label=\"Keyboard shortcuts\"\n title=\"Keyboard shortcuts\"\n class=\"rounded-full border border-slate-300 bg-surface p-2 text-slate-700 shadow-sm transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\">\n <rect x=\"2.5\" y=\"6.5\" width=\"19\" height=\"11\" rx=\"2\" stroke-width=\"1.5\"></rect>\n <path d=\"M6 10.5h1.5M9 10.5h1.5M12 10.5h1.5M15 10.5h1.5M18 10.5h.01M6 13.5h1.5M9 13.5h6M16.5 13.5h1.5\" stroke-width=\"1.5\" stroke-linecap=\"round\"></path>\n </svg>\n </button>\n <div class=\"pointer-events-none absolute left-0 bottom-full mb-2 min-w-[220px] translate-y-1 rounded-lg bg-slate-900 px-3 py-2.5 text-slate-200 opacity-0 shadow-xl transition-all duration-150 group-hover:pointer-events-auto group-hover:translate-y-0 group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:translate-y-0 group-focus-within:opacity-100\">\n <div\n v-for=\"shortcut in keyboardShortcuts\"\n :key=\"shortcut.command + shortcut.description\"\n class=\"flex items-center justify-between gap-3 text-xs\"\n >\n <span class=\"font-mono font-semibold text-slate-50\">{{shortcut.command}}</span>\n <span class=\"text-slate-300\">{{shortcut.description}}</span>\n </div>\n </div>\n </div>\n </div>\n</div>\n";
|
|
52339
52903
|
|
|
52340
52904
|
/***/ },
|
|
52341
52905
|
|
|
@@ -52368,7 +52932,7 @@ module.exports = "<div v-if=\"isOpen\" class=\"fixed inset-0 z-50 execute-script
|
|
|
52368
52932
|
(module) {
|
|
52369
52933
|
|
|
52370
52934
|
"use strict";
|
|
52371
|
-
module.exports = "<div class=\"w-full\">\n <ace-editor\n
|
|
52935
|
+
module.exports = "<div class=\"w-full\">\n <div v-if=\"!useTableEditor\">\n <ace-editor\n :value=\"arrayStr\"\n @input=\"onEditorInput\"\n mode=\"json\"\n :line-numbers=\"true\"\n class=\"min-h-[300px]\"\n />\n </div>\n <div v-else class=\"overflow-hidden rounded-lg border border-edge bg-surface\">\n <div class=\"border-b border-edge bg-slate-50/90 px-3 py-2.5\">\n <div class=\"flex flex-col gap-2 lg:flex-row lg:items-center lg:gap-4\">\n <div class=\"flex flex-wrap items-stretch gap-2\">\n <button\n type=\"button\"\n class=\"rounded-md bg-primary px-3 py-1.5 text-xs font-semibold text-primary-text shadow-sm hover:bg-primary-hover\"\n @click=\"addRow\">\n + Row\n </button>\n <div class=\"flex min-w-0 max-w-full sm:max-w-md\">\n <input\n id=\"edit-array-new-column\"\n v-model=\"newColumnKeyInput\"\n type=\"text\"\n class=\"min-w-0 flex-1 rounded-l-md border border-r-0 border-edge bg-surface px-2.5 py-1.5 text-xs font-mono text-content placeholder:text-content-tertiary focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\n placeholder=\"New field name\"\n autocomplete=\"off\"\n @keyup.enter=\"addColumn\"\n />\n <button\n type=\"button\"\n class=\"shrink-0 rounded-r-md border border-edge bg-slate-100 px-3 py-1.5 text-xs font-semibold text-content hover:bg-slate-200\"\n @click=\"addColumn\">\n Add field\n </button>\n </div>\n </div>\n <p class=\"text-[11px] leading-snug text-content-tertiary lg:ml-auto lg:max-w-[18rem] lg:text-right\">\n Each cell is JSON. Clear a cell to remove that field from the row.\n </p>\n </div>\n <p v-if=\"newColumnKeyError\" class=\"mt-2 text-xs font-medium text-red-600\">{{ newColumnKeyError }}</p>\n </div>\n\n <div v-if=\"arrayValue.length > 0\" class=\"border-b border-edge bg-slate-50/50 px-3 py-2\">\n <div class=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3\">\n <div class=\"relative min-w-0 flex-1 sm:max-w-md\">\n <input\n v-model=\"arraySearchQuery\"\n type=\"search\"\n autocomplete=\"off\"\n aria-label=\"Search rows in this array\"\n placeholder=\"Search rows…\"\n class=\"w-full rounded-md border border-edge bg-surface py-1.5 pl-2 pr-16 text-xs text-content placeholder:text-content-tertiary focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\n />\n <button\n v-if=\"arraySearchNormalized\"\n type=\"button\"\n class=\"absolute right-1 top-1/2 -translate-y-1/2 rounded px-1.5 py-0.5 text-[11px] text-content-tertiary hover:bg-slate-200 hover:text-content\"\n title=\"Clear search\"\n @click=\"arraySearchQuery = ''\">\n Clear\n </button>\n </div>\n <span v-if=\"arraySearchNormalized\" class=\"shrink-0 text-xs text-content-tertiary\">\n {{ filteredTableRows.length }} of {{ arrayValue.length }} shown\n </span>\n </div>\n </div>\n\n <div v-if=\"arrayValue.length === 0\" class=\"px-4 py-10 text-center text-sm text-content-secondary\">\n No rows yet. Use <span class=\"font-medium text-content\">+ Row</span> or enter a field name and choose <span class=\"font-medium text-content\">Add field</span>.\n </div>\n\n <div\n v-else-if=\"filteredTableRows.length === 0\"\n class=\"px-4 py-8 text-center text-sm text-content-secondary\">\n No rows match \"{{ arraySearchQuery.trim() }}\".\n </div>\n\n <div v-else class=\"overflow-x-auto\">\n <table class=\"min-w-full text-left text-xs font-mono\">\n <thead class=\"border-b border-edge bg-slate-100 text-content-secondary\">\n <tr>\n <th scope=\"col\" class=\"w-24 border-r border-edge px-2 py-2 text-left text-[11px] font-semibold uppercase tracking-wide\">\n Actions\n </th>\n <th\n v-for=\"key in tableColumnKeys\"\n :key=\"key\"\n scope=\"col\"\n class=\"border-r border-edge px-3 py-2 text-left text-xs font-semibold last:border-r-0 min-w-[8rem] whitespace-nowrap\">\n {{ key }}\n </th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-edge bg-surface\">\n <tr v-for=\"entry in filteredTableRows\" :key=\"entry.index\" class=\"hover:bg-slate-50/60\">\n <td class=\"align-middle border-r border-edge p-2\">\n <div class=\"flex flex-col gap-1\">\n <span class=\"text-center text-[10px] font-medium text-content-tertiary\">#{{ entry.index }}</span>\n <button\n type=\"button\"\n class=\"rounded border border-edge bg-surface px-2 py-1 text-[11px] font-medium hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-40\"\n :disabled=\"entry.index === 0\"\n title=\"Move row up\"\n @click=\"moveRow(entry.index, -1)\">\n Up\n </button>\n <button\n type=\"button\"\n class=\"rounded border border-edge bg-surface px-2 py-1 text-[11px] font-medium hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-40\"\n :disabled=\"entry.index === arrayValue.length - 1\"\n title=\"Move row down\"\n @click=\"moveRow(entry.index, 1)\">\n Down\n </button>\n <button\n type=\"button\"\n class=\"rounded border border-red-200 bg-surface px-2 py-1 text-[11px] font-medium text-red-700 hover:bg-red-50\"\n title=\"Remove this row\"\n @click=\"removeRow(entry.index)\">\n Remove\n </button>\n </div>\n </td>\n <td\n v-for=\"key in tableColumnKeys\"\n :key=\"key\"\n class=\"max-w-md min-w-[8rem] border-r border-edge p-1.5 align-top last:border-r-0\">\n <textarea\n rows=\"2\"\n class=\"w-full resize-y rounded border border-edge bg-surface px-2 py-1.5 text-xs text-content focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\n :value=\"serializeCell(entry.item[key])\"\n @blur=\"onCellChange(entry.index, key, $event.target.value)\"\n ></textarea>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n</div>\n";
|
|
52372
52936
|
|
|
52373
52937
|
/***/ },
|
|
52374
52938
|
|
|
@@ -52614,6 +53178,17 @@ module.exports = "<transition name=\"modal\">\n <div class=\"modal-mask\">\n
|
|
|
52614
53178
|
|
|
52615
53179
|
/***/ },
|
|
52616
53180
|
|
|
53181
|
+
/***/ "./frontend/src/models/analyze-schema-modal/analyze-schema-modal.html"
|
|
53182
|
+
/*!****************************************************************************!*\
|
|
53183
|
+
!*** ./frontend/src/models/analyze-schema-modal/analyze-schema-modal.html ***!
|
|
53184
|
+
\****************************************************************************/
|
|
53185
|
+
(module) {
|
|
53186
|
+
|
|
53187
|
+
"use strict";
|
|
53188
|
+
module.exports = "<div>\n <div class=\"modal-exit\" @click=\"close\">×</div>\n <div class=\"text-xl font-bold mb-2\">Analyze Schema</div>\n <div class=\"space-y-4\">\n <div class=\"grid grid-cols-2 gap-3 text-sm md:grid-cols-4\">\n <div>\n <div class=\"font-semibold text-content-secondary\">Documents</div>\n <div v-if=\"schemaAnalysis\" class=\"text-content\">{{ formatNumber(schemaAnalysis.documentCount) }}</div>\n <div v-else class=\"mt-1 h-4 w-20 animate-pulse rounded bg-muted\"></div>\n </div>\n <div>\n <div class=\"font-semibold text-content-secondary\">Analyzed</div>\n <div v-if=\"schemaAnalysis\" class=\"text-content\">{{ formatNumber(schemaAnalysis.sampleSize) }}{{ schemaAnalysis.sampled ? ' sampled' : '' }}</div>\n <div v-else class=\"mt-1 h-4 w-24 animate-pulse rounded bg-muted\"></div>\n </div>\n <div>\n <div class=\"font-semibold text-content-secondary\">Valid</div>\n <div v-if=\"schemaAnalysis\" class=\"text-content\">\n {{ formatNumber(schemaAnalysis.validDocumentCount) }} ({{ formatPercent(schemaAnalysis.validDocumentCount, schemaAnalysis.sampleSize) }})\n <router-link\n v-if=\"schemaAnalysis.firstValidDocumentId\"\n :to=\"documentRoute(schemaAnalysis.firstValidDocumentId)\"\n class=\"ml-1 text-xs text-primary underline\"\n >[Example]</router-link>\n </div>\n <div v-else class=\"mt-1 h-4 w-28 animate-pulse rounded bg-muted\"></div>\n </div>\n <div>\n <div class=\"flex items-center gap-2 font-semibold text-content-secondary\">\n <span>Invalid</span>\n <button\n v-if=\"schemaAnalysis && schemaAnalysis.invalidDocuments.length > 0\"\n type=\"button\"\n class=\"text-xs font-semibold text-primary underline\"\n @click=\"showInvalidDocuments = !showInvalidDocuments\"\n >{{ showInvalidDocuments ? 'Hide' : 'Show' }}</button>\n </div>\n <div v-if=\"schemaAnalysis\" class=\"text-content\">\n {{ formatNumber(schemaAnalysis.invalidDocumentCount) }} ({{ formatPercent(schemaAnalysis.invalidDocumentCount, schemaAnalysis.sampleSize) }})\n <router-link\n v-if=\"schemaAnalysis.firstInvalidDocumentId\"\n :to=\"documentRoute(schemaAnalysis.firstInvalidDocumentId)\"\n class=\"ml-1 text-xs text-primary underline\"\n >[Example]</router-link>\n </div>\n <div v-else class=\"mt-1 h-4 w-28 animate-pulse rounded bg-muted\"></div>\n </div>\n </div>\n <div v-if=\"schemaAnalysis && showInvalidDocuments && schemaAnalysis.invalidDocuments.length > 0\" class=\"rounded border border-edge\">\n <div class=\"max-h-48 overflow-auto\">\n <div\n v-for=\"invalidDocument in schemaAnalysis.invalidDocuments\"\n :key=\"invalidDocument.documentId\"\n class=\"grid gap-2 border-b border-edge px-3 py-2 text-sm last:border-b-0 md:grid-cols-[minmax(180px,240px)_1fr]\"\n >\n <router-link\n :to=\"documentRoute(invalidDocument.documentId)\"\n class=\"font-mono text-primary underline\"\n >{{ invalidDocument.documentId }}</router-link>\n <div class=\"text-content-secondary\">{{ invalidDocument.error }}</div>\n </div>\n </div>\n </div>\n <div class=\"px-px py-px\">\n <input\n ref=\"fieldSearch\"\n v-model=\"fieldSearch\"\n type=\"text\"\n :disabled=\"!schemaAnalysis\"\n placeholder=\"Filter fields...\"\n class=\"w-full rounded border border-edge bg-surface px-3 py-2 text-sm text-content placeholder:text-content-tertiary focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary disabled:cursor-wait disabled:bg-muted disabled:text-content-tertiary\"\n />\n </div>\n <div class=\"max-h-[55vh] overflow-auto rounded border border-edge\">\n <table class=\"min-w-full table-fixed border-collapse text-sm\">\n <colgroup>\n <col class=\"w-[40%]\" />\n <col class=\"w-[60%]\" />\n </colgroup>\n <thead class=\"sticky top-0 bg-slate-100 border-b border-edge\">\n <tr>\n <th class=\"px-3 py-2 text-left font-medium text-content border-r border-edge\">Property</th>\n <th class=\"px-3 py-2 text-left font-medium text-content\">\n <div class=\"flex flex-wrap items-center justify-between gap-2\">\n <span>Types</span>\n <div class=\"isolate inline-flex shrink-0 rounded-md shadow-sm\">\n <button\n type=\"button\"\n class=\"relative inline-flex items-center rounded-l-md px-2.5 py-1 text-xs font-semibold ring-1 ring-inset ring-edge-strong focus:z-10\"\n :class=\"typeDisplayMode === 'badges' ? 'bg-merlot-700 text-white' : 'bg-surface text-content-secondary hover:bg-muted'\"\n @click=\"typeDisplayMode = 'badges'\"\n >\n Badges\n </button>\n <button\n type=\"button\"\n class=\"relative -ml-px inline-flex items-center rounded-r-md px-2.5 py-1 text-xs font-semibold ring-1 ring-inset ring-edge-strong focus:z-10\"\n :class=\"typeDisplayMode === 'bars' ? 'bg-merlot-700 text-white' : 'bg-surface text-content-secondary hover:bg-muted'\"\n @click=\"typeDisplayMode = 'bars'\"\n >\n Bars\n </button>\n </div>\n </div>\n </th>\n </tr>\n </thead>\n <tbody class=\"bg-surface\">\n <template v-if=\"!schemaAnalysis\">\n <tr v-for=\"index in 6\" :key=\"'skeleton-' + index\" class=\"border-b border-edge last:border-b-0\">\n <td class=\"px-3 py-3 align-top border-r border-edge\">\n <div class=\"h-4 w-44 animate-pulse rounded bg-muted\"></div>\n </td>\n <td class=\"px-3 py-3 align-top\">\n <div class=\"flex flex-wrap gap-1.5\">\n <div class=\"h-6 w-24 animate-pulse rounded bg-muted\"></div>\n <div class=\"h-6 w-20 animate-pulse rounded bg-muted\"></div>\n <div class=\"h-6 w-28 animate-pulse rounded bg-muted\"></div>\n </div>\n </td>\n </tr>\n </template>\n <tr v-for=\"path in filteredPaths\" :key=\"path.path\" class=\"border-b border-edge last:border-b-0\">\n <td class=\"px-3 py-2 align-top border-r border-edge text-xs text-content\">\n <div class=\"flex flex-wrap items-center gap-1.5\">\n <span class=\"font-mono\" v-html=\"highlightPath(path.path)\"></span>\n <span\n v-if=\"primaryType(path)\"\n class=\"rounded px-1.5 py-0.5 text-[11px] font-semibold\"\n :style=\"typeBadgeStyle(primaryType(path).type)\"\n >{{ primaryType(path).type }}</span>\n <span v-if=\"path.required\" class=\"rounded bg-red-50 px-1.5 py-0.5 text-[11px] font-semibold text-red-700\">required</span>\n </div>\n </td>\n <td class=\"px-3 py-2 align-top\">\n <div v-if=\"typeDisplayMode === 'badges'\" class=\"flex flex-wrap gap-1.5\">\n <span\n v-for=\"typeInfo in path.types\"\n :key=\"typeInfo.type\"\n class=\"rounded px-2 py-1 text-xs font-semibold\"\n :style=\"typeBadgeStyle(typeInfo.type)\"\n >\n <span class=\"font-mono\">{{ typeInfo.type }}</span>:\n {{ formatPercent(typeInfo.count, pathValueCount(path)) }}\n </span>\n </div>\n <div v-else class=\"min-w-[260px]\">\n <div class=\"flex h-7 overflow-hidden rounded-lg border border-edge bg-surface\">\n <div\n v-for=\"typeInfo in path.types\"\n :key=\"typeInfo.type\"\n :title=\"`${typeInfo.type}: ${formatPercent(typeInfo.count, pathValueCount(path))}`\"\n class=\"flex h-full items-center overflow-hidden px-2 text-xs font-semibold\"\n :style=\"typeSegmentStyle(typeInfo.type, typeInfo.count, pathValueCount(path))\"\n >\n <span class=\"truncate\">{{ typeInfo.type }}</span>\n </div>\n </div>\n </div>\n </td>\n </tr>\n <tr v-if=\"schemaAnalysis && filteredPaths.length === 0\">\n <td colspan=\"2\" class=\"px-3 py-4 text-sm text-content-tertiary\">No fields match \"{{ fieldSearch }}\".</td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n</div>\n";
|
|
53189
|
+
|
|
53190
|
+
/***/ },
|
|
53191
|
+
|
|
52617
53192
|
/***/ "./frontend/src/models/document-search/document-search.html"
|
|
52618
53193
|
/*!******************************************************************!*\
|
|
52619
53194
|
!*** ./frontend/src/models/document-search/document-search.html ***!
|
|
@@ -52654,7 +53229,7 @@ module.exports = ".models {\n position: relative;\n display: flex;\n flex-dir
|
|
|
52654
53229
|
(module) {
|
|
52655
53230
|
|
|
52656
53231
|
"use strict";
|
|
52657
|
-
module.exports = "<div class=\"models flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <aside class=\"bg-page border-r overflow-hidden transition-all duration-300 ease-in-out z-20 w-0 lg:w-64 fixed lg:relative shrink-0 flex flex-col top-[55px] bottom-0 lg:top-auto lg:bottom-auto lg:h-full\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-64' : ''\">\n <!-- Search -->\n <div class=\"p-3 shrink-0\">\n <div class=\"relative\">\n <svg class=\"absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z\" />\n </svg>\n <input\n v-model=\"modelSearch\"\n type=\"text\"\n placeholder=\"Find model...\"\n @keydown.esc=\"modelSearch = ''\"\n class=\"w-full rounded-md border border-edge bg-surface py-1.5 pl-8 pr-3 text-sm text-content placeholder:text-gray-400 focus:border-edge-strong focus:outline-none focus:ring-1 focus:ring-gray-300\"\n />\n </div>\n </div>\n <!-- Model list (scrollable) -->\n <nav class=\"flex-1 overflow-y-auto px-2 pb-2\">\n <!-- Recently Viewed -->\n <div v-if=\"filteredRecentModels.length > 0 && !modelSearch.trim()\">\n <div class=\"px-2 py-1 text-xs font-semibold text-gray-400 uppercase tracking-wider\">Recently Viewed</div>\n <ul role=\"list\">\n <li v-for=\"model in filteredRecentModels\" :key=\"'recent-' + model\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"flex items-center rounded-md py-1.5 px-2 text-sm text-content-secondary\"\n :class=\"model === currentModel ? 'bg-gray-200 font-semibold text-content' : 'hover:bg-muted'\">\n <span class=\"truncate\" v-html=\"highlightMatch(model)\"></span>\n <span\n v-if=\"modelDocumentCounts && modelDocumentCounts[model] !== undefined && model !== currentModel\"\n class=\"ml-auto text-xs text-gray-400 bg-muted rounded px-1.5 py-[1px]\"\n >\n {{formatCompactCount(modelDocumentCounts[model])}}\n </span>\n </router-link>\n </li>\n </ul>\n <div class=\"border-b border-edge my-2\"></div>\n </div>\n <!-- All Models / Search Results -->\n <div class=\"px-2 py-1 text-xs font-semibold text-gray-400 uppercase tracking-wider\">{{ modelSearch.trim() ? 'Search Results' : 'All Models' }}</div>\n <ul role=\"list\">\n <li v-for=\"model in filteredModels\" :key=\"'all-' + model\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"flex items-center rounded-md py-1.5 px-2 text-sm text-content-secondary\"\n :class=\"model === currentModel ? 'bg-gray-200 font-semibold text-content' : 'hover:bg-muted'\">\n <span class=\"truncate\" v-html=\"highlightMatch(model)\"></span>\n <span\n v-if=\"modelDocumentCounts && modelDocumentCounts[model] !== undefined && model !== currentModel\"\n class=\"ml-auto text-xs text-gray-400 bg-muted rounded px-1.5 py-[1px]\"\n >\n {{formatCompactCount(modelDocumentCounts[model])}}\n </span>\n </router-link>\n </li>\n </ul>\n <div v-if=\"filteredModels.length === 0 && modelSearch.trim()\" class=\"px-2 py-2 text-sm text-content-tertiary\">\n No models match \"{{modelSearch}}\"\n </div>\n <div v-if=\"models.length === 0 && status === 'loaded'\" class=\"p-2 bg-red-100 rounded-md\">\n No models found\n </div>\n </nav>\n <!-- Bottom toolbar -->\n <div class=\"shrink-0 border-t border-edge bg-page px-2 py-1.5 flex items-center gap-1\">\n <button\n type=\"button\"\n @click=\"hideSidebar = true\"\n class=\"rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n title=\"Hide sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m18.75 4.5-7.5 7.5 7.5 7.5m-6-15L5.25 12l7.5 7.5\" />\n </svg>\n </button>\n <button\n type=\"button\"\n @click=\"openModelSwitcher\"\n class=\"rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n title=\"Quick switch (Ctrl+P)\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z\" />\n </svg>\n </button>\n </div>\n </aside>\n <div class=\"documents bg-slate-50 min-w-0\" ref=\"documentsList\">\n <div class=\"documents-menu bg-slate-50\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <button\n v-if=\"hideSidebar === true || hideSidebar === null\"\n type=\"button\"\n @click=\"hideSidebar = false\"\n class=\"shrink-0 rounded-md p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n :class=\"hideSidebar === null ? 'lg:hidden' : ''\"\n title=\"Show sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m5.25 4.5 7.5 7.5-7.5 7.5m6-15 7.5 7.5-7.5 7.5\" />\n </svg>\n </button>\n <document-search\n ref=\"documentSearch\"\n :value=\"searchText\"\n :schema-paths=\"schemaPaths\"\n @search=\"search\"\n >\n </document-search>\n <div>\n <span v-if=\"numDocuments == null\">Loading ...</span>\n <span v-else-if=\"typeof numDocuments === 'number'\">{{documents.length}}/{{numDocuments === 1 ? numDocuments + ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{\n 'bg-gray-700 ring-inset ring-2 ring-gray-300 text-white hover:bg-gray-600': selectMultiple,\n 'bg-primary hover:bg-primary-hover text-primary-text': !selectMultiple\n }\"\n class=\"rounded px-2 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\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 <div class=\"relative\" v-show=\"!selectMultiple\" ref=\"actionsMenuContainer\" @keyup.esc.prevent=\"closeActionsMenu\">\n <button\n @click=\"toggleActionsMenu\"\n type=\"button\"\n aria-label=\"More actions\"\n class=\"rounded bg-surface px-2 py-2 text-sm font-semibold text-content-secondary shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-page focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 6.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Zm0 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Zm0 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Z\" />\n </svg>\n </button>\n <div\n v-if=\"showActionsMenu\"\n class=\"absolute right-0 mt-2 w-48 origin-top-right rounded-md bg-surface shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none z-50\"\n >\n <div class=\"py-1\">\n <button\n @click=\"shouldShowExportModal = true; showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Export\n </button>\n <button\n @click=\"shouldShowCreateModal = true; showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Create\n </button>\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Indexes\n </button>\n <button\n @click=\"openCollectionInfo\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Collection Info\n </button>\n <button\n @click=\"findOldestDocument\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Find oldest document\n </button>\n <button\n @click=\"openDropCollectionModal\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-red-700 hover:bg-red-50\"\n >\n Drop Collection\n </button>\n <button\n @click=\"toggleRowNumbers()\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n {{ showRowNumbers ? 'Hide row numbers' : 'Show row numbers' }}\n </button>\n <button\n @click=\"resetFilter()\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Reset Filter\n </button>\n <button\n @click=\"toggleProjectionMenu(); showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm hover:bg-muted\"\n :class=\"isProjectionMenuSelected ? 'text-primary font-semibold' : 'text-content-secondary'\"\n >\n {{ isProjectionMenuSelected ? 'Projection (On)' : 'Projection' }}\n </button>\n <button\n v-if=\"isProjectionMenuSelected\"\n @click=\"clearProjection()\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Reset Projection\n </button>\n <async-button\n v-if=\"isProjectionMenuSelected\"\n type=\"button\"\n @click=\"applyDefaultProjectionColumns()\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Default Projection\n </async-button>\n </div>\n </div>\n </div>\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-page focus:z-10\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-surface'\">\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 px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-page focus:z-10\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-surface'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n <button\n @click=\"setOutputType('map')\"\n :disabled=\"geoJsonFields.length === 0\"\n type=\"button\"\n :title=\"geoJsonFields.length > 0 ? 'Map view' : 'No GeoJSON fields detected'\"\n class=\"relative -ml-px inline-flex items-center rounded-none rounded-r-md px-2 py-2 ring-1 ring-inset ring-gray-300 focus:z-10\"\n :class=\"[\n geoJsonFields.length === 0 ? 'text-gray-300 cursor-not-allowed bg-muted' : 'text-gray-400 hover:bg-page',\n outputType === 'map' ? 'bg-gray-200' : (geoJsonFields.length > 0 ? 'bg-surface' : '')\n ]\">\n <svg class=\"h-5 w-5\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 6.75V15m6-6v8.25m.503 3.498 4.875-2.437c.381-.19.622-.58.622-1.006V4.82c0-.836-.88-1.38-1.628-1.006l-3.869 1.934c-.317.159-.69.159-1.006 0L9.503 3.252a1.125 1.125 0 0 0-1.006 0L3.622 5.689C3.24 5.88 3 6.27 3 6.695V19.18c0 .836.88 1.38 1.628 1.006l3.869-1.934c.317-.159.69-.159 1.006 0l4.994 2.497c.317.158.69.158 1.006 0Z\" />\n </svg>\n </button>\n </span>\n </div>\n <div v-if=\"isProjectionMenuSelected && (outputType === 'table' || outputType === 'json')\" class=\"flex items-center gap-2 w-full mt-2 flex-shrink-0\">\n <projection-search\n ref=\"projectionSearch\"\n :value=\"projectionText\"\n :schema-paths=\"schemaPaths\"\n @projection-input=\"projectionText = $event\"\n @apply=\"applyProjectionFromInput\"\n />\n </div>\n </div>\n <!-- In JSON view, this container is the scrollable element used for infinite scroll. -->\n <div class=\"documents-container relative\" ref=\"documentsContainerScroll\" @scroll=\"checkIfScrolledToBottom\">\n <div v-if=\"error\">\n <div class=\"bg-red-100 border border-red-400 text-red-700 px-4 py-3 relative m-4 rounded-md\" role=\"alert\">\n <span class=\"block font-bold\">Error</span>\n <span class=\"block\">{{ error }}</span>\n <span class=\"block mt-2\">\n Need help?\n <a\n href=\"https://discord.gg/P3YCfKYxpy\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"underline font-medium text-red-800 hover:text-red-900\"\n >Ask in Discord</a>\n or\n <a\n href=\"https://github.com/mongoosejs/studio/issues\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"underline font-medium text-red-800 hover:text-red-900\"\n >\n open a GitHub issue.\n </a>\n </span>\n </div>\n </div>\n <div v-else-if=\"outputType === 'table'\" class=\"flex-1 min-h-0 flex flex-col overflow-hidden\">\n <div\n ref=\"documentsScrollContainer\"\n class=\"overflow-x-auto overflow-y-auto flex-1 min-h-0 border border-edge rounded-lg bg-surface\"\n @scroll=\"checkIfScrolledToBottom\"\n >\n <table class=\"min-w-full border-collapse text-sm\">\n <thead class=\"sticky top-0 z-10 bg-slate-100 dark:bg-shark-800 border-b border-edge\">\n <tr>\n <th v-if=\"showRowNumbers\" class=\"px-3 py-2.5 text-left font-medium text-content border-r border-edge whitespace-nowrap align-middle w-0\">\n #\n </th>\n <th\n v-for=\"path in tableDisplayPaths\"\n :key=\"path.path\"\n class=\"px-3 py-2.5 text-left font-medium text-content border-r border-edge last:border-r-0 whitespace-nowrap align-middle\"\n >\n <div class=\"flex items-center gap-2\">\n <span\n @click=\"addPathFilter(path.path)\"\n class=\"cursor-pointer hover:text-primary truncate min-w-0\"\n :title=\"path.path\"\n >\n {{ path.path }}\n </span>\n <span class=\"text-xs text-content-tertiary shrink-0\">({{ path.instance || 'unknown' }})</span>\n <span class=\"inline-flex shrink-0 gap-0.5 items-center\">\n <button\n type=\"button\"\n @click.stop=\"sortDocs(1, path.path)\"\n class=\"p-0.5 rounded text-content-tertiary hover:text-content hover:bg-muted\"\n :class=\"{ 'text-primary font-semibold': sortBy[path.path] === 1 }\"\n :title=\"sortBy[path.path] === 1 ? 'Clear sort' : 'Sort ascending'\"\n >\n ↑\n </button>\n <button\n type=\"button\"\n @click.stop=\"sortDocs(-1, path.path)\"\n class=\"p-0.5 rounded text-content-tertiary hover:text-content hover:bg-muted\"\n :class=\"{ 'text-primary font-semibold': sortBy[path.path] === -1 }\"\n :title=\"sortBy[path.path] === -1 ? 'Clear sort' : 'Sort descending'\"\n >\n ↓\n </button>\n <button\n v-if=\"filteredPaths.length > 0\"\n type=\"button\"\n @click.stop=\"removeField(path)\"\n class=\"p-1.5 rounded-md border border-transparent text-content-tertiary hover:text-valencia-600 hover:bg-valencia-50 hover:border-valencia-200 focus:outline-none focus:ring-2 focus:ring-valencia-500/30\"\n title=\"Remove column\"\n aria-label=\"Remove column\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18 18 6M6 6l12 12\" />\n </svg>\n </button>\n </span>\n </div>\n </th>\n <th v-if=\"filteredPaths.length > 0\" class=\"px-2 py-2.5 border-r border-edge last:border-r-0 align-middle w-0 bg-slate-50 dark:bg-shark-800/80\">\n <div class=\"relative\" ref=\"addFieldContainer\">\n <button\n type=\"button\"\n @click=\"toggleAddFieldDropdown()\"\n class=\"flex items-center justify-center w-8 h-8 rounded border border-dashed border-edge text-content-tertiary hover:border-primary hover:text-primary hover:bg-primary-subtle/30\"\n title=\"Add column\"\n aria-label=\"Add column\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" class=\"w-5 h-5\"> <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 4.5v15m7.5-7.5h-15\" /> </svg>\n </button>\n <div\n v-if=\"showAddFieldDropdown\"\n class=\"absolute right-0 top-full mt-1 z-[100] min-w-[180px] max-w-[280px] rounded-md border border-edge bg-surface shadow-lg py-1 max-h-48 overflow-y-auto\"\n >\n <input\n v-if=\"availablePathsToAdd.length > 5\"\n ref=\"addFieldFilterInput\"\n v-model=\"addFieldFilterText\"\n type=\"text\"\n placeholder=\"Filter fields...\"\n class=\"mx-2 mb-1 w-[calc(100%-1rem)] rounded border border-edge px-2 py-1 text-sm\"\n @click.stop\n />\n <button\n v-for=\"p in filteredPathsToAdd\"\n :key=\"p.path\"\n type=\"button\"\n class=\"w-full px-3 py-1.5 text-left text-sm hover:bg-muted\"\n @click.stop=\"addField(p)\"\n >\n {{ p.path }}\n </button>\n <p v-if=\"filteredPathsToAdd.length === 0\" class=\"px-3 py-2 text-sm text-content-tertiary\">\n {{ addFieldFilterText.trim() ? 'No matching fields' : 'All fields added' }}\n </p>\n </div>\n </div>\n </th>\n </tr>\n </thead>\n <tbody class=\"bg-surface\">\n <tr\n v-for=\"(document, docIndex) in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentClick(document, $event)\"\n class=\"border-b border-edge cursor-pointer transition-colors\"\n :class=\"selectedDocuments.some(x => x._id.toString() === document._id.toString())\n ? 'bg-blue-200'\n : 'hover:bg-muted'\"\n >\n <td v-if=\"showRowNumbers\" class=\"px-3 py-2 border-r border-edge align-top text-content-tertiary whitespace-nowrap\">\n {{ docIndex + 1 }}\n </td>\n <td\n v-for=\"schemaPath in tableDisplayPaths\"\n :key=\"schemaPath.path\"\n class=\"px-3 py-2 border-r border-edge last:border-r-0 align-top max-w-[280px]\"\n >\n <div class=\"table-cell-content flex items-center gap-1.5 min-w-0 group\">\n <span class=\"min-w-0 overflow-hidden text-ellipsis flex-1\">\n <component\n :is=\"getComponentForPath(schemaPath)\"\n :value=\"getValueForPath(document, schemaPath.path)\"\n :allude=\"getReferenceModel(schemaPath)\"\n />\n </span>\n <button\n type=\"button\"\n class=\"table-cell-copy shrink-0 p-1 rounded text-content-tertiary hover:text-content hover:bg-muted focus:outline-none focus:ring-1 focus:ring-edge\"\n aria-label=\"Copy cell value\"\n @click.stop=\"copyCellValue(getValueForPath(document, schemaPath.path))\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8 5H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1M8 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M8 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m0 0h2a2 2 0 0 1 2 2v3m2 4H10m0 0l3-3m-3 3l3 3\" />\n </svg>\n </button>\n </div>\n </td>\n <td v-if=\"filteredPaths.length > 0\" class=\"w-0 px-0 py-0 border-r border-edge last:border-r-0 bg-slate-50/50 dark:bg-shark-800/30\"></td>\n </tr>\n </tbody>\n </table>\n </div>\n <div v-if=\"outputType === 'table' && (loadingMore || (status === 'loading' && documents.length > 0))\" class=\"flex items-center justify-center gap-2 py-3 text-sm text-content-tertiary border-t border-edge bg-surface\">\n <img src=\"images/loader.gif\" alt=\"\" class=\"h-5 w-5\">\n <span>Loading documents…</span>\n </div>\n <p v-if=\"outputType === 'table' && documents.length === 0 && status === 'loaded'\" class=\"mt-2 text-sm text-content-tertiary px-1\">\n No documents to show. Use Projection in the menu to choose columns.\n </p>\n </div>\n <div v-else-if=\"outputType === 'json'\" class=\"flex min-w-0 flex-col space-y-2 p-1 mt-1\">\n <div\n v-for=\"document in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentContainerClick(document, $event)\"\n :class=\"[\n 'group relative min-w-0 max-w-full transition-colors rounded-md border border-slate-100',\n selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:shadow-sm hover:border-slate-300 bg-surface'\n ]\"\n >\n <router-link\n class=\"absolute top-2 right-2 z-10 inline-flex items-center rounded bg-primary px-2 py-1 text-xs font-semibold text-primary-text shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n :to=\"{ path: '/model/' + currentModel + '/document/' + document._id, query: $route.query }\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n @click.stop\n >\n Open\n </router-link>\n <list-json :value=\"filterDocument(document)\" :references=\"referenceMap\">\n </list-json>\n </div>\n <div v-if=\"outputType === 'json' && (loadingMore || (status === 'loading' && documents.length > 0))\" class=\"flex items-center justify-center gap-2 py-3 text-sm text-content-tertiary\">\n <img src=\"images/loader.gif\" alt=\"\" class=\"h-5 w-5\">\n <span>Loading documents…</span>\n </div>\n </div>\n <div v-else-if=\"outputType === 'map'\" class=\"flex flex-col h-full\">\n <div class=\"p-2 bg-surface border-b flex items-center gap-2\">\n <label class=\"text-sm font-medium text-content-secondary\">GeoJSON Field:</label>\n <select\n :value=\"selectedGeoField\"\n @change=\"setSelectedGeoField($event.target.value)\"\n class=\"rounded-md border border-edge-strong py-1 px-2 text-sm focus:border-primary focus:ring-primary\"\n >\n <option v-for=\"field in geoJsonFields\" :key=\"field.path\" :value=\"field.path\">\n {{ field.label }}\n </option>\n </select>\n <async-button\n @click=\"loadMoreDocuments\"\n :disabled=\"loadedAllDocs\"\n type=\"button\"\n class=\"rounded px-2 py-1 text-xs font-semibold text-primary-text shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n :class=\"loadedAllDocs ? 'bg-gray-400 cursor-not-allowed' : 'bg-primary hover:bg-primary-hover'\"\n >\n Load more\n </async-button>\n </div>\n <div class=\"flex-1 min-h-[400px]\" ref=\"modelsMap\"></div>\n </div>\n <div v-if=\"status === 'loading' && !loadingMore && documents.length === 0\" class=\"loader loader-overlay\" aria-busy=\"true\">\n <img src=\"images/loader.gif\" alt=\"Loading\">\n </div>\n </div>\n </div>\n <modal v-if=\"shouldShowExportModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowExportModal = false\">×</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\">×</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 flex items-center gap-2\">\n <div>{{ index.name }}</div>\n <div v-if=\"isTTLIndex(index)\" class=\"rounded-full bg-primary-subtle px-2 py-0.5 text-xs font-semibold text-primary\">\n TTL: {{ formatTTL(index.expireAfterSeconds) }}\n </div>\n </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=\"shouldShowCollectionInfoModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCollectionInfoModal = false\">×</div>\n <div class=\"text-xl font-bold mb-2\">Collection Info</div>\n <div v-if=\"!collectionInfo\" class=\"text-gray-600\">Loading collection details...</div>\n <div v-else class=\"space-y-3\">\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Documents</div>\n <div class=\"text-content\">{{ formatNumber(collectionInfo.documentCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Indexes</div>\n <div class=\"text-content\">{{ formatNumber(collectionInfo.indexCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Total Index Size</div>\n <div class=\"text-content\">{{ formatCollectionSize(collectionInfo.totalIndexSize) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Total Storage Size</div>\n <div class=\"text-content\">{{ formatCollectionSize(collectionInfo.size) }}</div>\n </div>\n <div class=\"flex flex-col gap-1\">\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Collation</div>\n <div class=\"text-content\">{{ collectionInfo.hasCollation ? 'Yes' : 'No' }}</div>\n </div>\n <div v-if=\"collectionInfo.hasCollation\" class=\"rounded bg-muted p-3 text-sm text-gray-800 overflow-x-auto\">\n <pre class=\"whitespace-pre-wrap\">{{ JSON.stringify(collectionInfo.collation, null, 2) }}</pre>\n </div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Capped</div>\n <div class=\"text-content\">{{ collectionInfo.capped ? 'Yes' : 'No' }}</div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCreateModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCreateModal = false;\">×</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;\">×</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;\">×</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-page0 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 <modal v-if=\"shouldShowDropCollectionModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDropCollectionModal = false\">×</div>\n <h2 class=\"text-xl font-bold mb-3\">Are you sure?</h2>\n <p class=\"text-content-secondary mb-3\">\n This will permanently drop the <span class=\"font-semibold\">{{ currentModel }}</span> collection and all its documents.\n </p>\n <div v-if=\"requiresDropCollectionNameConfirmation()\" class=\"rounded border border-red-300 bg-red-50 px-3 py-2 mb-4\">\n <p class=\"text-sm font-semibold text-red-700\">Danger zone</p>\n <p class=\"text-sm text-red-700 mb-2\">Type <span class=\"font-mono font-semibold\">{{ currentModel }}</span> to confirm.</p>\n <input\n v-model=\"dropCollectionConfirmName\"\n type=\"text\"\n :placeholder=\"currentModel\"\n class=\"w-full rounded border border-red-300 px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-red-200\"\n />\n </div>\n <div class=\"flex gap-4\">\n <async-button\n @click=\"dropCollection\"\n :disabled=\"requiresDropCollectionNameConfirmation() && dropCollectionConfirmName !== currentModel\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n Drop Collection\n </async-button>\n <button @click=\"shouldShowDropCollectionModal = false\" class=\"rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-page0\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n <model-switcher\n :show=\"showModelSwitcher\"\n :models=\"models\"\n :recently-viewed-models=\"recentlyViewedModels\"\n :model-document-counts=\"modelDocumentCounts\"\n @close=\"showModelSwitcher = false\"\n @select=\"selectSwitcherModel\"\n ></model-switcher>\n</div>\n";
|
|
53232
|
+
module.exports = "<div class=\"models flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <aside class=\"bg-page border-r overflow-hidden transition-all duration-300 ease-in-out z-20 w-0 lg:w-64 fixed lg:relative shrink-0 flex flex-col top-[55px] bottom-0 lg:top-auto lg:bottom-auto lg:h-full\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-64' : ''\">\n <!-- Search -->\n <div class=\"p-3 shrink-0\">\n <div class=\"relative\">\n <svg class=\"absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z\" />\n </svg>\n <input\n v-model=\"modelSearch\"\n type=\"text\"\n placeholder=\"Find model...\"\n @keydown.esc=\"modelSearch = ''\"\n class=\"w-full rounded-md border border-edge bg-surface py-1.5 pl-8 pr-3 text-sm text-content placeholder:text-gray-400 focus:border-edge-strong focus:outline-none focus:ring-1 focus:ring-gray-300\"\n />\n </div>\n </div>\n <!-- Model list (scrollable) -->\n <nav class=\"flex-1 overflow-y-auto px-2 pb-2\">\n <!-- Recently Viewed -->\n <div v-if=\"filteredRecentModels.length > 0 && !modelSearch.trim()\">\n <div class=\"px-2 py-1 text-xs font-semibold text-gray-400 uppercase tracking-wider\">Recently Viewed</div>\n <ul role=\"list\">\n <li v-for=\"model in filteredRecentModels\" :key=\"'recent-' + model\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"flex items-center rounded-md py-1.5 px-2 text-sm text-content-secondary\"\n :class=\"model === currentModel ? 'bg-gray-200 font-semibold text-content' : 'hover:bg-muted'\">\n <span class=\"truncate\" v-html=\"highlightMatch(model)\"></span>\n <span\n v-if=\"modelDocumentCounts && modelDocumentCounts[model] !== undefined && model !== currentModel\"\n class=\"ml-auto text-xs text-gray-400 bg-muted rounded px-1.5 py-[1px]\"\n >\n {{formatCompactCount(modelDocumentCounts[model])}}\n </span>\n </router-link>\n </li>\n </ul>\n <div class=\"border-b border-edge my-2\"></div>\n </div>\n <!-- All Models / Search Results -->\n <div class=\"px-2 py-1 text-xs font-semibold text-gray-400 uppercase tracking-wider\">{{ modelSearch.trim() ? 'Search Results' : 'All Models' }}</div>\n <ul role=\"list\">\n <li v-for=\"model in filteredModels\" :key=\"'all-' + model\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"flex items-center rounded-md py-1.5 px-2 text-sm text-content-secondary\"\n :class=\"model === currentModel ? 'bg-gray-200 font-semibold text-content' : 'hover:bg-muted'\">\n <span class=\"truncate\" v-html=\"highlightMatch(model)\"></span>\n <span\n v-if=\"modelDocumentCounts && modelDocumentCounts[model] !== undefined && model !== currentModel\"\n class=\"ml-auto text-xs text-gray-400 bg-muted rounded px-1.5 py-[1px]\"\n >\n {{formatCompactCount(modelDocumentCounts[model])}}\n </span>\n </router-link>\n </li>\n </ul>\n <div v-if=\"filteredModels.length === 0 && modelSearch.trim()\" class=\"px-2 py-2 text-sm text-content-tertiary\">\n No models match \"{{modelSearch}}\"\n </div>\n <div v-if=\"models.length === 0 && status === 'loaded'\" class=\"p-2 bg-red-100 rounded-md\">\n No models found\n </div>\n </nav>\n <!-- Bottom toolbar -->\n <div class=\"shrink-0 border-t border-edge bg-page px-2 py-1.5 flex items-center gap-1\">\n <button\n type=\"button\"\n @click=\"hideSidebar = true\"\n class=\"rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n title=\"Hide sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m18.75 4.5-7.5 7.5 7.5 7.5m-6-15L5.25 12l7.5 7.5\" />\n </svg>\n </button>\n <button\n type=\"button\"\n @click=\"openModelSwitcher\"\n class=\"rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n title=\"Quick switch (Ctrl+P)\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z\" />\n </svg>\n </button>\n </div>\n </aside>\n <div class=\"documents bg-slate-50 min-w-0\" ref=\"documentsList\">\n <div class=\"documents-menu bg-slate-50\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <button\n v-if=\"hideSidebar === true || hideSidebar === null\"\n type=\"button\"\n @click=\"hideSidebar = false\"\n class=\"shrink-0 rounded-md p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n :class=\"hideSidebar === null ? 'lg:hidden' : ''\"\n title=\"Show sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m5.25 4.5 7.5 7.5-7.5 7.5m6-15 7.5 7.5-7.5 7.5\" />\n </svg>\n </button>\n <document-search\n ref=\"documentSearch\"\n :value=\"searchText\"\n :schema-paths=\"schemaPaths\"\n @search=\"search\"\n >\n </document-search>\n <div>\n <span v-if=\"numDocuments == null\">Loading ...</span>\n <span v-else-if=\"typeof numDocuments === 'number'\">{{documents.length}}/{{numDocuments === 1 ? numDocuments + ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{\n 'bg-gray-700 ring-inset ring-2 ring-gray-300 text-white hover:bg-gray-600': selectMultiple,\n 'bg-primary hover:bg-primary-hover text-primary-text': !selectMultiple\n }\"\n class=\"rounded px-2 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\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 <div class=\"relative\" v-show=\"!selectMultiple\" ref=\"actionsMenuContainer\" @keyup.esc.prevent=\"closeActionsMenu\">\n <button\n @click=\"toggleActionsMenu\"\n type=\"button\"\n aria-label=\"More actions\"\n class=\"rounded bg-surface px-2 py-2 text-sm font-semibold text-content-secondary shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-page focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 6.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Zm0 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Zm0 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Z\" />\n </svg>\n </button>\n <div\n v-if=\"showActionsMenu\"\n class=\"absolute right-0 mt-2 w-48 origin-top-right rounded-md bg-surface shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none z-50\"\n >\n <div class=\"py-1\">\n <button\n @click=\"shouldShowExportModal = true; showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Export\n </button>\n <button\n @click=\"shouldShowCreateModal = true; showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Create\n </button>\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Indexes\n </button>\n <button\n @click=\"openAnalyzeSchemaModal\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Analyze Schema\n </button>\n <button\n @click=\"openCollectionInfo\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Collection Info\n </button>\n <button\n @click=\"toggleRowNumbers()\"\n :disabled=\"outputType !== 'table'\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm\"\n :class=\"outputType === 'table' ? 'text-content-secondary hover:bg-muted' : 'text-content-tertiary cursor-not-allowed opacity-50'\"\n >\n {{ showRowNumbers ? 'Hide row numbers' : 'Show row numbers' }}\n </button>\n <button\n @click=\"resetFilter()\"\n :disabled=\"searchText.trim().length === 0\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm\"\n :class=\"searchText.trim().length > 0 ? 'text-content-secondary hover:bg-muted' : 'text-content-tertiary cursor-not-allowed opacity-50'\"\n >\n Reset Filter\n </button>\n <button\n @click=\"toggleProjectionMenu(); showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm hover:bg-muted\"\n :class=\"isProjectionMenuSelected ? 'text-primary font-semibold' : 'text-content-secondary'\"\n >\n {{ isProjectionMenuSelected ? 'Projection (On)' : 'Projection' }}\n </button>\n <button\n v-if=\"isProjectionMenuSelected\"\n @click=\"clearProjection()\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Reset Projection\n </button>\n <async-button\n v-if=\"isProjectionMenuSelected\"\n type=\"button\"\n @click=\"applyDefaultProjectionColumns()\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Default Projection\n </async-button>\n <button\n @click=\"openDropCollectionModal\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-red-700 hover:bg-red-50\"\n >\n Drop Collection\n </button>\n </div>\n </div>\n </div>\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-page focus:z-10\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-surface'\">\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 px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-page focus:z-10\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-surface'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n <button\n @click=\"setOutputType('map')\"\n :disabled=\"geoJsonFields.length === 0\"\n type=\"button\"\n :title=\"geoJsonFields.length > 0 ? 'Map view' : 'No GeoJSON fields detected'\"\n class=\"relative -ml-px inline-flex items-center rounded-none rounded-r-md px-2 py-2 ring-1 ring-inset ring-gray-300 focus:z-10\"\n :class=\"[\n geoJsonFields.length === 0 ? 'text-gray-300 cursor-not-allowed bg-muted' : 'text-gray-400 hover:bg-page',\n outputType === 'map' ? 'bg-gray-200' : (geoJsonFields.length > 0 ? 'bg-surface' : '')\n ]\">\n <svg class=\"h-5 w-5\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 6.75V15m6-6v8.25m.503 3.498 4.875-2.437c.381-.19.622-.58.622-1.006V4.82c0-.836-.88-1.38-1.628-1.006l-3.869 1.934c-.317.159-.69.159-1.006 0L9.503 3.252a1.125 1.125 0 0 0-1.006 0L3.622 5.689C3.24 5.88 3 6.27 3 6.695V19.18c0 .836.88 1.38 1.628 1.006l3.869-1.934c.317-.159.69-.159 1.006 0l4.994 2.497c.317.158.69.158 1.006 0Z\" />\n </svg>\n </button>\n </span>\n </div>\n <div v-if=\"isProjectionMenuSelected && (outputType === 'table' || outputType === 'json')\" class=\"flex items-center gap-2 w-full mt-2 flex-shrink-0\">\n <projection-search\n ref=\"projectionSearch\"\n :value=\"projectionText\"\n :schema-paths=\"schemaPaths\"\n @projection-input=\"projectionText = $event\"\n @apply=\"applyProjectionFromInput\"\n />\n </div>\n </div>\n <!-- In JSON view, this container is the scrollable element used for infinite scroll. -->\n <div class=\"documents-container relative\" ref=\"documentsContainerScroll\" @scroll=\"checkIfScrolledToBottom\">\n <div v-if=\"error\">\n <div class=\"bg-red-100 border border-red-400 text-red-700 px-4 py-3 relative m-4 rounded-md\" role=\"alert\">\n <span class=\"block font-bold\">Error</span>\n <span class=\"block\">{{ error }}</span>\n <span class=\"block mt-2\">\n Need help?\n <a\n href=\"https://discord.gg/P3YCfKYxpy\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"underline font-medium text-red-800 hover:text-red-900\"\n >Ask in Discord</a>\n or\n <a\n href=\"https://github.com/mongoosejs/studio/issues\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"underline font-medium text-red-800 hover:text-red-900\"\n >\n open a GitHub issue.\n </a>\n </span>\n </div>\n </div>\n <div v-else-if=\"outputType === 'table'\" class=\"flex-1 min-h-0 flex flex-col overflow-hidden\">\n <div\n ref=\"documentsScrollContainer\"\n class=\"overflow-x-auto overflow-y-auto flex-1 min-h-0 border border-edge rounded-lg bg-surface\"\n @scroll=\"checkIfScrolledToBottom\"\n >\n <table class=\"min-w-full border-collapse text-sm\">\n <thead class=\"sticky top-0 z-10 bg-slate-100 dark:bg-shark-800 border-b border-edge\">\n <tr>\n <th v-if=\"showRowNumbers\" class=\"px-3 py-2.5 text-left font-medium text-content border-r border-edge whitespace-nowrap align-middle w-0\">\n #\n </th>\n <th\n v-for=\"path in tableDisplayPaths\"\n :key=\"path.path\"\n class=\"px-3 py-2.5 text-left font-medium text-content border-r border-edge last:border-r-0 whitespace-nowrap align-middle\"\n >\n <div class=\"flex items-center gap-2\">\n <span\n @click=\"addPathFilter(path.path)\"\n class=\"cursor-pointer hover:text-primary truncate min-w-0\"\n :title=\"path.path\"\n >\n {{ path.path }}\n </span>\n <span class=\"text-xs text-content-tertiary shrink-0\">({{ path.instance || 'unknown' }})</span>\n <span class=\"inline-flex shrink-0 gap-0.5 items-center\">\n <button\n type=\"button\"\n @click.stop=\"sortDocs(1, path.path)\"\n class=\"p-0.5 rounded text-content-tertiary hover:text-content hover:bg-muted\"\n :class=\"{ 'text-primary font-semibold': sortBy[path.path] === 1 }\"\n :title=\"sortBy[path.path] === 1 ? 'Clear sort' : 'Sort ascending'\"\n >\n ↑\n </button>\n <button\n type=\"button\"\n @click.stop=\"sortDocs(-1, path.path)\"\n class=\"p-0.5 rounded text-content-tertiary hover:text-content hover:bg-muted\"\n :class=\"{ 'text-primary font-semibold': sortBy[path.path] === -1 }\"\n :title=\"sortBy[path.path] === -1 ? 'Clear sort' : 'Sort descending'\"\n >\n ↓\n </button>\n <button\n v-if=\"filteredPaths.length > 0\"\n type=\"button\"\n @click.stop=\"removeField(path)\"\n class=\"p-1.5 rounded-md border border-transparent text-content-tertiary hover:text-valencia-600 hover:bg-valencia-50 hover:border-valencia-200 focus:outline-none focus:ring-2 focus:ring-valencia-500/30\"\n title=\"Remove column\"\n aria-label=\"Remove column\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18 18 6M6 6l12 12\" />\n </svg>\n </button>\n </span>\n </div>\n </th>\n <th v-if=\"filteredPaths.length > 0\" class=\"px-2 py-2.5 border-r border-edge last:border-r-0 align-middle w-0 bg-slate-50 dark:bg-shark-800/80\">\n <div class=\"relative\" ref=\"addFieldContainer\">\n <button\n type=\"button\"\n @click=\"toggleAddFieldDropdown()\"\n class=\"flex items-center justify-center w-8 h-8 rounded border border-dashed border-edge text-content-tertiary hover:border-primary hover:text-primary hover:bg-primary-subtle/30\"\n title=\"Add column\"\n aria-label=\"Add column\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" class=\"w-5 h-5\"> <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 4.5v15m7.5-7.5h-15\" /> </svg>\n </button>\n <div\n v-if=\"showAddFieldDropdown\"\n class=\"absolute right-0 top-full mt-1 z-[100] min-w-[180px] max-w-[280px] rounded-md border border-edge bg-surface shadow-lg py-1 max-h-48 overflow-y-auto\"\n >\n <input\n v-if=\"availablePathsToAdd.length > 5\"\n ref=\"addFieldFilterInput\"\n v-model=\"addFieldFilterText\"\n type=\"text\"\n placeholder=\"Filter fields...\"\n class=\"mx-2 mb-1 w-[calc(100%-1rem)] rounded border border-edge px-2 py-1 text-sm\"\n @click.stop\n />\n <button\n v-for=\"p in filteredPathsToAdd\"\n :key=\"p.path\"\n type=\"button\"\n class=\"w-full px-3 py-1.5 text-left text-sm hover:bg-muted\"\n @click.stop=\"addField(p)\"\n >\n {{ p.path }}\n </button>\n <p v-if=\"filteredPathsToAdd.length === 0\" class=\"px-3 py-2 text-sm text-content-tertiary\">\n {{ addFieldFilterText.trim() ? 'No matching fields' : 'All fields added' }}\n </p>\n </div>\n </div>\n </th>\n </tr>\n </thead>\n <tbody class=\"bg-surface\">\n <tr\n v-for=\"(document, docIndex) in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentClick(document, $event)\"\n class=\"border-b border-edge cursor-pointer transition-colors\"\n :class=\"selectedDocuments.some(x => x._id.toString() === document._id.toString())\n ? 'bg-blue-200'\n : 'hover:bg-muted'\"\n >\n <td v-if=\"showRowNumbers\" class=\"px-3 py-2 border-r border-edge align-top text-content-tertiary whitespace-nowrap\">\n {{ docIndex + 1 }}\n </td>\n <td\n v-for=\"schemaPath in tableDisplayPaths\"\n :key=\"schemaPath.path\"\n class=\"px-3 py-2 border-r border-edge last:border-r-0 align-top max-w-[280px]\"\n >\n <div class=\"table-cell-content flex items-center gap-1.5 min-w-0 group\">\n <span class=\"min-w-0 overflow-hidden text-ellipsis flex-1\">\n <component\n :is=\"getComponentForPath(schemaPath)\"\n :value=\"getValueForPath(document, schemaPath.path)\"\n :allude=\"getReferenceModel(schemaPath)\"\n />\n </span>\n <button\n type=\"button\"\n class=\"table-cell-copy shrink-0 p-1 rounded text-content-tertiary hover:text-content hover:bg-muted focus:outline-none focus:ring-1 focus:ring-edge\"\n aria-label=\"Copy cell value\"\n @click.stop=\"copyCellValue(getValueForPath(document, schemaPath.path))\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8 5H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1M8 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M8 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m0 0h2a2 2 0 0 1 2 2v3m2 4H10m0 0l3-3m-3 3l3 3\" />\n </svg>\n </button>\n </div>\n </td>\n <td v-if=\"filteredPaths.length > 0\" class=\"w-0 px-0 py-0 border-r border-edge last:border-r-0 bg-slate-50/50 dark:bg-shark-800/30\"></td>\n </tr>\n </tbody>\n </table>\n </div>\n <div v-if=\"outputType === 'table' && (loadingMore || (status === 'loading' && documents.length > 0))\" class=\"flex items-center justify-center gap-2 py-3 text-sm text-content-tertiary border-t border-edge bg-surface\">\n <img src=\"images/loader.gif\" alt=\"\" class=\"h-5 w-5\">\n <span>Loading documents…</span>\n </div>\n <p v-if=\"outputType === 'table' && documents.length === 0 && status === 'loaded'\" class=\"mt-2 text-sm text-content-tertiary px-1\">\n No documents to show. Use Projection in the menu to choose columns.\n </p>\n </div>\n <div v-else-if=\"outputType === 'json'\" class=\"flex min-w-0 flex-col space-y-2 p-1 mt-1\">\n <div\n v-for=\"document in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentContainerClick(document, $event)\"\n :class=\"[\n 'group relative min-w-0 max-w-full transition-colors rounded-md border border-slate-100',\n selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:shadow-sm hover:border-slate-300 bg-surface'\n ]\"\n >\n <router-link\n class=\"absolute top-2 right-2 z-10 inline-flex items-center rounded bg-primary px-2 py-1 text-xs font-semibold text-primary-text shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n :to=\"{ path: '/model/' + currentModel + '/document/' + document._id, query: $route.query }\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n @click.stop\n >\n Open\n </router-link>\n <list-json :value=\"filterDocument(document)\" :references=\"referenceMap\">\n </list-json>\n </div>\n <div v-if=\"outputType === 'json' && (loadingMore || (status === 'loading' && documents.length > 0))\" class=\"flex items-center justify-center gap-2 py-3 text-sm text-content-tertiary\">\n <img src=\"images/loader.gif\" alt=\"\" class=\"h-5 w-5\">\n <span>Loading documents…</span>\n </div>\n </div>\n <div v-else-if=\"outputType === 'map'\" class=\"flex flex-col h-full\">\n <div class=\"p-2 bg-surface border-b flex items-center gap-2\">\n <label class=\"text-sm font-medium text-content-secondary\">GeoJSON Field:</label>\n <select\n :value=\"selectedGeoField\"\n @change=\"setSelectedGeoField($event.target.value)\"\n class=\"rounded-md border border-edge-strong py-1 px-2 text-sm focus:border-primary focus:ring-primary\"\n >\n <option v-for=\"field in geoJsonFields\" :key=\"field.path\" :value=\"field.path\">\n {{ field.label }}\n </option>\n </select>\n <async-button\n @click=\"loadMoreDocuments\"\n :disabled=\"loadedAllDocs\"\n type=\"button\"\n class=\"rounded px-2 py-1 text-xs font-semibold text-primary-text shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n :class=\"loadedAllDocs ? 'bg-gray-400 cursor-not-allowed' : 'bg-primary hover:bg-primary-hover'\"\n >\n Load more\n </async-button>\n </div>\n <div class=\"flex-1 min-h-[400px]\" ref=\"modelsMap\"></div>\n </div>\n <div v-if=\"status === 'loading' && !loadingMore && documents.length === 0\" class=\"loader loader-overlay\" aria-busy=\"true\">\n <img src=\"images/loader.gif\" alt=\"Loading\">\n </div>\n </div>\n </div>\n <modal v-if=\"shouldShowExportModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowExportModal = false\">×</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\">×</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 flex items-center gap-2\">\n <div>{{ index.name }}</div>\n <div v-if=\"isTTLIndex(index)\" class=\"rounded-full bg-primary-subtle px-2 py-0.5 text-xs font-semibold text-primary\">\n TTL: {{ formatTTL(index.expireAfterSeconds) }}\n </div>\n </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=\"shouldShowAnalyzeSchemaModal\" containerClass=\"md:!w-[70vw]\">\n <template v-slot:body>\n <analyze-schema-modal\n :current-model=\"currentModel\"\n @close=\"shouldShowAnalyzeSchemaModal = false\"\n ></analyze-schema-modal>\n </template>\n </modal>\n <modal v-if=\"shouldShowCollectionInfoModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCollectionInfoModal = false\">×</div>\n <div class=\"text-xl font-bold mb-2\">Collection Info</div>\n <div v-if=\"!collectionInfo\" class=\"text-gray-600\">Loading collection details...</div>\n <div v-else class=\"space-y-3\">\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Documents</div>\n <div class=\"text-content\">{{ formatNumber(collectionInfo.documentCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Indexes</div>\n <div class=\"text-content\">{{ formatNumber(collectionInfo.indexCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Total Index Size</div>\n <div class=\"text-content\">{{ formatCollectionSize(collectionInfo.totalIndexSize) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Total Storage Size</div>\n <div class=\"text-content\">{{ formatCollectionSize(collectionInfo.size) }}</div>\n </div>\n <div class=\"flex flex-col gap-1\">\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Collation</div>\n <div class=\"text-content\">{{ collectionInfo.hasCollation ? 'Yes' : 'No' }}</div>\n </div>\n <div v-if=\"collectionInfo.hasCollation\" class=\"rounded bg-muted p-3 text-sm text-gray-800 overflow-x-auto\">\n <pre class=\"whitespace-pre-wrap\">{{ JSON.stringify(collectionInfo.collation, null, 2) }}</pre>\n </div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Capped</div>\n <div class=\"text-content\">{{ collectionInfo.capped ? 'Yes' : 'No' }}</div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCreateModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCreateModal = false;\">×</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;\">×</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;\">×</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-page0 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 <modal v-if=\"shouldShowDropCollectionModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDropCollectionModal = false\">×</div>\n <h2 class=\"text-xl font-bold mb-3\">Are you sure?</h2>\n <p class=\"text-content-secondary mb-3\">\n This will permanently drop the <span class=\"font-semibold\">{{ currentModel }}</span> collection and all its documents.\n </p>\n <div v-if=\"requiresDropCollectionNameConfirmation()\" class=\"rounded border border-red-300 bg-red-50 px-3 py-2 mb-4\">\n <p class=\"text-sm font-semibold text-red-700\">Danger zone</p>\n <p class=\"text-sm text-red-700 mb-2\">Type <span class=\"font-mono font-semibold\">{{ currentModel }}</span> to confirm.</p>\n <input\n v-model=\"dropCollectionConfirmName\"\n type=\"text\"\n :placeholder=\"currentModel\"\n class=\"w-full rounded border border-red-300 px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-red-200\"\n />\n </div>\n <div class=\"flex gap-4\">\n <async-button\n @click=\"dropCollection\"\n :disabled=\"requiresDropCollectionNameConfirmation() && dropCollectionConfirmName !== currentModel\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n Drop Collection\n </async-button>\n <button @click=\"shouldShowDropCollectionModal = false\" class=\"rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-page0\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n <model-switcher\n :show=\"showModelSwitcher\"\n :models=\"models\"\n :recently-viewed-models=\"recentlyViewedModels\"\n :model-document-counts=\"modelDocumentCounts\"\n @close=\"showModelSwitcher = false\"\n @select=\"selectSwitcherModel\"\n ></model-switcher>\n</div>\n";
|
|
52658
53233
|
|
|
52659
53234
|
/***/ },
|
|
52660
53235
|
|
|
@@ -52687,7 +53262,7 @@ module.exports = ".active {\n text-decoration: underline;\n}\n\n.navbar .nav-le
|
|
|
52687
53262
|
(module) {
|
|
52688
53263
|
|
|
52689
53264
|
"use strict";
|
|
52690
|
-
module.exports = "<div>\n<div class=\"navbar w-full bg-page border-b border-edge !h-[55px] hidden md:grid grid-cols-[1fr_auto_1fr] items-stretch px-4\">\n <!-- Left: Logo + Brand -->\n <div class=\"flex items-center gap-2 self-center\">\n <router-link class=\"flex items-center gap-2\" :to=\"{ name: defaultRoute }\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-10 text-primary\" viewBox=\"0 0 250 250\" fill=\"currentColor\" aria-label=\"Mongoose Studio Logo\">\n <path d=\"M 241.66,125.01 C 237.81,112.98 227.41,111.95 216.76,110.36 C 215.12,95.83 210.99,83.09 202.29,70.33 C 202.99,68.37 201.92,66.65 200.74,65.11 C 183.14,41.46 157.52,27 124.66,27 C 92.62,27 66.09,40.77 48.28,63.38 C 46.21,65.85 46.26,67.71 47.12,69.36 C 37.81,81.15 33.21,93.78 30.99,109.86 C 20.19,110.58 11.71,115.92 9.04,124.94 C 3.28,145.66 5.62,157.95 10.01,169.16 C 16.14,184.65 29.12,186.31 36.04,185.81 C 42.48,185.68 44.94,184.31 44.72,182.11 C 44.26,177.79 41.01,165.31 41.01,149.12 C 41.01,138.22 43.19,127.74 46.21,117.7 C 47.51,113.33 44.66,112.05 39.16,110.28 C 41.01,96.88 45.88,84.85 52.61,73.79 C 55.17,75.56 58.07,74.88 59.61,72.71 C 75.86,54.31 97.89,45.16 124.61,45.16 C 149.71,45.16 171.91,55.91 189.29,73.47 C 191.95,76.59 193.91,76.67 197.6,74.42 C 204.93,86.26 208.28,98.24 208.98,110.39 C 203.84,111.81 201.28,113.19 202.92,118.61 C 206.51,128.99 207.92,137.93 207.92,148.73 C 207.92,162.11 204.8,174.71 202.84,182.71 C 201.92,186.23 204.31,186.91 210.79,186.99 H 213.91 C 225.87,186.61 235.39,180.64 239.49,169.05 C 244.83,155.26 245.08,138.01 241.66,125.01 Z\"/>\n <path d=\"M 124.71,70.81 C 79.63,70.81 47.49,107.12 47.49,148.72 C 47.49,169.16 56.41,188.19 73.71,200.61 L 73.96,200.77 C 77.43,177.81 84.16,162.21 95.29,145.42 C 92.04,137.09 91.71,130.87 92.62,120.83 C 93.81,111.23 96.08,107.12 102.19,107.68 C 110.04,108.87 117.98,116.18 126.22,124.96 C 139.89,124.05 157.81,131.25 178.33,139.09 C 183.11,140.86 183.72,142.63 182.97,148.33 C 179.45,160.93 170.86,166.63 158.11,174.05 C 143.09,183.07 141.13,199.42 153.88,220.09 L 153.96,221.01 L 154.71,221.17 C 181.31,208.43 201.34,181.01 201.34,148.72 C 201.34,108.15 169.29,70.81 124.71,70.81 Z\"/>\n </svg>\n <span class=\"text-base font-semibold text-content whitespace-nowrap\">Mongoose Studio</span>\n </router-link>\n </div>\n <!-- Center: Nav Links -->\n <nav class=\"flex items-stretch gap-6\">\n <a v-if=\"hasAccess(roles, 'root')\"\n href=\"#/\"\n class=\"inline-flex items-center px-1 border-b-2 text-sm font-medium\"\n :class=\"documentView ? 'text-content border-primary' : 'border-transparent text-content-tertiary hover:text-content'\">Documents</a>\n <span v-else class=\"inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-gray-300 cursor-not-allowed\" aria-disabled=\"true\">\n Documents\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'dashboards')\"\n href=\"#/dashboards\"\n class=\"inline-flex items-center px-1 border-b-2 text-sm font-medium\"\n :class=\"dashboardView ? 'text-content border-primary' : 'border-transparent text-content-tertiary hover:text-content'\">Dashboards</a>\n <span v-else class=\"inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-gray-300 cursor-not-allowed\">\n Dashboards\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'chat') && hasLLMAPIKey\"\n href=\"#/chat\"\n class=\"inline-flex items-center px-1 border-b-2 text-sm font-medium\"\n :class=\"chatView ? 'text-content border-primary' : 'border-transparent text-content-tertiary hover:text-content'\">Chat</a>\n <span v-else-if=\"hasAccess(roles, 'chat') && !hasLLMAPIKey\"\n class=\"inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-gray-300 cursor-not-allowed\"\n :title=\"chatDisabledReason\">\n Chat\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span v-else class=\"inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-gray-300 cursor-not-allowed\">\n Chat\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a\n v-if=\"hasTaskVisualizer\"\n href=\"#/tasks\"\n class=\"inline-flex items-center px-1 border-b-2 text-sm font-medium\"\n :class=\"taskView ? 'text-content border-primary' : 'border-transparent text-content-tertiary hover:text-content'\">\n Tasks\n </a>\n <a\n href=\"https://studio.mongoosejs.io/docs\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-content-tertiary hover:text-content\"\n >\n Docs\n </a>\n </nav>\n <!-- Right: Env Badge + User -->\n <div class=\"flex items-center justify-end gap-3 self-center\">\n <button\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md p-2 text-content-tertiary hover:bg-muted hover:text-content-secondary\"\n title=\"Recent pages\"\n aria-label=\"Recent pages\"\n aria-haspopup=\"dialog\"\n :aria-expanded=\"showRecentPagesModal ? 'true' : 'false'\"\n @click=\"openRecentPagesModal\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-5 w-5\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n </button>\n <div\n v-if=\"!!state.nodeEnv\"\n title=\"NODE_ENV\"\n class=\"inline-flex items-center rounded px-2 py-1 text-xs text-content-tertiary bg-surface border border-edge gap-2\"\n >\n <span\n :class=\"warnEnv ? 'bg-red-400' : 'bg-yellow-400'\"\n class=\"inline-block rounded-full\"\n style=\"width: 0.5em; height: 0.5em;\"\n ></span>\n <span>{{state.nodeEnv}}</span>\n </div>\n <button\n v-if=\"!user || !hasAPIKey\"\n type=\"button\"\n @click=\"toggleDarkMode\"\n :title=\"darkMode ? 'Switch to light mode' : 'Switch to dark mode'\"\n class=\"inline-flex items-center justify-center rounded-md p-2 text-content-tertiary hover:text-content-secondary hover:bg-muted\"\n aria-label=\"Toggle dark mode\">\n <svg v-if=\"darkMode\" xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z\"/>\n </svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z\"/>\n </svg>\n </button>\n <div class=\"flex items-center\" v-if=\"!user && hasAPIKey\">\n <button\n type=\"button\"\n @click=\"loginWithGithub\"\n class=\"rounded bg-primary px-2 py-2 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\">\n Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"flex items-center relative\" v-clickOutside=\"hideFlyout\">\n <button type=\"button\" @click=\"showFlyout = !showFlyout\" class=\"relative flex rounded-full text-sm focus:outline-none focus:ring-2 focus:ring-gray-300\" id=\"user-menu-button\" aria-expanded=\"false\" aria-haspopup=\"true\">\n <span class=\"absolute -inset-1.5\"></span>\n <span class=\"sr-only\">Open user menu</span>\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n </button>\n\n <div\n v-if=\"showFlyout\"\n class=\"absolute right-0 top-[90%] w-48 origin-top-right rounded-md bg-surface py-1 shadow-lg ring-1 ring-black/5 focus:outline-none\"\n role=\"menu\"\n aria-orientation=\"vertical\"\n aria-labelledby=\"user-menu-button\"\n style=\"z-index: 10000\"\n tabindex=\"-1\">\n <button\n type=\"button\"\n @click=\"toggleDarkMode(); showFlyout = false\"\n class=\"w-full cursor-pointer block px-4 py-2 text-sm text-content-secondary hover:bg-primary-subtle text-left flex items-center gap-2\"\n role=\"menuitem\"\n tabindex=\"-1\">\n <svg v-if=\"darkMode\" xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z\"/></svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z\"/></svg>\n {{ darkMode ? 'Light mode' : 'Dark mode' }}\n </button>\n <router-link to=\"/team\" v-if=\"hasAccess(roles, 'team')\" @click=\"showFlyout = false\" class=\"cursor-pointer block px-4 py-2 text-sm text-content-secondary hover:bg-primary-subtle\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Team</router-link>\n <span v-else class=\"block px-4 py-2 text-sm text-gray-300 cursor-not-allowed\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">\n Team\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span @click=\"logout\" class=\"cursor-pointer block px-4 py-2 text-sm text-content-secondary hover:bg-primary-subtle\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Sign out</span>\n </div>\n </div>\n </div>\n</div>\n<!-- Mobile navbar -->\n<div class=\"navbar w-full bg-page flex justify-between border-b border-edge !h-[55px] md:hidden\">\n <div class=\"flex items-center gap-2 pl-4\">\n <router-link class=\"flex items-center gap-2\" :to=\"{ name: defaultRoute }\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-8 text-primary\" viewBox=\"0 0 250 250\" fill=\"currentColor\" aria-label=\"Mongoose Studio Logo\">\n <path d=\"M 241.66,125.01 C 237.81,112.98 227.41,111.95 216.76,110.36 C 215.12,95.83 210.99,83.09 202.29,70.33 C 202.99,68.37 201.92,66.65 200.74,65.11 C 183.14,41.46 157.52,27 124.66,27 C 92.62,27 66.09,40.77 48.28,63.38 C 46.21,65.85 46.26,67.71 47.12,69.36 C 37.81,81.15 33.21,93.78 30.99,109.86 C 20.19,110.58 11.71,115.92 9.04,124.94 C 3.28,145.66 5.62,157.95 10.01,169.16 C 16.14,184.65 29.12,186.31 36.04,185.81 C 42.48,185.68 44.94,184.31 44.72,182.11 C 44.26,177.79 41.01,165.31 41.01,149.12 C 41.01,138.22 43.19,127.74 46.21,117.7 C 47.51,113.33 44.66,112.05 39.16,110.28 C 41.01,96.88 45.88,84.85 52.61,73.79 C 55.17,75.56 58.07,74.88 59.61,72.71 C 75.86,54.31 97.89,45.16 124.61,45.16 C 149.71,45.16 171.91,55.91 189.29,73.47 C 191.95,76.59 193.91,76.67 197.6,74.42 C 204.93,86.26 208.28,98.24 208.98,110.39 C 203.84,111.81 201.28,113.19 202.92,118.61 C 206.51,128.99 207.92,137.93 207.92,148.73 C 207.92,162.11 204.8,174.71 202.84,182.71 C 201.92,186.23 204.31,186.91 210.79,186.99 H 213.91 C 225.87,186.61 235.39,180.64 239.49,169.05 C 244.83,155.26 245.08,138.01 241.66,125.01 Z\"/>\n <path d=\"M 124.71,70.81 C 79.63,70.81 47.49,107.12 47.49,148.72 C 47.49,169.16 56.41,188.19 73.71,200.61 L 73.96,200.77 C 77.43,177.81 84.16,162.21 95.29,145.42 C 92.04,137.09 91.71,130.87 92.62,120.83 C 93.81,111.23 96.08,107.12 102.19,107.68 C 110.04,108.87 117.98,116.18 126.22,124.96 C 139.89,124.05 157.81,131.25 178.33,139.09 C 183.11,140.86 183.72,142.63 182.97,148.33 C 179.45,160.93 170.86,166.63 158.11,174.05 C 143.09,183.07 141.13,199.42 153.88,220.09 L 153.96,221.01 L 154.71,221.17 C 181.31,208.43 201.34,181.01 201.34,148.72 C 201.34,108.15 169.29,70.81 124.71,70.81 Z\"/>\n </svg>\n <span class=\"text-base font-semibold text-content\">Mongoose Studio</span>\n </router-link>\n </div>\n <div class=\"flex items-center gap-1 pr-2\">\n <button\n type=\"button\"\n class=\"rounded-md p-2 text-content-tertiary hover:bg-muted hover:text-content-secondary md:hidden\"\n title=\"Recent pages\"\n aria-label=\"Recent pages\"\n aria-haspopup=\"dialog\"\n :aria-expanded=\"showRecentPagesModal ? 'true' : 'false'\"\n @click=\"openRecentPagesModal\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n </button>\n <!-- Mobile menu toggle, controls the 'mobileMenuOpen' state. -->\n <button type=\"button\" id=\"open-mobile-menu\" class=\"-ml-2 rounded-md p-2 text-gray-400\">\n <span class=\"sr-only\">Open menu</span>\n <svg class=\"h-6 w-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5\" />\n </svg>\n </button>\n </div>\n\n <!-- Mobile menu mask -->\n <div id=\"mobile-menu-mask\" class=\"fixed inset-0 bg-black bg-opacity-40 z-40 hidden\"></div>\n <!-- Mobile menu drawer -->\n <div id=\"mobile-menu\" style=\"z-index: 100000\" class=\"fixed inset-0 bg-page shadow-lg transform translate-x-full transition-transform duration-200 ease-in-out flex flex-col\">\n <div class=\"flex items-center justify-between px-4 !h-[55px] border-b border-edge\">\n <router-link :to=\"{ name: defaultRoute }\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-[32px] text-primary\" viewBox=\"0 0 250 250\" fill=\"currentColor\" aria-label=\"Mongoose Studio Logo\">\n <path d=\"M 241.66,125.01 C 237.81,112.98 227.41,111.95 216.76,110.36 C 215.12,95.83 210.99,83.09 202.29,70.33 C 202.99,68.37 201.92,66.65 200.74,65.11 C 183.14,41.46 157.52,27 124.66,27 C 92.62,27 66.09,40.77 48.28,63.38 C 46.21,65.85 46.26,67.71 47.12,69.36 C 37.81,81.15 33.21,93.78 30.99,109.86 C 20.19,110.58 11.71,115.92 9.04,124.94 C 3.28,145.66 5.62,157.95 10.01,169.16 C 16.14,184.65 29.12,186.31 36.04,185.81 C 42.48,185.68 44.94,184.31 44.72,182.11 C 44.26,177.79 41.01,165.31 41.01,149.12 C 41.01,138.22 43.19,127.74 46.21,117.7 C 47.51,113.33 44.66,112.05 39.16,110.28 C 41.01,96.88 45.88,84.85 52.61,73.79 C 55.17,75.56 58.07,74.88 59.61,72.71 C 75.86,54.31 97.89,45.16 124.61,45.16 C 149.71,45.16 171.91,55.91 189.29,73.47 C 191.95,76.59 193.91,76.67 197.6,74.42 C 204.93,86.26 208.28,98.24 208.98,110.39 C 203.84,111.81 201.28,113.19 202.92,118.61 C 206.51,128.99 207.92,137.93 207.92,148.73 C 207.92,162.11 204.8,174.71 202.84,182.71 C 201.92,186.23 204.31,186.91 210.79,186.99 H 213.91 C 225.87,186.61 235.39,180.64 239.49,169.05 C 244.83,155.26 245.08,138.01 241.66,125.01 Z\"/>\n <path d=\"M 124.71,70.81 C 79.63,70.81 47.49,107.12 47.49,148.72 C 47.49,169.16 56.41,188.19 73.71,200.61 L 73.96,200.77 C 77.43,177.81 84.16,162.21 95.29,145.42 C 92.04,137.09 91.71,130.87 92.62,120.83 C 93.81,111.23 96.08,107.12 102.19,107.68 C 110.04,108.87 117.98,116.18 126.22,124.96 C 139.89,124.05 157.81,131.25 178.33,139.09 C 183.11,140.86 183.72,142.63 182.97,148.33 C 179.45,160.93 170.86,166.63 158.11,174.05 C 143.09,183.07 141.13,199.42 153.88,220.09 L 153.96,221.01 L 154.71,221.17 C 181.31,208.43 201.34,181.01 201.34,148.72 C 201.34,108.15 169.29,70.81 124.71,70.81 Z\"/>\n </svg>\n </router-link>\n <button type=\"button\" id=\"close-mobile-menu\" class=\"text-gray-400 p-2 rounded-md\">\n <span class=\"sr-only\">Close menu</span>\n <svg class=\"h-6 w-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <nav class=\"flex-1 px-4 py-4 space-y-2\">\n <div class=\"flex items-center gap-2 px-3 py-2\" v-if=\"!user || !hasAPIKey\">\n <button\n type=\"button\"\n @click=\"toggleDarkMode\"\n class=\"inline-flex items-center gap-2 rounded-md px-3 py-2 text-base font-medium text-content-secondary hover:bg-muted\"\n aria-label=\"Toggle dark mode\">\n <svg v-if=\"darkMode\" xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z\"/></svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z\"/></svg>\n <span>{{ darkMode ? 'Light mode' : 'Dark mode' }}</span>\n </button>\n </div>\n <a v-if=\"hasAccess(roles, 'root')\"\n href=\"#/\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"documentView ? 'text-content bg-primary-subtle' : 'text-content-secondary hover:bg-muted'\">Documents</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Documents\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'dashboards')\"\n href=\"#/dashboards\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"dashboardView ? 'text-content bg-primary-subtle' : 'text-content-secondary hover:bg-muted'\">Dashboards</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Dashboards\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'chat') && hasLLMAPIKey\"\n href=\"#/chat\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"chatView ? 'text-content bg-primary-subtle' : 'text-content-secondary hover:bg-muted'\">Chat</a>\n <span v-else-if=\"hasAccess(roles, 'chat') && !hasLLMAPIKey\"\n class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\"\n :title=\"chatDisabledReason\">\n Chat\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Chat\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasTaskVisualizer\"\n href=\"#/tasks\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"taskView ? 'text-content bg-primary-subtle' : 'text-content-secondary hover:bg-muted'\">Tasks</a>\n <div v-if=\"!user && hasAPIKey\" class=\"mt-4\">\n <button\n type=\"button\"\n @click=\"loginWithGithub\"\n class=\"w-full rounded bg-muted text-content px-3 py-2 text-base font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\">\n Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"mt-4\">\n <div class=\"flex items-center gap-3 px-3 py-2 bg-page rounded-md\">\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n <span class=\"text-content font-medium\">{{ user.name }}</span>\n </div>\n <div class=\"mt-2 space-y-1\">\n <button\n type=\"button\"\n @click=\"toggleDarkMode\"\n class=\"w-full inline-flex items-center gap-2 px-3 py-2 rounded-md text-base text-content-secondary hover:bg-primary-subtle text-left\">\n <svg v-if=\"darkMode\" xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z\"/></svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z\"/></svg>\n {{ darkMode ? 'Light mode' : 'Dark mode' }}\n </button>\n <router-link to=\"/team\" v-if=\"hasAccess(roles, 'team')\" class=\"block px-3 py-2 rounded-md text-base text-content-secondary hover:bg-primary-subtle\">Team</router-link>\n <span v-else class=\"block px-3 py-2 rounded-md text-base text-gray-300 cursor-not-allowed\">\n Team\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span @click=\"logout\" class=\"block px-3 py-2 rounded-md text-base text-content-secondary hover:bg-primary-subtle cursor-pointer\">Sign out</span>\n </div>\n </div>\n </nav>\n </div>\n</div>\n\n<modal\n v-if=\"showRecentPagesModal\"\n flush-container\n containerClass=\"!max-w-md !w-[min(94vw,28rem)] !overflow-hidden !rounded-lg !border !border-edge !bg-surface !shadow-xl dark:!bg-shark-900 text-content\"\n bodyClass=\"modal-recent-pages-body !p-0\"\n>\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"closeRecentPagesModal\" role=\"button\" aria-label=\"Close recent pages\">×</div>\n <div\n class=\"shrink-0 flex items-center gap-3 border-b border-edge bg-surface px-4 py-4 pr-12 dark:bg-shark-900\"\n >\n <h2 class=\"m-0 text-lg font-semibold text-content\">Recent pages</h2>\n <button\n type=\"button\"\n class=\"rounded px-2 py-1 text-sm font-medium text-content-secondary hover:bg-muted disabled:opacity-50\"\n @click=\"clearRecentPagesHistory\"\n :disabled=\"recentPagesList.length === 0\"\n >\n Clear\n </button>\n </div>\n <div class=\"modal-recent-pages-scroll\">\n <div\n v-if=\"recentPagesList.length === 0\"\n class=\"m-4 rounded border border-dashed border-edge px-3 py-6 text-center text-sm text-content-tertiary\"\n >\n No recent pages yet.\n </div>\n <button\n v-for=\"entry in recentPagesList\"\n :key=\"entry.path + '-' + entry.visitedAt\"\n type=\"button\"\n class=\"w-full px-4 py-2.5 text-left hover:bg-muted\"\n :class=\"entry.path === $route.fullPath ? 'bg-primary-subtle' : ''\"\n @click=\"goToRecentPage(entry)\"\n >\n <div class=\"truncate text-sm font-medium text-content\">{{ entry.label }}</div>\n <div class=\"truncate font-mono text-xs text-content-tertiary\">{{ entry.path }}</div>\n </button>\n </div>\n </template>\n</modal>\n</div>\n";
|
|
53265
|
+
module.exports = "<div>\n<div class=\"navbar w-full bg-page border-b border-edge !h-[55px] hidden md:grid grid-cols-[1fr_auto_1fr] items-stretch px-4\">\n <!-- Left: Logo + Brand -->\n <div class=\"flex items-center gap-2 self-center\">\n <router-link class=\"flex items-center gap-2\" :to=\"{ name: defaultRoute }\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-10 text-primary\" viewBox=\"0 0 250 250\" fill=\"currentColor\" aria-label=\"Mongoose Studio Logo\">\n <path d=\"M 241.66,125.01 C 237.81,112.98 227.41,111.95 216.76,110.36 C 215.12,95.83 210.99,83.09 202.29,70.33 C 202.99,68.37 201.92,66.65 200.74,65.11 C 183.14,41.46 157.52,27 124.66,27 C 92.62,27 66.09,40.77 48.28,63.38 C 46.21,65.85 46.26,67.71 47.12,69.36 C 37.81,81.15 33.21,93.78 30.99,109.86 C 20.19,110.58 11.71,115.92 9.04,124.94 C 3.28,145.66 5.62,157.95 10.01,169.16 C 16.14,184.65 29.12,186.31 36.04,185.81 C 42.48,185.68 44.94,184.31 44.72,182.11 C 44.26,177.79 41.01,165.31 41.01,149.12 C 41.01,138.22 43.19,127.74 46.21,117.7 C 47.51,113.33 44.66,112.05 39.16,110.28 C 41.01,96.88 45.88,84.85 52.61,73.79 C 55.17,75.56 58.07,74.88 59.61,72.71 C 75.86,54.31 97.89,45.16 124.61,45.16 C 149.71,45.16 171.91,55.91 189.29,73.47 C 191.95,76.59 193.91,76.67 197.6,74.42 C 204.93,86.26 208.28,98.24 208.98,110.39 C 203.84,111.81 201.28,113.19 202.92,118.61 C 206.51,128.99 207.92,137.93 207.92,148.73 C 207.92,162.11 204.8,174.71 202.84,182.71 C 201.92,186.23 204.31,186.91 210.79,186.99 H 213.91 C 225.87,186.61 235.39,180.64 239.49,169.05 C 244.83,155.26 245.08,138.01 241.66,125.01 Z\"/>\n <path d=\"M 124.71,70.81 C 79.63,70.81 47.49,107.12 47.49,148.72 C 47.49,169.16 56.41,188.19 73.71,200.61 L 73.96,200.77 C 77.43,177.81 84.16,162.21 95.29,145.42 C 92.04,137.09 91.71,130.87 92.62,120.83 C 93.81,111.23 96.08,107.12 102.19,107.68 C 110.04,108.87 117.98,116.18 126.22,124.96 C 139.89,124.05 157.81,131.25 178.33,139.09 C 183.11,140.86 183.72,142.63 182.97,148.33 C 179.45,160.93 170.86,166.63 158.11,174.05 C 143.09,183.07 141.13,199.42 153.88,220.09 L 153.96,221.01 L 154.71,221.17 C 181.31,208.43 201.34,181.01 201.34,148.72 C 201.34,108.15 169.29,70.81 124.71,70.81 Z\"/>\n </svg>\n <span class=\"text-base font-semibold text-content whitespace-nowrap\">Mongoose Studio</span>\n </router-link>\n </div>\n <!-- Center: Nav Links -->\n <nav class=\"flex items-stretch gap-6\">\n <a v-if=\"hasAccess(roles, 'root')\"\n href=\"#/\"\n class=\"inline-flex items-center px-1 border-b-2 text-sm font-medium\"\n :class=\"documentView ? 'text-content border-primary' : 'border-transparent text-content-tertiary hover:text-content'\">Documents</a>\n <span v-else class=\"inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-gray-300 cursor-not-allowed\" aria-disabled=\"true\">\n Documents\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'dashboards')\"\n href=\"#/dashboards\"\n class=\"inline-flex items-center px-1 border-b-2 text-sm font-medium\"\n :class=\"dashboardView ? 'text-content border-primary' : 'border-transparent text-content-tertiary hover:text-content'\">Dashboards</a>\n <span v-else class=\"inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-gray-300 cursor-not-allowed\">\n Dashboards\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'chat') && supportsAI\"\n href=\"#/chat\"\n class=\"inline-flex items-center px-1 border-b-2 text-sm font-medium\"\n :class=\"chatView ? 'text-content border-primary' : 'border-transparent text-content-tertiary hover:text-content'\">Chat</a>\n <span v-else-if=\"hasAccess(roles, 'chat') && !supportsAI\"\n class=\"inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-gray-300 cursor-not-allowed\"\n :title=\"chatDisabledReason\">\n Chat\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span v-else class=\"inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-gray-300 cursor-not-allowed\">\n Chat\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a\n v-if=\"hasTaskVisualizer\"\n href=\"#/tasks\"\n class=\"inline-flex items-center px-1 border-b-2 text-sm font-medium\"\n :class=\"taskView ? 'text-content border-primary' : 'border-transparent text-content-tertiary hover:text-content'\">\n Tasks\n </a>\n <a\n href=\"https://studio.mongoosejs.io/docs\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-content-tertiary hover:text-content\"\n >\n Docs\n </a>\n </nav>\n <!-- Right: Env Badge + User -->\n <div class=\"flex items-center justify-end gap-3 self-center\">\n <button\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md p-2 text-content-tertiary hover:bg-muted hover:text-content-secondary\"\n title=\"Recent pages\"\n aria-label=\"Recent pages\"\n aria-haspopup=\"dialog\"\n :aria-expanded=\"showRecentPagesModal ? 'true' : 'false'\"\n @click=\"openRecentPagesModal\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-5 w-5\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n </button>\n <div\n v-if=\"!!state.nodeEnv\"\n title=\"NODE_ENV\"\n class=\"inline-flex items-center rounded px-2 py-1 text-xs text-content-tertiary bg-surface border border-edge gap-2\"\n >\n <span\n :class=\"warnEnv ? 'bg-red-400' : 'bg-yellow-400'\"\n class=\"inline-block rounded-full\"\n style=\"width: 0.5em; height: 0.5em;\"\n ></span>\n <span>{{state.nodeEnv}}</span>\n </div>\n <button\n v-if=\"!user || !hasAPIKey\"\n type=\"button\"\n @click=\"toggleDarkMode\"\n :title=\"darkMode ? 'Switch to light mode' : 'Switch to dark mode'\"\n class=\"inline-flex items-center justify-center rounded-md p-2 text-content-tertiary hover:text-content-secondary hover:bg-muted\"\n aria-label=\"Toggle dark mode\">\n <svg v-if=\"darkMode\" xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z\"/>\n </svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z\"/>\n </svg>\n </button>\n <div class=\"flex items-center\" v-if=\"!user && hasAPIKey\">\n <button\n type=\"button\"\n @click=\"loginWithGithub\"\n class=\"rounded bg-primary px-2 py-2 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\">\n Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"flex items-center relative\" v-clickOutside=\"hideFlyout\">\n <button type=\"button\" @click=\"showFlyout = !showFlyout\" class=\"relative flex rounded-full text-sm focus:outline-none focus:ring-2 focus:ring-gray-300\" id=\"user-menu-button\" aria-expanded=\"false\" aria-haspopup=\"true\">\n <span class=\"absolute -inset-1.5\"></span>\n <span class=\"sr-only\">Open user menu</span>\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n </button>\n\n <div\n v-if=\"showFlyout\"\n class=\"absolute right-0 top-[90%] w-48 origin-top-right rounded-md bg-surface py-1 shadow-lg ring-1 ring-black/5 focus:outline-none\"\n role=\"menu\"\n aria-orientation=\"vertical\"\n aria-labelledby=\"user-menu-button\"\n style=\"z-index: 10000\"\n tabindex=\"-1\">\n <button\n type=\"button\"\n @click=\"toggleDarkMode(); showFlyout = false\"\n class=\"w-full cursor-pointer block px-4 py-2 text-sm text-content-secondary hover:bg-primary-subtle text-left flex items-center gap-2\"\n role=\"menuitem\"\n tabindex=\"-1\">\n <svg v-if=\"darkMode\" xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z\"/></svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z\"/></svg>\n {{ darkMode ? 'Light mode' : 'Dark mode' }}\n </button>\n <router-link to=\"/team\" v-if=\"hasAccess(roles, 'team')\" @click=\"showFlyout = false\" class=\"cursor-pointer block px-4 py-2 text-sm text-content-secondary hover:bg-primary-subtle\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Team</router-link>\n <span v-else class=\"block px-4 py-2 text-sm text-gray-300 cursor-not-allowed\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">\n Team\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span @click=\"logout\" class=\"cursor-pointer block px-4 py-2 text-sm text-content-secondary hover:bg-primary-subtle\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Sign out</span>\n </div>\n </div>\n </div>\n</div>\n<!-- Mobile navbar -->\n<div class=\"navbar w-full bg-page flex justify-between border-b border-edge !h-[55px] md:hidden\">\n <div class=\"flex items-center gap-2 pl-4\">\n <router-link class=\"flex items-center gap-2\" :to=\"{ name: defaultRoute }\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-8 text-primary\" viewBox=\"0 0 250 250\" fill=\"currentColor\" aria-label=\"Mongoose Studio Logo\">\n <path d=\"M 241.66,125.01 C 237.81,112.98 227.41,111.95 216.76,110.36 C 215.12,95.83 210.99,83.09 202.29,70.33 C 202.99,68.37 201.92,66.65 200.74,65.11 C 183.14,41.46 157.52,27 124.66,27 C 92.62,27 66.09,40.77 48.28,63.38 C 46.21,65.85 46.26,67.71 47.12,69.36 C 37.81,81.15 33.21,93.78 30.99,109.86 C 20.19,110.58 11.71,115.92 9.04,124.94 C 3.28,145.66 5.62,157.95 10.01,169.16 C 16.14,184.65 29.12,186.31 36.04,185.81 C 42.48,185.68 44.94,184.31 44.72,182.11 C 44.26,177.79 41.01,165.31 41.01,149.12 C 41.01,138.22 43.19,127.74 46.21,117.7 C 47.51,113.33 44.66,112.05 39.16,110.28 C 41.01,96.88 45.88,84.85 52.61,73.79 C 55.17,75.56 58.07,74.88 59.61,72.71 C 75.86,54.31 97.89,45.16 124.61,45.16 C 149.71,45.16 171.91,55.91 189.29,73.47 C 191.95,76.59 193.91,76.67 197.6,74.42 C 204.93,86.26 208.28,98.24 208.98,110.39 C 203.84,111.81 201.28,113.19 202.92,118.61 C 206.51,128.99 207.92,137.93 207.92,148.73 C 207.92,162.11 204.8,174.71 202.84,182.71 C 201.92,186.23 204.31,186.91 210.79,186.99 H 213.91 C 225.87,186.61 235.39,180.64 239.49,169.05 C 244.83,155.26 245.08,138.01 241.66,125.01 Z\"/>\n <path d=\"M 124.71,70.81 C 79.63,70.81 47.49,107.12 47.49,148.72 C 47.49,169.16 56.41,188.19 73.71,200.61 L 73.96,200.77 C 77.43,177.81 84.16,162.21 95.29,145.42 C 92.04,137.09 91.71,130.87 92.62,120.83 C 93.81,111.23 96.08,107.12 102.19,107.68 C 110.04,108.87 117.98,116.18 126.22,124.96 C 139.89,124.05 157.81,131.25 178.33,139.09 C 183.11,140.86 183.72,142.63 182.97,148.33 C 179.45,160.93 170.86,166.63 158.11,174.05 C 143.09,183.07 141.13,199.42 153.88,220.09 L 153.96,221.01 L 154.71,221.17 C 181.31,208.43 201.34,181.01 201.34,148.72 C 201.34,108.15 169.29,70.81 124.71,70.81 Z\"/>\n </svg>\n <span class=\"text-base font-semibold text-content\">Mongoose Studio</span>\n </router-link>\n </div>\n <div class=\"flex items-center gap-1 pr-2\">\n <button\n type=\"button\"\n class=\"rounded-md p-2 text-content-tertiary hover:bg-muted hover:text-content-secondary md:hidden\"\n title=\"Recent pages\"\n aria-label=\"Recent pages\"\n aria-haspopup=\"dialog\"\n :aria-expanded=\"showRecentPagesModal ? 'true' : 'false'\"\n @click=\"openRecentPagesModal\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n </button>\n <!-- Mobile menu toggle, controls the 'mobileMenuOpen' state. -->\n <button type=\"button\" id=\"open-mobile-menu\" class=\"-ml-2 rounded-md p-2 text-gray-400\">\n <span class=\"sr-only\">Open menu</span>\n <svg class=\"h-6 w-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5\" />\n </svg>\n </button>\n </div>\n\n <!-- Mobile menu mask -->\n <div id=\"mobile-menu-mask\" class=\"fixed inset-0 bg-black bg-opacity-40 z-40 hidden\"></div>\n <!-- Mobile menu drawer -->\n <div id=\"mobile-menu\" style=\"z-index: 100000\" class=\"fixed inset-0 bg-page shadow-lg transform translate-x-full transition-transform duration-200 ease-in-out flex flex-col\">\n <div class=\"flex items-center justify-between px-4 !h-[55px] border-b border-edge\">\n <router-link :to=\"{ name: defaultRoute }\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-[32px] text-primary\" viewBox=\"0 0 250 250\" fill=\"currentColor\" aria-label=\"Mongoose Studio Logo\">\n <path d=\"M 241.66,125.01 C 237.81,112.98 227.41,111.95 216.76,110.36 C 215.12,95.83 210.99,83.09 202.29,70.33 C 202.99,68.37 201.92,66.65 200.74,65.11 C 183.14,41.46 157.52,27 124.66,27 C 92.62,27 66.09,40.77 48.28,63.38 C 46.21,65.85 46.26,67.71 47.12,69.36 C 37.81,81.15 33.21,93.78 30.99,109.86 C 20.19,110.58 11.71,115.92 9.04,124.94 C 3.28,145.66 5.62,157.95 10.01,169.16 C 16.14,184.65 29.12,186.31 36.04,185.81 C 42.48,185.68 44.94,184.31 44.72,182.11 C 44.26,177.79 41.01,165.31 41.01,149.12 C 41.01,138.22 43.19,127.74 46.21,117.7 C 47.51,113.33 44.66,112.05 39.16,110.28 C 41.01,96.88 45.88,84.85 52.61,73.79 C 55.17,75.56 58.07,74.88 59.61,72.71 C 75.86,54.31 97.89,45.16 124.61,45.16 C 149.71,45.16 171.91,55.91 189.29,73.47 C 191.95,76.59 193.91,76.67 197.6,74.42 C 204.93,86.26 208.28,98.24 208.98,110.39 C 203.84,111.81 201.28,113.19 202.92,118.61 C 206.51,128.99 207.92,137.93 207.92,148.73 C 207.92,162.11 204.8,174.71 202.84,182.71 C 201.92,186.23 204.31,186.91 210.79,186.99 H 213.91 C 225.87,186.61 235.39,180.64 239.49,169.05 C 244.83,155.26 245.08,138.01 241.66,125.01 Z\"/>\n <path d=\"M 124.71,70.81 C 79.63,70.81 47.49,107.12 47.49,148.72 C 47.49,169.16 56.41,188.19 73.71,200.61 L 73.96,200.77 C 77.43,177.81 84.16,162.21 95.29,145.42 C 92.04,137.09 91.71,130.87 92.62,120.83 C 93.81,111.23 96.08,107.12 102.19,107.68 C 110.04,108.87 117.98,116.18 126.22,124.96 C 139.89,124.05 157.81,131.25 178.33,139.09 C 183.11,140.86 183.72,142.63 182.97,148.33 C 179.45,160.93 170.86,166.63 158.11,174.05 C 143.09,183.07 141.13,199.42 153.88,220.09 L 153.96,221.01 L 154.71,221.17 C 181.31,208.43 201.34,181.01 201.34,148.72 C 201.34,108.15 169.29,70.81 124.71,70.81 Z\"/>\n </svg>\n </router-link>\n <button type=\"button\" id=\"close-mobile-menu\" class=\"text-gray-400 p-2 rounded-md\">\n <span class=\"sr-only\">Close menu</span>\n <svg class=\"h-6 w-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <nav class=\"flex-1 px-4 py-4 space-y-2\">\n <div class=\"flex items-center gap-2 px-3 py-2\" v-if=\"!user || !hasAPIKey\">\n <button\n type=\"button\"\n @click=\"toggleDarkMode\"\n class=\"inline-flex items-center gap-2 rounded-md px-3 py-2 text-base font-medium text-content-secondary hover:bg-muted\"\n aria-label=\"Toggle dark mode\">\n <svg v-if=\"darkMode\" xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z\"/></svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z\"/></svg>\n <span>{{ darkMode ? 'Light mode' : 'Dark mode' }}</span>\n </button>\n </div>\n <a v-if=\"hasAccess(roles, 'root')\"\n href=\"#/\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"documentView ? 'text-content bg-primary-subtle' : 'text-content-secondary hover:bg-muted'\">Documents</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Documents\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'dashboards')\"\n href=\"#/dashboards\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"dashboardView ? 'text-content bg-primary-subtle' : 'text-content-secondary hover:bg-muted'\">Dashboards</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Dashboards\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'chat') && supportsAI\"\n href=\"#/chat\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"chatView ? 'text-content bg-primary-subtle' : 'text-content-secondary hover:bg-muted'\">Chat</a>\n <span v-else-if=\"hasAccess(roles, 'chat') && !supportsAI\"\n class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\"\n :title=\"chatDisabledReason\">\n Chat\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Chat\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasTaskVisualizer\"\n href=\"#/tasks\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"taskView ? 'text-content bg-primary-subtle' : 'text-content-secondary hover:bg-muted'\">Tasks</a>\n <div v-if=\"!user && hasAPIKey\" class=\"mt-4\">\n <button\n type=\"button\"\n @click=\"loginWithGithub\"\n class=\"w-full rounded bg-muted text-content px-3 py-2 text-base font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\">\n Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"mt-4\">\n <div class=\"flex items-center gap-3 px-3 py-2 bg-page rounded-md\">\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n <span class=\"text-content font-medium\">{{ user.name }}</span>\n </div>\n <div class=\"mt-2 space-y-1\">\n <button\n type=\"button\"\n @click=\"toggleDarkMode\"\n class=\"w-full inline-flex items-center gap-2 px-3 py-2 rounded-md text-base text-content-secondary hover:bg-primary-subtle text-left\">\n <svg v-if=\"darkMode\" xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z\"/></svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z\"/></svg>\n {{ darkMode ? 'Light mode' : 'Dark mode' }}\n </button>\n <router-link to=\"/team\" v-if=\"hasAccess(roles, 'team')\" class=\"block px-3 py-2 rounded-md text-base text-content-secondary hover:bg-primary-subtle\">Team</router-link>\n <span v-else class=\"block px-3 py-2 rounded-md text-base text-gray-300 cursor-not-allowed\">\n Team\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span @click=\"logout\" class=\"block px-3 py-2 rounded-md text-base text-content-secondary hover:bg-primary-subtle cursor-pointer\">Sign out</span>\n </div>\n </div>\n </nav>\n </div>\n</div>\n\n<modal\n v-if=\"showRecentPagesModal\"\n flush-container\n containerClass=\"!max-w-md !w-[min(94vw,28rem)] !overflow-hidden !rounded-lg !border !border-edge !bg-surface !shadow-xl dark:!bg-shark-900 text-content\"\n bodyClass=\"modal-recent-pages-body !p-0\"\n>\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"closeRecentPagesModal\" role=\"button\" aria-label=\"Close recent pages\">×</div>\n <div\n class=\"shrink-0 flex items-center gap-3 border-b border-edge bg-surface px-4 py-4 pr-12 dark:bg-shark-900\"\n >\n <h2 class=\"m-0 text-lg font-semibold text-content\">Recent pages</h2>\n <button\n type=\"button\"\n class=\"rounded px-2 py-1 text-sm font-medium text-content-secondary hover:bg-muted disabled:opacity-50\"\n @click=\"clearRecentPagesHistory\"\n :disabled=\"recentPagesList.length === 0\"\n >\n Clear\n </button>\n </div>\n <div class=\"modal-recent-pages-scroll\">\n <div\n v-if=\"recentPagesList.length === 0\"\n class=\"m-4 rounded border border-dashed border-edge px-3 py-6 text-center text-sm text-content-tertiary\"\n >\n No recent pages yet.\n </div>\n <button\n v-for=\"entry in recentPagesList\"\n :key=\"entry.path + '-' + entry.visitedAt\"\n type=\"button\"\n class=\"w-full px-4 py-2.5 text-left hover:bg-muted\"\n :class=\"entry.path === $route.fullPath ? 'bg-primary-subtle' : ''\"\n @click=\"goToRecentPage(entry)\"\n >\n <div class=\"truncate text-sm font-medium text-content\">{{ entry.label }}</div>\n <div class=\"truncate font-mono text-xs text-content-tertiary\">{{ entry.path }}</div>\n </button>\n </div>\n </template>\n</modal>\n</div>\n";
|
|
52691
53266
|
|
|
52692
53267
|
/***/ },
|
|
52693
53268
|
|
|
@@ -64161,7 +64736,7 @@ var src_default = VueToastificationPlugin;
|
|
|
64161
64736
|
(module) {
|
|
64162
64737
|
|
|
64163
64738
|
"use strict";
|
|
64164
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.5.
|
|
64739
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.5.5","description":"AI-Powered Data Workspace for MongoDB: Turn your MongoDB data into dashboards, maps, and interactive workflows - powered by your Mongoose models and AI.","homepage":"https://mongoosestudio.app/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"@ai-sdk/anthropic":"2.x","@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","ace-builds":"^1.43.6","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","mpath":"^0.9.0","node-inspect-extracted":"3.x","regexp.escape":"^2.0.1","tailwindcss":"3.4.0","time-commando":"1.0.1","typescript":"^6.0.2","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x","xss":"^1.0.15"},"peerDependencies":{"mongoose":"7.x || 8.x || ^9.0.0"},"optionalPeerDependencies":{"@mongoosejs/task":"0.5.x || 0.6.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","mongodb-memory-server":"^11.0.1","mongoose":"9.x","sinon":"^21.0.1"},"scripts":{"lint":"eslint .","seed":"node seed/index.js","start":"node ./local.js","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js","test:frontend":"mocha test/frontend/*.test.js","eval:agent":"node eval/agent/run.js"}}');
|
|
64165
64740
|
|
|
64166
64741
|
/***/ }
|
|
64167
64742
|
|