@mongoosejs/studio 0.1.19 → 0.2.0
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/ChatMessage/executeScript.js +4 -1
- package/backend/actions/ChatThread/createChatMessage.js +28 -22
- package/backend/actions/Model/getDocument.js +2 -1
- package/backend/actions/Model/getDocuments.js +3 -2
- package/backend/actions/Model/getDocumentsStream.js +3 -2
- package/backend/helpers/evaluateFilter.js +38 -1
- package/backend/helpers/getRefFromSchemaType.js +5 -0
- package/express.js +4 -2
- package/frontend/public/app.js +704 -405
- package/frontend/public/index.html +1 -1
- package/frontend/public/style.css +1 -1
- package/frontend/public/tw.css +81 -62
- package/frontend/src/_util/document-search-autocomplete.js +229 -0
- package/frontend/src/chat/chat-message-script/chat-message-script.html +27 -20
- package/frontend/src/chat/chat.html +20 -17
- package/frontend/src/chat/chat.js +2 -0
- package/frontend/src/document/document.css +1 -8
- package/frontend/src/document/document.html +202 -164
- package/frontend/src/document/document.js +1 -0
- package/frontend/src/document-details/document-details.html +1 -11
- package/frontend/src/document-details/document-details.js +43 -1
- package/frontend/src/document-details/document-property/document-property.html +4 -4
- package/frontend/src/index.js +36 -15
- package/frontend/src/json-node/json-node.html +118 -0
- package/frontend/src/json-node/json-node.js +272 -0
- package/frontend/src/list-array/list-array.html +15 -3
- package/frontend/src/list-array/list-array.js +21 -3
- package/frontend/src/list-default/list-default.js +2 -2
- package/frontend/src/list-json/json-node.html +1 -1
- package/frontend/src/list-json/list-json.html +1 -1
- package/frontend/src/list-json/list-json.js +11 -248
- package/frontend/src/list-subdocument/list-subdocument.html +13 -4
- package/frontend/src/list-subdocument/list-subdocument.js +11 -6
- package/frontend/src/models/document-search/document-search.html +1 -1
- package/frontend/src/models/document-search/document-search.js +22 -116
- package/frontend/src/models/models.css +5 -15
- package/frontend/src/models/models.html +34 -34
- package/frontend/src/models/models.js +1 -1
- package/frontend/src/navbar/navbar.html +15 -6
- package/package.json +3 -3
package/frontend/public/app.js
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
var map = {
|
|
11
11
|
"./": "./frontend/src/index.js",
|
|
12
|
+
"./_util/document-search-autocomplete": "./frontend/src/_util/document-search-autocomplete.js",
|
|
13
|
+
"./_util/document-search-autocomplete.js": "./frontend/src/_util/document-search-autocomplete.js",
|
|
12
14
|
"./api": "./frontend/src/api.js",
|
|
13
15
|
"./api.js": "./frontend/src/api.js",
|
|
14
16
|
"./appendCSS": "./frontend/src/appendCSS.js",
|
|
@@ -124,6 +126,9 @@ var map = {
|
|
|
124
126
|
"./format.js": "./frontend/src/format.js",
|
|
125
127
|
"./index": "./frontend/src/index.js",
|
|
126
128
|
"./index.js": "./frontend/src/index.js",
|
|
129
|
+
"./json-node/json-node": "./frontend/src/json-node/json-node.js",
|
|
130
|
+
"./json-node/json-node.html": "./frontend/src/json-node/json-node.html",
|
|
131
|
+
"./json-node/json-node.js": "./frontend/src/json-node/json-node.js",
|
|
127
132
|
"./list-array/list-array": "./frontend/src/list-array/list-array.js",
|
|
128
133
|
"./list-array/list-array.html": "./frontend/src/list-array/list-array.html",
|
|
129
134
|
"./list-array/list-array.js": "./frontend/src/list-array/list-array.js",
|
|
@@ -203,6 +208,246 @@ webpackContext.resolve = webpackContextResolve;
|
|
|
203
208
|
module.exports = webpackContext;
|
|
204
209
|
webpackContext.id = "./frontend/src sync recursive ^\\.\\/.*$";
|
|
205
210
|
|
|
211
|
+
/***/ },
|
|
212
|
+
|
|
213
|
+
/***/ "./frontend/src/_util/document-search-autocomplete.js"
|
|
214
|
+
/*!************************************************************!*\
|
|
215
|
+
!*** ./frontend/src/_util/document-search-autocomplete.js ***!
|
|
216
|
+
\************************************************************/
|
|
217
|
+
(module, __unused_webpack_exports, __webpack_require__) {
|
|
218
|
+
|
|
219
|
+
"use strict";
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
const { Trie } = __webpack_require__(/*! ../models/trie */ "./frontend/src/models/trie.js");
|
|
223
|
+
|
|
224
|
+
const QUERY_SELECTORS = [
|
|
225
|
+
'$eq',
|
|
226
|
+
'$ne',
|
|
227
|
+
'$gt',
|
|
228
|
+
'$gte',
|
|
229
|
+
'$lt',
|
|
230
|
+
'$lte',
|
|
231
|
+
'$in',
|
|
232
|
+
'$nin',
|
|
233
|
+
'$exists',
|
|
234
|
+
'$regex',
|
|
235
|
+
'$options',
|
|
236
|
+
'$text',
|
|
237
|
+
'$search',
|
|
238
|
+
'$and',
|
|
239
|
+
'$or',
|
|
240
|
+
'$nor',
|
|
241
|
+
'$not',
|
|
242
|
+
'$elemMatch',
|
|
243
|
+
'$size',
|
|
244
|
+
'$all',
|
|
245
|
+
'$type',
|
|
246
|
+
'$expr',
|
|
247
|
+
'$jsonSchema',
|
|
248
|
+
'$mod'
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
const VALUE_HELPERS = [
|
|
252
|
+
'objectIdRange',
|
|
253
|
+
'ObjectId',
|
|
254
|
+
'Date',
|
|
255
|
+
'Math'
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
// Helpers that are function calls and should have () added
|
|
259
|
+
const FUNCTION_HELPERS = new Set([
|
|
260
|
+
'objectIdRange',
|
|
261
|
+
'ObjectId',
|
|
262
|
+
'Date'
|
|
263
|
+
]);
|
|
264
|
+
|
|
265
|
+
function buildAutocompleteTrie(schemaPaths) {
|
|
266
|
+
const trie = new Trie();
|
|
267
|
+
// Query operators can appear as field names in nested objects, so add with both roles
|
|
268
|
+
trie.bulkInsert(QUERY_SELECTORS, 5, 'operator');
|
|
269
|
+
trie.bulkInsert(QUERY_SELECTORS, 5, 'fieldName');
|
|
270
|
+
trie.bulkInsert(VALUE_HELPERS, 5, 'value');
|
|
271
|
+
|
|
272
|
+
if (Array.isArray(schemaPaths) && schemaPaths.length > 0) {
|
|
273
|
+
const paths = schemaPaths
|
|
274
|
+
.map(path => path?.path)
|
|
275
|
+
.filter(path => typeof path === 'string' && path.length > 0);
|
|
276
|
+
for (const path of schemaPaths) {
|
|
277
|
+
if (path.schema) {
|
|
278
|
+
paths.push(...Object.keys(path.schema).map(subpath => `${path.path}.${subpath}`));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
trie.bulkInsert(paths, 10, 'fieldName');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return trie;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function getAutocompleteContext(searchText, cursorPos) {
|
|
288
|
+
const before = searchText.slice(0, cursorPos);
|
|
289
|
+
|
|
290
|
+
// Check if we're in a field name context (after { or ,)
|
|
291
|
+
// This takes precedence over value context to handle cases like { _id: { $gt
|
|
292
|
+
const fieldMatch = before.match(/(?:\{|,)\s*([^:\s]*)$/);
|
|
293
|
+
if (fieldMatch) {
|
|
294
|
+
const token = fieldMatch[1];
|
|
295
|
+
return {
|
|
296
|
+
token,
|
|
297
|
+
role: 'fieldName',
|
|
298
|
+
startPos: cursorPos - token.length
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Check if we're in a value context (after a colon)
|
|
303
|
+
// Match the last colon followed by optional whitespace and capture everything after
|
|
304
|
+
const valueMatch = before.match(/:\s*([^\s,\}\]:]*)$/);
|
|
305
|
+
if (valueMatch) {
|
|
306
|
+
const token = valueMatch[1];
|
|
307
|
+
return {
|
|
308
|
+
token,
|
|
309
|
+
role: 'value',
|
|
310
|
+
startPos: cursorPos - token.length
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function getAutocompleteSuggestions(trie, searchText, cursorPos, schemaPaths) {
|
|
318
|
+
const context = getAutocompleteContext(searchText, cursorPos);
|
|
319
|
+
|
|
320
|
+
if (!context) {
|
|
321
|
+
return [];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const { token, role } = context;
|
|
325
|
+
|
|
326
|
+
// Extract the actual term without quotes
|
|
327
|
+
const leadingQuoteMatch = token.match(/^["']/);
|
|
328
|
+
const trailingQuoteMatch = token.length > 1 && /["']$/.test(token)
|
|
329
|
+
? token[token.length - 1]
|
|
330
|
+
: '';
|
|
331
|
+
const term = token
|
|
332
|
+
.replace(/^["']/, '')
|
|
333
|
+
.replace(trailingQuoteMatch ? new RegExp(`[${trailingQuoteMatch}]$`) : '', '')
|
|
334
|
+
.trim();
|
|
335
|
+
|
|
336
|
+
if (!term) {
|
|
337
|
+
return [];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const primarySuggestions = trie.getSuggestions(term, 10, role);
|
|
341
|
+
const suggestionsSet = new Set(primarySuggestions);
|
|
342
|
+
|
|
343
|
+
// Add schema path suggestions for field names
|
|
344
|
+
if (role === 'fieldName' && Array.isArray(schemaPaths) && schemaPaths.length > 0) {
|
|
345
|
+
for (const schemaPath of schemaPaths) {
|
|
346
|
+
const path = schemaPath?.path;
|
|
347
|
+
if (
|
|
348
|
+
typeof path === 'string' &&
|
|
349
|
+
path.startsWith(`${term}.`) &&
|
|
350
|
+
!suggestionsSet.has(path)
|
|
351
|
+
) {
|
|
352
|
+
suggestionsSet.add(path);
|
|
353
|
+
if (suggestionsSet.size >= 10) {
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
let suggestions = Array.from(suggestionsSet);
|
|
361
|
+
|
|
362
|
+
// Preserve quotes if present
|
|
363
|
+
if (leadingQuoteMatch) {
|
|
364
|
+
const leadingQuote = leadingQuoteMatch[0];
|
|
365
|
+
suggestions = suggestions.map(suggestion => `${leadingQuote}${suggestion}`);
|
|
366
|
+
}
|
|
367
|
+
if (trailingQuoteMatch) {
|
|
368
|
+
suggestions = suggestions.map(suggestion =>
|
|
369
|
+
suggestion.endsWith(trailingQuoteMatch) ? suggestion : `${suggestion}${trailingQuoteMatch}`
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return suggestions;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function applySuggestion(searchText, cursorPos, suggestion) {
|
|
377
|
+
const before = searchText.slice(0, cursorPos);
|
|
378
|
+
const after = searchText.slice(cursorPos);
|
|
379
|
+
|
|
380
|
+
// Check if we're in a value context
|
|
381
|
+
const valueMatch = before.match(/:\s*([^\s,\}\]:]*)$/);
|
|
382
|
+
if (valueMatch) {
|
|
383
|
+
const token = valueMatch[1];
|
|
384
|
+
const start = cursorPos - token.length;
|
|
385
|
+
let replacement = suggestion;
|
|
386
|
+
let cursorOffset = replacement.length;
|
|
387
|
+
|
|
388
|
+
// Add parentheses for function helpers and position cursor inside
|
|
389
|
+
if (FUNCTION_HELPERS.has(suggestion)) {
|
|
390
|
+
replacement = `${suggestion}()`;
|
|
391
|
+
cursorOffset = suggestion.length + 1; // Position cursor between ()
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
text: searchText.slice(0, start) + replacement + after,
|
|
396
|
+
newCursorPos: start + cursorOffset
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Check if we're in a field name context
|
|
401
|
+
const fieldMatch = before.match(/(?:\{|,)\s*([^:\s]*)$/);
|
|
402
|
+
if (fieldMatch) {
|
|
403
|
+
const token = fieldMatch[1];
|
|
404
|
+
const start = cursorPos - token.length;
|
|
405
|
+
let replacement = suggestion;
|
|
406
|
+
|
|
407
|
+
const leadingQuote = token.startsWith('"') || token.startsWith('\'') ? token[0] : '';
|
|
408
|
+
const trailingQuote = token.length > 1 && (token.endsWith('"') || token.endsWith('\'')) ? token[token.length - 1] : '';
|
|
409
|
+
const colonNeeded = !/^\s*:/.test(after);
|
|
410
|
+
|
|
411
|
+
// If suggestion already has quotes, use it as-is
|
|
412
|
+
const suggestionHasQuotes = (suggestion.startsWith('"') || suggestion.startsWith('\'')) &&
|
|
413
|
+
(suggestion.endsWith('"') || suggestion.endsWith('\''));
|
|
414
|
+
if (suggestionHasQuotes) {
|
|
415
|
+
replacement = suggestion;
|
|
416
|
+
} else {
|
|
417
|
+
if (leadingQuote && !replacement.startsWith(leadingQuote)) {
|
|
418
|
+
replacement = `${leadingQuote}${replacement}`;
|
|
419
|
+
}
|
|
420
|
+
if (trailingQuote && !replacement.endsWith(trailingQuote)) {
|
|
421
|
+
replacement = `${replacement}${trailingQuote}`;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Only insert : if we know the user isn't entering in a nested path
|
|
426
|
+
// If suggestion has full quotes or user typed both quotes, add colon
|
|
427
|
+
if (colonNeeded && (suggestionHasQuotes || !leadingQuote || trailingQuote)) {
|
|
428
|
+
replacement = `${replacement}:`;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return {
|
|
432
|
+
text: searchText.slice(0, start) + replacement + after,
|
|
433
|
+
newCursorPos: start + replacement.length
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
module.exports = {
|
|
441
|
+
buildAutocompleteTrie,
|
|
442
|
+
getAutocompleteContext,
|
|
443
|
+
getAutocompleteSuggestions,
|
|
444
|
+
applySuggestion,
|
|
445
|
+
QUERY_SELECTORS,
|
|
446
|
+
VALUE_HELPERS,
|
|
447
|
+
FUNCTION_HELPERS
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
|
|
206
451
|
/***/ },
|
|
207
452
|
|
|
208
453
|
/***/ "./frontend/src/api.js"
|
|
@@ -754,7 +999,7 @@ module.exports = app => app.component('async-button', {
|
|
|
754
999
|
(module) {
|
|
755
1000
|
|
|
756
1001
|
"use strict";
|
|
757
|
-
module.exports = "<div class=\"relative border rounded bg-gray-100 text-black text-sm overflow-hidden\">\n <div class=\"flex border-b pt-[1px] text-xs font-medium bg-gray-200\">\n <button\n class=\"px-3 py-1 border-r border-gray-300 hover:bg-green-300\"\n :class=\"{'bg-gray-300': activeTab === 'code', 'bg-green-300': activeTab === 'code'}\"\n @click=\"activeTab = 'code'\">\n Code\n </button>\n <button\n class=\"px-3 py-1 hover:bg-green-300\"\n :class=\"{'bg-green-300': activeTab === 'output'}\"\n @click=\"activeTab = 'output'\">\n Output\n </button>\n <div class=\"ml-auto mr-1 flex\">\n <button\n v-if=\"activeTab === 'output'\"\n class=\"px-2 py-1 mr-1 text-xs bg-gray-500 text-white border-none rounded cursor-pointer hover:bg-gray-600 transition-colors flex items-center\"\n @click=\"copyOutput\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3\" />\n </svg>\n </button>\n <button\n v-if=\"activeTab === 'output'\"\n class=\"px-2 py-1 mr-1 text-xs bg-blue-500 text-white border-none rounded cursor-pointer hover:bg-blue-600 transition-colors flex items-center\"\n @click=\"openDetailModal\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5\" />\n </svg>\n </button>\n <button\n v-if=\"activeTab === 'code' && !isEditing\"\n class=\"px-2 py-1 mr-1 text-xs bg-gray-500 text-white border-none rounded cursor-pointer hover:bg-gray-600 transition-colors flex items-center\"\n @click.stop=\"startEditing\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM4 13.5V16h2.5l7.086-7.086-2.828-2.828L4 13.5z\" />\n </svg>\n </button>\n <async-button\n v-if=\"!isEditing\"\n class=\"px-2 py-1 text-xs bg-green-500 text-white border-none rounded cursor-pointer hover:bg-green-600 transition-colors disabled:bg-gray-400\"\n @click=\"executeScript\">\n Execute\n </async-button>\n <div class=\"relative ml-1\" ref=\"dropdown\">\n <button\n @click.stop=\"toggleDropdown\"\n class=\"px-1 py-1 text-xs hover:bg-gray-300 rounded flex items-center\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4z\" />\n </svg>\n </button>\n <div\n v-if=\"showDropdown\"\n class=\"absolute right-0 z-10 mt-1 w-64 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5\">\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100\"\n @click=\"openCreateDashboardModal(); showDropdown = false\">\n Create Dashboard\n </button>\n <button\n v-if=\"canOverwriteDashboard\"\n class=\"block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100\"\n @click=\"openOverwriteDashboardConfirmation(); showDropdown = false\">\n Overwrite Dashboard\n </button>\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100\"\n @click=\"$emit('copyMessage'); showDropdown = false\">\n Copy Full Message\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"p-3 max-h-[50vh] max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] overflow-y-auto\" v-show=\"activeTab === 'code'\">\n <div v-if=\"isEditing\" class=\"flex flex-col space-y-2\">\n <div class=\"border border-gray-200\">\n <textarea ref=\"scriptEditor\" class=\"w-full h-[45vh]\" @input=\"handleScriptInput\"></textarea>\n </div>\n <div class=\"flex justify-end gap-2\">\n <button\n class=\"px-2 py-1 text-xs bg-gray-300 text-gray-800 border-none rounded cursor-pointer hover:bg-gray-400 transition-colors\"\n @click.stop=\"cancelEditing\">\n Cancel\n </button>\n <async-button\n class=\"px-2 py-1 text-xs bg-green-500 text-white border-none rounded cursor-pointer hover:bg-green-600 transition-colors disabled:bg-gray-400\"\n @click=\"executeScript\">\n Execute\n </async-button>\n </div>\n </div>\n <pre v-else class=\"whitespace-pre-wrap\"><code v-text=\"script\" ref=\"code\" :class=\"'language-' + language\"></code></pre>\n </div>\n\n <div class=\"p-3 whitespace-pre-wrap max-h-[50vh] overflow-y-auto bg-white border-t max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] relative\" v-show=\"activeTab === 'output'\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" />\n <dashboard-map v-else-if=\"message.executionResult?.output?.$featureCollection\" :value=\"message.executionResult?.output\" />\n <pre v-else>{{ message.executionResult?.output || 'No output' }}</pre>\n\n <div v-if=\"message.executionResult?.logs?.length\" class=\"mt-3 pt-3 border-t border-gray-200\">\n <div class=\"text-xs font-semibold text-gray-600 uppercase tracking-wide\">Console</div>\n <pre class=\"mt-1 bg-gray-100 text-gray-900 p-3 rounded whitespace-pre-wrap overflow-x-auto max-h-[280px]\">{{ message.executionResult.logs }}</pre>\n </div>\n </div>\n\n <modal ref=\"outputModal\" v-if=\"showDetailModal\" containerClass=\"!h-[90vh] !w-[90vw]\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showDetailModal = false;\">×</div>\n <div class=\"h-full overflow-auto\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" :responsive=\"true\" />\n <dashboard-map\n v-else-if=\"message.executionResult?.output?.$featureCollection\"\n :value=\"message.executionResult?.output\"\n height=\"80vh\" />\n <pre v-else class=\"whitespace-pre-wrap\">{{ message.executionResult?.output || 'No output' }}</pre>\n </div>\n </template>\n </modal>\n <modal v-if=\"showCreateDashboardModal\">\n <template #body>\n <div class=\"modal-exit\" @click=\"showCreateDashboardModal = false\">×</div>\n <div>\n <div class=\"mt-4 text-gray-900 font-semibold\">Create Dashboard</div>\n <div class=\"mt-4\">\n <label class=\"block text-sm font-medium leading-6 text-gray-900\">Title</label>\n <div class=\"mt-2\">\n <div class=\"w-full flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-teal-600\">\n <input type=\"text\" v-model=\"newDashboardTitle\" class=\"outline-none block flex-1 border-0 bg-transparent py-1.5 pl-1 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6\" placeholder=\"My Dashboard\">\n </div>\n </div>\n </div>\n <div class=\"my-4\">\n <label class=\"block text-sm font-medium leading-6 text-gray-900\">Code</label>\n <div class=\"border border-gray-200\">\n <textarea class=\"p-2 h-[300px] w-full\" ref=\"dashboardCodeEditor\"></textarea>\n </div>\n </div>\n <async-button\n @click=\"createDashboardFromScript\"\n class=\"rounded-md bg-teal-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n Submit\n </async-button>\n <div v-if=\"createError\" class=\"rounded-md bg-red-50 p-4 mt-1\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">Error</h3>\n <div class=\"mt-2 text-sm text-red-700\">\n {{createError}}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"showOverwriteDashboardConfirmationModal\">\n <template #body>\n <div class=\"modal-exit\" @click=\"showOverwriteDashboardConfirmationModal = false\">×</div>\n <div>\n <div class=\"mt-4 text-gray-900 font-semibold\">Overwrite Dashboard</div>\n <p class=\"mt-2 text-sm text-gray-700\">\n This will replace the linked dashboard's code with the script below.\n </p>\n <p class=\"mt-1 text-xs text-gray-600 break-all\" v-if=\"targetDashboardId\">\n Dashboard ID: {{ targetDashboardId }}\n </p>\n <div class=\"my-4 border border-gray-200 bg-gray-50 rounded\">\n <pre class=\"p-2 h-[300px] overflow-auto whitespace-pre-wrap text-xs\">{{ overwriteDashboardCode }}</pre>\n </div>\n <div class=\"flex items-center gap-2\">\n <async-button\n @click=\"confirmOverwriteDashboard\"\n class=\"rounded-md bg-teal-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n Confirm Overwrite\n </async-button>\n <button\n class=\"px-2.5 py-1.5 rounded-md text-sm font-semibold text-gray-700 bg-gray-200 hover:bg-gray-300\"\n @click=\"showOverwriteDashboardConfirmationModal = false\">\n Cancel\n </button>\n </div>\n <div v-if=\"overwriteError\" class=\"rounded-md bg-red-50 p-4 mt-3\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">Error</h3>\n <div class=\"mt-2 text-sm text-red-700\">\n {{overwriteError}}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
1002
|
+
module.exports = "<div class=\"relative border rounded bg-white my-1 text-black text-sm overflow-hidden\">\n <div class=\"flex border-b py-1 pl-1 text-xs font-medium bg-white\">\n <button\n class=\"px-4 py-1 border-r border-gray-300 text-gray-700 font-semibold transition-colors duration-200 focus:outline-none\"\n :class=\"[\n 'rounded-l-md',\n activeTab === 'code'\n ? 'bg-gray-700 text-white shadow'\n : 'bg-gray-100 hover:bg-gray-200 text-gray-600'\n ]\"\n @click=\"activeTab = 'code'\">\n Code\n </button>\n <button\n class=\"px-4 py-1 text-gray-700 font-semibold transition-colors duration-200 focus:outline-none\"\n :class=\"[\n 'rounded-r-md',\n activeTab === 'output'\n ? 'bg-gray-700 text-white shadow'\n : 'bg-gray-100 hover:bg-gray-200 text-gray-600'\n ]\"\n @click=\"activeTab = 'output'\">\n Output\n </button>\n <div class=\"ml-auto mr-1 flex\">\n <button\n v-if=\"activeTab === 'output'\"\n class=\"px-2 py-1 mr-1 text-xs bg-blue-500 text-white border-none rounded cursor-pointer hover:bg-blue-600 transition-colors flex items-center\"\n @click=\"openDetailModal\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5\" />\n </svg>\n </button>\n <button\n v-if=\"activeTab === 'code' && !isEditing\"\n class=\"px-2 py-1 mr-1 text-xs bg-gray-500 text-white border-none rounded cursor-pointer hover:bg-gray-600 transition-colors flex items-center\"\n @click.stop=\"startEditing\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM4 13.5V16h2.5l7.086-7.086-2.828-2.828L4 13.5z\" />\n </svg>\n </button>\n <async-button\n v-if=\"!isEditing\"\n class=\"px-2 py-1 text-xs bg-blue-600 hover:bg-blue-700 text-white border-none rounded cursor-pointer transition-colors disabled:bg-gray-400\"\n @click=\"executeScript\">\n Run\n </async-button>\n <div class=\"relative ml-1\" ref=\"dropdown\">\n <button\n @click.stop=\"toggleDropdown\"\n class=\"px-1 py-1 text-xs hover:bg-gray-300 rounded flex items-center\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4z\" />\n </svg>\n </button>\n <div\n v-if=\"showDropdown\"\n class=\"absolute right-0 z-10 mt-1 w-64 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5\">\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100\"\n @click=\"openCreateDashboardModal(); showDropdown = false\">\n Create Dashboard\n </button>\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100\"\n @click=\"copyOutput(); showDropdown = false\">\n Copy Output As Text\n </button>\n <button\n v-if=\"canOverwriteDashboard\"\n class=\"block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100\"\n @click=\"openOverwriteDashboardConfirmation(); showDropdown = false\">\n Overwrite Dashboard\n </button>\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100\"\n @click=\"$emit('copyMessage'); showDropdown = false\">\n Copy Full Message\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"p-0 max-h-[50vh] max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] overflow-y-auto\" v-show=\"activeTab === 'code'\">\n <div v-if=\"isEditing\" class=\"flex flex-col space-y-2\">\n <div class=\"border border-gray-200\">\n <textarea ref=\"scriptEditor\" class=\"w-full h-[45vh]\" @input=\"handleScriptInput\"></textarea>\n </div>\n <div class=\"flex justify-end gap-2 pb-2\">\n <button\n class=\"px-2 py-1 text-xs bg-gray-300 text-gray-800 border-none rounded cursor-pointer hover:bg-gray-400 transition-colors\"\n @click.stop=\"cancelEditing\">\n Cancel\n </button>\n <async-button\n class=\"px-2 py-1 text-xs bg-blue-600 text-white border-none rounded cursor-pointer hover:bg-blue-700 transition-colors disabled:bg-gray-400\"\n @click=\"executeScript\">\n Execute\n </async-button>\n </div>\n </div>\n <pre v-else class=\"whitespace-pre-wrap !my-0 !bg-zinc-50\"><code v-text=\"script\" ref=\"code\" :class=\"'language-' + language\"></code></pre>\n </div>\n\n <div class=\"p-3 whitespace-pre-wrap max-h-[50vh] overflow-y-auto bg-white border-t max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] relative\" v-show=\"activeTab === 'output'\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" />\n <dashboard-map v-else-if=\"message.executionResult?.output?.$featureCollection\" :value=\"message.executionResult?.output\" />\n <pre v-else>{{ message.executionResult?.output || 'No output' }}</pre>\n\n <div v-if=\"message.executionResult?.logs?.length\" class=\"mt-3 pt-3 border-t border-gray-200\">\n <div class=\"text-xs font-semibold text-gray-600 uppercase tracking-wide\">Console</div>\n <pre class=\"mt-1 bg-gray-100 text-gray-900 p-3 rounded whitespace-pre-wrap overflow-x-auto max-h-[280px]\">{{ message.executionResult.logs }}</pre>\n </div>\n </div>\n\n <modal ref=\"outputModal\" v-if=\"showDetailModal\" containerClass=\"!h-[90vh] !w-[90vw]\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showDetailModal = false;\">×</div>\n <div class=\"h-full overflow-auto\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" :responsive=\"true\" />\n <dashboard-map\n v-else-if=\"message.executionResult?.output?.$featureCollection\"\n :value=\"message.executionResult?.output\"\n height=\"80vh\" />\n <pre v-else class=\"whitespace-pre-wrap\">{{ message.executionResult?.output || 'No output' }}</pre>\n </div>\n </template>\n </modal>\n <modal v-if=\"showCreateDashboardModal\">\n <template #body>\n <div class=\"modal-exit\" @click=\"showCreateDashboardModal = false\">×</div>\n <div>\n <div class=\"mt-4 text-gray-900 font-semibold\">Create Dashboard</div>\n <div class=\"mt-4\">\n <label class=\"block text-sm font-medium leading-6 text-gray-900\">Title</label>\n <div class=\"mt-2\">\n <div class=\"w-full flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-teal-600\">\n <input type=\"text\" v-model=\"newDashboardTitle\" class=\"outline-none block flex-1 border-0 bg-transparent py-1.5 pl-1 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6\" placeholder=\"My Dashboard\">\n </div>\n </div>\n </div>\n <div class=\"my-4\">\n <label class=\"block text-sm font-medium leading-6 text-gray-900\">Code</label>\n <div class=\"border border-gray-200\">\n <textarea class=\"p-2 h-[300px] w-full\" ref=\"dashboardCodeEditor\"></textarea>\n </div>\n </div>\n <async-button\n @click=\"createDashboardFromScript\"\n class=\"rounded-md bg-teal-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n Submit\n </async-button>\n <div v-if=\"createError\" class=\"rounded-md bg-red-50 p-4 mt-1\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">Error</h3>\n <div class=\"mt-2 text-sm text-red-700\">\n {{createError}}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"showOverwriteDashboardConfirmationModal\">\n <template #body>\n <div class=\"modal-exit\" @click=\"showOverwriteDashboardConfirmationModal = false\">×</div>\n <div>\n <div class=\"mt-4 text-gray-900 font-semibold\">Overwrite Dashboard</div>\n <p class=\"mt-2 text-sm text-gray-700\">\n This will replace the linked dashboard's code with the script below.\n </p>\n <p class=\"mt-1 text-xs text-gray-600 break-all\" v-if=\"targetDashboardId\">\n Dashboard ID: {{ targetDashboardId }}\n </p>\n <div class=\"my-4 border border-gray-200 bg-gray-50 rounded\">\n <pre class=\"p-2 h-[300px] overflow-auto whitespace-pre-wrap text-xs\">{{ overwriteDashboardCode }}</pre>\n </div>\n <div class=\"flex items-center gap-2\">\n <async-button\n @click=\"confirmOverwriteDashboard\"\n class=\"rounded-md bg-teal-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n Confirm Overwrite\n </async-button>\n <button\n class=\"px-2.5 py-1.5 rounded-md text-sm font-semibold text-gray-700 bg-gray-200 hover:bg-gray-300\"\n @click=\"showOverwriteDashboardConfirmationModal = false\">\n Cancel\n </button>\n </div>\n <div v-if=\"overwriteError\" class=\"rounded-md bg-red-50 p-4 mt-3\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">Error</h3>\n <div class=\"mt-2 text-sm text-red-700\">\n {{overwriteError}}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
758
1003
|
|
|
759
1004
|
/***/ },
|
|
760
1005
|
|
|
@@ -1128,7 +1373,7 @@ module.exports = app => app.component('chat-message', {
|
|
|
1128
1373
|
(module) {
|
|
1129
1374
|
|
|
1130
1375
|
"use strict";
|
|
1131
|
-
module.exports = "<div class=\"flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <div
|
|
1376
|
+
module.exports = "<div class=\"flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <div\n class=\"fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10\"\n @click=\"hideSidebar = false\"\n v-show=\"hideSidebar\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"#5f6368\"><path d=\"M360-120v-720h80v720h-80Zm160-160v-400l200 200-200 200Z\"/></svg>\n </div>\n <button\n class=\"fixed top-[65px] right-4 z-10 p-2 rounded-md shadow bg-white\"\n :class=\"hasWorkspace ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 cursor-not-allowed bg-gray-50'\"\n @click=\"toggleShareThread\"\n :disabled=\"!hasWorkspace || !chatThreadId || sharingThread\"\n aria-label=\"Share thread with workspace\"\n title=\"Share thread with workspace\"\n >\n <svg v-if=\"hasWorkspace\" xmlns=\"http://www.w3.org/2000/svg\" class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><path d=\"M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7a2.48 2.48 0 0 0 0-1.39l7.02-4.11a2.5 2.5 0 1 0-.87-1.37L8.04 9.94a2.5 2.5 0 1 0 0 4.12l7.12 4.16a2.5 2.5 0 1 0 .84-1.34l-7.05-4.12c-.04-.02-.08-.05-.11-.07a2.48 2.48 0 0 0 0-1.39c.03-.02.07-.04.11-.07l7.11-4.16c.52.47 1.2.76 1.94.76a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0-1.94.94L7.97 8.43a2.5 2.5 0 1 0 0 7.14l9.09 5.3c.52-.47 1.2-.76 1.94-.76a2.5 2.5 0 1 0 0-5z\"/></svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><path d=\"M12 1a5 5 0 00-5 5v3H6a2 2 0 00-2 2v9a2 2 0 002 2h12a2 2 0 002-2v-9a2 2 0 00-2-2h-1V6a5 5 0 00-5-5zm-3 8V6a3 3 0 016 0v3H9zm9 2v9H6v-9h12z\"/></svg>\n </button>\n <!-- Sidebar: Chat Threads -->\n <aside class=\"border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-64 fixed lg:relative\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-64' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-64 overflow-x-hidden\">\n <div class=\"p-1 ml-2 font-bold\">Chat Threads</div>\n <button\n @click=\"hideSidebar = true\"\n class=\"ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none\"\n aria-label=\"Close sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"currentColor\"><path d=\"M660-320v-320L500-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z\"/></svg>\n </button>\n </div>\n <div class=\"p-4 w-64\">\n <async-button\n @click=\"createNewThread\"\n class=\"w-full bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700\"\n >\n Create New Thread\n </async-button>\n </div>\n <div v-if=\"status === 'loaded' && chatThreads.length === 0\" class=\"p-4 text-sm text-gray-700\">\n No threads yet\n </div>\n <ul v-if=\"status === 'loaded'\" class=\"w-full\">\n <li\n v-for=\"thread in chatThreads\"\n :key=\"thread._id\"\n @click=\"selectThread(thread._id)\"\n class=\"w-full p-2 hover:bg-ultramarine-100 cursor-pointer text-sm text-gray-700\"\n :class=\"{ 'bg-ultramarine-200 text-gray-900': thread._id === chatThreadId }\"\n >\n {{ thread.title || 'Untitled Thread' }}\n </li>\n </ul>\n </aside>\n\n <!-- Main Chat Area -->\n <main class=\"flex-1 flex flex-col bg-slate-50\">\n <div class=\"flex-1 overflow-y-auto p-6 space-y-4\" ref=\"messagesContainer\">\n <div v-if=\"chatMessages?.length === 0\">\n <div class=\"flex items-center w-full h-full justify-center py-3 mb-4\">\n <p class=\"mx-4 font-bold text-gray-900\">\n Ask Mongoose Studio to analyze your data or generate a script\n </p>\n </div>\n </div>\n <ul role=\"list\" class=\"space-y-4\" v-else>\n <li v-for=\"message in chatMessages\" :key=\"message._id\">\n <chat-message :message=\"message\" :target-dashboard-id=\"currentThread?.dashboardId\"></chat-message>\n </li>\n </ul>\n </div>\n\n\n <!-- Input Area -->\n <div class=\"border-t p-4\">\n <form @submit.prevent=\"sendMessage\" :disabled=\"sendingMessage\" class=\"flex gap-2 items-end justify-end\">\n <textarea\n v-model=\"newMessage\"\n :placeholder=\"sendingMessage ? 'Sending...' : 'Ask about your data, generate a query, or build a chart…'\"\n class=\"flex-1 border rounded px-4 py-2 resize-none overflow-y-auto shadow-sm\"\n :disabled=\"sendingMessage\"\n rows=\"2\"\n ref=\"messageInput\"\n @input=\"adjustTextareaHeight\"\n @keydown.enter.exact.prevent=\"handleEnter\"\n ></textarea>\n <button class=\"bg-blue-600 text-white px-4 h-[42px] rounded disabled:bg-gray-600\" :disabled=\"sendingMessage\">\n <svg v-if=\"sendingMessage\" style=\"height: 1em\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <g>\n <circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" opacity=\"0.3\" />\n <path d=\"M12 2a10 10 0 0 1 10 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <animateTransform attributeName=\"transform\" type=\"rotate\" from=\"0 12 12\" to=\"360 12 12\" dur=\"1s\" repeatCount=\"indefinite\" />\n </path>\n </g>\n </svg>\n <span v-else>Send</span>\n </button>\n </form>\n </div>\n </main>\n</div>\n";
|
|
1132
1377
|
|
|
1133
1378
|
/***/ },
|
|
1134
1379
|
|
|
@@ -1322,6 +1567,8 @@ module.exports = app => app.component('chat', {
|
|
|
1322
1567
|
});
|
|
1323
1568
|
});
|
|
1324
1569
|
}
|
|
1570
|
+
|
|
1571
|
+
this.$refs.messageInput.focus();
|
|
1325
1572
|
}
|
|
1326
1573
|
});
|
|
1327
1574
|
|
|
@@ -2418,7 +2665,7 @@ module.exports = ".document-details {\n width: 100%;\n}\n\n.document-details .v
|
|
|
2418
2665
|
(module) {
|
|
2419
2666
|
|
|
2420
2667
|
"use strict";
|
|
2421
|
-
module.exports = "<div class=\"document-details\">\n <!-- View Toggle and Search/Filter Bar -->\n <div class=\"sticky top-[60px] z-40 bg-white p-4 border-b border-gray-200 shadow-sm\">\n\n <!-- Search and Filter Bar (only show in fields view) -->\n <div v-if=\"viewMode === 'fields'\" class=\"flex md:gap-3\">\n <!-- Search Bar -->\n <div class=\"relative flex-1\">\n <input\n ref=\"searchInput\"\n v-model=\"searchQuery\"\n type=\"text\"\n placeholder=\"Search fields...\"\n class=\"w-full px-4 py-2 pl-10 pr-4 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n <div class=\"absolute inset-y-0 left-0 flex items-center pl-3\">\n <svg class=\"w-4 h-4 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"></path>\n </svg>\n </div>\n </div>\n\n <!-- Data Type Filter -->\n <div class=\"relative hidden md:block\">\n <select\n v-model=\"selectedType\"\n class=\"px-4 py-2 pr-8 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white min-w-[140px] appearance-none\"\n >\n <option value=\"\">All Types</option>\n <option v-for=\"type in availableTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <div class=\"absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none\">\n <svg class=\"w-4 h-4 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n </div>\n </div>\n\n <!-- Add Field Button -->\n <button\n @click=\"openAddFieldModal\"\n class=\"hidden md:flex px-4 py-2 text-sm font-medium text-white bg-green-600 hover:bg-green-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 items-center gap-2\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 4v16m8-8H4\"></path>\n </svg> Add Field\n </button>\n </div>\n </div>\n\n <!-- Fields View -->\n <div v-if=\"viewMode === 'fields'\">\n <!-- 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-gray-200 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-gray-200 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-gray-500 mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-gray-900\">{{path.name}}</span>\n <span v-if=\"path.isVirtual\" class=\"ml-2 text-sm text-purple-600\">(virtual - {{getVirtualFieldType(path)}})</span>\n <span v-else class=\"ml-2 text-sm text-blue-600\">(user-added - {{getVirtualFieldType(path)}})</span>\n </div>\n </div>\n\n <!-- Virtual Field Content (Collapsible) -->\n <div v-if=\"!isVirtualFieldCollapsed(path.name)\" class=\"p-3\">\n <div v-if=\"path.value == null\" class=\"text-sky-800\">\n {{'' + path.value}}\n </div>\n <div v-else>\n {{path.value}}\n </div>\n </div>\n </div>\n\n <!-- 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-gray-200 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-gray-50 hover:bg-gray-100 cursor-pointer flex items-center justify-between border-b border-gray-200 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-gray-500 mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-gray-900\">{{path.name}}</span>\n <span v-if=\"path.isVirtual\" class=\"ml-2 text-sm text-purple-600\">(virtual - {{getVirtualFieldType(path)}})</span>\n <span v-else class=\"ml-2 text-sm text-blue-600\">(user-added - {{getVirtualFieldType(path)}})</span>\n </div>\n </div>\n\n <!-- Virtual Field Content (Collapsible) -->\n <div v-if=\"!isVirtualFieldCollapsed(path.name)\" class=\"p-3\">\n <div v-if=\"path.value == null\" class=\"text-sky-800\">\n {{'' + path.value}}\n </div>\n <div v-else>\n {{path.value}}\n </div>\n </div>\n </div>\n\n <!-- No Results Message -->\n <div v-if=\"searchQuery.trim() && !hasSearchMatches\" class=\"text-center py-8 text-gray-500\">\n <svg class=\"w-12 h-12 mx-auto mb-4 text-gray-300\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9.172 16.172a4 4 0 015.656 0M9 12h6m-6-4h6m2 5.291A7.962 7.962 0 0112 15c-2.34 0-4.29-1.009-5.824-2.709M15 6.291A7.962 7.962 0 0012 5c-2.34 0-4.29 1.009-5.824 2.709\"></path>\n </svg>\n <p>No fields found matching \"{{searchQuery}}\"</p>\n </div>\n </div>\n\n <!-- JSON View -->\n <div v-if=\"viewMode === 'json'\" class=\"json-view\">\n <div class=\"border border-gray-300 rounded-lg bg-gray-50 p-4 overflow-auto\">\n <pre class=\"text-sm font-mono text-gray-800 whitespace-pre\">{{formattedJson}}</pre>\n </div>\n </div>\n\n <!-- Add Field Modal -->\n <modal v-if=\"showAddFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"closeAddFieldModal\">×</div>\n <div class=\"add-field-modal\">\n <div class=\"mb-6\">\n <h2 class=\"text-xl font-semibold text-gray-900 mb-2\">Add New Field</h2>\n <p class=\"text-sm text-gray-600\">Create a new field for this document</p>\n </div>\n\n <form @submit.prevent=\"handleAddFieldSubmit\" class=\"space-y-4\">\n <!-- Field Name -->\n <div>\n <label for=\"fieldName\" class=\"block text-sm font-medium text-gray-700 mb-1\">\n Field Name *\n </label>\n <input\n id=\"fieldName\"\n v-model=\"fieldData.name\"\n type=\"text\"\n required\n placeholder=\"Enter field name...\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.name }\"\n />\n <p v-if=\"fieldErrors.name\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.name}}</p>\n <p v-if=\"fieldData.name && getTransformedFieldName() !== fieldData.name.trim()\" class=\"mt-1 text-sm text-blue-600\">\n Field name will be: <code class=\"bg-blue-50 px-1 py-0.5 rounded text-xs\">{{getTransformedFieldName()}}</code>\n </p>\n </div>\n\n <!-- Field Type -->\n <div>\n <label for=\"fieldType\" class=\"block text-sm font-medium text-gray-700 mb-1\">\n Field Type *\n </label>\n <select\n id=\"fieldType\"\n v-model=\"fieldData.type\"\n required\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.type }\"\n >\n <option value=\"\">Select field type...</option>\n <option v-for=\"type in allFieldTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <p v-if=\"fieldErrors.type\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.type}}</p>\n </div>\n\n <!-- Field Value -->\n <div>\n <label for=\"fieldValue\" class=\"block text-sm font-medium text-gray-700 mb-1\">\n Initial Value\n </label>\n\n <!-- Date picker for Date type -->\n <input\n v-if=\"shouldUseDatePicker\"\n v-model=\"fieldData.value\"\n type=\"date\"\n id=\"fieldValue\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <!-- Simple input for basic types -->\n <input\n v-else-if=\"!shouldUseCodeMirror\"\n v-model=\"fieldData.value\"\n type=\"text\"\n id=\"fieldValue\"\n placeholder=\"Enter initial value (optional)...\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <!-- CodeMirror textarea for complex types -->\n <textarea\n v-else\n ref=\"fieldValueEditor\"\n id=\"fieldValue\"\n rows=\"3\"\n placeholder=\"Enter initial value (optional)...\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n ></textarea>\n\n <p v-if=\"fieldErrors.value\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.value}}</p>\n <p class=\"mt-1 text-xs text-gray-500\">\n <span v-if=\"shouldUseDatePicker\">Select a date or leave empty for null/undefined values.</span>\n <span v-else-if=\"shouldUseCodeMirror\">Leave empty for null/undefined values. Use valid JSON format.</span>\n <span v-else>Leave empty for null/undefined values.</span>\n </p>\n </div>\n\n\n <!-- Action Buttons -->\n <div class=\"flex justify-end gap-3 pt-4 border-t border-gray-200\">\n <button\n type=\"button\"\n @click=\"closeAddFieldModal\"\n class=\"px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n :disabled=\"isSubmittingField\"\n class=\"px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <span v-if=\"isSubmittingField\">Adding...</span>\n <span v-else>Add Field</span>\n </button>\n </div>\n </form>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
2668
|
+
module.exports = "<div class=\"document-details\">\n <!-- View Toggle and Search/Filter Bar -->\n <div class=\"sticky top-[60px] z-40 bg-white rounded-md p-4 border-b border-gray-200 shadow-sm\">\n\n <!-- Search and Filter Bar (only show in fields view) -->\n <div v-if=\"viewMode === 'fields'\" class=\"flex md:gap-3\">\n <!-- Search Bar -->\n <div class=\"relative flex-1\">\n <input\n ref=\"searchInput\"\n v-model=\"searchQuery\"\n type=\"text\"\n placeholder=\"Search fields...\"\n class=\"w-full px-4 py-2 pl-10 pr-4 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n <div class=\"absolute inset-y-0 left-0 flex items-center pl-3\">\n <svg class=\"w-4 h-4 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"></path>\n </svg>\n </div>\n </div>\n\n <!-- Data Type Filter -->\n <div class=\"relative hidden md:block\">\n <select\n v-model=\"selectedType\"\n class=\"px-4 py-2 pr-8 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white min-w-[140px] appearance-none\"\n >\n <option value=\"\">All Types</option>\n <option v-for=\"type in availableTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <div class=\"absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none\">\n <svg class=\"w-4 h-4 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n </div>\n </div>\n </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-gray-200 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-gray-200 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-gray-500 mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-gray-900\">{{path.name}}</span>\n <span v-if=\"path.isVirtual\" class=\"ml-2 text-sm text-purple-600\">(virtual - {{getVirtualFieldType(path)}})</span>\n <span v-else class=\"ml-2 text-sm text-blue-600\">(user-added - {{getVirtualFieldType(path)}})</span>\n </div>\n </div>\n\n <!-- Virtual Field Content (Collapsible) -->\n <div v-if=\"!isVirtualFieldCollapsed(path.name)\" class=\"p-3\">\n <div v-if=\"path.value == null\" class=\"text-sky-800\">\n {{'' + path.value}}\n </div>\n <div v-else>\n {{path.value}}\n </div>\n </div>\n </div>\n\n <!-- 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-gray-200 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-gray-50 hover:bg-gray-100 cursor-pointer flex items-center justify-between border-b border-gray-200 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-gray-500 mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-gray-900\">{{path.name}}</span>\n <span v-if=\"path.isVirtual\" class=\"ml-2 text-sm text-purple-600\">(virtual - {{getVirtualFieldType(path)}})</span>\n <span v-else class=\"ml-2 text-sm text-blue-600\">(user-added - {{getVirtualFieldType(path)}})</span>\n </div>\n </div>\n\n <!-- Virtual Field Content (Collapsible) -->\n <div v-if=\"!isVirtualFieldCollapsed(path.name)\" class=\"p-3\">\n <div v-if=\"path.value == null\" class=\"text-sky-800\">\n {{'' + path.value}}\n </div>\n <div v-else>\n {{path.value}}\n </div>\n </div>\n </div>\n\n <!-- No Results Message -->\n <div v-if=\"searchQuery.trim() && !hasSearchMatches\" class=\"text-center py-8 text-gray-500\">\n <svg class=\"w-12 h-12 mx-auto mb-4 text-gray-300\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9.172 16.172a4 4 0 015.656 0M9 12h6m-6-4h6m2 5.291A7.962 7.962 0 0112 15c-2.34 0-4.29-1.009-5.824-2.709M15 6.291A7.962 7.962 0 0012 5c-2.34 0-4.29 1.009-5.824 2.709\"></path>\n </svg>\n <p>No fields found matching \"{{searchQuery}}\"</p>\n </div>\n </div>\n\n <!-- JSON View -->\n <div v-if=\"viewMode === 'json'\" class=\"json-view\">\n <div class=\"border border-gray-300 rounded-lg bg-gray-50 p-4 overflow-auto\">\n <pre class=\"text-sm font-mono text-gray-800 whitespace-pre\">{{formattedJson}}</pre>\n </div>\n </div>\n\n <!-- Add Field Modal -->\n <modal v-if=\"showAddFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"closeAddFieldModal\">×</div>\n <div class=\"add-field-modal\">\n <div class=\"mb-6\">\n <h2 class=\"text-xl font-semibold text-gray-900 mb-2\">Add New Field</h2>\n <p class=\"text-sm text-gray-600\">Create a new field for this document</p>\n </div>\n\n <form @submit.prevent=\"handleAddFieldSubmit\" class=\"space-y-4\">\n <!-- Field Name -->\n <div>\n <label for=\"fieldName\" class=\"block text-sm font-medium text-gray-700 mb-1\">\n Field Name *\n </label>\n <input\n id=\"fieldName\"\n v-model=\"fieldData.name\"\n type=\"text\"\n required\n placeholder=\"Enter field name...\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.name }\"\n />\n <p v-if=\"fieldErrors.name\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.name}}</p>\n <p v-if=\"fieldData.name && getTransformedFieldName() !== fieldData.name.trim()\" class=\"mt-1 text-sm text-blue-600\">\n Field name will be: <code class=\"bg-blue-50 px-1 py-0.5 rounded text-xs\">{{getTransformedFieldName()}}</code>\n </p>\n </div>\n\n <!-- Field Type -->\n <div>\n <label for=\"fieldType\" class=\"block text-sm font-medium text-gray-700 mb-1\">\n Field Type *\n </label>\n <select\n id=\"fieldType\"\n v-model=\"fieldData.type\"\n required\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.type }\"\n >\n <option value=\"\">Select field type...</option>\n <option v-for=\"type in allFieldTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <p v-if=\"fieldErrors.type\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.type}}</p>\n </div>\n\n <!-- Field Value -->\n <div>\n <label for=\"fieldValue\" class=\"block text-sm font-medium text-gray-700 mb-1\">\n Initial Value\n </label>\n\n <!-- Date picker for Date type -->\n <input\n v-if=\"shouldUseDatePicker\"\n v-model=\"fieldData.value\"\n type=\"date\"\n id=\"fieldValue\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <!-- Simple input for basic types -->\n <input\n v-else-if=\"!shouldUseCodeMirror\"\n v-model=\"fieldData.value\"\n type=\"text\"\n id=\"fieldValue\"\n placeholder=\"Enter initial value (optional)...\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <!-- CodeMirror textarea for complex types -->\n <textarea\n v-else\n ref=\"fieldValueEditor\"\n id=\"fieldValue\"\n rows=\"3\"\n placeholder=\"Enter initial value (optional)...\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n ></textarea>\n\n <p v-if=\"fieldErrors.value\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.value}}</p>\n <p class=\"mt-1 text-xs text-gray-500\">\n <span v-if=\"shouldUseDatePicker\">Select a date or leave empty for null/undefined values.</span>\n <span v-else-if=\"shouldUseCodeMirror\">Leave empty for null/undefined values. Use valid JSON format.</span>\n <span v-else>Leave empty for null/undefined values.</span>\n </p>\n </div>\n\n\n <!-- Action Buttons -->\n <div class=\"flex justify-end gap-3 pt-4 border-t border-gray-200\">\n <button\n type=\"button\"\n @click=\"closeAddFieldModal\"\n class=\"px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n :disabled=\"isSubmittingField\"\n class=\"px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <span v-if=\"isSubmittingField\">Adding...</span>\n <span v-else>Add Field</span>\n </button>\n </div>\n </form>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
2422
2669
|
|
|
2423
2670
|
/***/ },
|
|
2424
2671
|
|
|
@@ -2440,7 +2687,7 @@ appendCSS(__webpack_require__(/*! ./document-details.css */ "./frontend/src/docu
|
|
|
2440
2687
|
|
|
2441
2688
|
module.exports = app => app.component('document-details', {
|
|
2442
2689
|
template,
|
|
2443
|
-
props: ['document', 'schemaPaths', 'virtualPaths', 'editting', 'changes', 'invalid', 'viewMode'],
|
|
2690
|
+
props: ['document', 'schemaPaths', 'virtualPaths', 'editting', 'changes', 'invalid', 'viewMode', 'model'],
|
|
2444
2691
|
data() {
|
|
2445
2692
|
return {
|
|
2446
2693
|
searchQuery: '',
|
|
@@ -2468,6 +2715,8 @@ module.exports = app => app.component('document-details', {
|
|
|
2468
2715
|
this.initializeFieldValueEditor();
|
|
2469
2716
|
}
|
|
2470
2717
|
});
|
|
2718
|
+
|
|
2719
|
+
this.searchQuery = this.getSearchQueryFromRoute();
|
|
2471
2720
|
},
|
|
2472
2721
|
beforeDestroy() {
|
|
2473
2722
|
this.destroyFieldValueEditor();
|
|
@@ -2486,6 +2735,17 @@ module.exports = app => app.component('document-details', {
|
|
|
2486
2735
|
});
|
|
2487
2736
|
}
|
|
2488
2737
|
}
|
|
2738
|
+
},
|
|
2739
|
+
searchQuery(newValue) {
|
|
2740
|
+
this.syncSearchQueryToUrl(newValue);
|
|
2741
|
+
},
|
|
2742
|
+
'$route.query.fieldSearch': {
|
|
2743
|
+
handler(newValue) {
|
|
2744
|
+
const nextValue = typeof newValue === 'string' ? newValue : '';
|
|
2745
|
+
if (nextValue !== this.searchQuery) {
|
|
2746
|
+
this.searchQuery = nextValue;
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2489
2749
|
}
|
|
2490
2750
|
},
|
|
2491
2751
|
computed: {
|
|
@@ -2676,6 +2936,35 @@ module.exports = app => app.component('document-details', {
|
|
|
2676
2936
|
}
|
|
2677
2937
|
},
|
|
2678
2938
|
methods: {
|
|
2939
|
+
getSearchQueryFromRoute() {
|
|
2940
|
+
return this.$route?.query?.fieldSearch || '';
|
|
2941
|
+
},
|
|
2942
|
+
syncSearchQueryToUrl(value) {
|
|
2943
|
+
if (typeof window === 'undefined') {
|
|
2944
|
+
return;
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2947
|
+
const normalizedValue = typeof value === 'string' ? value : '';
|
|
2948
|
+
const shouldStore = normalizedValue.trim().length > 0;
|
|
2949
|
+
const hash = window.location.hash.replace(/^#?/, '');
|
|
2950
|
+
const [hashPath, hashQueryString = ''] = hash.split('?');
|
|
2951
|
+
const params = new URLSearchParams(hashQueryString);
|
|
2952
|
+
const currentValue = params.get('fieldSearch') || '';
|
|
2953
|
+
|
|
2954
|
+
if (normalizedValue === currentValue || (!shouldStore && !currentValue)) {
|
|
2955
|
+
return;
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
if (shouldStore) {
|
|
2959
|
+
params.set('fieldSearch', normalizedValue);
|
|
2960
|
+
} else {
|
|
2961
|
+
params.delete('fieldSearch');
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
const nextQueryString = params.toString();
|
|
2965
|
+
const nextHash = nextQueryString ? `${hashPath}?${nextQueryString}` : hashPath;
|
|
2966
|
+
window.history.replaceState(window.history.state, '', `#${nextHash}`);
|
|
2967
|
+
},
|
|
2679
2968
|
toggleVirtualField(fieldName) {
|
|
2680
2969
|
if (this.collapsedVirtuals.has(fieldName)) {
|
|
2681
2970
|
this.collapsedVirtuals.delete(fieldName);
|
|
@@ -2892,7 +3181,7 @@ module.exports = ".document-details {\n width: 100%;\n }\n \n .document-de
|
|
|
2892
3181
|
(module) {
|
|
2893
3182
|
|
|
2894
3183
|
"use strict";
|
|
2895
|
-
module.exports = "<div class=\"border border-gray-200 rounded-lg mb-2\">\n <!-- Collapsible Header -->\n <div\n @click=\"toggleCollapse\"\n class=\"p-
|
|
3184
|
+
module.exports = "<div class=\"border border-gray-200 bg-white rounded-lg mb-2\">\n <!-- Collapsible Header -->\n <div\n @click=\"toggleCollapse\"\n class=\"p-1 cursor-pointer flex items-center justify-between border-b border-gray-200 transition-colors duration-200 ease-in-out\"\n :class=\"{ 'bg-amber-100 hover:bg-amber-200': highlight, 'bg-slate-100 hover:bg-gray-100': !highlight }\"\n >\n <div class=\"flex items-center\" >\n <svg\n :class=\"isCollapsed ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-gray-500 mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-gray-900\">{{path.path}}</span>\n <span class=\"ml-2 text-sm text-gray-500\">({{(path.instance || 'unknown').toLowerCase()}})</span>\n </div>\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"flex items-center gap-1 text-sm text-gray-600 hover:text-gray-800 px-2 py-1 rounded-md border border-transparent hover:border-gray-300 bg-white\"\n @click.stop.prevent=\"copyPropertyValue\"\n title=\"Copy value\"\n aria-label=\"Copy property value\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7h8m-8 4h8m-8 4h5m-7-9a2 2 0 012-2h7a2 2 0 012 2v10a2 2 0 01-2 2H8l-4-4V7a2 2 0 012-2z\" />\n </svg>\n {{copyButtonLabel}}\n </button>\n <router-link\n v-if=\"path.ref && getValueForPath(path.path)\"\n :to=\"`/model/${path.ref}/document/${getValueForPath(path.path)}`\"\n class=\"bg-ultramarine-600 hover:bg-ultramarine-500 text-white px-2 py-1 text-sm rounded-md\"\n @click.stop\n >View Document\n </router-link>\n </div>\n </div>\n\n <!-- Collapsible Content -->\n <div v-if=\"!isCollapsed\" class=\"p-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 <component\n :is=\"getEditComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :format=\"dateType\"\n v-bind=\"getEditComponentProps(path)\"\n @input=\"changes[path.path] = $event; delete invalid[path.path];\"\n @error=\"invalid[path.path] = $event;\"\n >\n </component>\n </div>\n <div v-else>\n <!-- Show truncated or full value based on needsTruncation and isValueExpanded -->\n <!-- 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-gray-500 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-gray-700 whitespace-pre-wrap break-words font-mono text-sm\">{{truncatedString}}</div>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show more ({{valueAsString.length}} characters)\n </button>\n </div>\n <!-- Expanded view -->\n <div v-else-if=\"needsTruncation && isValueExpanded\" class=\"relative\">\n <component :is=\"getComponentForPath(path)\" :value=\"getValueForPath(path.path)\"></component>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4 rotate-180\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show less\n </button>\n </div>\n <!-- Full view (no truncation needed) -->\n <div v-else>\n <component :is=\"getComponentForPath(path)\" :value=\"getValueForPath(path.path)\"></component>\n </div>\n </div>\n </div>\n</div>\n";
|
|
2896
3185
|
|
|
2897
3186
|
/***/ },
|
|
2898
3187
|
|
|
@@ -3215,7 +3504,7 @@ module.exports = app => app.component('confirm-delete', {
|
|
|
3215
3504
|
(module) {
|
|
3216
3505
|
|
|
3217
3506
|
"use strict";
|
|
3218
|
-
module.exports = ".document
|
|
3507
|
+
module.exports = ".document .document-menu {\n display: flex;\n position: sticky;\n top: 0;\n z-index: 100;\n background-color: white;\n border-radius: 5px;\n padding: 15px 15px;\n margin: -15px 0 15px 0;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n}\n\n.document .document-menu .left {\n flex-grow: 1;\n}\n\n.document .document-menu .right {\n flex-grow: 1;\n text-align: right;\n}\n\n.document .document-menu .right button:not(:last-child) {\n margin-right: 0.5em;\n}\n\n.document button img {\n height: 1em;\n}\n";
|
|
3219
3508
|
|
|
3220
3509
|
/***/ },
|
|
3221
3510
|
|
|
@@ -3226,7 +3515,7 @@ module.exports = ".document {\n max-width: 1200px;\n margin-left: auto;\n mar
|
|
|
3226
3515
|
(module) {
|
|
3227
3516
|
|
|
3228
3517
|
"use strict";
|
|
3229
|
-
module.exports = "<div class=\"document px-1 md:px-0\">\n <div class=\"flex justify-between sticky top-0 z-50 bg-white p-4 border-b border-gray-200 shadow-sm\">\n <div class=\"flex\">\n <button\n @click=\"goBack\"\n class=\"mr-2 rounded-md bg-gray-400 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n ‹ Back\n </button>\n <button\n @click=\"viewMode = 'fields'\"\n :class=\"viewMode === 'fields'\n ? 'bg-blue-600 text-white z-10'\n : 'bg-gray-200 text-gray-700 hover:bg-gray-300'\"\n class=\"px-4 py-2 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-gray-300 border-r-0 rounded-l-lg rounded-r-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 6h16M4 10h16M4 14h16M4 18h16\"></path>\n </svg>\n Fields\n </button>\n <button\n @click=\"viewMode = 'json'\"\n :class=\"viewMode === 'json'\n ? 'bg-blue-600 text-white z-10'\n : 'bg-gray-200 text-gray-700 hover:bg-gray-300'\"\n class=\"px-4 py-2 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-gray-300 rounded-r-lg rounded-l-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4\"></path>\n </svg>\n JSON\n </button>\n </div>\n\n <div class=\"gap-2 hidden md:flex\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true\"\n :disabled=\"!canEdit\"\n :class=\"{'cursor-not-allowed opacity-50': !canEdit}\"\n type=\"button\"\n class=\"rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n <img src=\"images/edit.svg\" class=\"inline\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false\"\n type=\"button\"\n class=\"rounded-md bg-slate-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n × Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n @click=\"shouldShowConfirmModal=true;\"\n type=\"button\"\n class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">\n <img src=\"images/save.svg\" class=\"inline\" /> Save\n </button>\n <button\n @click=\"shouldShowDeleteModal=true;\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n type=\"button\"\n class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">\n <img src=\"images/delete.svg\" class=\"inline\" /> Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true;\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n type=\"button\"\n class=\"rounded-md bg-pink-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">\n <img src=\"images/duplicate.svg\" class=\"inline\" /> Clone\n </button>\n </div>\n <div class=\"md:hidden flex items-center\">\n <div class=\"relative\">\n <button\n @click=\"mobileMenuOpen = !mobileMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"mobileMenuOpen\"\n aria-label=\"Open menu\"\n >\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\n d=\"M4 6h16M4 12h16M4 18h16\"></path>\n </svg>\n </button>\n <div\n v-show=\"mobileMenuOpen\"\n @click.away=\"mobileMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-52 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true; mobileMenuOpen = false\"\n :disabled=\"!canEdit\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canEdit ? 'cursor-not-allowed opacity-50' : 'hover:bg-ultramarine-100']\"\n type=\"button\"\n >\n <img src=\"images/edit.svg\" class=\"inline mr-2\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false; mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-slate-100\"\n >\n × Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-green-100']\"\n @click=\"shouldShowConfirmModal=true; mobileMenuOpen = false\"\n type=\"button\"\n >\n <img src=\"images/save.svg\" class=\"inline mr-2\" /> Save\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n <img src=\"images/delete.svg\" class=\"inline mr-2\" /> Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n <img src=\"images/duplicate.svg\" class=\"inline mr-2\" /> Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div v-if=\"status === 'loaded'\">\n <document-details\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :virtualPaths=\"virtualPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :invalid=\"invalid\"\n :viewMode=\"viewMode\"\n @add-field=\"addField\"\n @view-mode-change=\"updateViewMode\"></document-details>\n <modal v-if=\"shouldShowConfirmModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowConfirmModal = false;\">×</div>\n <confirm-changes @close=\"shouldShowConfirmModal = false;\" @save=\"save\" :value=\"changes\"></confirm-changes>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteModal = false;\">×</div>\n <confirm-delete @close=\"shouldShowDeleteModal = false;\" @remove=\"remove\" :value=\"document\"></confirm-delete>\n </template>\n </modal>\n <modal v-if=\"shouldShowCloneModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCloneModal = false;\">×</div>\n <clone-document :currentModel=\"model\" :doc=\"document\" :schemaPaths=\"schemaPaths\" @close=\"showClonedDocument\"></clone-document>\n </template>\n </modal>\n </div>\n</div>\n";
|
|
3518
|
+
module.exports = "<div class=\"document px-1 pt-4 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-white p-4 border-b border-gray-200 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-blue-600 text-white z-10'\n : 'bg-gray-200 text-gray-700 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-gray-300 border-r-0 rounded-l-lg rounded-r-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 6h16M4 10h16M4 14h16M4 18h16\"></path>\n </svg>\n Fields\n </button>\n <button\n @click=\"viewMode = 'json'\"\n :class=\"viewMode === 'json'\n ? 'bg-blue-600 text-white z-10'\n : 'bg-gray-200 text-gray-700 hover:bg-gray-300'\"\n class=\"px-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-gray-300 rounded-r-lg rounded-l-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4\"></path>\n </svg>\n JSON\n </button>\n </div>\n\n <div class=\"gap-2 hidden md:flex 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-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n <img src=\"images/edit.svg\" class=\"inline\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false\"\n type=\"button\"\n class=\"rounded-md bg-slate-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n × Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n @click=\"shouldShowConfirmModal=true;\"\n type=\"button\"\n class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">\n <img src=\"images/save.svg\" class=\"inline\" /> Save\n </button>\n\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-gray-700 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-white ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n @click=\"addField(); desktopMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-green-100\"\n >\n Add Field\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-gray-700', !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-gray-700', !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-gray-700 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"mobileMenuOpen\"\n aria-label=\"Open menu\"\n >\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\n d=\"M4 6h16M4 12h16M4 18h16\"></path>\n </svg>\n </button>\n <div\n v-show=\"mobileMenuOpen\"\n @click.away=\"mobileMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-52 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true; mobileMenuOpen = false\"\n :disabled=\"!canEdit\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canEdit ? 'cursor-not-allowed opacity-50' : 'hover:bg-ultramarine-100']\"\n type=\"button\"\n >\n Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false; mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-slate-100\"\n >\n Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-green-100']\"\n @click=\"shouldShowConfirmModal=true; mobileMenuOpen = false\"\n type=\"button\"\n >\n Save\n </button>\n <button\n @click=\"addField(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n 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 </div>\n</div>\n";
|
|
3230
3519
|
|
|
3231
3520
|
/***/ },
|
|
3232
3521
|
|
|
@@ -3260,6 +3549,7 @@ module.exports = app => app.component('document', {
|
|
|
3260
3549
|
virtuals: [],
|
|
3261
3550
|
virtualPaths: [],
|
|
3262
3551
|
mobileMenuOpen: false,
|
|
3552
|
+
desktopMenuOpen: false,
|
|
3263
3553
|
viewMode: 'fields',
|
|
3264
3554
|
shouldShowConfirmModal: false,
|
|
3265
3555
|
shouldShowDeleteModal: false,
|
|
@@ -4149,42 +4439,63 @@ app.component('app-component', {
|
|
|
4149
4439
|
if (hashParams.has('code')) {
|
|
4150
4440
|
const code = hashParams.get('code');
|
|
4151
4441
|
const provider = hashParams.get('provider');
|
|
4442
|
+
|
|
4443
|
+
let user;
|
|
4444
|
+
let accessToken;
|
|
4445
|
+
let roles;
|
|
4152
4446
|
try {
|
|
4153
|
-
|
|
4447
|
+
({ accessToken, user, roles } = provider === 'github' ? await mothership.github(code) : await mothership.google(code));
|
|
4154
4448
|
if (roles == null) {
|
|
4155
4449
|
this.authError = 'You are not authorized to access this workspace';
|
|
4156
4450
|
this.status = 'loaded';
|
|
4157
4451
|
return;
|
|
4158
4452
|
}
|
|
4159
|
-
this.user = user;
|
|
4160
|
-
this.roles = roles;
|
|
4161
|
-
window.localStorage.setItem('_mongooseStudioAccessToken', accessToken._id);
|
|
4162
4453
|
} catch (err) {
|
|
4163
4454
|
this.authError = 'An error occurred while logging in. Please try again.';
|
|
4164
4455
|
this.status = 'loaded';
|
|
4165
4456
|
return;
|
|
4166
|
-
} finally {
|
|
4167
|
-
setTimeout(() => {
|
|
4168
|
-
this.$router.replace(this.$router.currentRoute.value.path);
|
|
4169
|
-
}, 0);
|
|
4170
4457
|
}
|
|
4171
4458
|
|
|
4172
|
-
|
|
4173
|
-
|
|
4459
|
+
try {
|
|
4460
|
+
const { nodeEnv } = await api.status();
|
|
4461
|
+
this.nodeEnv = nodeEnv;
|
|
4462
|
+
} catch (err) {
|
|
4463
|
+
this.authError = 'Error connecting to Mongoose Studio API: ' + err.response?.data?.message ?? err.message;
|
|
4464
|
+
this.status = 'loaded';
|
|
4465
|
+
return;
|
|
4466
|
+
}
|
|
4467
|
+
|
|
4468
|
+
this.user = user;
|
|
4469
|
+
this.roles = roles;
|
|
4470
|
+
window.localStorage.setItem('_mongooseStudioAccessToken', accessToken._id);
|
|
4471
|
+
setTimeout(() => {
|
|
4472
|
+
this.$router.replace(this.$router.currentRoute.value.path);
|
|
4473
|
+
}, 0);
|
|
4174
4474
|
} else {
|
|
4175
4475
|
const token = window.localStorage.getItem('_mongooseStudioAccessToken');
|
|
4176
4476
|
if (token) {
|
|
4177
4477
|
const { user, roles } = await mothership.me();
|
|
4478
|
+
|
|
4479
|
+
try {
|
|
4480
|
+
const { nodeEnv } = await api.status();
|
|
4481
|
+
this.nodeEnv = nodeEnv;
|
|
4482
|
+
} catch (err) {
|
|
4483
|
+
this.authError = 'Error connecting to Mongoose Studio API: ' + (err.response?.data?.message ?? err.message);
|
|
4484
|
+
this.status = 'loaded';
|
|
4485
|
+
return;
|
|
4486
|
+
}
|
|
4487
|
+
|
|
4178
4488
|
this.user = user;
|
|
4179
4489
|
this.roles = roles;
|
|
4180
|
-
|
|
4181
|
-
const { nodeEnv } = await api.status();
|
|
4182
|
-
this.nodeEnv = nodeEnv;
|
|
4183
4490
|
}
|
|
4184
4491
|
}
|
|
4185
4492
|
} else {
|
|
4186
|
-
|
|
4187
|
-
|
|
4493
|
+
try {
|
|
4494
|
+
const { nodeEnv } = await api.status();
|
|
4495
|
+
this.nodeEnv = nodeEnv;
|
|
4496
|
+
} catch (err) {
|
|
4497
|
+
this.authError = 'Error connecting to Mongoose Studio API: ' + (err.response?.data?.message ?? err.message);
|
|
4498
|
+
}
|
|
4188
4499
|
}
|
|
4189
4500
|
|
|
4190
4501
|
this.status = 'loaded';
|
|
@@ -4226,6 +4537,300 @@ app.use(router);
|
|
|
4226
4537
|
app.mount('#content');
|
|
4227
4538
|
|
|
4228
4539
|
|
|
4540
|
+
/***/ },
|
|
4541
|
+
|
|
4542
|
+
/***/ "./frontend/src/json-node/json-node.html"
|
|
4543
|
+
/*!***********************************************!*\
|
|
4544
|
+
!*** ./frontend/src/json-node/json-node.html ***!
|
|
4545
|
+
\***********************************************/
|
|
4546
|
+
(module) {
|
|
4547
|
+
|
|
4548
|
+
"use strict";
|
|
4549
|
+
module.exports = "<div>\n <div class=\"flex items-baseline whitespace-pre\" :style=\"indentStyle\">\n <button\n v-if=\"showToggle\"\n type=\"button\"\n class=\"w-4 h-4 mr-1 inline-flex items-center justify-center leading-none text-gray-500 hover:text-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400 cursor-pointer\"\n @click.stop=\"handleToggle\"\n >\n {{ isCollapsedNode ? '+' : '-' }}\n </button>\n <span v-else class=\"w-4 h-4 mr-1 inline-flex items-center justify-center invisible flex-shrink-0\"></span>\n <template v-if=\"hasKey\">\n <span class=\"text-blue-600\">\"{{ nodeKey }}\"</span><span>: </span>\n </template>\n <template v-if=\"isComplex\">\n <template v-if=\"hasChildren\">\n <span>{{ openingBracket }}</span>\n <span v-if=\"isCollapsedNode\" class=\"mx-1\">…</span>\n <span v-if=\"isCollapsedNode\">{{ closingBracket }}{{ comma }}</span>\n </template>\n <template v-else>\n <span>{{ openingBracket }}{{ closingBracket }}{{ comma }}</span>\n </template>\n </template>\n <template v-else>\n <!--\n If value is a string and overflows its container (i.e. goes over one line), show an ellipsis.\n This is done via CSS ellipsis strategy.\n -->\n <span\n v-if=\"shouldShowReferenceLink\"\n class=\"inline-flex items-baseline group\"\n >\n <span\n :class=\"[...valueClasses, 'underline', 'decoration-dotted', 'underline-offset-2']\"\n :style=\"typeof value === 'string'\n ? {\n display: 'inline-block',\n maxWidth: '100%',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n verticalAlign: 'bottom'\n }\n : {}\"\n :title=\"typeof value === 'string' && $el && $el.scrollWidth > $el.clientWidth ? value : undefined\"\n >\n {{ formattedValue }}\n </span>\n <span>\n {{ comma }}\n </span>\n <a\n href=\"#\"\n class=\"ml-1 text-sm text-sky-700 opacity-0 group-hover:opacity-100 focus:opacity-100 transition-opacity\"\n @click.stop.prevent=\"goToReference()\"\n >\n View Document\n </a>\n </span>\n <span\n v-else\n :class=\"valueClasses\"\n :style=\"typeof value === 'string'\n ? {\n display: 'inline-block',\n maxWidth: '100%',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n verticalAlign: 'bottom'\n }\n : {}\"\n :title=\"typeof value === 'string' && $el && $el.scrollWidth > $el.clientWidth ? value : undefined\"\n >\n {{ formattedValue }}{{ comma }}\n </span>\n </template>\n </div>\n <template v-if=\"isComplex && hasChildren && !isCollapsedNode\">\n <json-node\n v-for=\"child in children\"\n :key=\"child.path\"\n :node-key=\"child.displayKey\"\n :value=\"child.value\"\n :level=\"level + 1\"\n :is-last=\"child.isLast\"\n :path=\"child.path\"\n :toggle-collapse=\"toggleCollapse\"\n :is-collapsed=\"isCollapsed\"\n :create-child-path=\"createChildPath\"\n :indent-size=\"indentSize\"\n :max-top-level-fields=\"maxTopLevelFields\"\n :top-level-expanded=\"topLevelExpanded\"\n :expand-top-level=\"expandTopLevel\"\n :references=\"references\"\n ></json-node>\n <div\n v-if=\"hasHiddenRootChildren\"\n class=\"flex items-baseline whitespace-pre\"\n :style=\"indentStyle\"\n >\n <span class=\"w-4 h-4 mr-1 inline-flex items-center justify-center invisible\"></span>\n <button\n type=\"button\"\n class=\"text-xs inline-flex items-center gap-1 ml-4 text-slate-500 hover:text-slate-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400\"\n :title=\"hiddenChildrenTooltip\"\n @click.stop=\"handleExpandTopLevel\"\n >\n <span aria-hidden=\"true\">{{hiddenChildrenLabel}}…</span>\n </button>\n </div>\n <div class=\"flex items-baseline whitespace-pre\" :style=\"indentStyle\">\n <span class=\"w-4 h-4 mr-1 inline-flex items-center justify-center invisible\"></span>\n <span>{{ closingBracket }}{{ comma }}</span>\n </div>\n </template>\n</div>\n";
|
|
4550
|
+
|
|
4551
|
+
/***/ },
|
|
4552
|
+
|
|
4553
|
+
/***/ "./frontend/src/json-node/json-node.js"
|
|
4554
|
+
/*!*********************************************!*\
|
|
4555
|
+
!*** ./frontend/src/json-node/json-node.js ***!
|
|
4556
|
+
\*********************************************/
|
|
4557
|
+
(module, __unused_webpack_exports, __webpack_require__) {
|
|
4558
|
+
|
|
4559
|
+
"use strict";
|
|
4560
|
+
|
|
4561
|
+
|
|
4562
|
+
const template = __webpack_require__(/*! ./json-node.html */ "./frontend/src/json-node/json-node.html");
|
|
4563
|
+
|
|
4564
|
+
module.exports = app => app.component('json-node', {
|
|
4565
|
+
name: 'JsonNode',
|
|
4566
|
+
template: template,
|
|
4567
|
+
props: {
|
|
4568
|
+
nodeKey: {
|
|
4569
|
+
type: [String, Number],
|
|
4570
|
+
default: null
|
|
4571
|
+
},
|
|
4572
|
+
value: {
|
|
4573
|
+
required: true
|
|
4574
|
+
},
|
|
4575
|
+
level: {
|
|
4576
|
+
type: Number,
|
|
4577
|
+
required: true
|
|
4578
|
+
},
|
|
4579
|
+
isLast: {
|
|
4580
|
+
type: Boolean,
|
|
4581
|
+
default: false
|
|
4582
|
+
},
|
|
4583
|
+
path: {
|
|
4584
|
+
type: String,
|
|
4585
|
+
required: true
|
|
4586
|
+
},
|
|
4587
|
+
toggleCollapse: {
|
|
4588
|
+
type: Function,
|
|
4589
|
+
required: true
|
|
4590
|
+
},
|
|
4591
|
+
isCollapsed: {
|
|
4592
|
+
type: Function,
|
|
4593
|
+
required: true
|
|
4594
|
+
},
|
|
4595
|
+
createChildPath: {
|
|
4596
|
+
type: Function,
|
|
4597
|
+
required: true
|
|
4598
|
+
},
|
|
4599
|
+
indentSize: {
|
|
4600
|
+
type: Number,
|
|
4601
|
+
required: true
|
|
4602
|
+
},
|
|
4603
|
+
maxTopLevelFields: {
|
|
4604
|
+
type: Number,
|
|
4605
|
+
default: null
|
|
4606
|
+
},
|
|
4607
|
+
topLevelExpanded: {
|
|
4608
|
+
type: Boolean,
|
|
4609
|
+
default: false
|
|
4610
|
+
},
|
|
4611
|
+
expandTopLevel: {
|
|
4612
|
+
type: Function,
|
|
4613
|
+
default: null
|
|
4614
|
+
},
|
|
4615
|
+
references: {
|
|
4616
|
+
type: Object,
|
|
4617
|
+
default: () => ({})
|
|
4618
|
+
}
|
|
4619
|
+
},
|
|
4620
|
+
computed: {
|
|
4621
|
+
hasKey() {
|
|
4622
|
+
return this.nodeKey !== null && this.nodeKey !== undefined;
|
|
4623
|
+
},
|
|
4624
|
+
isRoot() {
|
|
4625
|
+
return this.path === 'root';
|
|
4626
|
+
},
|
|
4627
|
+
isArray() {
|
|
4628
|
+
return Array.isArray(this.value);
|
|
4629
|
+
},
|
|
4630
|
+
isObject() {
|
|
4631
|
+
if (this.value === null || this.isArray) {
|
|
4632
|
+
return false;
|
|
4633
|
+
}
|
|
4634
|
+
return Object.prototype.toString.call(this.value) === '[object Object]';
|
|
4635
|
+
},
|
|
4636
|
+
isComplex() {
|
|
4637
|
+
return this.isArray || this.isObject;
|
|
4638
|
+
},
|
|
4639
|
+
children() {
|
|
4640
|
+
if (!this.isComplex) {
|
|
4641
|
+
return [];
|
|
4642
|
+
}
|
|
4643
|
+
if (this.isArray) {
|
|
4644
|
+
return this.value.map((childValue, index) => ({
|
|
4645
|
+
displayKey: null,
|
|
4646
|
+
value: childValue,
|
|
4647
|
+
isLast: index === this.value.length - 1,
|
|
4648
|
+
path: this.createChildPath(this.path, index, true)
|
|
4649
|
+
}));
|
|
4650
|
+
}
|
|
4651
|
+
const keys = Object.keys(this.value);
|
|
4652
|
+
const visibleKeys = this.visibleObjectKeys(keys);
|
|
4653
|
+
const hasHidden = this.hasHiddenRootChildren;
|
|
4654
|
+
return visibleKeys.map((key, index) => ({
|
|
4655
|
+
displayKey: key,
|
|
4656
|
+
value: this.value[key],
|
|
4657
|
+
isLast: !hasHidden && index === visibleKeys.length - 1,
|
|
4658
|
+
path: this.createChildPath(this.path, key, false)
|
|
4659
|
+
}));
|
|
4660
|
+
},
|
|
4661
|
+
hasChildren() {
|
|
4662
|
+
return this.children.length > 0;
|
|
4663
|
+
},
|
|
4664
|
+
totalObjectChildCount() {
|
|
4665
|
+
if (!this.isObject) {
|
|
4666
|
+
return 0;
|
|
4667
|
+
}
|
|
4668
|
+
return Object.keys(this.value).length;
|
|
4669
|
+
},
|
|
4670
|
+
hasHiddenRootChildren() {
|
|
4671
|
+
if (!this.isRoot || !this.isObject) {
|
|
4672
|
+
return false;
|
|
4673
|
+
}
|
|
4674
|
+
if (this.topLevelExpanded) {
|
|
4675
|
+
return false;
|
|
4676
|
+
}
|
|
4677
|
+
if (typeof this.maxTopLevelFields !== 'number') {
|
|
4678
|
+
return false;
|
|
4679
|
+
}
|
|
4680
|
+
return this.totalObjectChildCount > this.maxTopLevelFields;
|
|
4681
|
+
},
|
|
4682
|
+
hiddenRootChildrenCount() {
|
|
4683
|
+
if (!this.hasHiddenRootChildren) {
|
|
4684
|
+
return 0;
|
|
4685
|
+
}
|
|
4686
|
+
return this.totalObjectChildCount - this.maxTopLevelFields;
|
|
4687
|
+
},
|
|
4688
|
+
showToggle() {
|
|
4689
|
+
return this.hasChildren && !this.isRoot;
|
|
4690
|
+
},
|
|
4691
|
+
openingBracket() {
|
|
4692
|
+
return this.isArray ? '[' : '{';
|
|
4693
|
+
},
|
|
4694
|
+
closingBracket() {
|
|
4695
|
+
return this.isArray ? ']' : '}';
|
|
4696
|
+
},
|
|
4697
|
+
isCollapsedNode() {
|
|
4698
|
+
return this.isCollapsed(this.path);
|
|
4699
|
+
},
|
|
4700
|
+
formattedValue() {
|
|
4701
|
+
if (typeof this.value === 'bigint') {
|
|
4702
|
+
return `${this.value.toString()}n`;
|
|
4703
|
+
}
|
|
4704
|
+
const stringified = JSON.stringify(this.value);
|
|
4705
|
+
if (stringified === undefined) {
|
|
4706
|
+
if (typeof this.value === 'symbol') {
|
|
4707
|
+
return this.value.toString();
|
|
4708
|
+
}
|
|
4709
|
+
return String(this.value);
|
|
4710
|
+
}
|
|
4711
|
+
return stringified;
|
|
4712
|
+
},
|
|
4713
|
+
valueClasses() {
|
|
4714
|
+
const classes = ['text-slate-700'];
|
|
4715
|
+
if (this.value === null) {
|
|
4716
|
+
classes.push('text-gray-500', 'italic');
|
|
4717
|
+
return classes;
|
|
4718
|
+
}
|
|
4719
|
+
const type = typeof this.value;
|
|
4720
|
+
if (type === 'string') {
|
|
4721
|
+
classes.push('text-emerald-600');
|
|
4722
|
+
return classes;
|
|
4723
|
+
}
|
|
4724
|
+
if (type === 'number' || type === 'bigint') {
|
|
4725
|
+
classes.push('text-amber-600');
|
|
4726
|
+
return classes;
|
|
4727
|
+
}
|
|
4728
|
+
if (type === 'boolean') {
|
|
4729
|
+
classes.push('text-violet-600');
|
|
4730
|
+
return classes;
|
|
4731
|
+
}
|
|
4732
|
+
if (type === 'undefined') {
|
|
4733
|
+
classes.push('text-gray-500');
|
|
4734
|
+
return classes;
|
|
4735
|
+
}
|
|
4736
|
+
return classes;
|
|
4737
|
+
},
|
|
4738
|
+
comma() {
|
|
4739
|
+
return this.isLast ? '' : ',';
|
|
4740
|
+
},
|
|
4741
|
+
indentStyle() {
|
|
4742
|
+
return {
|
|
4743
|
+
paddingLeft: `${this.level * this.indentSize}px`
|
|
4744
|
+
};
|
|
4745
|
+
},
|
|
4746
|
+
hiddenChildrenLabel() {
|
|
4747
|
+
if (!this.hasHiddenRootChildren) {
|
|
4748
|
+
return '';
|
|
4749
|
+
}
|
|
4750
|
+
const count = this.hiddenRootChildrenCount;
|
|
4751
|
+
const suffix = count === 1 ? 'field' : 'fields';
|
|
4752
|
+
return `${count} more ${suffix}`;
|
|
4753
|
+
},
|
|
4754
|
+
hiddenChildrenTooltip() {
|
|
4755
|
+
return this.hiddenChildrenLabel;
|
|
4756
|
+
},
|
|
4757
|
+
normalizedPath() {
|
|
4758
|
+
if (typeof this.path !== 'string') {
|
|
4759
|
+
return '';
|
|
4760
|
+
}
|
|
4761
|
+
return this.path
|
|
4762
|
+
.replace(/^root\.?/, '')
|
|
4763
|
+
.replace(/\[\d+\]/g, '')
|
|
4764
|
+
.replace(/^\./, '');
|
|
4765
|
+
},
|
|
4766
|
+
referenceModel() {
|
|
4767
|
+
if (!this.normalizedPath || !this.references) {
|
|
4768
|
+
return null;
|
|
4769
|
+
}
|
|
4770
|
+
return this.references[this.normalizedPath] || null;
|
|
4771
|
+
},
|
|
4772
|
+
shouldShowReferenceLink() {
|
|
4773
|
+
return this.referenceId != null;
|
|
4774
|
+
},
|
|
4775
|
+
referenceId() {
|
|
4776
|
+
if (!this.referenceModel) {
|
|
4777
|
+
return null;
|
|
4778
|
+
}
|
|
4779
|
+
if (this.value == null) {
|
|
4780
|
+
return null;
|
|
4781
|
+
}
|
|
4782
|
+
const type = typeof this.value;
|
|
4783
|
+
if (type === 'string' || type === 'number' || type === 'bigint') {
|
|
4784
|
+
return String(this.value);
|
|
4785
|
+
}
|
|
4786
|
+
if (type === 'object') {
|
|
4787
|
+
if (this.value._id != null) {
|
|
4788
|
+
return String(this.value._id);
|
|
4789
|
+
}
|
|
4790
|
+
if (typeof this.value.toString === 'function') {
|
|
4791
|
+
const stringified = this.value.toString();
|
|
4792
|
+
if (typeof stringified === 'string' && stringified !== '[object Object]') {
|
|
4793
|
+
return stringified;
|
|
4794
|
+
}
|
|
4795
|
+
}
|
|
4796
|
+
}
|
|
4797
|
+
return null;
|
|
4798
|
+
}
|
|
4799
|
+
},
|
|
4800
|
+
methods: {
|
|
4801
|
+
visibleObjectKeys(keys) {
|
|
4802
|
+
if (!this.isRoot || this.topLevelExpanded) {
|
|
4803
|
+
return keys;
|
|
4804
|
+
}
|
|
4805
|
+
if (typeof this.maxTopLevelFields !== 'number') {
|
|
4806
|
+
return keys;
|
|
4807
|
+
}
|
|
4808
|
+
if (keys.length <= this.maxTopLevelFields) {
|
|
4809
|
+
return keys;
|
|
4810
|
+
}
|
|
4811
|
+
return keys.slice(0, this.maxTopLevelFields);
|
|
4812
|
+
},
|
|
4813
|
+
handleToggle() {
|
|
4814
|
+
if (!this.isRoot) {
|
|
4815
|
+
this.toggleCollapse(this.path);
|
|
4816
|
+
}
|
|
4817
|
+
},
|
|
4818
|
+
handleExpandTopLevel() {
|
|
4819
|
+
if (this.isRoot && typeof this.expandTopLevel === 'function') {
|
|
4820
|
+
this.expandTopLevel();
|
|
4821
|
+
}
|
|
4822
|
+
},
|
|
4823
|
+
goToReference() {
|
|
4824
|
+
const id = this.referenceId;
|
|
4825
|
+
if (!this.referenceModel || id == null) {
|
|
4826
|
+
return;
|
|
4827
|
+
}
|
|
4828
|
+
this.$router.push({ path: `/model/${this.referenceModel}/document/${id}` });
|
|
4829
|
+
}
|
|
4830
|
+
}
|
|
4831
|
+
});
|
|
4832
|
+
|
|
4833
|
+
|
|
4229
4834
|
/***/ },
|
|
4230
4835
|
|
|
4231
4836
|
/***/ "./frontend/src/list-array/list-array.html"
|
|
@@ -4235,7 +4840,7 @@ app.mount('#content');
|
|
|
4235
4840
|
(module) {
|
|
4236
4841
|
|
|
4237
4842
|
"use strict";
|
|
4238
|
-
module.exports = "<div class=\"list-array\">\n <
|
|
4843
|
+
module.exports = "<div class=\"list-array tooltip\">\n <div>{{displayValue}}</div>\n <div class=\"tooltiptext\" style=\"display:flex; width: 100%; justify-content: space-around; align-items: center; min-width: 180px;\">\n <div class=\"tooltiptextchild\" @click.stop=\"showViewDataModal = true\">View data</div>\n <div class=\"tooltiptextchild\" @click.stop=\"copyText(value)\">Copy 📋</div>\n </div>\n <modal ref=\"viewDataModal\" v-if=\"showViewDataModal\" containerClass=\"!h-[90vh] !w-[90vw]\">\n <template #body>\n <div @click.stop class=\"w-full h-full cursor-auto\">\n <div class=\"modal-exit\" @click=\"showViewDataModal = false\">×</div>\n <list-json :value=\"value\" :expandedFields=\"Object.keys(value || []).map(key => `root[${key}]`)\" />\n </div>\n </template>\n </modal>\n</div>\n";
|
|
4239
4844
|
|
|
4240
4845
|
/***/ },
|
|
4241
4846
|
|
|
@@ -4253,13 +4858,31 @@ const template = __webpack_require__(/*! ./list-array.html */ "./frontend/src/li
|
|
|
4253
4858
|
module.exports = app => app.component('list-array', {
|
|
4254
4859
|
template: template,
|
|
4255
4860
|
props: ['value'],
|
|
4861
|
+
data: () => ({ showViewDataModal: false }),
|
|
4256
4862
|
computed: {
|
|
4257
4863
|
displayValue() {
|
|
4258
|
-
|
|
4864
|
+
if (this.value == null) {
|
|
4865
|
+
return this.value;
|
|
4866
|
+
}
|
|
4867
|
+
const value = JSON.stringify(this.value);
|
|
4868
|
+
if (value.length > 50) {
|
|
4869
|
+
return `${value.slice(0, 50)}…`;
|
|
4870
|
+
}
|
|
4871
|
+
return value;
|
|
4259
4872
|
}
|
|
4260
4873
|
},
|
|
4261
|
-
|
|
4262
|
-
|
|
4874
|
+
methods: {
|
|
4875
|
+
copyText(value) {
|
|
4876
|
+
const storage = document.createElement('textarea');
|
|
4877
|
+
storage.value = JSON.stringify(value);
|
|
4878
|
+
const elem = this.$el;
|
|
4879
|
+
elem.appendChild(storage);
|
|
4880
|
+
storage.select();
|
|
4881
|
+
storage.setSelectionRange(0, 99999);
|
|
4882
|
+
document.execCommand('copy');
|
|
4883
|
+
elem.removeChild(storage);
|
|
4884
|
+
this.$toast.success('Text copied!');
|
|
4885
|
+
}
|
|
4263
4886
|
}
|
|
4264
4887
|
});
|
|
4265
4888
|
|
|
@@ -4329,7 +4952,7 @@ module.exports = app => app.component('list-default', {
|
|
|
4329
4952
|
if (this.value === undefined) {
|
|
4330
4953
|
return 'undefined';
|
|
4331
4954
|
}
|
|
4332
|
-
if (this.value.length > 30) {
|
|
4955
|
+
if (this.value.length > 30 && typeof this.value === 'string') {
|
|
4333
4956
|
return this.value.substring(0, 30) + '...';
|
|
4334
4957
|
}
|
|
4335
4958
|
return this.value;
|
|
@@ -4340,6 +4963,7 @@ module.exports = app => app.component('list-default', {
|
|
|
4340
4963
|
}
|
|
4341
4964
|
});
|
|
4342
4965
|
|
|
4966
|
+
|
|
4343
4967
|
/***/ },
|
|
4344
4968
|
|
|
4345
4969
|
/***/ "./frontend/src/list-json/json-node.html"
|
|
@@ -4349,7 +4973,7 @@ module.exports = app => app.component('list-default', {
|
|
|
4349
4973
|
(module) {
|
|
4350
4974
|
|
|
4351
4975
|
"use strict";
|
|
4352
|
-
module.exports = "<div>\n <div class=\"flex items-baseline whitespace-pre\" :style=\"indentStyle\">\n <button\n v-if=\"showToggle\"\n type=\"button\"\n class=\"w-4 h-4 mr-1 inline-flex items-center justify-center leading-none text-gray-500 hover:text-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400 cursor-pointer\"\n @click.stop=\"handleToggle\"\n >\n {{ isCollapsedNode ? '+' : '-' }}\n </button>\n <span v-else class=\"w-4 h-4 mr-1 inline-flex items-center justify-center invisible flex-shrink-0\"></span>\n <template v-if=\"hasKey\">\n <span class=\"text-blue-600\">\"{{ nodeKey }}\"</span><span>: </span>\n </template>\n <template v-if=\"isComplex\">\n <template v-if=\"hasChildren\">\n <span>{{ openingBracket }}</span>\n <span v-if=\"isCollapsedNode\" class=\"mx-1\">…</span>\n <span v-if=\"isCollapsedNode\">{{ closingBracket }}{{ comma }}</span>\n </template>\n <template v-else>\n <span>{{ openingBracket }}{{ closingBracket }}{{ comma }}</span>\n </template>\n </template>\n <template v-else>\n <!--\n If value is a string and overflows its container (i.e. goes over one line), show an ellipsis.\n This is done via CSS ellipsis strategy.\n -->\n <span\n v-if=\"shouldShowReferenceLink\"\n class=\"inline-flex items-baseline group\"\n >\n <span\n :class=\"[...valueClasses, 'underline', 'decoration-dotted', 'underline-offset-2']\"\n :style=\"typeof value === 'string'\n ? {\n display: 'inline-block',\n maxWidth: '100%',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n verticalAlign: 'bottom'\n }\n : {}\"\n :title=\"typeof value === 'string' && $el && $el.scrollWidth > $el.clientWidth ? value : undefined\"\n >\n {{ formattedValue }}\n </span>\n <span>\n {{ comma }}\n </span>\n <a\n href=\"#\"\n class=\"ml-1 text-sm text-sky-700 opacity-0 group-hover:opacity-100 focus:opacity-100 transition-opacity\"\n @click.stop.prevent=\"goToReference(
|
|
4976
|
+
module.exports = "<div>\n <div class=\"flex items-baseline whitespace-pre\" :style=\"indentStyle\">\n <button\n v-if=\"showToggle\"\n type=\"button\"\n class=\"w-4 h-4 mr-1 inline-flex items-center justify-center leading-none text-gray-500 hover:text-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400 cursor-pointer\"\n @click.stop=\"handleToggle\"\n >\n {{ isCollapsedNode ? '+' : '-' }}\n </button>\n <span v-else class=\"w-4 h-4 mr-1 inline-flex items-center justify-center invisible flex-shrink-0\"></span>\n <template v-if=\"hasKey\">\n <span class=\"text-blue-600\">\"{{ nodeKey }}\"</span><span>: </span>\n </template>\n <template v-if=\"isComplex\">\n <template v-if=\"hasChildren\">\n <span>{{ openingBracket }}</span>\n <span v-if=\"isCollapsedNode\" class=\"mx-1\">…</span>\n <span v-if=\"isCollapsedNode\">{{ closingBracket }}{{ comma }}</span>\n </template>\n <template v-else>\n <span>{{ openingBracket }}{{ closingBracket }}{{ comma }}</span>\n </template>\n </template>\n <template v-else>\n <!--\n If value is a string and overflows its container (i.e. goes over one line), show an ellipsis.\n This is done via CSS ellipsis strategy.\n -->\n <span\n v-if=\"shouldShowReferenceLink\"\n class=\"inline-flex items-baseline group\"\n >\n <span\n :class=\"[...valueClasses, 'underline', 'decoration-dotted', 'underline-offset-2']\"\n :style=\"typeof value === 'string'\n ? {\n display: 'inline-block',\n maxWidth: '100%',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n verticalAlign: 'bottom'\n }\n : {}\"\n :title=\"typeof value === 'string' && $el && $el.scrollWidth > $el.clientWidth ? value : undefined\"\n >\n {{ formattedValue }}\n </span>\n <span>\n {{ comma }}\n </span>\n <a\n href=\"#\"\n class=\"ml-1 text-sm text-sky-700 opacity-0 group-hover:opacity-100 focus:opacity-100 transition-opacity\"\n @click.stop.prevent=\"goToReference()\"\n >\n View Document\n </a>\n </span>\n <span\n v-else\n :class=\"valueClasses\"\n :style=\"typeof value === 'string'\n ? {\n display: 'inline-block',\n maxWidth: '100%',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n verticalAlign: 'bottom'\n }\n : {}\"\n :title=\"typeof value === 'string' && $el && $el.scrollWidth > $el.clientWidth ? value : undefined\"\n >\n {{ formattedValue }}{{ comma }}\n </span>\n </template>\n </div>\n <template v-if=\"isComplex && hasChildren && !isCollapsedNode\">\n <json-node\n v-for=\"child in children\"\n :key=\"child.path\"\n :node-key=\"child.displayKey\"\n :value=\"child.value\"\n :level=\"level + 1\"\n :is-last=\"child.isLast\"\n :path=\"child.path\"\n :toggle-collapse=\"toggleCollapse\"\n :is-collapsed=\"isCollapsed\"\n :create-child-path=\"createChildPath\"\n :indent-size=\"indentSize\"\n :max-top-level-fields=\"maxTopLevelFields\"\n :top-level-expanded=\"topLevelExpanded\"\n :expand-top-level=\"expandTopLevel\"\n :references=\"references\"\n ></json-node>\n <div\n v-if=\"hasHiddenRootChildren\"\n class=\"flex items-baseline whitespace-pre\"\n :style=\"indentStyle\"\n >\n <span class=\"w-4 h-4 mr-1 inline-flex items-center justify-center invisible\"></span>\n <button\n type=\"button\"\n class=\"text-xs inline-flex items-center gap-1 ml-4 text-slate-500 hover:text-slate-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400\"\n :title=\"hiddenChildrenTooltip\"\n @click.stop=\"handleExpandTopLevel\"\n >\n <span aria-hidden=\"true\">{{hiddenChildrenLabel}}…</span>\n </button>\n </div>\n <div class=\"flex items-baseline whitespace-pre\" :style=\"indentStyle\">\n <span class=\"w-4 h-4 mr-1 inline-flex items-center justify-center invisible\"></span>\n <span>{{ closingBracket }}{{ comma }}</span>\n </div>\n </template>\n</div>\n";
|
|
4353
4977
|
|
|
4354
4978
|
/***/ },
|
|
4355
4979
|
|
|
@@ -4360,7 +4984,7 @@ module.exports = "<div>\n <div class=\"flex items-baseline whitespace-pre\" :st
|
|
|
4360
4984
|
(module) {
|
|
4361
4985
|
|
|
4362
4986
|
"use strict";
|
|
4363
|
-
module.exports = "<div class=\"tooltip w-full font-mono text-sm py-
|
|
4987
|
+
module.exports = "<div class=\"tooltip w-full font-mono text-sm py-1 text-slate-800\">\n <div class=\"w-full\">\n <json-node\n :node-key=\"null\"\n :value=\"value\"\n :level=\"0\"\n :is-last=\"true\"\n path=\"root\"\n :toggle-collapse=\"toggleCollapse\"\n :is-collapsed=\"isPathCollapsed\"\n :create-child-path=\"createChildPath\"\n :indent-size=\"indentSize\"\n :max-top-level-fields=\"maxTopLevelFields\"\n :top-level-expanded=\"topLevelExpanded\"\n :expand-top-level=\"expandTopLevel\"\n :references=\"references\"\n ></json-node>\n </div>\n</div>\n";
|
|
4364
4988
|
|
|
4365
4989
|
/***/ },
|
|
4366
4990
|
|
|
@@ -4375,8 +4999,6 @@ module.exports = "<div class=\"tooltip w-full font-mono text-sm py-3 text-slate-
|
|
|
4375
4999
|
|
|
4376
5000
|
const template = __webpack_require__(/*! ./list-json.html */ "./frontend/src/list-json/list-json.html");
|
|
4377
5001
|
|
|
4378
|
-
const JsonNodeTemplate = __webpack_require__(/*! ./json-node.html */ "./frontend/src/list-json/json-node.html");
|
|
4379
|
-
|
|
4380
5002
|
module.exports = app => app.component('list-json', {
|
|
4381
5003
|
template: template,
|
|
4382
5004
|
props: {
|
|
@@ -4386,13 +5008,20 @@ module.exports = app => app.component('list-json', {
|
|
|
4386
5008
|
references: {
|
|
4387
5009
|
type: Object,
|
|
4388
5010
|
default: () => ({})
|
|
5011
|
+
},
|
|
5012
|
+
maxTopLevelFields: {
|
|
5013
|
+
type: Number,
|
|
5014
|
+
default: 15
|
|
5015
|
+
},
|
|
5016
|
+
expandedFields: {
|
|
5017
|
+
type: Array,
|
|
5018
|
+
default: () => []
|
|
4389
5019
|
}
|
|
4390
5020
|
},
|
|
4391
5021
|
data() {
|
|
4392
5022
|
return {
|
|
4393
5023
|
collapsedMap: {},
|
|
4394
5024
|
indentSize: 16,
|
|
4395
|
-
maxTopLevelFields: 15,
|
|
4396
5025
|
topLevelExpanded: false
|
|
4397
5026
|
};
|
|
4398
5027
|
},
|
|
@@ -4405,6 +5034,9 @@ module.exports = app => app.component('list-json', {
|
|
|
4405
5034
|
},
|
|
4406
5035
|
created() {
|
|
4407
5036
|
this.resetCollapse();
|
|
5037
|
+
for (const field of this.expandedFields) {
|
|
5038
|
+
this.toggleCollapse(field);
|
|
5039
|
+
}
|
|
4408
5040
|
},
|
|
4409
5041
|
methods: {
|
|
4410
5042
|
resetCollapse() {
|
|
@@ -4439,251 +5071,6 @@ module.exports = app => app.component('list-json', {
|
|
|
4439
5071
|
expandTopLevel() {
|
|
4440
5072
|
this.topLevelExpanded = true;
|
|
4441
5073
|
}
|
|
4442
|
-
},
|
|
4443
|
-
components: {
|
|
4444
|
-
JsonNode: {
|
|
4445
|
-
name: 'JsonNode',
|
|
4446
|
-
template: JsonNodeTemplate,
|
|
4447
|
-
props: {
|
|
4448
|
-
nodeKey: {
|
|
4449
|
-
type: [String, Number],
|
|
4450
|
-
default: null
|
|
4451
|
-
},
|
|
4452
|
-
value: {
|
|
4453
|
-
required: true
|
|
4454
|
-
},
|
|
4455
|
-
level: {
|
|
4456
|
-
type: Number,
|
|
4457
|
-
required: true
|
|
4458
|
-
},
|
|
4459
|
-
isLast: {
|
|
4460
|
-
type: Boolean,
|
|
4461
|
-
default: false
|
|
4462
|
-
},
|
|
4463
|
-
path: {
|
|
4464
|
-
type: String,
|
|
4465
|
-
required: true
|
|
4466
|
-
},
|
|
4467
|
-
toggleCollapse: {
|
|
4468
|
-
type: Function,
|
|
4469
|
-
required: true
|
|
4470
|
-
},
|
|
4471
|
-
isCollapsed: {
|
|
4472
|
-
type: Function,
|
|
4473
|
-
required: true
|
|
4474
|
-
},
|
|
4475
|
-
createChildPath: {
|
|
4476
|
-
type: Function,
|
|
4477
|
-
required: true
|
|
4478
|
-
},
|
|
4479
|
-
indentSize: {
|
|
4480
|
-
type: Number,
|
|
4481
|
-
required: true
|
|
4482
|
-
},
|
|
4483
|
-
maxTopLevelFields: {
|
|
4484
|
-
type: Number,
|
|
4485
|
-
default: null
|
|
4486
|
-
},
|
|
4487
|
-
topLevelExpanded: {
|
|
4488
|
-
type: Boolean,
|
|
4489
|
-
default: false
|
|
4490
|
-
},
|
|
4491
|
-
expandTopLevel: {
|
|
4492
|
-
type: Function,
|
|
4493
|
-
default: null
|
|
4494
|
-
},
|
|
4495
|
-
references: {
|
|
4496
|
-
type: Object,
|
|
4497
|
-
default: () => ({})
|
|
4498
|
-
}
|
|
4499
|
-
},
|
|
4500
|
-
computed: {
|
|
4501
|
-
hasKey() {
|
|
4502
|
-
return this.nodeKey !== null && this.nodeKey !== undefined;
|
|
4503
|
-
},
|
|
4504
|
-
isRoot() {
|
|
4505
|
-
return this.path === 'root';
|
|
4506
|
-
},
|
|
4507
|
-
isArray() {
|
|
4508
|
-
return Array.isArray(this.value);
|
|
4509
|
-
},
|
|
4510
|
-
isObject() {
|
|
4511
|
-
if (this.value === null || this.isArray) {
|
|
4512
|
-
return false;
|
|
4513
|
-
}
|
|
4514
|
-
return Object.prototype.toString.call(this.value) === '[object Object]';
|
|
4515
|
-
},
|
|
4516
|
-
isComplex() {
|
|
4517
|
-
return this.isArray || this.isObject;
|
|
4518
|
-
},
|
|
4519
|
-
children() {
|
|
4520
|
-
if (!this.isComplex) {
|
|
4521
|
-
return [];
|
|
4522
|
-
}
|
|
4523
|
-
if (this.isArray) {
|
|
4524
|
-
return this.value.map((childValue, index) => ({
|
|
4525
|
-
displayKey: null,
|
|
4526
|
-
value: childValue,
|
|
4527
|
-
isLast: index === this.value.length - 1,
|
|
4528
|
-
path: this.createChildPath(this.path, index, true)
|
|
4529
|
-
}));
|
|
4530
|
-
}
|
|
4531
|
-
const keys = Object.keys(this.value);
|
|
4532
|
-
const visibleKeys = this.visibleObjectKeys(keys);
|
|
4533
|
-
const hasHidden = this.hasHiddenRootChildren;
|
|
4534
|
-
return visibleKeys.map((key, index) => ({
|
|
4535
|
-
displayKey: key,
|
|
4536
|
-
value: this.value[key],
|
|
4537
|
-
isLast: !hasHidden && index === visibleKeys.length - 1,
|
|
4538
|
-
path: this.createChildPath(this.path, key, false)
|
|
4539
|
-
}));
|
|
4540
|
-
},
|
|
4541
|
-
hasChildren() {
|
|
4542
|
-
return this.children.length > 0;
|
|
4543
|
-
},
|
|
4544
|
-
totalObjectChildCount() {
|
|
4545
|
-
if (!this.isObject) {
|
|
4546
|
-
return 0;
|
|
4547
|
-
}
|
|
4548
|
-
return Object.keys(this.value).length;
|
|
4549
|
-
},
|
|
4550
|
-
hasHiddenRootChildren() {
|
|
4551
|
-
if (!this.isRoot || !this.isObject) {
|
|
4552
|
-
return false;
|
|
4553
|
-
}
|
|
4554
|
-
if (this.topLevelExpanded) {
|
|
4555
|
-
return false;
|
|
4556
|
-
}
|
|
4557
|
-
if (typeof this.maxTopLevelFields !== 'number') {
|
|
4558
|
-
return false;
|
|
4559
|
-
}
|
|
4560
|
-
return this.totalObjectChildCount > this.maxTopLevelFields;
|
|
4561
|
-
},
|
|
4562
|
-
hiddenRootChildrenCount() {
|
|
4563
|
-
if (!this.hasHiddenRootChildren) {
|
|
4564
|
-
return 0;
|
|
4565
|
-
}
|
|
4566
|
-
return this.totalObjectChildCount - this.maxTopLevelFields;
|
|
4567
|
-
},
|
|
4568
|
-
showToggle() {
|
|
4569
|
-
return this.hasChildren && !this.isRoot;
|
|
4570
|
-
},
|
|
4571
|
-
openingBracket() {
|
|
4572
|
-
return this.isArray ? '[' : '{';
|
|
4573
|
-
},
|
|
4574
|
-
closingBracket() {
|
|
4575
|
-
return this.isArray ? ']' : '}';
|
|
4576
|
-
},
|
|
4577
|
-
isCollapsedNode() {
|
|
4578
|
-
return this.isCollapsed(this.path);
|
|
4579
|
-
},
|
|
4580
|
-
formattedValue() {
|
|
4581
|
-
if (typeof this.value === 'bigint') {
|
|
4582
|
-
return `${this.value.toString()}n`;
|
|
4583
|
-
}
|
|
4584
|
-
const stringified = JSON.stringify(this.value);
|
|
4585
|
-
if (stringified === undefined) {
|
|
4586
|
-
if (typeof this.value === 'symbol') {
|
|
4587
|
-
return this.value.toString();
|
|
4588
|
-
}
|
|
4589
|
-
return String(this.value);
|
|
4590
|
-
}
|
|
4591
|
-
return stringified;
|
|
4592
|
-
},
|
|
4593
|
-
valueClasses() {
|
|
4594
|
-
const classes = ['text-slate-700'];
|
|
4595
|
-
if (this.value === null) {
|
|
4596
|
-
classes.push('text-gray-500', 'italic');
|
|
4597
|
-
return classes;
|
|
4598
|
-
}
|
|
4599
|
-
const type = typeof this.value;
|
|
4600
|
-
if (type === 'string') {
|
|
4601
|
-
classes.push('text-emerald-600');
|
|
4602
|
-
return classes;
|
|
4603
|
-
}
|
|
4604
|
-
if (type === 'number' || type === 'bigint') {
|
|
4605
|
-
classes.push('text-amber-600');
|
|
4606
|
-
return classes;
|
|
4607
|
-
}
|
|
4608
|
-
if (type === 'boolean') {
|
|
4609
|
-
classes.push('text-violet-600');
|
|
4610
|
-
return classes;
|
|
4611
|
-
}
|
|
4612
|
-
if (type === 'undefined') {
|
|
4613
|
-
classes.push('text-gray-500');
|
|
4614
|
-
return classes;
|
|
4615
|
-
}
|
|
4616
|
-
return classes;
|
|
4617
|
-
},
|
|
4618
|
-
comma() {
|
|
4619
|
-
return this.isLast ? '' : ',';
|
|
4620
|
-
},
|
|
4621
|
-
indentStyle() {
|
|
4622
|
-
return {
|
|
4623
|
-
paddingLeft: `${this.level * this.indentSize}px`
|
|
4624
|
-
};
|
|
4625
|
-
},
|
|
4626
|
-
hiddenChildrenLabel() {
|
|
4627
|
-
if (!this.hasHiddenRootChildren) {
|
|
4628
|
-
return '';
|
|
4629
|
-
}
|
|
4630
|
-
const count = this.hiddenRootChildrenCount;
|
|
4631
|
-
const suffix = count === 1 ? 'field' : 'fields';
|
|
4632
|
-
return `${count} more ${suffix}`;
|
|
4633
|
-
},
|
|
4634
|
-
hiddenChildrenTooltip() {
|
|
4635
|
-
return this.hiddenChildrenLabel;
|
|
4636
|
-
},
|
|
4637
|
-
normalizedPath() {
|
|
4638
|
-
if (typeof this.path !== 'string') {
|
|
4639
|
-
return '';
|
|
4640
|
-
}
|
|
4641
|
-
return this.path
|
|
4642
|
-
.replace(/^root\.?/, '')
|
|
4643
|
-
.replace(/\[\d+\]/g, '')
|
|
4644
|
-
.replace(/^\./, '');
|
|
4645
|
-
},
|
|
4646
|
-
referenceModel() {
|
|
4647
|
-
if (!this.normalizedPath || !this.references) {
|
|
4648
|
-
return null;
|
|
4649
|
-
}
|
|
4650
|
-
return this.references[this.normalizedPath] || null;
|
|
4651
|
-
},
|
|
4652
|
-
shouldShowReferenceLink() {
|
|
4653
|
-
return Boolean(this.referenceModel) && typeof this.value === 'string';
|
|
4654
|
-
}
|
|
4655
|
-
},
|
|
4656
|
-
methods: {
|
|
4657
|
-
visibleObjectKeys(keys) {
|
|
4658
|
-
if (!this.isRoot || this.topLevelExpanded) {
|
|
4659
|
-
return keys;
|
|
4660
|
-
}
|
|
4661
|
-
if (typeof this.maxTopLevelFields !== 'number') {
|
|
4662
|
-
return keys;
|
|
4663
|
-
}
|
|
4664
|
-
if (keys.length <= this.maxTopLevelFields) {
|
|
4665
|
-
return keys;
|
|
4666
|
-
}
|
|
4667
|
-
return keys.slice(0, this.maxTopLevelFields);
|
|
4668
|
-
},
|
|
4669
|
-
handleToggle() {
|
|
4670
|
-
if (!this.isRoot) {
|
|
4671
|
-
this.toggleCollapse(this.path);
|
|
4672
|
-
}
|
|
4673
|
-
},
|
|
4674
|
-
handleExpandTopLevel() {
|
|
4675
|
-
if (this.isRoot && typeof this.expandTopLevel === 'function') {
|
|
4676
|
-
this.expandTopLevel();
|
|
4677
|
-
}
|
|
4678
|
-
},
|
|
4679
|
-
goToReference(id) {
|
|
4680
|
-
if (!this.referenceModel) {
|
|
4681
|
-
return;
|
|
4682
|
-
}
|
|
4683
|
-
this.$router.push({ path: `/model/${this.referenceModel}/document/${id}` });
|
|
4684
|
-
}
|
|
4685
|
-
}
|
|
4686
|
-
}
|
|
4687
5074
|
}
|
|
4688
5075
|
});
|
|
4689
5076
|
|
|
@@ -4843,7 +5230,7 @@ module.exports = ".list-subdocument pre {\n max-height: 6.5em;\n max-width: 30
|
|
|
4843
5230
|
(module) {
|
|
4844
5231
|
|
|
4845
5232
|
"use strict";
|
|
4846
|
-
module.exports = "<div class=\"list-subdocument tooltip\">\n <
|
|
5233
|
+
module.exports = "<div class=\"list-subdocument tooltip\">\n <div>{{shortenValue}}</div>\n <div class=\"tooltiptext\" style=\"display:flex; width: 100%; justify-content: space-around; align-items: center; min-width: 180px;\">\n <div class=\"tooltiptextchild\" @click.stop=\"showViewDataModal = true\">View data</div>\n <div class=\"tooltiptextchild\" @click.stop=\"copyText(value)\">Copy 📋</div>\n </div>\n <modal ref=\"viewDataModal\" v-if=\"showViewDataModal\" containerClass=\"!h-[90vh] !w-[90vw]\">\n <template #body>\n <div @click.stop class=\"w-full h-full cursor-auto\">\n <div class=\"modal-exit\" @click=\"showViewDataModal = false\">×</div>\n <list-json :value=\"value\" :maxTopLevelFields=\"null\" />\n </div>\n </template>\n </modal>\n</div>\n";
|
|
4847
5234
|
|
|
4848
5235
|
/***/ },
|
|
4849
5236
|
|
|
@@ -4864,16 +5251,24 @@ __webpack_require__(/*! ../appendCSS */ "./frontend/src/appendCSS.js")(__webpack
|
|
|
4864
5251
|
module.exports = app => app.component('list-subdocument', {
|
|
4865
5252
|
template: template,
|
|
4866
5253
|
props: ['value'],
|
|
5254
|
+
data: () => ({ showViewDataModal: false }),
|
|
4867
5255
|
computed: {
|
|
4868
5256
|
shortenValue() {
|
|
4869
|
-
|
|
5257
|
+
if (this.value == null) {
|
|
5258
|
+
return this.value;
|
|
5259
|
+
}
|
|
5260
|
+
const value = JSON.stringify(this.value);
|
|
5261
|
+
if (value.length > 50) {
|
|
5262
|
+
return `${value.slice(0, 50)}…`;
|
|
5263
|
+
}
|
|
5264
|
+
return value;
|
|
4870
5265
|
}
|
|
4871
5266
|
},
|
|
4872
5267
|
methods: {
|
|
4873
5268
|
copyText(value) {
|
|
4874
5269
|
const storage = document.createElement('textarea');
|
|
4875
5270
|
storage.value = JSON.stringify(value);
|
|
4876
|
-
const elem = this.$
|
|
5271
|
+
const elem = this.$el;
|
|
4877
5272
|
elem.appendChild(storage);
|
|
4878
5273
|
storage.select();
|
|
4879
5274
|
storage.setSelectionRange(0, 99999);
|
|
@@ -4881,12 +5276,10 @@ module.exports = app => app.component('list-subdocument', {
|
|
|
4881
5276
|
elem.removeChild(storage);
|
|
4882
5277
|
this.$toast.success('Text copied!');
|
|
4883
5278
|
}
|
|
4884
|
-
},
|
|
4885
|
-
mounted: function() {
|
|
4886
|
-
Prism.highlightElement(this.$refs.SubDocCode);
|
|
4887
5279
|
}
|
|
4888
5280
|
});
|
|
4889
5281
|
|
|
5282
|
+
|
|
4890
5283
|
/***/ },
|
|
4891
5284
|
|
|
4892
5285
|
/***/ "./frontend/src/modal/modal.css"
|
|
@@ -4940,7 +5333,7 @@ module.exports = app => app.component('modal', {
|
|
|
4940
5333
|
(module) {
|
|
4941
5334
|
|
|
4942
5335
|
"use strict";
|
|
4943
|
-
module.exports = "<form @submit.prevent=\"emitSearch\" class=\"relative flex-grow m-0\">\n <input\n ref=\"searchInput\"\n class=\"w-full font-mono rounded-md p-1 border border-gray-300 outline-gray-300 text-lg focus:ring-1 focus:ring-ultramarine-200 focus:ring-offset-0 focus:outline-none\"\n type=\"text\"\n placeholder=\"Filter\"\n v-model=\"searchText\"\n @click=\"initFilter\"\n @input=\"updateAutocomplete\"\n @keydown=\"handleKeyDown\"\n />\n <ul v-if=\"autocompleteSuggestions.length\" class=\"absolute z-[9999] bg-white border border-gray-300 rounded mt-1 w-full max-h-40 overflow-y-auto shadow\">\n <li\n v-for=\"(suggestion, index) in autocompleteSuggestions\"\n :key=\"suggestion\"\n class=\"px-2 py-1 cursor-pointer\"\n :class=\"{ 'bg-ultramarine-100': index === autocompleteIndex }\"\n @mousedown.prevent=\"applySuggestion(index)\"\n >\n {{ suggestion }}\n </li>\n </ul>\n</form>\n";
|
|
5336
|
+
module.exports = "<form @submit.prevent=\"emitSearch\" class=\"relative flex-grow m-0\">\n <input\n ref=\"searchInput\"\n class=\"w-full font-mono rounded-md p-1 border border-gray-300 outline-gray-300 text-lg focus:ring-1 focus:ring-ultramarine-200 focus:ring-offset-0 focus:outline-none\"\n type=\"text\"\n placeholder=\"Filter (supports JS, dates, ObjectIds, and more)\"\n v-model=\"searchText\"\n @click=\"initFilter\"\n @input=\"updateAutocomplete\"\n @keydown=\"handleKeyDown\"\n />\n <ul v-if=\"autocompleteSuggestions.length\" class=\"absolute z-[9999] bg-white border border-gray-300 rounded mt-1 w-full max-h-40 overflow-y-auto shadow\">\n <li\n v-for=\"(suggestion, index) in autocompleteSuggestions\"\n :key=\"suggestion\"\n class=\"px-2 py-1 cursor-pointer\"\n :class=\"{ 'bg-ultramarine-100': index === autocompleteIndex }\"\n @mousedown.prevent=\"applySuggestion(index)\"\n >\n {{ suggestion }}\n </li>\n </ul>\n</form>\n";
|
|
4944
5337
|
|
|
4945
5338
|
/***/ },
|
|
4946
5339
|
|
|
@@ -4954,34 +5347,11 @@ module.exports = "<form @submit.prevent=\"emitSearch\" class=\"relative flex-gro
|
|
|
4954
5347
|
|
|
4955
5348
|
|
|
4956
5349
|
const template = __webpack_require__(/*! ./document-search.html */ "./frontend/src/models/document-search/document-search.html");
|
|
4957
|
-
const {
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
'$gt',
|
|
4963
|
-
'$gte',
|
|
4964
|
-
'$lt',
|
|
4965
|
-
'$lte',
|
|
4966
|
-
'$in',
|
|
4967
|
-
'$nin',
|
|
4968
|
-
'$exists',
|
|
4969
|
-
'$regex',
|
|
4970
|
-
'$options',
|
|
4971
|
-
'$text',
|
|
4972
|
-
'$search',
|
|
4973
|
-
'$and',
|
|
4974
|
-
'$or',
|
|
4975
|
-
'$nor',
|
|
4976
|
-
'$not',
|
|
4977
|
-
'$elemMatch',
|
|
4978
|
-
'$size',
|
|
4979
|
-
'$all',
|
|
4980
|
-
'$type',
|
|
4981
|
-
'$expr',
|
|
4982
|
-
'$jsonSchema',
|
|
4983
|
-
'$mod'
|
|
4984
|
-
];
|
|
5350
|
+
const {
|
|
5351
|
+
buildAutocompleteTrie,
|
|
5352
|
+
getAutocompleteSuggestions,
|
|
5353
|
+
applySuggestion
|
|
5354
|
+
} = __webpack_require__(/*! ../../_util/document-search-autocomplete */ "./frontend/src/_util/document-search-autocomplete.js");
|
|
4985
5355
|
|
|
4986
5356
|
module.exports = app => app.component('document-search', {
|
|
4987
5357
|
template,
|
|
@@ -5023,19 +5393,7 @@ module.exports = app => app.component('document-search', {
|
|
|
5023
5393
|
this.$emit('search', this.searchText);
|
|
5024
5394
|
},
|
|
5025
5395
|
buildAutocompleteTrie() {
|
|
5026
|
-
this.autocompleteTrie =
|
|
5027
|
-
this.autocompleteTrie.bulkInsert(QUERY_SELECTORS, 5, 'operator');
|
|
5028
|
-
if (Array.isArray(this.schemaPaths) && this.schemaPaths.length > 0) {
|
|
5029
|
-
const paths = this.schemaPaths
|
|
5030
|
-
.map(path => path?.path)
|
|
5031
|
-
.filter(path => typeof path === 'string' && path.length > 0);
|
|
5032
|
-
for (const path of this.schemaPaths) {
|
|
5033
|
-
if (path.schema) {
|
|
5034
|
-
paths.push(...Object.keys(path.schema).map(subpath => `${path.path}.${subpath}`));
|
|
5035
|
-
}
|
|
5036
|
-
}
|
|
5037
|
-
this.autocompleteTrie.bulkInsert(paths, 10, 'fieldName');
|
|
5038
|
-
}
|
|
5396
|
+
this.autocompleteTrie = buildAutocompleteTrie(this.schemaPaths);
|
|
5039
5397
|
},
|
|
5040
5398
|
initFilter(ev) {
|
|
5041
5399
|
if (!this.searchText) {
|
|
@@ -5048,60 +5406,18 @@ module.exports = app => app.component('document-search', {
|
|
|
5048
5406
|
updateAutocomplete() {
|
|
5049
5407
|
const input = this.$refs.searchInput;
|
|
5050
5408
|
const cursorPos = input ? input.selectionStart : 0;
|
|
5051
|
-
const before = this.searchText.slice(0, cursorPos);
|
|
5052
|
-
const match = before.match(/(?:\{|,)\s*([^:\s]*)$/);
|
|
5053
|
-
if (match && match[1]) {
|
|
5054
|
-
const token = match[1];
|
|
5055
|
-
const leadingQuoteMatch = token.match(/^["']/);
|
|
5056
|
-
const trailingQuoteMatch = token.length > 1 && /["']$/.test(token)
|
|
5057
|
-
? token[token.length - 1]
|
|
5058
|
-
: '';
|
|
5059
|
-
const term = token
|
|
5060
|
-
.replace(/^["']/, '')
|
|
5061
|
-
.replace(trailingQuoteMatch ? new RegExp(`[${trailingQuoteMatch}]$`) : '', '')
|
|
5062
|
-
.trim();
|
|
5063
|
-
if (!term) {
|
|
5064
|
-
this.autocompleteSuggestions = [];
|
|
5065
|
-
return;
|
|
5066
|
-
}
|
|
5067
5409
|
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
typeof path === 'string' &&
|
|
5079
|
-
path.startsWith(`${term}.`) &&
|
|
5080
|
-
!suggestionsSet.has(path)
|
|
5081
|
-
) {
|
|
5082
|
-
suggestionsSet.add(path);
|
|
5083
|
-
if (suggestionsSet.size >= 10) {
|
|
5084
|
-
break;
|
|
5085
|
-
}
|
|
5086
|
-
}
|
|
5087
|
-
}
|
|
5088
|
-
}
|
|
5089
|
-
let suggestions = Array.from(suggestionsSet);
|
|
5090
|
-
if (leadingQuoteMatch) {
|
|
5091
|
-
const leadingQuote = leadingQuoteMatch[0];
|
|
5092
|
-
suggestions = suggestions.map(suggestion => `${leadingQuote}${suggestion}`);
|
|
5093
|
-
}
|
|
5094
|
-
if (trailingQuoteMatch) {
|
|
5095
|
-
suggestions = suggestions.map(suggestion =>
|
|
5096
|
-
suggestion.endsWith(trailingQuoteMatch) ? suggestion : `${suggestion}${trailingQuoteMatch}`
|
|
5097
|
-
);
|
|
5098
|
-
}
|
|
5099
|
-
this.autocompleteSuggestions = suggestions;
|
|
5100
|
-
this.autocompleteIndex = 0;
|
|
5101
|
-
return;
|
|
5102
|
-
}
|
|
5410
|
+
if (this.autocompleteTrie) {
|
|
5411
|
+
this.autocompleteSuggestions = getAutocompleteSuggestions(
|
|
5412
|
+
this.autocompleteTrie,
|
|
5413
|
+
this.searchText,
|
|
5414
|
+
cursorPos,
|
|
5415
|
+
this.schemaPaths
|
|
5416
|
+
);
|
|
5417
|
+
this.autocompleteIndex = 0;
|
|
5418
|
+
} else {
|
|
5419
|
+
this.autocompleteSuggestions = [];
|
|
5103
5420
|
}
|
|
5104
|
-
this.autocompleteSuggestions = [];
|
|
5105
5421
|
},
|
|
5106
5422
|
handleKeyDown(ev) {
|
|
5107
5423
|
if (this.autocompleteSuggestions.length === 0) {
|
|
@@ -5125,32 +5441,15 @@ module.exports = app => app.component('document-search', {
|
|
|
5125
5441
|
}
|
|
5126
5442
|
const input = this.$refs.searchInput;
|
|
5127
5443
|
const cursorPos = input.selectionStart;
|
|
5128
|
-
|
|
5129
|
-
const
|
|
5130
|
-
|
|
5131
|
-
const colonNeeded = !/^\s*:/.test(after);
|
|
5132
|
-
if (!match) {
|
|
5444
|
+
|
|
5445
|
+
const result = applySuggestion(this.searchText, cursorPos, suggestion);
|
|
5446
|
+
if (!result) {
|
|
5133
5447
|
return;
|
|
5134
5448
|
}
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
let replacement = suggestion;
|
|
5138
|
-
const leadingQuote = token.startsWith('"') || token.startsWith('\'') ? token[0] : '';
|
|
5139
|
-
const trailingQuote = token.length > 1 && (token.endsWith('"') || token.endsWith('\'')) ? token[token.length - 1] : '';
|
|
5140
|
-
if (leadingQuote && !replacement.startsWith(leadingQuote)) {
|
|
5141
|
-
replacement = `${leadingQuote}${replacement}`;
|
|
5142
|
-
}
|
|
5143
|
-
if (trailingQuote && !replacement.endsWith(trailingQuote)) {
|
|
5144
|
-
replacement = `${replacement}${trailingQuote}`;
|
|
5145
|
-
}
|
|
5146
|
-
// Only insert : if we know the user isn't entering in a nested path
|
|
5147
|
-
if (colonNeeded && (!leadingQuote || trailingQuote)) {
|
|
5148
|
-
replacement = `${replacement}:`;
|
|
5149
|
-
}
|
|
5150
|
-
this.searchText = this.searchText.slice(0, start) + replacement + after;
|
|
5449
|
+
|
|
5450
|
+
this.searchText = result.text;
|
|
5151
5451
|
this.$nextTick(() => {
|
|
5152
|
-
|
|
5153
|
-
input.setSelectionRange(pos, pos);
|
|
5452
|
+
input.setSelectionRange(result.newCursorPos, result.newCursorPos);
|
|
5154
5453
|
});
|
|
5155
5454
|
this.autocompleteSuggestions = [];
|
|
5156
5455
|
},
|
|
@@ -5189,7 +5488,7 @@ module.exports = app => app.component('document-search', {
|
|
|
5189
5488
|
(module) {
|
|
5190
5489
|
|
|
5191
5490
|
"use strict";
|
|
5192
|
-
module.exports = ".models {\n position: relative;\n display: flex;\n flex-direction: row;\n min-height: calc(100% - 56px);\n}\n\n.models button.gray {\n color: black;\n background-color: #eee;\n}\n\n.models .model-selector {\n background-color: #eee;\n flex-grow: 0;\n padding: 15px;\n padding-top: 0px;\n}\n\n.models h1 {\n margin-top: 0px;\n}\n\n.models .documents {\n flex-grow: 1;\n overflow: scroll;\n max-height: calc(100vh - 56px);\n}\n\n.models .documents table {\n /* max-width: -moz-fit-content;\n max-width: fit-content; */\n width: 100%;\n table-layout: auto;\n font-size: small;\n padding: 0;\n margin-right: 1em;\n white-space: nowrap;\n z-index: -1;\n border-collapse: collapse;\n line-height: 1.5em;\n}\n\n.models .documents table th {\n position: sticky;\n top: 42px;\n
|
|
5491
|
+
module.exports = ".models {\n position: relative;\n display: flex;\n flex-direction: row;\n min-height: calc(100% - 56px);\n}\n\n.models button.gray {\n color: black;\n background-color: #eee;\n}\n\n.models .model-selector {\n background-color: #eee;\n flex-grow: 0;\n padding: 15px;\n padding-top: 0px;\n}\n\n.models h1 {\n margin-top: 0px;\n}\n\n.models .documents {\n flex-grow: 1;\n overflow: scroll;\n max-height: calc(100vh - 56px);\n}\n\n.models .documents table {\n /* max-width: -moz-fit-content;\n max-width: fit-content; */\n width: 100%;\n table-layout: auto;\n font-size: small;\n padding: 0;\n margin-right: 1em;\n white-space: nowrap;\n z-index: -1;\n border-collapse: collapse;\n line-height: 1.5em;\n}\n\n.models .documents table th {\n position: sticky;\n top: 42px;\n z-index: 1;\n}\n\n.models .documents table th:after {\n content: \"\";\n position: absolute;\n left: 0;\n width: 100%;\n bottom: -1px;\n border-bottom: thin solid rgba(0, 0, 0, 0.12);\n}\n\n.models .documents table tr {\n color: black;\n border-spacing: 0px 0px;\n}\n\n.models .documents table th {\n border-bottom: thin solid rgba(0, 0, 0, 0.12);\n text-align: left;\n height: 48px;\n}\n\n.models .documents table td {\n border-bottom: thin solid rgba(0, 0, 0, 0.12);\n text-align: left;\n}\n\n.models textarea {\n font-size: 1.2em;\n}\n\n.models .path-type {\n color: rgba(0, 0, 0, 0.36);\n font-size: 0.8em;\n}\n\n.models .documents-menu {\n position: fixed;\n z-index: 1;\n padding: 4px;\n display: flex;\n width: 100vw;\n}\n\n@media (min-width: 1024px) {\n .models .documents-menu {\n width: calc(100vw - 12rem);\n }\n}\n\n.models .documents-menu .search-input {\n flex-grow: 1;\n align-items: center;\n}\n\n.models .search-input input {\n padding: 0.25em 0.5em;\n font-size: 1.1em;\n border: 1px solid #ddd;\n border-radius: 3px;\n width: calc(100% - 1em);\n}\n\n.models .sort-arrow {\n padding-left: 10px;\n padding-right: 10px;\n}\n\n.models .loader {\n width: 100%;\n text-align: center;\n}\n\n.models .loader img {\n height: 4em;\n}\n\n.models .documents .buttons {\n display: inline-flex;\n justify-content: space-around;\n align-items: center;\n}\n";
|
|
5193
5492
|
|
|
5194
5493
|
/***/ },
|
|
5195
5494
|
|
|
@@ -5200,7 +5499,7 @@ module.exports = ".models {\n position: relative;\n display: flex;\n flex-dir
|
|
|
5200
5499
|
(module) {
|
|
5201
5500
|
|
|
5202
5501
|
"use strict";
|
|
5203
|
-
module.exports = "<div class=\"models flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <div class=\"fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10\" @click=\"hideSidebar = false\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"#5f6368\"><path d=\"M360-120v-720h80v720h-80Zm160-160v-400l200 200-200 200Z\"/></svg>\n </div>\n <aside class=\"bg-white border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-48 fixed lg:relative shrink-0\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-48' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-48 overflow-x-hidden\">\n <div class=\"p-4 font-bold text-lg\">Models</div>\n <button\n @click=\"hideSidebar = true\"\n class=\"ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none\"\n aria-label=\"Close sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"currentColor\"><path d=\"M660-320v-320L500-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z\"/></svg>\n </button>\n </div>\n <nav class=\"flex flex-1 flex-col\">\n <ul role=\"list\" class=\"flex flex-1 flex-col gap-y-7\">\n <li>\n <ul role=\"list\">\n <li v-for=\"model in models\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"block truncate rounded-md py-2 pr-2 pl-2 text-sm font-semibold text-gray-700\"\n :class=\"model === currentModel ? 'bg-ultramarine-100 font-bold' : 'hover:bg-ultramarine-100'\">\n {{model}}\n </router-link>\n </li>\n </ul>\n </li>\n </ul>\n <div v-if=\"models.length === 0 && status === 'loaded'\" class=\"p-2 bg-red-100\">\n No models found\n </div>\n </nav>\n </aside>\n <div class=\"documents\" ref=\"documentsList\">\n <div class=\"relative h-[42px] z-10\">\n <div class=\"documents-menu\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <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'\">{{numDocuments === 1 ? numDocuments+ ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"shouldShowExportModal = true\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Export\n </button>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{ 'bg-gray-500 ring-inset ring-2 ring-gray-300 hover:bg-gray-600': selectMultiple, 'bg-ultramarine-600 hover:bg-ultramarine-500' : !selectMultiple }\"\n class=\"rounded px-2 py-2 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n >\n {{ selectMultiple ? 'Cancel' : 'Select' }}\n </button>\n <button\n v-show=\"selectMultiple\"\n @click=\"shouldShowUpdateMultipleModal=true;\"\n type=\"button\"\n class=\"rounded bg-green-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\"\n >\n Update\n </button>\n <button\n @click=\"shouldShowDeleteMultipleModal=true;\"\n type=\"button\"\n v-show=\"selectMultiple\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCreateModal = true;\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Create\n </button>\n <button\n @click=\"openFieldSelection\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Fields\n </button>\n <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-white px-2 py-2 text-sm font-semibold text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\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-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none z-20\"\n >\n <div class=\"py-1\">\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100\"\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-gray-700 hover:bg-gray-100\"\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-gray-700 hover:bg-gray-100\"\n >\n Find oldest document\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-gray-50 focus:z-10\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/table.svg\">\n </button>\n <button\n @click=\"setOutputType('json')\"\n type=\"button\"\n class=\"relative -ml-px inline-flex items-center rounded-none rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n </span>\n </div>\n </div>\n </div>\n <div class=\"documents-container relative\">\n <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 </div>\n </div>\n <table v-else-if=\"outputType === 'table'\">\n <thead>\n <th v-for=\"path in filteredPaths\" @click=\"addPathFilter(path.path)\" class=\"cursor-pointer\">\n {{path.path}}\n <span class=\"path-type\">\n ({{(path.instance || 'unknown')}})\n </span>\n <span class=\"sort-arrow\" @click=\"sortDocs(1, path.path)\">{{sortBy[path.path] == 1 ? 'X' : '↑'}}</span>\n <span class=\"sort-arrow\" @click=\"sortDocs(-1, path.path)\">{{sortBy[path.path] == -1 ? 'X' : '↓'}}</span>\n </th>\n </thead>\n <tbody>\n <tr v-for=\"document in documents\" @click=\"handleDocumentClick(document, $event)\" :key=\"document._id\">\n <td v-for=\"schemaPath in filteredPaths\" :class=\"{ 'bg-blue-200': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }\">\n <component\n :is=\"getComponentForPath(schemaPath)\"\n :value=\"getValueForPath(document, schemaPath.path)\"\n :allude=\"getReferenceModel(schemaPath)\">\n </component>\n </td>\n </tr>\n </tbody>\n </table>\n <div v-else-if=\"outputType === 'json'\" class=\"flex flex-col space-y-6\">\n <div\n v-for=\"document in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentContainerClick(document, $event)\"\n :class=\"[\n 'group relative transition-colors',\n selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:bg-slate-100'\n ]\"\n >\n <button\n type=\"button\"\n class=\"absolute top-2 right-2 z-10 inline-flex items-center rounded bg-ultramarine-600 px-2 py-1 text-xs font-semibold text-white shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n @click.stop=\"openDocument(document)\"\n >\n Open this Document\n </button>\n <list-json :value=\"filterDocument(document)\" :references=\"referenceMap\">\n </list-json>\n </div>\n </div>\n <div v-if=\"status === 'loading'\" class=\"loader\">\n <img src=\"images/loader.gif\">\n </div>\n </div>\n </div>\n <modal v-if=\"shouldShowExportModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowExportModal = false\">×</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-ultramarine-100 px-2 py-0.5 text-xs font-semibold text-ultramarine-700\">\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-gray-700\">Documents</div>\n <div class=\"text-gray-900\">{{ formatNumber(collectionInfo.documentCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Indexes</div>\n <div class=\"text-gray-900\">{{ formatNumber(collectionInfo.indexCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Total Index Size</div>\n <div class=\"text-gray-900\">{{ formatCollectionSize(collectionInfo.totalIndexSize) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Total Storage Size</div>\n <div class=\"text-gray-900\">{{ 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-gray-700\">Collation</div>\n <div class=\"text-gray-900\">{{ collectionInfo.hasCollation ? 'Yes' : 'No' }}</div>\n </div>\n <div v-if=\"collectionInfo.hasCollation\" class=\"rounded bg-gray-100 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-gray-700\">Capped</div>\n <div class=\"text-gray-900\">{{ collectionInfo.capped ? 'Yes' : 'No' }}</div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowFieldModal = false; selectedPaths = [...filteredPaths];\">×</div>\n <div v-for=\"(path, index) in schemaPaths\" :key=\"index\" class=\"w-5 flex items-center\">\n <input class=\"mt-0 h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-600 accent-sky-600\" type=\"checkbox\" :id=\"'path.path'+index\" @change=\"addOrRemove(path)\" :value=\"path.path\" :checked=\"isSelected(path.path)\" />\n <div class=\"ml-2 text-gray-700 grow shrink text-left\">\n <label :for=\"'path.path' + index\">{{path.path}}</label>\n </div>\n </div>\n <div class=\"mt-4 flex gap-2\">\n <button type=\"button\" @click=\"filterDocuments()\" class=\"rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">Filter Selection</button>\n <button type=\"button\" @click=\"selectAll()\" class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">Select All</button>\n <button type=\"button\" @click=\"deselectAll()\" class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">Deselect All</button>\n <button type=\"button\" @click=\"resetDocuments()\" class=\"rounded-md bg-gray-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\" >Cancel</button>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCreateModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCreateModal = false;\">×</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-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
5502
|
+
module.exports = "<div class=\"models flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <div class=\"fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10\" @click=\"hideSidebar = false\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"#5f6368\"><path d=\"M360-120v-720h80v720h-80Zm160-160v-400l200 200-200 200Z\"/></svg>\n </div>\n <aside class=\"bg-white border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-48 fixed lg:relative shrink-0\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-48' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-48 overflow-x-hidden\">\n <div class=\"p-1 ml-2 font-bold\">Models</div>\n <button\n @click=\"hideSidebar = true\"\n class=\"ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none\"\n aria-label=\"Close sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"currentColor\"><path d=\"M660-320v-320L500-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z\"/></svg>\n </button>\n </div>\n <nav class=\"flex flex-1 flex-col\">\n <ul role=\"list\" class=\"flex flex-1 flex-col gap-y-7 p-1\">\n <li>\n <ul role=\"list\">\n <li v-for=\"model in models\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"block truncate rounded-md py-2 pr-2 pl-2 text-sm text-gray-700\"\n :class=\"model === currentModel ? 'bg-ultramarine-100 font-bold text-gray-900' : 'hover:bg-ultramarine-50'\">\n {{model}}\n </router-link>\n </li>\n </ul>\n </li>\n </ul>\n <div v-if=\"models.length === 0 && status === 'loaded'\" class=\"p-2 bg-red-100\">\n No models found\n </div>\n </nav>\n </aside>\n <div class=\"documents bg-slate-50\" ref=\"documentsList\">\n <div class=\"relative h-[42px] z-10\">\n <div class=\"documents-menu bg-slate-50\">\n <div class=\"flex flex-row items-center w-full gap-2\">\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'\">{{numDocuments === 1 ? numDocuments+ ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{ 'bg-gray-500 ring-inset ring-2 ring-gray-300 hover:bg-gray-600': selectMultiple, 'bg-ultramarine-600 hover:bg-ultramarine-500' : !selectMultiple }\"\n class=\"rounded px-2 py-2 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n >\n {{ selectMultiple ? 'Cancel' : 'Select' }}\n </button>\n <button\n v-show=\"selectMultiple\"\n @click=\"shouldShowUpdateMultipleModal=true;\"\n type=\"button\"\n class=\"rounded bg-green-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\"\n >\n Update\n </button>\n <button\n @click=\"shouldShowDeleteMultipleModal=true;\"\n type=\"button\"\n v-show=\"selectMultiple\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500\"\n >\n Delete\n </button>\n <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-white px-2 py-2 text-sm font-semibold text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\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-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none z-20\"\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-gray-700 hover:bg-gray-100\"\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-gray-700 hover:bg-gray-100\"\n >\n Create\n </button>\n <button\n @click=\"openFieldSelection(); showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100\"\n >\n Projection\n </button>\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100\"\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-gray-700 hover:bg-gray-100\"\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-gray-700 hover:bg-gray-100\"\n >\n Find oldest document\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-gray-50 focus:z-10\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/table.svg\">\n </button>\n <button\n @click=\"setOutputType('json')\"\n type=\"button\"\n class=\"relative -ml-px inline-flex items-center rounded-none rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n </span>\n </div>\n </div>\n </div>\n <div class=\"documents-container relative\">\n <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 </div>\n </div>\n <table v-else-if=\"outputType === 'table'\">\n <thead class=\"bg-slate-50\">\n <th v-for=\"path in filteredPaths\" @click=\"addPathFilter(path.path)\" class=\"cursor-pointer p-3\">\n {{path.path}}\n <span class=\"path-type\">\n ({{(path.instance || 'unknown')}})\n </span>\n <span class=\"sort-arrow\" @click=\"sortDocs(1, path.path)\">{{sortBy[path.path] == 1 ? 'X' : '↑'}}</span>\n <span class=\"sort-arrow\" @click=\"sortDocs(-1, path.path)\">{{sortBy[path.path] == -1 ? 'X' : '↓'}}</span>\n </th>\n </thead>\n <tbody>\n <tr v-for=\"document in documents\" @click=\"handleDocumentClick(document, $event)\" :key=\"document._id\" class=\"bg-white hover:bg-slate-50\">\n <td v-for=\"schemaPath in filteredPaths\" class=\"p-3 cursor-pointer\" :class=\"{ 'bg-blue-200': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }\">\n <component\n :is=\"getComponentForPath(schemaPath)\"\n :value=\"getValueForPath(document, schemaPath.path)\"\n :allude=\"getReferenceModel(schemaPath)\">\n </component>\n </td>\n </tr>\n </tbody>\n </table>\n <div v-else-if=\"outputType === 'json'\" class=\"flex 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 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-white'\n ]\"\n >\n <button\n type=\"button\"\n class=\"absolute top-2 right-2 z-10 inline-flex items-center rounded bg-ultramarine-600 px-2 py-1 text-xs font-semibold text-white shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n @click.stop=\"openDocument(document)\"\n >\n Open this Document\n </button>\n <list-json :value=\"filterDocument(document)\" :references=\"referenceMap\">\n </list-json>\n </div>\n </div>\n <div v-if=\"status === 'loading'\" class=\"loader\">\n <img src=\"images/loader.gif\">\n </div>\n </div>\n </div>\n <modal v-if=\"shouldShowExportModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowExportModal = false\">×</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-ultramarine-100 px-2 py-0.5 text-xs font-semibold text-ultramarine-700\">\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-gray-700\">Documents</div>\n <div class=\"text-gray-900\">{{ formatNumber(collectionInfo.documentCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Indexes</div>\n <div class=\"text-gray-900\">{{ formatNumber(collectionInfo.indexCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Total Index Size</div>\n <div class=\"text-gray-900\">{{ formatCollectionSize(collectionInfo.totalIndexSize) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Total Storage Size</div>\n <div class=\"text-gray-900\">{{ 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-gray-700\">Collation</div>\n <div class=\"text-gray-900\">{{ collectionInfo.hasCollation ? 'Yes' : 'No' }}</div>\n </div>\n <div v-if=\"collectionInfo.hasCollation\" class=\"rounded bg-gray-100 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-gray-700\">Capped</div>\n <div class=\"text-gray-900\">{{ collectionInfo.capped ? 'Yes' : 'No' }}</div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowFieldModal = false; selectedPaths = [...filteredPaths];\">×</div>\n <div v-for=\"(path, index) in schemaPaths\" :key=\"index\" class=\"w-5 flex items-center\">\n <input class=\"mt-0 h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-600 accent-sky-600\" type=\"checkbox\" :id=\"'path.path'+index\" @change=\"addOrRemove(path)\" :value=\"path.path\" :checked=\"isSelected(path.path)\" />\n <div class=\"ml-2 text-gray-700 grow shrink text-left\">\n <label :for=\"'path.path' + index\">{{path.path}}</label>\n </div>\n </div>\n <div class=\"mt-4 flex gap-2\">\n <button type=\"button\" @click=\"filterDocuments()\" class=\"rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">Filter Selection</button>\n <button type=\"button\" @click=\"selectAll()\" class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">Select All</button>\n <button type=\"button\" @click=\"deselectAll()\" class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">Deselect All</button>\n <button type=\"button\" @click=\"resetDocuments()\" class=\"rounded-md bg-gray-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\" >Cancel</button>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCreateModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCreateModal = false;\">×</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-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
5204
5503
|
|
|
5205
5504
|
/***/ },
|
|
5206
5505
|
|
|
@@ -5298,7 +5597,7 @@ module.exports = app => app.component('models', {
|
|
|
5298
5597
|
this.error = 'No models found and Mongoose is not connected. Check our documentation for more information.';
|
|
5299
5598
|
}
|
|
5300
5599
|
}
|
|
5301
|
-
|
|
5600
|
+
|
|
5302
5601
|
await this.initSearchFromUrl();
|
|
5303
5602
|
},
|
|
5304
5603
|
computed: {
|
|
@@ -6057,7 +6356,7 @@ module.exports = ".active {\n text-decoration: underline;\n}\n\n.navbar .nav-le
|
|
|
6057
6356
|
(module) {
|
|
6058
6357
|
|
|
6059
6358
|
"use strict";
|
|
6060
|
-
module.exports = "<div class=\"navbar w-full bg-gray-50 flex justify-between border-b border-gray-200 !h-[55px]\">\n <div class=\"flex items-center gap-4 h-full pl-4\">\n <router-link :to=\"{ name: defaultRoute }\">\n <img src=\"images/logo.svg\" class=\"h-[32px] mr-1\" alt=\"Mongoose Studio Logo\" />\n </router-link>\n <div v-if=\"!!state.nodeEnv\" title=\"NODE_ENV\" class=\"inline-flex items-center rounded-md px-2 py-1 text-sm font-medium text-gray-900\" :class=\"warnEnv ? 'bg-red-300' : 'bg-yellow-300'\">\n {{state.nodeEnv}}\n </div>\n </div>\n <div class=\"h-full pr-4 hidden md:block\">\n <div class=\"sm:ml-6 sm:flex sm:space-x-8 h-full\">\n <a v-if=\"hasAccess(roles, 'root')\"\n href=\"#/\"\n class=\"inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium\"\n :class=\"documentView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Documents</a>\n <span v-else class=\"inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium text-gray-300 cursor-not-allowed\" aria-disabled=\"true\">\n Documents\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'dashboards')\"\n href=\"#/dashboards\"\n class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium\"\n :class=\"dashboardView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Dashboards</a>\n <span v-else class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium text-gray-300 cursor-not-allowed\">\n Dashboards\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'chat')\"\n href=\"#/chat\"\n class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium\"\n :class=\"chatView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Chat</a>\n <span v-else class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium text-gray-300 cursor-not-allowed\">\n Chat\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a\n href=\"https://studio.mongoosejs.io/docs\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700 border-transparent\"\n >\n Docs\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"ml-1 h-4 w-4\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z\"/></svg>\n </a>\n\n <div class=\"h-full flex items-center\" v-if=\"!user && hasAPIKey\">\n <button\n type=\"button\"\n @click=\"loginWithGithub\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"h-full flex items-center relative\" v-clickOutside=\"hideFlyout\">\n <div>\n <button type=\"button\" @click=\"showFlyout = !showFlyout\" class=\"relative flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800\" id=\"user-menu-button\" aria-expanded=\"false\" aria-haspopup=\"true\">\n <span class=\"absolute -inset-1.5\"></span>\n <span class=\"sr-only\">Open user menu</span>\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n </button>\n </div>\n\n <div v-if=\"showFlyout\" class=\"absolute right-0 z-[100] top-[90%] w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5 focus:outline-none\" role=\"menu\" aria-orientation=\"vertical\" aria-labelledby=\"user-menu-button\" tabindex=\"-1\">\n <router-link to=\"/team\" v-if=\"hasAccess(roles, 'team')\" @click=\"showFlyout = false\" class=\"cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Team</router-link>\n <span v-else class=\"block px-4 py-2 text-sm text-gray-300 cursor-not-allowed\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">\n Team\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span @click=\"logout\" class=\"cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Sign out</span>\n </div>\n </div>\n\n </div>\n </div>\n <div class=\"md:hidden flex items-center\">\n <!-- Mobile menu toggle, controls the 'mobileMenuOpen' state. -->\n <button type=\"button\" id=\"open-mobile-menu\" class=\"-ml-2 rounded-md p-2 pr-4 text-gray-400\">\n <span class=\"sr-only\">Open menu</span>\n <svg class=\"h-6 w-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5\" />\n </svg>\n </button>\n </div>\n\n <!-- Mobile menu mask -->\n <div id=\"mobile-menu-mask\" class=\"fixed inset-0 bg-black bg-opacity-40 z-40 hidden\"></div>\n <!-- Mobile menu drawer -->\n <div id=\"mobile-menu\" class=\"fixed inset-0 bg-white shadow-lg z-50 transform translate-x-full transition-transform duration-200 ease-in-out flex flex-col\">\n <div class=\"flex items-center justify-between px-4 !h-[55px] border-b border-gray-200\">\n <router-link :to=\"{ name: defaultRoute }\">\n <img src=\"images/logo.svg\" class=\"h-[32px]\" alt=\"Mongoose Studio Logo\" />\n </router-link>\n <button type=\"button\" id=\"close-mobile-menu\" class=\"text-gray-400 p-2 rounded-md\">\n <span class=\"sr-only\">Close menu</span>\n <svg class=\"h-6 w-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <nav class=\"flex-1 px-4 py-4 space-y-2\">\n <a v-if=\"hasAccess(roles, 'root')\"\n href=\"#/\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"documentView ? 'text-ultramarine-700 bg-ultramarine-100' : 'text-gray-700 hover:bg-gray-100'\">Documents</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Documents\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'dashboards')\"\n href=\"#/dashboards\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"dashboardView ? 'text-ultramarine-700 bg-ultramarine-100' : 'text-gray-700 hover:bg-gray-100'\">Dashboards</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Dashboards\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'chat')\"\n href=\"#/chat\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"chatView ? 'text-ultramarine-700 bg-ultramarine-100' : 'text-gray-700 hover:bg-gray-100'\">Chat</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Chat\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <div v-if=\"!user && hasAPIKey\" class=\"mt-4\">\n <button\n type=\"button\"\n @click=\"loginWithGithub\"\n class=\"w-full rounded bg-ultramarine-600 px-3 py-2 text-base font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"mt-4\">\n <div class=\"flex items-center gap-3 px-3 py-2 bg-gray-50 rounded-md\">\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n <span class=\"text-gray-900 font-medium\">{{ user.name }}</span>\n </div>\n <div class=\"mt-2 space-y-1\">\n <router-link to=\"/team\" v-if=\"hasAccess(roles, 'team')\" class=\"block px-3 py-2 rounded-md text-base text-gray-700 hover:bg-ultramarine-100\">Team</router-link>\n <span v-else class=\"block px-3 py-2 rounded-md text-base text-gray-300 cursor-not-allowed\">\n Team\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span @click=\"logout\" class=\"block px-3 py-2 rounded-md text-base text-gray-700 hover:bg-ultramarine-100 cursor-pointer\">Sign out</span>\n </div>\n </div>\n </nav>\n </div>\n</div>\n";
|
|
6359
|
+
module.exports = "<div class=\"navbar w-full bg-white flex justify-between border-b border-gray-200 !h-[55px]\">\n <div class=\"flex items-center gap-4 md:gap-6 h-full pl-4\">\n <router-link :to=\"{ name: defaultRoute }\">\n <img src=\"images/logo.svg\" class=\"h-10 mr-1\" alt=\"Mongoose Studio Logo\" />\n </router-link>\n <div\n v-if=\"!!state.nodeEnv\"\n title=\"NODE_ENV\"\n class=\"inline-flex items-center rounded px-2 py-1 text-xs text-gray-500 bg-white border border-gray-200 gap-2\"\n >\n <span\n :class=\"warnEnv ? 'bg-red-400' : 'bg-yellow-400'\"\n class=\"inline-block rounded-full\"\n style=\"width: 0.5em; height: 0.5em;\"\n ></span>\n <span>{{state.nodeEnv}}</span>\n </div>\n </div>\n <div class=\"h-full pr-4 hidden md:block\">\n <div class=\"sm:ml-6 sm:flex sm:space-x-8 h-full\">\n <a v-if=\"hasAccess(roles, 'root')\"\n href=\"#/\"\n class=\"inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium\"\n :class=\"documentView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Documents</a>\n <span v-else class=\"inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium text-gray-300 cursor-not-allowed\" aria-disabled=\"true\">\n Documents\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'dashboards')\"\n href=\"#/dashboards\"\n class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium\"\n :class=\"dashboardView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Dashboards</a>\n <span v-else class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium text-gray-300 cursor-not-allowed\">\n Dashboards\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'chat')\"\n href=\"#/chat\"\n class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium\"\n :class=\"chatView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Chat</a>\n <span v-else class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium text-gray-300 cursor-not-allowed\">\n Chat\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a\n href=\"https://studio.mongoosejs.io/docs\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700 border-transparent\"\n >\n Docs\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"ml-1 h-4 w-4\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z\"/></svg>\n </a>\n\n <div class=\"h-full flex items-center\" v-if=\"!user && hasAPIKey\">\n <button\n type=\"button\"\n @click=\"loginWithGithub\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"h-full flex items-center relative\" v-clickOutside=\"hideFlyout\">\n <div>\n <button type=\"button\" @click=\"showFlyout = !showFlyout\" class=\"relative flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800\" id=\"user-menu-button\" aria-expanded=\"false\" aria-haspopup=\"true\">\n <span class=\"absolute -inset-1.5\"></span>\n <span class=\"sr-only\">Open user menu</span>\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n </button>\n </div>\n\n <div v-if=\"showFlyout\" class=\"absolute right-0 z-[100] top-[90%] w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5 focus:outline-none\" role=\"menu\" aria-orientation=\"vertical\" aria-labelledby=\"user-menu-button\" tabindex=\"-1\">\n <router-link to=\"/team\" v-if=\"hasAccess(roles, 'team')\" @click=\"showFlyout = false\" class=\"cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Team</router-link>\n <span v-else class=\"block px-4 py-2 text-sm text-gray-300 cursor-not-allowed\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">\n Team\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span @click=\"logout\" class=\"cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Sign out</span>\n </div>\n </div>\n\n </div>\n </div>\n <div class=\"md:hidden flex items-center\">\n <!-- Mobile menu toggle, controls the 'mobileMenuOpen' state. -->\n <button type=\"button\" id=\"open-mobile-menu\" class=\"-ml-2 rounded-md p-2 pr-4 text-gray-400\">\n <span class=\"sr-only\">Open menu</span>\n <svg class=\"h-6 w-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5\" />\n </svg>\n </button>\n </div>\n\n <!-- Mobile menu mask -->\n <div id=\"mobile-menu-mask\" class=\"fixed inset-0 bg-black bg-opacity-40 z-40 hidden\"></div>\n <!-- Mobile menu drawer -->\n <div id=\"mobile-menu\" style=\"z-index: 1000\" class=\"fixed inset-0 bg-white shadow-lg transform translate-x-full transition-transform duration-200 ease-in-out flex flex-col\">\n <div class=\"flex items-center justify-between px-4 !h-[55px] border-b border-gray-200\">\n <router-link :to=\"{ name: defaultRoute }\">\n <img src=\"images/logo.svg\" class=\"h-[32px]\" alt=\"Mongoose Studio Logo\" />\n </router-link>\n <button type=\"button\" id=\"close-mobile-menu\" class=\"text-gray-400 p-2 rounded-md\">\n <span class=\"sr-only\">Close menu</span>\n <svg class=\"h-6 w-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <nav class=\"flex-1 px-4 py-4 space-y-2\">\n <a v-if=\"hasAccess(roles, 'root')\"\n href=\"#/\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"documentView ? 'text-ultramarine-700 bg-ultramarine-100' : 'text-gray-700 hover:bg-gray-100'\">Documents</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Documents\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'dashboards')\"\n href=\"#/dashboards\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"dashboardView ? 'text-ultramarine-700 bg-ultramarine-100' : 'text-gray-700 hover:bg-gray-100'\">Dashboards</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Dashboards\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'chat')\"\n href=\"#/chat\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"chatView ? 'text-ultramarine-700 bg-ultramarine-100' : 'text-gray-700 hover:bg-gray-100'\">Chat</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Chat\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <div v-if=\"!user && hasAPIKey\" class=\"mt-4\">\n <button\n type=\"button\"\n @click=\"loginWithGithub\"\n class=\"w-full rounded bg-ultramarine-600 px-3 py-2 text-base font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"mt-4\">\n <div class=\"flex items-center gap-3 px-3 py-2 bg-gray-50 rounded-md\">\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n <span class=\"text-gray-900 font-medium\">{{ user.name }}</span>\n </div>\n <div class=\"mt-2 space-y-1\">\n <router-link to=\"/team\" v-if=\"hasAccess(roles, 'team')\" class=\"block px-3 py-2 rounded-md text-base text-gray-700 hover:bg-ultramarine-100\">Team</router-link>\n <span v-else class=\"block px-3 py-2 rounded-md text-base text-gray-300 cursor-not-allowed\">\n Team\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span @click=\"logout\" class=\"block px-3 py-2 rounded-md text-base text-gray-700 hover:bg-ultramarine-100 cursor-pointer\">Sign out</span>\n </div>\n </div>\n </nav>\n </div>\n</div>\n";
|
|
6061
6360
|
|
|
6062
6361
|
/***/ },
|
|
6063
6362
|
|
|
@@ -31935,7 +32234,7 @@ const compile = () => {
|
|
|
31935
32234
|
(module) {
|
|
31936
32235
|
|
|
31937
32236
|
"use strict";
|
|
31938
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.
|
|
32237
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.2.0","description":"A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.","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","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x"},"peerDependencies":{"mongoose":"7.x || 8.x || ^9.0.0"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongoose":"9.x"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js"}}');
|
|
31939
32238
|
|
|
31940
32239
|
/***/ }
|
|
31941
32240
|
|