@mongoosejs/studio 0.5.4 → 0.5.6
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/Dashboard/createDashboard.js +12 -2
- package/backend/actions/Dashboard/getDashboard.js +6 -2
- package/backend/actions/Dashboard/getDashboards.js +3 -1
- package/backend/actions/Dashboard/updateDashboard.js +19 -6
- 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/db/dashboardSchema.js +31 -0
- package/backend/helpers/validateDocumentWithTimeout.js +39 -0
- package/backend/netlify.js +1 -0
- package/backend/next.js +1 -0
- package/express.js +1 -0
- package/frontend/public/app.js +859 -102
- package/frontend/public/images/pin-off.svg +1 -0
- package/frontend/public/images/pin.svg +1 -0
- package/frontend/public/tw.css +249 -28
- package/frontend/src/api.js +12 -0
- package/frontend/src/array-utils.js +60 -2
- package/frontend/src/dashboards/dashboards.html +52 -4
- package/frontend/src/dashboards/dashboards.js +96 -1
- 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/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/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
|
|
|
@@ -3196,11 +3266,73 @@ module.exports = app => app.component('dashboards', {
|
|
|
3196
3266
|
data: () => ({
|
|
3197
3267
|
status: 'loading',
|
|
3198
3268
|
dashboards: [],
|
|
3269
|
+
searchText: '',
|
|
3199
3270
|
showCreateDashboardModal: false,
|
|
3200
3271
|
showDeleteDashboardModal: null,
|
|
3201
3272
|
openMenuId: null
|
|
3202
3273
|
}),
|
|
3274
|
+
computed: {
|
|
3275
|
+
filteredDashboards() {
|
|
3276
|
+
const searchWords = getSearchWords(this.searchText);
|
|
3277
|
+
let dashboards = this.dashboards;
|
|
3278
|
+
if (searchWords.length === 0) {
|
|
3279
|
+
return sortPinnedFirst(dashboards);
|
|
3280
|
+
}
|
|
3281
|
+
const searchPatterns = searchWords.map(word => new RegExp(escapeRegExp(word), 'i'));
|
|
3282
|
+
|
|
3283
|
+
dashboards = dashboards.filter(dashboard => {
|
|
3284
|
+
const searchableText = `${dashboard.title || ''} ${dashboard.description || ''}`;
|
|
3285
|
+
return searchPatterns.every(pattern => pattern.test(searchableText));
|
|
3286
|
+
});
|
|
3287
|
+
|
|
3288
|
+
return sortPinnedFirst(dashboards);
|
|
3289
|
+
}
|
|
3290
|
+
},
|
|
3203
3291
|
methods: {
|
|
3292
|
+
highlightTitle(value) {
|
|
3293
|
+
return this.highlightSearchText(value, 'underline decoration-primary underline-offset-2');
|
|
3294
|
+
},
|
|
3295
|
+
highlightDescription(value) {
|
|
3296
|
+
return this.highlightSearchText(value, 'font-semibold text-content-secondary');
|
|
3297
|
+
},
|
|
3298
|
+
highlightSearchText(value, className) {
|
|
3299
|
+
const text = value || '';
|
|
3300
|
+
const searchWords = getSearchWords(this.searchText);
|
|
3301
|
+
if (searchWords.length === 0) {
|
|
3302
|
+
return escapeHtml(text);
|
|
3303
|
+
}
|
|
3304
|
+
const searchPattern = new RegExp(searchWords.map(escapeRegExp).join('|'), 'ig');
|
|
3305
|
+
|
|
3306
|
+
let html = '';
|
|
3307
|
+
let lastIndex = 0;
|
|
3308
|
+
let match = searchPattern.exec(text);
|
|
3309
|
+
while (match != null) {
|
|
3310
|
+
html += escapeHtml(text.slice(lastIndex, match.index));
|
|
3311
|
+
html += `<span class="${className}">${escapeHtml(match[0])}</span>`;
|
|
3312
|
+
lastIndex = match.index + match[0].length;
|
|
3313
|
+
match = searchPattern.exec(text);
|
|
3314
|
+
}
|
|
3315
|
+
html += escapeHtml(text.slice(lastIndex));
|
|
3316
|
+
return html;
|
|
3317
|
+
},
|
|
3318
|
+
formatDate(value) {
|
|
3319
|
+
if (!value) {
|
|
3320
|
+
return '-';
|
|
3321
|
+
}
|
|
3322
|
+
const date = new Date(value);
|
|
3323
|
+
if (Number.isNaN(date.getTime())) {
|
|
3324
|
+
return '-';
|
|
3325
|
+
}
|
|
3326
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
3327
|
+
dateStyle: 'medium',
|
|
3328
|
+
timeStyle: 'short'
|
|
3329
|
+
}).format(date);
|
|
3330
|
+
},
|
|
3331
|
+
formatUser(dashboard) {
|
|
3332
|
+
return dashboard.createdBy?.name ||
|
|
3333
|
+
dashboard.createdBy?.email ||
|
|
3334
|
+
(dashboard.createdById ? dashboard.createdById.toString() : '-');
|
|
3335
|
+
},
|
|
3204
3336
|
toggleMenu(id) {
|
|
3205
3337
|
this.openMenuId = this.openMenuId === id ? null : id;
|
|
3206
3338
|
},
|
|
@@ -3220,8 +3352,19 @@ module.exports = app => app.component('dashboards', {
|
|
|
3220
3352
|
this.$toast.success('Dashboard deleted!');
|
|
3221
3353
|
},
|
|
3222
3354
|
insertNewDashboard(dashboard) {
|
|
3223
|
-
this.dashboards.
|
|
3355
|
+
this.dashboards.unshift(dashboard);
|
|
3224
3356
|
this.showCreateDashboardModal = false;
|
|
3357
|
+
},
|
|
3358
|
+
async togglePin(dashboard) {
|
|
3359
|
+
if (!dashboard) {
|
|
3360
|
+
return;
|
|
3361
|
+
}
|
|
3362
|
+
|
|
3363
|
+
const { doc } = await api.Dashboard.updateDashboard({
|
|
3364
|
+
dashboardId: dashboard._id,
|
|
3365
|
+
isPinned: !dashboard.isPinned
|
|
3366
|
+
});
|
|
3367
|
+
dashboard.isPinned = doc.isPinned;
|
|
3225
3368
|
}
|
|
3226
3369
|
},
|
|
3227
3370
|
directives: {
|
|
@@ -3246,6 +3389,28 @@ module.exports = app => app.component('dashboards', {
|
|
|
3246
3389
|
}
|
|
3247
3390
|
});
|
|
3248
3391
|
|
|
3392
|
+
function escapeHtml(value) {
|
|
3393
|
+
return value.toString().
|
|
3394
|
+
replace(/&/g, '&').
|
|
3395
|
+
replace(/</g, '<').
|
|
3396
|
+
replace(/>/g, '>').
|
|
3397
|
+
replace(/"/g, '"').
|
|
3398
|
+
replace(/'/g, ''');
|
|
3399
|
+
}
|
|
3400
|
+
|
|
3401
|
+
function escapeRegExp(value) {
|
|
3402
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3405
|
+
function getSearchWords(searchText) {
|
|
3406
|
+
return Array.from(new Set(searchText.trim().split(/\s+/).filter(Boolean))).
|
|
3407
|
+
sort((a, b) => b.length - a.length);
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3410
|
+
function sortPinnedFirst(dashboards) {
|
|
3411
|
+
return dashboards.slice().sort((a, b) => Number(Boolean(b.isPinned)) - Number(Boolean(a.isPinned)));
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3249
3414
|
|
|
3250
3415
|
/***/ },
|
|
3251
3416
|
|
|
@@ -3259,16 +3424,83 @@ module.exports = app => app.component('dashboards', {
|
|
|
3259
3424
|
|
|
3260
3425
|
|
|
3261
3426
|
const template = __webpack_require__(/*! ./detail-array.html */ "./frontend/src/detail-array/detail-array.html");
|
|
3427
|
+
const { isArrayOfObjects, unionKeysForArrayOfObjects, arrayItemMatchesSearch } = __webpack_require__(/*! ../array-utils */ "./frontend/src/array-utils.js");
|
|
3262
3428
|
|
|
3263
3429
|
module.exports = app => app.component('detail-array', {
|
|
3264
3430
|
template: template,
|
|
3265
|
-
props:
|
|
3431
|
+
props: {
|
|
3432
|
+
value: {},
|
|
3433
|
+
/** 'list' | 'table' — table applies to arrays of plain objects */
|
|
3434
|
+
viewMode: {
|
|
3435
|
+
type: String,
|
|
3436
|
+
default: 'list'
|
|
3437
|
+
},
|
|
3438
|
+
truncate: {
|
|
3439
|
+
type: Boolean,
|
|
3440
|
+
default: false
|
|
3441
|
+
},
|
|
3442
|
+
initialLimit: {
|
|
3443
|
+
type: Number,
|
|
3444
|
+
default: 2
|
|
3445
|
+
}
|
|
3446
|
+
},
|
|
3266
3447
|
data() {
|
|
3267
3448
|
return {
|
|
3268
|
-
arrayValue: []
|
|
3449
|
+
arrayValue: [],
|
|
3450
|
+
arraySearchQuery: '',
|
|
3451
|
+
isExpanded: false
|
|
3269
3452
|
};
|
|
3270
3453
|
},
|
|
3454
|
+
computed: {
|
|
3455
|
+
effectiveViewMode() {
|
|
3456
|
+
return this.viewMode === 'table' ? 'table' : 'list';
|
|
3457
|
+
},
|
|
3458
|
+
showAsTable() {
|
|
3459
|
+
return this.effectiveViewMode === 'table' && isArrayOfObjects(this.arrayValue);
|
|
3460
|
+
},
|
|
3461
|
+
tableColumnKeys() {
|
|
3462
|
+
return unionKeysForArrayOfObjects(this.arrayValue);
|
|
3463
|
+
},
|
|
3464
|
+
arraySearchNormalized() {
|
|
3465
|
+
return (this.arraySearchQuery || '').trim().toLowerCase();
|
|
3466
|
+
},
|
|
3467
|
+
filteredArrayRows() {
|
|
3468
|
+
if (!Array.isArray(this.arrayValue)) {
|
|
3469
|
+
return [];
|
|
3470
|
+
}
|
|
3471
|
+
return this.arrayValue
|
|
3472
|
+
.map((item, index) => ({ item, index }))
|
|
3473
|
+
.filter(({ item }) => arrayItemMatchesSearch(item, this.arraySearchNormalized));
|
|
3474
|
+
},
|
|
3475
|
+
displayedArrayRows() {
|
|
3476
|
+
if (!this.shouldLimitRows) {
|
|
3477
|
+
return this.filteredArrayRows;
|
|
3478
|
+
}
|
|
3479
|
+
return this.filteredArrayRows.slice(0, this.initialLimit);
|
|
3480
|
+
},
|
|
3481
|
+
shouldLimitRows() {
|
|
3482
|
+
return this.truncate
|
|
3483
|
+
&& !this.isExpanded
|
|
3484
|
+
&& !this.arraySearchNormalized
|
|
3485
|
+
&& this.filteredArrayRows.length > this.initialLimit;
|
|
3486
|
+
},
|
|
3487
|
+
canToggleRows() {
|
|
3488
|
+
return this.truncate && !this.arraySearchNormalized && this.arrayValue.length > this.initialLimit;
|
|
3489
|
+
},
|
|
3490
|
+
expansionToggleLabel() {
|
|
3491
|
+
return this.isExpanded ? 'Show less' : `Show all ${this.arrayValue.length} items`;
|
|
3492
|
+
},
|
|
3493
|
+
remainingArrayCount() {
|
|
3494
|
+
if (!this.shouldLimitRows) {
|
|
3495
|
+
return 0;
|
|
3496
|
+
}
|
|
3497
|
+
return this.filteredArrayRows.length - this.displayedArrayRows.length;
|
|
3498
|
+
}
|
|
3499
|
+
},
|
|
3271
3500
|
methods: {
|
|
3501
|
+
toggleExpansion() {
|
|
3502
|
+
this.isExpanded = !this.isExpanded;
|
|
3503
|
+
},
|
|
3272
3504
|
initializeArray() {
|
|
3273
3505
|
if (this.value == null) {
|
|
3274
3506
|
this.arrayValue = [];
|
|
@@ -3286,6 +3518,9 @@ module.exports = app => app.component('detail-array', {
|
|
|
3286
3518
|
value: {
|
|
3287
3519
|
handler(newValue) {
|
|
3288
3520
|
this.initializeArray();
|
|
3521
|
+
if (this.arrayValue.length <= this.initialLimit) {
|
|
3522
|
+
this.isExpanded = false;
|
|
3523
|
+
}
|
|
3289
3524
|
},
|
|
3290
3525
|
deep: true,
|
|
3291
3526
|
immediate: true
|
|
@@ -3293,6 +3528,7 @@ module.exports = app => app.component('detail-array', {
|
|
|
3293
3528
|
}
|
|
3294
3529
|
});
|
|
3295
3530
|
|
|
3531
|
+
|
|
3296
3532
|
/***/ },
|
|
3297
3533
|
|
|
3298
3534
|
/***/ "./frontend/src/detail-date/detail-date.js"
|
|
@@ -4978,6 +5214,34 @@ module.exports = app => app.component('document-details', {
|
|
|
4978
5214
|
|
|
4979
5215
|
return this.virtualSearchMatchSet.has(virtual.name);
|
|
4980
5216
|
},
|
|
5217
|
+
getSearchMatchParts(text) {
|
|
5218
|
+
const normalizedQuery = typeof this.searchQuery === 'string' ? this.searchQuery.trim() : '';
|
|
5219
|
+
if (!normalizedQuery) {
|
|
5220
|
+
return [{ text, matched: false }];
|
|
5221
|
+
}
|
|
5222
|
+
|
|
5223
|
+
const lowerText = String(text).toLowerCase();
|
|
5224
|
+
const lowerQuery = normalizedQuery.toLowerCase();
|
|
5225
|
+
const parts = [];
|
|
5226
|
+
let position = 0;
|
|
5227
|
+
let matchIndex = lowerText.indexOf(lowerQuery, position);
|
|
5228
|
+
|
|
5229
|
+
while (matchIndex !== -1) {
|
|
5230
|
+
if (matchIndex > position) {
|
|
5231
|
+
parts.push({ text: text.slice(position, matchIndex), matched: false });
|
|
5232
|
+
}
|
|
5233
|
+
const matchEnd = matchIndex + normalizedQuery.length;
|
|
5234
|
+
parts.push({ text: text.slice(matchIndex, matchEnd), matched: true });
|
|
5235
|
+
position = matchEnd;
|
|
5236
|
+
matchIndex = lowerText.indexOf(lowerQuery, position);
|
|
5237
|
+
}
|
|
5238
|
+
|
|
5239
|
+
if (position < text.length) {
|
|
5240
|
+
parts.push({ text: text.slice(position), matched: false });
|
|
5241
|
+
}
|
|
5242
|
+
|
|
5243
|
+
return parts.length > 0 ? parts : [{ text, matched: false }];
|
|
5244
|
+
},
|
|
4981
5245
|
isVirtualFieldCollapsed(fieldName) {
|
|
4982
5246
|
return this.collapsedVirtuals.has(fieldName);
|
|
4983
5247
|
},
|
|
@@ -5146,6 +5410,7 @@ const appendCSS = __webpack_require__(/*! ../../appendCSS */ "./frontend/src/app
|
|
|
5146
5410
|
appendCSS(__webpack_require__(/*! ./document-property.css */ "./frontend/src/document-details/document-property/document-property.css"));
|
|
5147
5411
|
|
|
5148
5412
|
const UNSET = Symbol('unset');
|
|
5413
|
+
const { isArrayOfObjects } = __webpack_require__(/*! ../../array-utils */ "./frontend/src/array-utils.js");
|
|
5149
5414
|
|
|
5150
5415
|
module.exports = app => app.component('document-property', {
|
|
5151
5416
|
template,
|
|
@@ -5157,6 +5422,8 @@ module.exports = app => app.component('document-property', {
|
|
|
5157
5422
|
renderedValue: UNSET,
|
|
5158
5423
|
isCollapsed: false, // Start uncollapsed by default
|
|
5159
5424
|
isValueExpanded: false, // Track if the value is expanded
|
|
5425
|
+
/** 'list' | 'table' for array-of-objects display and editing */
|
|
5426
|
+
arrayDetailViewMode: 'list',
|
|
5160
5427
|
detailViewMode: 'text',
|
|
5161
5428
|
copyButtonLabel: 'Copy',
|
|
5162
5429
|
copyResetTimeoutId: null,
|
|
@@ -5169,7 +5436,7 @@ module.exports = app => app.component('document-property', {
|
|
|
5169
5436
|
this.copyResetTimeoutId = null;
|
|
5170
5437
|
}
|
|
5171
5438
|
},
|
|
5172
|
-
props: ['path', 'document', 'schemaPaths', 'editting', 'changes', 'invalid', 'highlight'],
|
|
5439
|
+
props: ['path', 'document', 'schemaPaths', 'editting', 'changes', 'invalid', 'highlight', 'searchQuery'],
|
|
5173
5440
|
computed: {
|
|
5174
5441
|
isDatePath() {
|
|
5175
5442
|
return this.path?.instance === 'Date';
|
|
@@ -5232,18 +5499,6 @@ module.exports = app => app.component('document-property', {
|
|
|
5232
5499
|
}
|
|
5233
5500
|
return this.valueAsString;
|
|
5234
5501
|
},
|
|
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
5502
|
isGeoJsonGeometry() {
|
|
5248
5503
|
const value = this.getValueForPath(this.path.path);
|
|
5249
5504
|
return value != null
|
|
@@ -5263,6 +5518,34 @@ module.exports = app => app.component('document-property', {
|
|
|
5263
5518
|
isMultiPolygon() {
|
|
5264
5519
|
const value = this.getValueForPath(this.path.path);
|
|
5265
5520
|
return this.isGeoJsonGeometry && value.type === 'MultiPolygon';
|
|
5521
|
+
},
|
|
5522
|
+
arrayPathCurrentValue() {
|
|
5523
|
+
if (!this.path || this.path.instance !== 'Array') {
|
|
5524
|
+
return null;
|
|
5525
|
+
}
|
|
5526
|
+
if (this.editting) {
|
|
5527
|
+
return this.getEditValueForPath(this.path);
|
|
5528
|
+
}
|
|
5529
|
+
return this.getValueForPath(this.path.path);
|
|
5530
|
+
},
|
|
5531
|
+
canUseArrayTableView() {
|
|
5532
|
+
const v = this.arrayPathCurrentValue;
|
|
5533
|
+
return Array.isArray(v) && isArrayOfObjects(v);
|
|
5534
|
+
},
|
|
5535
|
+
propertyDetailViewMode() {
|
|
5536
|
+
if (this.isDatePath) {
|
|
5537
|
+
return this.dateViewMode;
|
|
5538
|
+
}
|
|
5539
|
+
if (this.isObjectIdPath) {
|
|
5540
|
+
return this.objectIdViewMode;
|
|
5541
|
+
}
|
|
5542
|
+
if (this.path?.instance === 'Array') {
|
|
5543
|
+
return this.arrayDetailViewMode;
|
|
5544
|
+
}
|
|
5545
|
+
return this.detailViewMode;
|
|
5546
|
+
},
|
|
5547
|
+
pathNameParts() {
|
|
5548
|
+
return this.getSearchMatchParts(this.path?.path || '', this.searchQuery);
|
|
5266
5549
|
}
|
|
5267
5550
|
},
|
|
5268
5551
|
watch: {
|
|
@@ -5279,12 +5562,56 @@ module.exports = app => app.component('document-property', {
|
|
|
5279
5562
|
if (newValue && this.isGeoJsonGeometry) {
|
|
5280
5563
|
this.detailViewMode = 'map';
|
|
5281
5564
|
}
|
|
5565
|
+
},
|
|
5566
|
+
canUseArrayTableView(can) {
|
|
5567
|
+
if (!can && this.arrayDetailViewMode === 'table') {
|
|
5568
|
+
this.arrayDetailViewMode = 'list';
|
|
5569
|
+
}
|
|
5570
|
+
},
|
|
5571
|
+
'path.path'() {
|
|
5572
|
+
this.arrayDetailViewMode = 'list';
|
|
5282
5573
|
}
|
|
5283
5574
|
},
|
|
5284
5575
|
methods: {
|
|
5576
|
+
getSearchMatchParts(text, query) {
|
|
5577
|
+
const normalizedQuery = typeof query === 'string' ? query.trim() : '';
|
|
5578
|
+
if (!normalizedQuery) {
|
|
5579
|
+
return [{ text, matched: false }];
|
|
5580
|
+
}
|
|
5581
|
+
|
|
5582
|
+
const lowerText = String(text).toLowerCase();
|
|
5583
|
+
const lowerQuery = normalizedQuery.toLowerCase();
|
|
5584
|
+
const parts = [];
|
|
5585
|
+
let position = 0;
|
|
5586
|
+
let matchIndex = lowerText.indexOf(lowerQuery, position);
|
|
5587
|
+
|
|
5588
|
+
while (matchIndex !== -1) {
|
|
5589
|
+
if (matchIndex > position) {
|
|
5590
|
+
parts.push({ text: text.slice(position, matchIndex), matched: false });
|
|
5591
|
+
}
|
|
5592
|
+
const matchEnd = matchIndex + normalizedQuery.length;
|
|
5593
|
+
parts.push({ text: text.slice(matchIndex, matchEnd), matched: true });
|
|
5594
|
+
position = matchEnd;
|
|
5595
|
+
matchIndex = lowerText.indexOf(lowerQuery, position);
|
|
5596
|
+
}
|
|
5597
|
+
|
|
5598
|
+
if (position < text.length) {
|
|
5599
|
+
parts.push({ text: text.slice(position), matched: false });
|
|
5600
|
+
}
|
|
5601
|
+
|
|
5602
|
+
return parts.length > 0 ? parts : [{ text, matched: false }];
|
|
5603
|
+
},
|
|
5604
|
+
setArrayDetailViewMode(mode) {
|
|
5605
|
+
this.arrayDetailViewMode = mode;
|
|
5606
|
+
if (mode === 'table') {
|
|
5607
|
+
if (this.isCollapsed) {
|
|
5608
|
+
this.isCollapsed = false;
|
|
5609
|
+
}
|
|
5610
|
+
}
|
|
5611
|
+
},
|
|
5285
5612
|
setDetailViewMode(mode) {
|
|
5286
5613
|
this.detailViewMode = mode;
|
|
5287
|
-
|
|
5614
|
+
|
|
5288
5615
|
// When switching to map view, expand the container and value so the map is visible
|
|
5289
5616
|
if (mode === 'map' && this.isGeoJsonGeometry) {
|
|
5290
5617
|
if (this.isCollapsed) {
|
|
@@ -5355,6 +5682,7 @@ module.exports = app => app.component('document-property', {
|
|
|
5355
5682
|
if (path.instance === 'Array') {
|
|
5356
5683
|
props.path = path;
|
|
5357
5684
|
props.schemaPaths = this.schemaPaths;
|
|
5685
|
+
props.viewMode = this.arrayDetailViewMode === 'table' ? 'table' : 'json';
|
|
5358
5686
|
}
|
|
5359
5687
|
return props;
|
|
5360
5688
|
},
|
|
@@ -5612,6 +5940,8 @@ module.exports = app => app.component('document', {
|
|
|
5612
5940
|
shouldShowConfirmModal: false,
|
|
5613
5941
|
shouldShowDeleteModal: false,
|
|
5614
5942
|
shouldShowCloneModal: false,
|
|
5943
|
+
shouldShowValidationModal: false,
|
|
5944
|
+
validationResult: null,
|
|
5615
5945
|
previousQuery: null,
|
|
5616
5946
|
lastUpdatedAt: null,
|
|
5617
5947
|
isRefreshing: false,
|
|
@@ -5937,6 +6267,17 @@ module.exports = app => app.component('document', {
|
|
|
5937
6267
|
this.desktopMenuOpen = false;
|
|
5938
6268
|
this.mobileMenuOpen = false;
|
|
5939
6269
|
},
|
|
6270
|
+
async openValidationModal() {
|
|
6271
|
+
this.desktopMenuOpen = false;
|
|
6272
|
+
this.mobileMenuOpen = false;
|
|
6273
|
+
this.shouldShowValidationModal = true;
|
|
6274
|
+
this.validationResult = null;
|
|
6275
|
+
const { result } = await api.Model.validateDocument({
|
|
6276
|
+
model: this.model,
|
|
6277
|
+
documentId: this.documentId
|
|
6278
|
+
});
|
|
6279
|
+
this.validationResult = result;
|
|
6280
|
+
},
|
|
5940
6281
|
closeScriptDrawer() {
|
|
5941
6282
|
this.scriptDrawerOpen = false;
|
|
5942
6283
|
},
|
|
@@ -6071,6 +6412,7 @@ module.exports = app => app.component('execute-script', {
|
|
|
6071
6412
|
|
|
6072
6413
|
const template = __webpack_require__(/*! ./edit-array.html */ "./frontend/src/edit-array/edit-array.html");
|
|
6073
6414
|
const { BSON } = __webpack_require__(/*! mongodb/lib/bson */ "./node_modules/mongodb/lib/bson.js");
|
|
6415
|
+
const { isArrayOfObjects, unionKeysForArrayOfObjects, arrayItemMatchesSearch } = __webpack_require__(/*! ../array-utils */ "./frontend/src/array-utils.js");
|
|
6074
6416
|
|
|
6075
6417
|
const ObjectId = new Proxy(BSON.ObjectId, {
|
|
6076
6418
|
apply(target, thisArg, argumentsList) {
|
|
@@ -6081,15 +6423,44 @@ const ObjectId = new Proxy(BSON.ObjectId, {
|
|
|
6081
6423
|
|
|
6082
6424
|
module.exports = app => app.component('edit-array', {
|
|
6083
6425
|
template: template,
|
|
6084
|
-
props:
|
|
6426
|
+
props: {
|
|
6427
|
+
value: {},
|
|
6428
|
+
path: {},
|
|
6429
|
+
schemaPaths: {},
|
|
6430
|
+
/** 'json' (ACE) or 'table' for arrays of plain objects */
|
|
6431
|
+
viewMode: {
|
|
6432
|
+
type: String,
|
|
6433
|
+
default: 'json'
|
|
6434
|
+
}
|
|
6435
|
+
},
|
|
6085
6436
|
data() {
|
|
6086
6437
|
return {
|
|
6087
|
-
arrayValue: []
|
|
6438
|
+
arrayValue: [],
|
|
6439
|
+
newColumnKeyInput: '',
|
|
6440
|
+
newColumnKeyError: '',
|
|
6441
|
+
arraySearchQuery: ''
|
|
6088
6442
|
};
|
|
6089
6443
|
},
|
|
6090
6444
|
computed: {
|
|
6091
6445
|
arrayStr() {
|
|
6092
6446
|
return JSON.stringify(this.arrayValue, null, 2);
|
|
6447
|
+
},
|
|
6448
|
+
useTableEditor() {
|
|
6449
|
+
return this.viewMode === 'table' && isArrayOfObjects(this.arrayValue);
|
|
6450
|
+
},
|
|
6451
|
+
tableColumnKeys() {
|
|
6452
|
+
return unionKeysForArrayOfObjects(this.arrayValue);
|
|
6453
|
+
},
|
|
6454
|
+
arraySearchNormalized() {
|
|
6455
|
+
return (this.arraySearchQuery || '').trim().toLowerCase();
|
|
6456
|
+
},
|
|
6457
|
+
filteredTableRows() {
|
|
6458
|
+
if (!Array.isArray(this.arrayValue)) {
|
|
6459
|
+
return [];
|
|
6460
|
+
}
|
|
6461
|
+
return this.arrayValue
|
|
6462
|
+
.map((item, index) => ({ item, index }))
|
|
6463
|
+
.filter(({ item }) => arrayItemMatchesSearch(item, this.arraySearchNormalized));
|
|
6093
6464
|
}
|
|
6094
6465
|
},
|
|
6095
6466
|
methods: {
|
|
@@ -6120,6 +6491,78 @@ module.exports = app => app.component('edit-array', {
|
|
|
6120
6491
|
} catch (err) {
|
|
6121
6492
|
this.$emit('error', err);
|
|
6122
6493
|
}
|
|
6494
|
+
},
|
|
6495
|
+
serializeCell(value) {
|
|
6496
|
+
if (value === undefined) {
|
|
6497
|
+
return '';
|
|
6498
|
+
}
|
|
6499
|
+
return JSON.stringify(value);
|
|
6500
|
+
},
|
|
6501
|
+
onCellChange(rowIndex, key, raw) {
|
|
6502
|
+
const row = this.arrayValue[rowIndex];
|
|
6503
|
+
if (row == null || typeof row !== 'object') {
|
|
6504
|
+
return;
|
|
6505
|
+
}
|
|
6506
|
+
const trimmed = raw.trim();
|
|
6507
|
+
if (trimmed === '') {
|
|
6508
|
+
delete row[key];
|
|
6509
|
+
this.emitUpdate();
|
|
6510
|
+
return;
|
|
6511
|
+
}
|
|
6512
|
+
try {
|
|
6513
|
+
row[key] = JSON.parse(trimmed);
|
|
6514
|
+
this.emitUpdate();
|
|
6515
|
+
} catch (err) {
|
|
6516
|
+
this.$emit('error', err);
|
|
6517
|
+
}
|
|
6518
|
+
},
|
|
6519
|
+
addRow() {
|
|
6520
|
+
const row = {};
|
|
6521
|
+
for (const k of this.tableColumnKeys) {
|
|
6522
|
+
row[k] = null;
|
|
6523
|
+
}
|
|
6524
|
+
this.arrayValue.push(row);
|
|
6525
|
+
this.emitUpdate();
|
|
6526
|
+
},
|
|
6527
|
+
addColumn() {
|
|
6528
|
+
const key = (this.newColumnKeyInput || '').trim();
|
|
6529
|
+
this.newColumnKeyError = '';
|
|
6530
|
+
if (!key) {
|
|
6531
|
+
this.newColumnKeyError = 'Enter a column name.';
|
|
6532
|
+
return;
|
|
6533
|
+
}
|
|
6534
|
+
if (this.tableColumnKeys.includes(key)) {
|
|
6535
|
+
this.newColumnKeyError = 'That column already exists.';
|
|
6536
|
+
return;
|
|
6537
|
+
}
|
|
6538
|
+
if (!Array.isArray(this.arrayValue)) {
|
|
6539
|
+
this.arrayValue = [];
|
|
6540
|
+
}
|
|
6541
|
+
if (this.arrayValue.length === 0) {
|
|
6542
|
+
this.arrayValue.push({ [key]: null });
|
|
6543
|
+
} else {
|
|
6544
|
+
for (const row of this.arrayValue) {
|
|
6545
|
+
if (row != null && typeof row === 'object' && !Array.isArray(row) && row.constructor === Object) {
|
|
6546
|
+
row[key] = null;
|
|
6547
|
+
}
|
|
6548
|
+
}
|
|
6549
|
+
}
|
|
6550
|
+
this.newColumnKeyInput = '';
|
|
6551
|
+
this.emitUpdate();
|
|
6552
|
+
},
|
|
6553
|
+
removeRow(index) {
|
|
6554
|
+
this.arrayValue.splice(index, 1);
|
|
6555
|
+
this.emitUpdate();
|
|
6556
|
+
},
|
|
6557
|
+
moveRow(index, delta) {
|
|
6558
|
+
const j = index + delta;
|
|
6559
|
+
if (j < 0 || j >= this.arrayValue.length) {
|
|
6560
|
+
return;
|
|
6561
|
+
}
|
|
6562
|
+
const copy = this.arrayValue[index];
|
|
6563
|
+
this.arrayValue.splice(index, 1);
|
|
6564
|
+
this.arrayValue.splice(j, 0, copy);
|
|
6565
|
+
this.emitUpdate();
|
|
6123
6566
|
}
|
|
6124
6567
|
},
|
|
6125
6568
|
mounted() {
|
|
@@ -7732,6 +8175,166 @@ module.exports = app => app.component('modal', {
|
|
|
7732
8175
|
});
|
|
7733
8176
|
|
|
7734
8177
|
|
|
8178
|
+
/***/ },
|
|
8179
|
+
|
|
8180
|
+
/***/ "./frontend/src/models/analyze-schema-modal/analyze-schema-modal.js"
|
|
8181
|
+
/*!**************************************************************************!*\
|
|
8182
|
+
!*** ./frontend/src/models/analyze-schema-modal/analyze-schema-modal.js ***!
|
|
8183
|
+
\**************************************************************************/
|
|
8184
|
+
(module, __unused_webpack_exports, __webpack_require__) {
|
|
8185
|
+
|
|
8186
|
+
"use strict";
|
|
8187
|
+
|
|
8188
|
+
|
|
8189
|
+
const api = __webpack_require__(/*! ../../api */ "./frontend/src/api.js");
|
|
8190
|
+
const template = __webpack_require__(/*! ./analyze-schema-modal.html */ "./frontend/src/models/analyze-schema-modal/analyze-schema-modal.html");
|
|
8191
|
+
const xss = __webpack_require__(/*! xss */ "./node_modules/xss/lib/index.js");
|
|
8192
|
+
|
|
8193
|
+
const TYPE_COLORS = {
|
|
8194
|
+
string: '#315f5b',
|
|
8195
|
+
number: '#465b86',
|
|
8196
|
+
objectid: '#2f6381',
|
|
8197
|
+
boolean: '#5b6540',
|
|
8198
|
+
date: '#5a5278',
|
|
8199
|
+
array: '#6b573d',
|
|
8200
|
+
object: '#3f6648',
|
|
8201
|
+
mixed: '#62546a',
|
|
8202
|
+
buffer: '#5a5f50',
|
|
8203
|
+
decimal128: '#4a6474',
|
|
8204
|
+
map: '#4e665d',
|
|
8205
|
+
null: '#3f4650',
|
|
8206
|
+
undefined: '#5b626b'
|
|
8207
|
+
};
|
|
8208
|
+
|
|
8209
|
+
const FALLBACK_TYPE_COLORS = [
|
|
8210
|
+
'#315f5b',
|
|
8211
|
+
'#465b86',
|
|
8212
|
+
'#2f6381',
|
|
8213
|
+
'#5b6540',
|
|
8214
|
+
'#5a5278',
|
|
8215
|
+
'#6b573d',
|
|
8216
|
+
'#3f6648',
|
|
8217
|
+
'#62546a'
|
|
8218
|
+
];
|
|
8219
|
+
|
|
8220
|
+
module.exports = app => app.component('analyze-schema-modal', {
|
|
8221
|
+
template,
|
|
8222
|
+
props: ['currentModel'],
|
|
8223
|
+
emits: ['close'],
|
|
8224
|
+
data: () => ({
|
|
8225
|
+
schemaAnalysis: null,
|
|
8226
|
+
fieldSearch: '',
|
|
8227
|
+
showInvalidDocuments: false,
|
|
8228
|
+
typeDisplayMode: 'bars'
|
|
8229
|
+
}),
|
|
8230
|
+
computed: {
|
|
8231
|
+
filteredPaths() {
|
|
8232
|
+
if (!this.schemaAnalysis) {
|
|
8233
|
+
return [];
|
|
8234
|
+
}
|
|
8235
|
+
const search = this.fieldSearch.trim().toLowerCase();
|
|
8236
|
+
if (!search) {
|
|
8237
|
+
return this.schemaAnalysis.paths;
|
|
8238
|
+
}
|
|
8239
|
+
return this.schemaAnalysis.paths.filter(path => path.path.toLowerCase().includes(search));
|
|
8240
|
+
}
|
|
8241
|
+
},
|
|
8242
|
+
async mounted() {
|
|
8243
|
+
window.analyzeSchemaModal = this;
|
|
8244
|
+
const { analysis } = await api.Model.analyzeSchema({ model: this.currentModel });
|
|
8245
|
+
this.schemaAnalysis = analysis;
|
|
8246
|
+
this.$nextTick(() => {
|
|
8247
|
+
this.$refs.fieldSearch?.focus();
|
|
8248
|
+
});
|
|
8249
|
+
},
|
|
8250
|
+
unmounted() {
|
|
8251
|
+
window.analyzeSchemaModal = null;
|
|
8252
|
+
},
|
|
8253
|
+
methods: {
|
|
8254
|
+
close() {
|
|
8255
|
+
this.$emit('close');
|
|
8256
|
+
},
|
|
8257
|
+
formatNumber(value) {
|
|
8258
|
+
if (typeof value !== 'number') {
|
|
8259
|
+
return 'Unknown';
|
|
8260
|
+
}
|
|
8261
|
+
|
|
8262
|
+
return value.toLocaleString();
|
|
8263
|
+
},
|
|
8264
|
+
formatPercent(value, total) {
|
|
8265
|
+
if (!total) {
|
|
8266
|
+
return '0%';
|
|
8267
|
+
}
|
|
8268
|
+
return `${Math.round((value / total) * 1000) / 10}%`;
|
|
8269
|
+
},
|
|
8270
|
+
typePercentValue(value, total) {
|
|
8271
|
+
if (!total) {
|
|
8272
|
+
return 0;
|
|
8273
|
+
}
|
|
8274
|
+
return Math.round((value / total) * 1000) / 10;
|
|
8275
|
+
},
|
|
8276
|
+
pathValueCount(path) {
|
|
8277
|
+
return typeof path?.valueCount === 'number' && path.valueCount > 0 ?
|
|
8278
|
+
path.valueCount :
|
|
8279
|
+
this.schemaAnalysis.sampleSize;
|
|
8280
|
+
},
|
|
8281
|
+
primaryType(path) {
|
|
8282
|
+
if (!path || !Array.isArray(path.types) || path.types.length === 0) {
|
|
8283
|
+
return null;
|
|
8284
|
+
}
|
|
8285
|
+
|
|
8286
|
+
return path.types.find(typeInfo => typeInfo.type !== 'null' && typeInfo.type !== 'undefined') || path.types[0];
|
|
8287
|
+
},
|
|
8288
|
+
typeColor(type) {
|
|
8289
|
+
const normalizedType = String(type || '').toLowerCase();
|
|
8290
|
+
if (TYPE_COLORS[normalizedType]) {
|
|
8291
|
+
return TYPE_COLORS[normalizedType];
|
|
8292
|
+
}
|
|
8293
|
+
|
|
8294
|
+
let hash = 0;
|
|
8295
|
+
for (let i = 0; i < normalizedType.length; ++i) {
|
|
8296
|
+
hash = ((hash << 5) - hash) + normalizedType.charCodeAt(i);
|
|
8297
|
+
hash |= 0;
|
|
8298
|
+
}
|
|
8299
|
+
const index = Math.abs(hash) % FALLBACK_TYPE_COLORS.length;
|
|
8300
|
+
return FALLBACK_TYPE_COLORS[index % FALLBACK_TYPE_COLORS.length];
|
|
8301
|
+
},
|
|
8302
|
+
typeBadgeStyle(type) {
|
|
8303
|
+
return {
|
|
8304
|
+
backgroundColor: this.typeColor(type),
|
|
8305
|
+
color: '#fff'
|
|
8306
|
+
};
|
|
8307
|
+
},
|
|
8308
|
+
typeSegmentStyle(type, count, total) {
|
|
8309
|
+
return {
|
|
8310
|
+
width: `${this.typePercentValue(count, total)}%`,
|
|
8311
|
+
backgroundColor: this.typeColor(type),
|
|
8312
|
+
color: '#fff'
|
|
8313
|
+
};
|
|
8314
|
+
},
|
|
8315
|
+
documentRoute(documentId) {
|
|
8316
|
+
return `/model/${this.currentModel}/document/${documentId}`;
|
|
8317
|
+
},
|
|
8318
|
+
highlightPath(path) {
|
|
8319
|
+
const search = this.fieldSearch.trim();
|
|
8320
|
+
if (!search) {
|
|
8321
|
+
return xss(path);
|
|
8322
|
+
}
|
|
8323
|
+
|
|
8324
|
+
const index = path.toLowerCase().indexOf(search.toLowerCase());
|
|
8325
|
+
if (index === -1) {
|
|
8326
|
+
return xss(path);
|
|
8327
|
+
}
|
|
8328
|
+
|
|
8329
|
+
const before = path.slice(0, index);
|
|
8330
|
+
const match = path.slice(index, index + search.length);
|
|
8331
|
+
const after = path.slice(index + search.length);
|
|
8332
|
+
return `${xss(before)}<strong>${xss(match)}</strong>${xss(after)}`;
|
|
8333
|
+
}
|
|
8334
|
+
}
|
|
8335
|
+
});
|
|
8336
|
+
|
|
8337
|
+
|
|
7735
8338
|
/***/ },
|
|
7736
8339
|
|
|
7737
8340
|
/***/ "./frontend/src/models/document-search/document-search.js"
|
|
@@ -8197,6 +8800,7 @@ module.exports = app => app.component('models', {
|
|
|
8197
8800
|
addFieldFilterText: '',
|
|
8198
8801
|
showAddFieldDropdown: false,
|
|
8199
8802
|
shouldShowIndexModal: false,
|
|
8803
|
+
shouldShowAnalyzeSchemaModal: false,
|
|
8200
8804
|
shouldShowCollectionInfoModal: false,
|
|
8201
8805
|
shouldShowDropCollectionModal: false,
|
|
8202
8806
|
dropCollectionConfirmName: '',
|
|
@@ -8919,6 +9523,10 @@ module.exports = app => app.component('models', {
|
|
|
8919
9523
|
this.mongoDBIndexes = mongoDBIndexes;
|
|
8920
9524
|
this.schemaIndexes = schemaIndexes;
|
|
8921
9525
|
},
|
|
9526
|
+
openAnalyzeSchemaModal() {
|
|
9527
|
+
this.closeActionsMenu();
|
|
9528
|
+
this.shouldShowAnalyzeSchemaModal = true;
|
|
9529
|
+
},
|
|
8922
9530
|
toggleActionsMenu() {
|
|
8923
9531
|
this.showActionsMenu = !this.showActionsMenu;
|
|
8924
9532
|
},
|
|
@@ -8979,19 +9587,6 @@ module.exports = app => app.component('models', {
|
|
|
8979
9587
|
this.$toast.success(`Collection ${this.currentModel} dropped.`);
|
|
8980
9588
|
await this.getDocuments();
|
|
8981
9589
|
},
|
|
8982
|
-
async findOldestDocument() {
|
|
8983
|
-
this.closeActionsMenu();
|
|
8984
|
-
const { docs } = await api.Model.getDocuments({
|
|
8985
|
-
model: this.currentModel,
|
|
8986
|
-
limit: 1,
|
|
8987
|
-
sortKey: '_id',
|
|
8988
|
-
sortDirection: 1
|
|
8989
|
-
});
|
|
8990
|
-
if (!Array.isArray(docs) || docs.length === 0) {
|
|
8991
|
-
throw new Error('No documents found');
|
|
8992
|
-
}
|
|
8993
|
-
this.openDocument(docs[0]);
|
|
8994
|
-
},
|
|
8995
9590
|
isTTLIndex(index) {
|
|
8996
9591
|
return index != null && index.expireAfterSeconds != null;
|
|
8997
9592
|
},
|
|
@@ -11963,6 +12558,9 @@ var map = {
|
|
|
11963
12558
|
"./modal/modal.css": "./frontend/src/modal/modal.css",
|
|
11964
12559
|
"./modal/modal.html": "./frontend/src/modal/modal.html",
|
|
11965
12560
|
"./modal/modal.js": "./frontend/src/modal/modal.js",
|
|
12561
|
+
"./models/analyze-schema-modal/analyze-schema-modal": "./frontend/src/models/analyze-schema-modal/analyze-schema-modal.js",
|
|
12562
|
+
"./models/analyze-schema-modal/analyze-schema-modal.html": "./frontend/src/models/analyze-schema-modal/analyze-schema-modal.html",
|
|
12563
|
+
"./models/analyze-schema-modal/analyze-schema-modal.js": "./frontend/src/models/analyze-schema-modal/analyze-schema-modal.js",
|
|
11966
12564
|
"./models/document-search/document-search": "./frontend/src/models/document-search/document-search.js",
|
|
11967
12565
|
"./models/document-search/document-search.html": "./frontend/src/models/document-search/document-search.html",
|
|
11968
12566
|
"./models/document-search/document-search.js": "./frontend/src/models/document-search/document-search.js",
|
|
@@ -12101,7 +12699,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
12101
12699
|
/* harmony export */ });
|
|
12102
12700
|
/* harmony import */ var _vue_shared__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
|
|
12103
12701
|
/**
|
|
12104
|
-
* @vue/reactivity v3.5.
|
|
12702
|
+
* @vue/reactivity v3.5.35
|
|
12105
12703
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
12106
12704
|
* @license MIT
|
|
12107
12705
|
**/
|
|
@@ -13484,9 +14082,6 @@ function targetTypeMap(rawType) {
|
|
|
13484
14082
|
return 0 /* INVALID */;
|
|
13485
14083
|
}
|
|
13486
14084
|
}
|
|
13487
|
-
function getTargetType(value) {
|
|
13488
|
-
return value["__v_skip"] || !Object.isExtensible(value) ? 0 /* INVALID */ : targetTypeMap((0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.toRawType)(value));
|
|
13489
|
-
}
|
|
13490
14085
|
// @__NO_SIDE_EFFECTS__
|
|
13491
14086
|
function reactive(target) {
|
|
13492
14087
|
if (/* @__PURE__ */ isReadonly(target)) {
|
|
@@ -13544,14 +14139,17 @@ function createReactiveObject(target, isReadonly2, baseHandlers, collectionHandl
|
|
|
13544
14139
|
if (target["__v_raw"] && !(isReadonly2 && target["__v_isReactive"])) {
|
|
13545
14140
|
return target;
|
|
13546
14141
|
}
|
|
13547
|
-
|
|
13548
|
-
if (targetType === 0 /* INVALID */) {
|
|
14142
|
+
if (target["__v_skip"] || !Object.isExtensible(target)) {
|
|
13549
14143
|
return target;
|
|
13550
14144
|
}
|
|
13551
14145
|
const existingProxy = proxyMap.get(target);
|
|
13552
14146
|
if (existingProxy) {
|
|
13553
14147
|
return existingProxy;
|
|
13554
14148
|
}
|
|
14149
|
+
const targetType = targetTypeMap((0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.toRawType)(target));
|
|
14150
|
+
if (targetType === 0 /* INVALID */) {
|
|
14151
|
+
return target;
|
|
14152
|
+
}
|
|
13555
14153
|
const proxy = new Proxy(
|
|
13556
14154
|
target,
|
|
13557
14155
|
targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers
|
|
@@ -14269,7 +14867,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
14269
14867
|
/* harmony import */ var _vue_reactivity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/reactivity */ "./node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js");
|
|
14270
14868
|
/* harmony import */ var _vue_shared__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
|
|
14271
14869
|
/**
|
|
14272
|
-
* @vue/runtime-core v3.5.
|
|
14870
|
+
* @vue/runtime-core v3.5.35
|
|
14273
14871
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
14274
14872
|
* @license MIT
|
|
14275
14873
|
**/
|
|
@@ -15429,19 +16027,18 @@ const TeleportImpl = {
|
|
|
15429
16027
|
target,
|
|
15430
16028
|
props
|
|
15431
16029
|
} = vnode;
|
|
15432
|
-
|
|
16030
|
+
const shouldRemove = doRemove || !isTeleportDisabled(props);
|
|
15433
16031
|
const pendingMount = pendingMounts.get(vnode);
|
|
15434
16032
|
if (pendingMount) {
|
|
15435
16033
|
pendingMount.flags |= 8;
|
|
15436
16034
|
pendingMounts.delete(vnode);
|
|
15437
|
-
shouldRemove = false;
|
|
15438
16035
|
}
|
|
15439
16036
|
if (target) {
|
|
15440
16037
|
hostRemove(targetStart);
|
|
15441
16038
|
hostRemove(targetAnchor);
|
|
15442
16039
|
}
|
|
15443
16040
|
doRemove && hostRemove(anchor);
|
|
15444
|
-
if (shapeFlag & 16) {
|
|
16041
|
+
if (!pendingMount && shapeFlag & 16) {
|
|
15445
16042
|
for (let i = 0; i < children.length; i++) {
|
|
15446
16043
|
const child = children[i];
|
|
15447
16044
|
unmount(
|
|
@@ -16411,20 +17008,16 @@ function createHydrationFunctions(rendererInternals) {
|
|
|
16411
17008
|
slotScopeIds,
|
|
16412
17009
|
optimized
|
|
16413
17010
|
);
|
|
16414
|
-
|
|
16415
|
-
|
|
16416
|
-
|
|
16417
|
-
|
|
16418
|
-
|
|
16419
|
-
`Hydration children mismatch on`,
|
|
16420
|
-
el,
|
|
16421
|
-
`
|
|
17011
|
+
if (next && !isMismatchAllowed(el, 1 /* CHILDREN */)) {
|
|
17012
|
+
( true) && warn$1(
|
|
17013
|
+
`Hydration children mismatch on`,
|
|
17014
|
+
el,
|
|
17015
|
+
`
|
|
16422
17016
|
Server rendered element contains more child nodes than client vdom.`
|
|
16423
|
-
|
|
16424
|
-
|
|
16425
|
-
|
|
16426
|
-
|
|
16427
|
-
}
|
|
17017
|
+
);
|
|
17018
|
+
logMismatchError();
|
|
17019
|
+
}
|
|
17020
|
+
while (next) {
|
|
16428
17021
|
const cur = next;
|
|
16429
17022
|
next = next.nextSibling;
|
|
16430
17023
|
remove(cur);
|
|
@@ -16488,7 +17081,7 @@ Server rendered element contains more child nodes than client vdom.`
|
|
|
16488
17081
|
optimized = optimized || !!parentVNode.dynamicChildren;
|
|
16489
17082
|
const children = parentVNode.children;
|
|
16490
17083
|
const l = children.length;
|
|
16491
|
-
let
|
|
17084
|
+
let hasCheckedMismatch = false;
|
|
16492
17085
|
for (let i = 0; i < l; i++) {
|
|
16493
17086
|
const vnode = optimized ? children[i] : children[i] = normalizeVNode(children[i]);
|
|
16494
17087
|
const isText = vnode.type === Text;
|
|
@@ -16516,17 +17109,17 @@ Server rendered element contains more child nodes than client vdom.`
|
|
|
16516
17109
|
} else if (isText && !vnode.children) {
|
|
16517
17110
|
insert(vnode.el = createText(""), container);
|
|
16518
17111
|
} else {
|
|
16519
|
-
if (!
|
|
16520
|
-
|
|
16521
|
-
|
|
17112
|
+
if (!hasCheckedMismatch) {
|
|
17113
|
+
hasCheckedMismatch = true;
|
|
17114
|
+
if (!isMismatchAllowed(container, 1 /* CHILDREN */)) {
|
|
17115
|
+
( true) && warn$1(
|
|
16522
17116
|
`Hydration children mismatch on`,
|
|
16523
17117
|
container,
|
|
16524
17118
|
`
|
|
16525
17119
|
Server rendered element contains fewer child nodes than client vdom.`
|
|
16526
17120
|
);
|
|
16527
|
-
|
|
17121
|
+
logMismatchError();
|
|
16528
17122
|
}
|
|
16529
|
-
logMismatchError();
|
|
16530
17123
|
}
|
|
16531
17124
|
patch(
|
|
16532
17125
|
null,
|
|
@@ -20837,9 +21430,13 @@ function baseCreateRenderer(options, createHydrationFns) {
|
|
|
20837
21430
|
const needTransition2 = moveType !== 2 && shapeFlag & 1 && transition;
|
|
20838
21431
|
if (needTransition2) {
|
|
20839
21432
|
if (moveType === 0) {
|
|
20840
|
-
transition.
|
|
20841
|
-
|
|
20842
|
-
|
|
21433
|
+
if (transition.persisted && !el[leaveCbKey]) {
|
|
21434
|
+
hostInsert(el, container, anchor);
|
|
21435
|
+
} else {
|
|
21436
|
+
transition.beforeEnter(el);
|
|
21437
|
+
hostInsert(el, container, anchor);
|
|
21438
|
+
queuePostRenderEffect(() => transition.enter(el), parentSuspense);
|
|
21439
|
+
}
|
|
20843
21440
|
} else {
|
|
20844
21441
|
const { leave, delayLeave, afterLeave } = transition;
|
|
20845
21442
|
const remove2 = () => {
|
|
@@ -20850,16 +21447,21 @@ function baseCreateRenderer(options, createHydrationFns) {
|
|
|
20850
21447
|
}
|
|
20851
21448
|
};
|
|
20852
21449
|
const performLeave = () => {
|
|
21450
|
+
const wasLeaving = el._isLeaving || !!el[leaveCbKey];
|
|
20853
21451
|
if (el._isLeaving) {
|
|
20854
21452
|
el[leaveCbKey](
|
|
20855
21453
|
true
|
|
20856
21454
|
/* cancelled */
|
|
20857
21455
|
);
|
|
20858
21456
|
}
|
|
20859
|
-
|
|
21457
|
+
if (transition.persisted && !wasLeaving) {
|
|
20860
21458
|
remove2();
|
|
20861
|
-
|
|
20862
|
-
|
|
21459
|
+
} else {
|
|
21460
|
+
leave(el, () => {
|
|
21461
|
+
remove2();
|
|
21462
|
+
afterLeave && afterLeave();
|
|
21463
|
+
});
|
|
21464
|
+
}
|
|
20863
21465
|
};
|
|
20864
21466
|
if (delayLeave) {
|
|
20865
21467
|
delayLeave(el, remove2, performLeave);
|
|
@@ -22868,7 +23470,7 @@ function isMemoSame(cached, memo) {
|
|
|
22868
23470
|
return true;
|
|
22869
23471
|
}
|
|
22870
23472
|
|
|
22871
|
-
const version = "3.5.
|
|
23473
|
+
const version = "3.5.35";
|
|
22872
23474
|
const warn = true ? warn$1 : 0;
|
|
22873
23475
|
const ErrorTypeStrings = ErrorTypeStrings$1 ;
|
|
22874
23476
|
const devtools = true ? devtools$1 : 0;
|
|
@@ -23079,7 +23681,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
23079
23681
|
/* harmony import */ var _vue_runtime_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/runtime-core */ "./node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js");
|
|
23080
23682
|
/* harmony import */ var _vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
|
|
23081
23683
|
/**
|
|
23082
|
-
* @vue/runtime-dom v3.5.
|
|
23684
|
+
* @vue/runtime-dom v3.5.35
|
|
23083
23685
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
23084
23686
|
* @license MIT
|
|
23085
23687
|
**/
|
|
@@ -23822,12 +24424,37 @@ function createInvoker(initialValue, instance) {
|
|
|
23822
24424
|
} else if (e._vts <= invoker.attached) {
|
|
23823
24425
|
return;
|
|
23824
24426
|
}
|
|
23825
|
-
|
|
23826
|
-
|
|
23827
|
-
|
|
23828
|
-
|
|
23829
|
-
|
|
23830
|
-
|
|
24427
|
+
const value = invoker.value;
|
|
24428
|
+
if ((0,_vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__.isArray)(value)) {
|
|
24429
|
+
const originalStop = e.stopImmediatePropagation;
|
|
24430
|
+
e.stopImmediatePropagation = () => {
|
|
24431
|
+
originalStop.call(e);
|
|
24432
|
+
e._stopped = true;
|
|
24433
|
+
};
|
|
24434
|
+
const handlers = value.slice();
|
|
24435
|
+
const args = [e];
|
|
24436
|
+
for (let i = 0; i < handlers.length; i++) {
|
|
24437
|
+
if (e._stopped) {
|
|
24438
|
+
break;
|
|
24439
|
+
}
|
|
24440
|
+
const handler = handlers[i];
|
|
24441
|
+
if (handler) {
|
|
24442
|
+
(0,_vue_runtime_core__WEBPACK_IMPORTED_MODULE_0__.callWithAsyncErrorHandling)(
|
|
24443
|
+
handler,
|
|
24444
|
+
instance,
|
|
24445
|
+
5,
|
|
24446
|
+
args
|
|
24447
|
+
);
|
|
24448
|
+
}
|
|
24449
|
+
}
|
|
24450
|
+
} else {
|
|
24451
|
+
(0,_vue_runtime_core__WEBPACK_IMPORTED_MODULE_0__.callWithAsyncErrorHandling)(
|
|
24452
|
+
value,
|
|
24453
|
+
instance,
|
|
24454
|
+
5,
|
|
24455
|
+
[e]
|
|
24456
|
+
);
|
|
24457
|
+
}
|
|
23831
24458
|
};
|
|
23832
24459
|
invoker.value = initialValue;
|
|
23833
24460
|
invoker.attached = getNow();
|
|
@@ -23843,20 +24470,6 @@ Expected function or array of functions, received type ${typeof value}.`
|
|
|
23843
24470
|
);
|
|
23844
24471
|
return _vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__.NOOP;
|
|
23845
24472
|
}
|
|
23846
|
-
function patchStopImmediatePropagation(e, value) {
|
|
23847
|
-
if ((0,_vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__.isArray)(value)) {
|
|
23848
|
-
const originalStop = e.stopImmediatePropagation;
|
|
23849
|
-
e.stopImmediatePropagation = () => {
|
|
23850
|
-
originalStop.call(e);
|
|
23851
|
-
e._stopped = true;
|
|
23852
|
-
};
|
|
23853
|
-
return value.map(
|
|
23854
|
-
(fn) => (e2) => !e2._stopped && fn && fn(e2)
|
|
23855
|
-
);
|
|
23856
|
-
} else {
|
|
23857
|
-
return value;
|
|
23858
|
-
}
|
|
23859
|
-
}
|
|
23860
24473
|
|
|
23861
24474
|
const isNativeOn = (key) => key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && // lowercase letter
|
|
23862
24475
|
key.charCodeAt(2) > 96 && key.charCodeAt(2) < 123;
|
|
@@ -25166,7 +25779,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
25166
25779
|
/* harmony export */ toTypeString: () => (/* binding */ toTypeString)
|
|
25167
25780
|
/* harmony export */ });
|
|
25168
25781
|
/**
|
|
25169
|
-
* @vue/shared v3.5.
|
|
25782
|
+
* @vue/shared v3.5.35
|
|
25170
25783
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
25171
25784
|
* @license MIT
|
|
25172
25785
|
**/
|
|
@@ -50010,6 +50623,128 @@ module.exports = {
|
|
|
50010
50623
|
|
|
50011
50624
|
"use strict";
|
|
50012
50625
|
|
|
50626
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
50627
|
+
exports.setUint32LE = exports.readUint8 = exports.readInt32LE = exports.UUID = exports.Timestamp = exports.serialize = exports.ObjectId = exports.NumberUtils = exports.MinKey = exports.MaxKey = exports.Long = exports.Int32 = exports.EJSON = exports.Double = exports.deserialize = exports.Decimal128 = exports.DBRef = exports.Code = exports.calculateObjectSize = exports.ByteUtils = exports.BSONType = exports.BSONSymbol = exports.BSONRegExp = exports.BSONError = exports.BSON = exports.Binary = void 0;
|
|
50628
|
+
exports.parseToElementsToArray = parseToElementsToArray;
|
|
50629
|
+
exports.pluckBSONSerializeOptions = pluckBSONSerializeOptions;
|
|
50630
|
+
exports.resolveBSONOptions = resolveBSONOptions;
|
|
50631
|
+
exports.parseUtf8ValidationOption = parseUtf8ValidationOption;
|
|
50632
|
+
/* eslint-disable no-restricted-imports */
|
|
50633
|
+
const bson_1 = __webpack_require__(/*! bson */ "./node_modules/bson/lib/bson.mjs");
|
|
50634
|
+
var bson_2 = __webpack_require__(/*! bson */ "./node_modules/bson/lib/bson.mjs");
|
|
50635
|
+
Object.defineProperty(exports, "Binary", ({ enumerable: true, get: function () { return bson_2.Binary; } }));
|
|
50636
|
+
Object.defineProperty(exports, "BSON", ({ enumerable: true, get: function () { return bson_2.BSON; } }));
|
|
50637
|
+
Object.defineProperty(exports, "BSONError", ({ enumerable: true, get: function () { return bson_2.BSONError; } }));
|
|
50638
|
+
Object.defineProperty(exports, "BSONRegExp", ({ enumerable: true, get: function () { return bson_2.BSONRegExp; } }));
|
|
50639
|
+
Object.defineProperty(exports, "BSONSymbol", ({ enumerable: true, get: function () { return bson_2.BSONSymbol; } }));
|
|
50640
|
+
Object.defineProperty(exports, "BSONType", ({ enumerable: true, get: function () { return bson_2.BSONType; } }));
|
|
50641
|
+
Object.defineProperty(exports, "ByteUtils", ({ enumerable: true, get: function () { return bson_2.ByteUtils; } }));
|
|
50642
|
+
Object.defineProperty(exports, "calculateObjectSize", ({ enumerable: true, get: function () { return bson_2.calculateObjectSize; } }));
|
|
50643
|
+
Object.defineProperty(exports, "Code", ({ enumerable: true, get: function () { return bson_2.Code; } }));
|
|
50644
|
+
Object.defineProperty(exports, "DBRef", ({ enumerable: true, get: function () { return bson_2.DBRef; } }));
|
|
50645
|
+
Object.defineProperty(exports, "Decimal128", ({ enumerable: true, get: function () { return bson_2.Decimal128; } }));
|
|
50646
|
+
Object.defineProperty(exports, "deserialize", ({ enumerable: true, get: function () { return bson_2.deserialize; } }));
|
|
50647
|
+
Object.defineProperty(exports, "Double", ({ enumerable: true, get: function () { return bson_2.Double; } }));
|
|
50648
|
+
Object.defineProperty(exports, "EJSON", ({ enumerable: true, get: function () { return bson_2.EJSON; } }));
|
|
50649
|
+
Object.defineProperty(exports, "Int32", ({ enumerable: true, get: function () { return bson_2.Int32; } }));
|
|
50650
|
+
Object.defineProperty(exports, "Long", ({ enumerable: true, get: function () { return bson_2.Long; } }));
|
|
50651
|
+
Object.defineProperty(exports, "MaxKey", ({ enumerable: true, get: function () { return bson_2.MaxKey; } }));
|
|
50652
|
+
Object.defineProperty(exports, "MinKey", ({ enumerable: true, get: function () { return bson_2.MinKey; } }));
|
|
50653
|
+
Object.defineProperty(exports, "NumberUtils", ({ enumerable: true, get: function () { return bson_2.NumberUtils; } }));
|
|
50654
|
+
Object.defineProperty(exports, "ObjectId", ({ enumerable: true, get: function () { return bson_2.ObjectId; } }));
|
|
50655
|
+
Object.defineProperty(exports, "serialize", ({ enumerable: true, get: function () { return bson_2.serialize; } }));
|
|
50656
|
+
Object.defineProperty(exports, "Timestamp", ({ enumerable: true, get: function () { return bson_2.Timestamp; } }));
|
|
50657
|
+
Object.defineProperty(exports, "UUID", ({ enumerable: true, get: function () { return bson_2.UUID; } }));
|
|
50658
|
+
function parseToElementsToArray(bytes, offset) {
|
|
50659
|
+
const res = bson_1.BSON.onDemand.parseToElements(bytes, offset);
|
|
50660
|
+
return Array.isArray(res) ? res : [...res];
|
|
50661
|
+
}
|
|
50662
|
+
// validates buffer inputs, used for read operations
|
|
50663
|
+
const validateBufferInputs = (buffer, offset, length) => {
|
|
50664
|
+
if (offset < 0 || offset + length > buffer.length) {
|
|
50665
|
+
throw new RangeError(`Attempt to access memory outside buffer bounds: buffer length: ${buffer.length}, offset: ${offset}, length: ${length}`);
|
|
50666
|
+
}
|
|
50667
|
+
};
|
|
50668
|
+
// readInt32LE, reads a 32-bit integer from buffer at given offset
|
|
50669
|
+
// throws if offset is out of bounds
|
|
50670
|
+
const readInt32LE = (buffer, offset) => {
|
|
50671
|
+
validateBufferInputs(buffer, offset, 4);
|
|
50672
|
+
return bson_1.NumberUtils.getInt32LE(buffer, offset);
|
|
50673
|
+
};
|
|
50674
|
+
exports.readInt32LE = readInt32LE;
|
|
50675
|
+
// readUint8, reads a single unsigned byte from buffer at given offset
|
|
50676
|
+
const readUint8 = (buffer, offset) => {
|
|
50677
|
+
validateBufferInputs(buffer, offset, 1);
|
|
50678
|
+
return buffer[offset];
|
|
50679
|
+
};
|
|
50680
|
+
exports.readUint8 = readUint8;
|
|
50681
|
+
const setUint32LE = (destination, offset, value) => {
|
|
50682
|
+
destination[offset] = value;
|
|
50683
|
+
value >>>= 8;
|
|
50684
|
+
destination[offset + 1] = value;
|
|
50685
|
+
value >>>= 8;
|
|
50686
|
+
destination[offset + 2] = value;
|
|
50687
|
+
value >>>= 8;
|
|
50688
|
+
destination[offset + 3] = value;
|
|
50689
|
+
return 4;
|
|
50690
|
+
};
|
|
50691
|
+
exports.setUint32LE = setUint32LE;
|
|
50692
|
+
function pluckBSONSerializeOptions(options) {
|
|
50693
|
+
const { fieldsAsRaw, useBigInt64, promoteValues, promoteBuffers, promoteLongs, serializeFunctions, ignoreUndefined, bsonRegExp, raw, enableUtf8Validation } = options;
|
|
50694
|
+
return {
|
|
50695
|
+
fieldsAsRaw,
|
|
50696
|
+
useBigInt64,
|
|
50697
|
+
promoteValues,
|
|
50698
|
+
promoteBuffers,
|
|
50699
|
+
promoteLongs,
|
|
50700
|
+
serializeFunctions,
|
|
50701
|
+
ignoreUndefined,
|
|
50702
|
+
bsonRegExp,
|
|
50703
|
+
raw,
|
|
50704
|
+
enableUtf8Validation
|
|
50705
|
+
};
|
|
50706
|
+
}
|
|
50707
|
+
/**
|
|
50708
|
+
* Merge the given BSONSerializeOptions, preferring options over the parent's options, and
|
|
50709
|
+
* substituting defaults for values not set.
|
|
50710
|
+
*
|
|
50711
|
+
* @internal
|
|
50712
|
+
*/
|
|
50713
|
+
function resolveBSONOptions(options, parent) {
|
|
50714
|
+
const parentOptions = parent?.bsonOptions;
|
|
50715
|
+
return {
|
|
50716
|
+
raw: options?.raw ?? parentOptions?.raw ?? false,
|
|
50717
|
+
useBigInt64: options?.useBigInt64 ?? parentOptions?.useBigInt64 ?? false,
|
|
50718
|
+
promoteLongs: options?.promoteLongs ?? parentOptions?.promoteLongs ?? true,
|
|
50719
|
+
promoteValues: options?.promoteValues ?? parentOptions?.promoteValues ?? true,
|
|
50720
|
+
promoteBuffers: options?.promoteBuffers ?? parentOptions?.promoteBuffers ?? false,
|
|
50721
|
+
ignoreUndefined: options?.ignoreUndefined ?? parentOptions?.ignoreUndefined ?? false,
|
|
50722
|
+
bsonRegExp: options?.bsonRegExp ?? parentOptions?.bsonRegExp ?? false,
|
|
50723
|
+
serializeFunctions: options?.serializeFunctions ?? parentOptions?.serializeFunctions ?? false,
|
|
50724
|
+
fieldsAsRaw: options?.fieldsAsRaw ?? parentOptions?.fieldsAsRaw ?? {},
|
|
50725
|
+
enableUtf8Validation: options?.enableUtf8Validation ?? parentOptions?.enableUtf8Validation ?? true
|
|
50726
|
+
};
|
|
50727
|
+
}
|
|
50728
|
+
/** @internal */
|
|
50729
|
+
function parseUtf8ValidationOption(options) {
|
|
50730
|
+
const enableUtf8Validation = options?.enableUtf8Validation;
|
|
50731
|
+
if (enableUtf8Validation === false) {
|
|
50732
|
+
return { utf8: false };
|
|
50733
|
+
}
|
|
50734
|
+
return { utf8: { writeErrors: false } };
|
|
50735
|
+
}
|
|
50736
|
+
//# sourceMappingURL=bson.js.map
|
|
50737
|
+
|
|
50738
|
+
/***/ },
|
|
50739
|
+
|
|
50740
|
+
/***/ "./node_modules/mongoose/node_modules/mongodb/lib/bson.js"
|
|
50741
|
+
/*!****************************************************************!*\
|
|
50742
|
+
!*** ./node_modules/mongoose/node_modules/mongodb/lib/bson.js ***!
|
|
50743
|
+
\****************************************************************/
|
|
50744
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
50745
|
+
|
|
50746
|
+
"use strict";
|
|
50747
|
+
|
|
50013
50748
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
50014
50749
|
exports.setUint32LE = exports.readInt32LE = exports.UUID = exports.Timestamp = exports.serialize = exports.ObjectId = exports.NumberUtils = exports.MinKey = exports.MaxKey = exports.Long = exports.Int32 = exports.EJSON = exports.Double = exports.deserialize = exports.Decimal128 = exports.DBRef = exports.Code = exports.calculateObjectSize = exports.ByteUtils = exports.BSONType = exports.BSONSymbol = exports.BSONRegExp = exports.BSONError = exports.BSON = exports.Binary = void 0;
|
|
50015
50750
|
exports.parseToElementsToArray = parseToElementsToArray;
|
|
@@ -50437,6 +51172,16 @@ exports.set = function(path, val, o, special, map, _copying) {
|
|
|
50437
51172
|
}
|
|
50438
51173
|
};
|
|
50439
51174
|
|
|
51175
|
+
/*!
|
|
51176
|
+
* Split a string path into components delimited by '.' or
|
|
51177
|
+
* '[\d+]'
|
|
51178
|
+
*
|
|
51179
|
+
* #### Example:
|
|
51180
|
+
* stringToParts('foo[0].bar.1'); // ['foo', '0', 'bar', '1']
|
|
51181
|
+
*/
|
|
51182
|
+
|
|
51183
|
+
exports.stringToParts = stringToParts;
|
|
51184
|
+
|
|
50440
51185
|
/*!
|
|
50441
51186
|
* Recursively set nested arrays
|
|
50442
51187
|
*/
|
|
@@ -50465,6 +51210,7 @@ function K(v) {
|
|
|
50465
51210
|
return v;
|
|
50466
51211
|
}
|
|
50467
51212
|
|
|
51213
|
+
|
|
50468
51214
|
/***/ },
|
|
50469
51215
|
|
|
50470
51216
|
/***/ "./node_modules/mpath/lib/stringToParts.js"
|
|
@@ -50864,7 +51610,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
50864
51610
|
/* 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");
|
|
50865
51611
|
/* 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");
|
|
50866
51612
|
/**
|
|
50867
|
-
* vue v3.5.
|
|
51613
|
+
* vue v3.5.35
|
|
50868
51614
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
50869
51615
|
* @license MIT
|
|
50870
51616
|
**/
|
|
@@ -52216,7 +52962,7 @@ module.exports = "<div class=\"dashboard-editor p-4 bg-muted rounded-lg shadow-l
|
|
|
52216
52962
|
(module) {
|
|
52217
52963
|
|
|
52218
52964
|
"use strict";
|
|
52219
|
-
module.exports = "<div class=\"dashboards bg-page min-h-[calc(100vh-55px)]\">\n <div class=\"max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-8\">\n <div class=\"flex items-center justify-between mb-6\">\n <h1 class=\"text-lg font-semibold text-content\">Dashboards</h1>\n <button\n type=\"button\"\n @click=\"showCreateDashboardModal = true\"\n class=\"rounded-md bg-primary px-3 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 New Dashboard\n </button>\n </div>\n\n <div v-if=\"status === 'loading'\" class=\"text-center py-12\">\n <svg\n class=\"inline w-8 h-8 animate-spin text-gray-400\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\n <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z\"></path>\n </svg>\n </div>\n\n <div v-else-if=\"dashboards.length === 0\" class=\"text-center py-12\">\n <h3 class=\"text-sm font-semibold text-content\">No dashboards yet</h3>\n <p class=\"mt-1 text-sm text-content-tertiary\">Get started by creating a new dashboard.</p>\n <div class=\"mt-4\">\n <button\n type=\"button\"\n @click=\"showCreateDashboardModal = true\"\n class=\"inline-flex items-center rounded-md bg-primary px-3 py-2 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover\">\n <svg class=\"-ml-0.5 mr-1.5 h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z\" />\n </svg>\n New Dashboard\n </button>\n </div>\n </div>\n\n <div v-else class=\"space-y-3\">\n <div\n v-for=\"dashboard in
|
|
52965
|
+
module.exports = "<div class=\"dashboards bg-page min-h-[calc(100vh-55px)]\">\n <div class=\"max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-8\">\n <div class=\"flex items-center justify-between mb-6\">\n <h1 class=\"text-lg font-semibold text-content\">Dashboards</h1>\n <button\n type=\"button\"\n @click=\"showCreateDashboardModal = true\"\n class=\"rounded-md bg-primary px-3 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 New Dashboard\n </button>\n </div>\n\n <div v-if=\"status !== 'loading'\" class=\"mb-5\">\n <label for=\"dashboard-search\" class=\"sr-only\">Search dashboards</label>\n <div class=\"relative max-w-lg\">\n <div class=\"pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3 text-content-tertiary\">\n <svg class=\"h-4 w-4\" 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=\"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 </div>\n <input\n id=\"dashboard-search\"\n v-model=\"searchText\"\n type=\"search\"\n class=\"block w-full rounded-md border-0 bg-surface py-2 pl-9 pr-3 text-sm text-content shadow-sm ring-1 ring-inset ring-edge placeholder:text-content-tertiary focus:ring-2 focus:ring-inset focus:ring-primary\"\n placeholder=\"Search by title or description\">\n </div>\n </div>\n\n <div v-if=\"status === 'loading'\" class=\"text-center py-12\">\n <svg\n class=\"inline w-8 h-8 animate-spin text-gray-400\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\n <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z\"></path>\n </svg>\n </div>\n\n <div v-else-if=\"dashboards.length === 0\" class=\"text-center py-12\">\n <h3 class=\"text-sm font-semibold text-content\">No dashboards yet</h3>\n <p class=\"mt-1 text-sm text-content-tertiary\">Get started by creating a new dashboard.</p>\n <div class=\"mt-4\">\n <button\n type=\"button\"\n @click=\"showCreateDashboardModal = true\"\n class=\"inline-flex items-center rounded-md bg-primary px-3 py-2 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover\">\n <svg class=\"-ml-0.5 mr-1.5 h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z\" />\n </svg>\n New Dashboard\n </button>\n </div>\n </div>\n\n <div v-else-if=\"filteredDashboards.length === 0\" class=\"text-center py-12\">\n <h3 class=\"text-sm font-semibold text-content\">No matching dashboards</h3>\n <p class=\"mt-1 text-sm text-content-tertiary\">Try a different title or description.</p>\n </div>\n\n <div v-else class=\"space-y-3\">\n <div\n v-for=\"dashboard in filteredDashboards\"\n :key=\"dashboard._id\"\n class=\"bg-surface rounded-lg border border-edge px-5 py-4 flex items-start justify-between hover:border-edge-strong transition-colors\"\n >\n <div class=\"min-w-0 flex-1 mr-4\">\n <router-link\n :to=\"'/dashboard/' + dashboard._id\"\n class=\"text-sm font-semibold text-content hover:text-primary block truncate\">\n <span v-html=\"highlightTitle(dashboard.title)\"></span>\n </router-link>\n <p\n v-if=\"dashboard.description\"\n class=\"mt-0.5 text-sm text-content-tertiary truncate\"\n v-html=\"highlightDescription(dashboard.description)\"></p>\n <dl class=\"mt-3 grid grid-cols-1 gap-x-4 gap-y-1 text-xs text-content-tertiary sm:grid-cols-3\">\n <div>\n <dt class=\"font-medium text-content-secondary\">Created At</dt>\n <dd>{{ formatDate(dashboard.createdAt) }}</dd>\n </div>\n <div>\n <dt class=\"font-medium text-content-secondary\">Last Evaluated At</dt>\n <dd>{{ formatDate(dashboard.lastEvaluatedAt) }}</dd>\n </div>\n <div v-show=\"dashboard.createdBy\">\n <dt class=\"font-medium text-content-secondary\">Created By</dt>\n <dd class=\"truncate\">{{ dashboard.createdBy?.name }}</dd>\n </div>\n </dl>\n </div>\n <div class=\"flex items-center gap-2 shrink-0\">\n <button\n type=\"button\"\n @click=\"togglePin(dashboard)\"\n :title=\"dashboard.isPinned ? 'Unpin dashboard' : 'Pin dashboard'\"\n :aria-label=\"dashboard.isPinned ? 'Unpin dashboard' : 'Pin dashboard'\"\n class=\"rounded-md bg-surface px-2 py-1.5 text-content-tertiary ring-1 ring-inset ring-gray-300 hover:bg-page\"\n :class=\"dashboard.isPinned ? 'text-primary ring-primary/40' : ''\">\n <img :src=\"dashboard.isPinned ? 'images/pin-off.svg' : 'images/pin.svg'\" class=\"w-4 h-4\" alt=\"\" />\n </button>\n <router-link\n :to=\"'/dashboard/' + dashboard._id\"\n class=\"rounded-md bg-primary px-3 py-1.5 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover\">\n View\n </router-link>\n <div class=\"relative\" v-click-outside=\"() => closeMenu(dashboard._id)\">\n <button\n type=\"button\"\n @click=\"toggleMenu(dashboard._id)\"\n class=\"rounded-md bg-surface px-2 py-1.5 text-content-tertiary ring-1 ring-inset ring-gray-300 hover:bg-page\">\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.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Zm6 0a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Zm6 0a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z\" />\n </svg>\n </button>\n <div\n v-if=\"openMenuId === dashboard._id\"\n class=\"absolute right-0 mt-1 w-36 origin-top-right rounded-md bg-surface shadow-lg ring-1 ring-black/5 z-10\"\n >\n <div class=\"py-1\">\n <router-link\n :to=\"'/dashboard/' + dashboard._id + '?edit=true'\"\n class=\"block px-4 py-2 text-sm text-content-secondary hover:bg-muted\">\n Edit\n </router-link>\n <button\n @click=\"showDeleteDashboardModal = dashboard; openMenuId = null\"\n type=\"button\"\n class=\"block w-full text-left px-4 py-2 text-sm text-red-600 hover:bg-muted\">\n Delete\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <modal v-if=\"showCreateDashboardModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"showCreateDashboardModal = false;\">×</div>\n <create-dashboard @close=\"insertNewDashboard\"></create-dashboard>\n </template>\n </modal>\n\n <modal v-if=\"showDeleteDashboardModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"showDeleteDashboardModal = null;\">×</div>\n <h2>Are you sure you want to delete \"{{showDeleteDashboardModal.title}}\"?</h2>\n <div class=\"flex space-x-2 mt-4\">\n <button class=\"px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-500 text-sm font-semibold\" @click=\"deleteDashboard(showDeleteDashboardModal)\">Yes, delete</button>\n <button class=\"px-4 py-2 bg-muted text-content-secondary rounded-md hover:bg-muted text-sm font-semibold\" @click=\"showDeleteDashboardModal=null;\">Cancel</button>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
52220
52966
|
|
|
52221
52967
|
/***/ },
|
|
52222
52968
|
|
|
@@ -52227,7 +52973,7 @@ module.exports = "<div class=\"dashboards bg-page min-h-[calc(100vh-55px)]\">\n
|
|
|
52227
52973
|
(module) {
|
|
52228
52974
|
|
|
52229
52975
|
"use strict";
|
|
52230
|
-
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=\"
|
|
52976
|
+
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";
|
|
52231
52977
|
|
|
52232
52978
|
/***/ },
|
|
52233
52979
|
|
|
@@ -52293,7 +53039,7 @@ module.exports = ".document-details {\n width: 100%;\n}\n\n.document-details .v
|
|
|
52293
53039
|
(module) {
|
|
52294
53040
|
|
|
52295
53041
|
"use strict";
|
|
52296
|
-
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";
|
|
53042
|
+
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";
|
|
52297
53043
|
|
|
52298
53044
|
/***/ },
|
|
52299
53045
|
|
|
@@ -52315,7 +53061,7 @@ module.exports = ".document-details {\n width: 100%;\n }\n \n .document-de
|
|
|
52315
53061
|
(module) {
|
|
52316
53062
|
|
|
52317
53063
|
"use strict";
|
|
52318
|
-
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";
|
|
53064
|
+
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";
|
|
52319
53065
|
|
|
52320
53066
|
/***/ },
|
|
52321
53067
|
|
|
@@ -52370,7 +53116,7 @@ module.exports = ".document .document-menu {\n display: flex;\n position: stic
|
|
|
52370
53116
|
(module) {
|
|
52371
53117
|
|
|
52372
53118
|
"use strict";
|
|
52373
|
-
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";
|
|
53119
|
+
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";
|
|
52374
53120
|
|
|
52375
53121
|
/***/ },
|
|
52376
53122
|
|
|
@@ -52403,7 +53149,7 @@ module.exports = "<div v-if=\"isOpen\" class=\"fixed inset-0 z-50 execute-script
|
|
|
52403
53149
|
(module) {
|
|
52404
53150
|
|
|
52405
53151
|
"use strict";
|
|
52406
|
-
module.exports = "<div class=\"w-full\">\n <ace-editor\n
|
|
53152
|
+
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";
|
|
52407
53153
|
|
|
52408
53154
|
/***/ },
|
|
52409
53155
|
|
|
@@ -52649,6 +53395,17 @@ module.exports = "<transition name=\"modal\">\n <div class=\"modal-mask\">\n
|
|
|
52649
53395
|
|
|
52650
53396
|
/***/ },
|
|
52651
53397
|
|
|
53398
|
+
/***/ "./frontend/src/models/analyze-schema-modal/analyze-schema-modal.html"
|
|
53399
|
+
/*!****************************************************************************!*\
|
|
53400
|
+
!*** ./frontend/src/models/analyze-schema-modal/analyze-schema-modal.html ***!
|
|
53401
|
+
\****************************************************************************/
|
|
53402
|
+
(module) {
|
|
53403
|
+
|
|
53404
|
+
"use strict";
|
|
53405
|
+
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";
|
|
53406
|
+
|
|
53407
|
+
/***/ },
|
|
53408
|
+
|
|
52652
53409
|
/***/ "./frontend/src/models/document-search/document-search.html"
|
|
52653
53410
|
/*!******************************************************************!*\
|
|
52654
53411
|
!*** ./frontend/src/models/document-search/document-search.html ***!
|
|
@@ -52689,7 +53446,7 @@ module.exports = ".models {\n position: relative;\n display: flex;\n flex-dir
|
|
|
52689
53446
|
(module) {
|
|
52690
53447
|
|
|
52691
53448
|
"use strict";
|
|
52692
|
-
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";
|
|
53449
|
+
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";
|
|
52693
53450
|
|
|
52694
53451
|
/***/ },
|
|
52695
53452
|
|
|
@@ -58296,7 +59053,7 @@ exports.validatorErrorSymbol = Symbol('mongoose#validatorError');
|
|
|
58296
59053
|
|
|
58297
59054
|
|
|
58298
59055
|
|
|
58299
|
-
const ObjectId = (__webpack_require__(/*! mongodb/lib/bson */ "./node_modules/mongodb/lib/bson.js").ObjectId);
|
|
59056
|
+
const ObjectId = (__webpack_require__(/*! mongodb/lib/bson */ "./node_modules/mongoose/node_modules/mongodb/lib/bson.js").ObjectId);
|
|
58300
59057
|
const objectIdSymbol = (__webpack_require__(/*! ../helpers/symbols */ "./node_modules/mongoose/lib/helpers/symbols.js").objectIdSymbol);
|
|
58301
59058
|
|
|
58302
59059
|
/**
|
|
@@ -64196,7 +64953,7 @@ var src_default = VueToastificationPlugin;
|
|
|
64196
64953
|
(module) {
|
|
64197
64954
|
|
|
64198
64955
|
"use strict";
|
|
64199
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.5.
|
|
64956
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.5.6","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"}}');
|
|
64200
64957
|
|
|
64201
64958
|
/***/ }
|
|
64202
64959
|
|