@mongoosejs/studio 0.5.7 → 0.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/frontend/public/app.js +952 -202
- package/frontend/public/tw.css +113 -0
- package/frontend/src/_util/calendar.js +155 -0
- package/frontend/src/_util/document-search-autocomplete.js +105 -23
- package/frontend/src/custom-date-picker/custom-date-picker.html +183 -0
- package/frontend/src/custom-date-picker/custom-date-picker.js +407 -0
- package/frontend/src/document/document.css +62 -0
- package/frontend/src/document/document.html +113 -2
- package/frontend/src/document/document.js +171 -9
- package/frontend/src/document-details/date-view-mode-picker/date-view-mode-picker.html +1 -0
- package/frontend/src/document-details/date-view-mode-picker/date-view-mode-picker.js +1 -1
- package/frontend/src/document-details/document-details.css +1 -63
- package/frontend/src/document-details/document-details.html +0 -122
- package/frontend/src/document-details/document-details.js +2 -138
- package/frontend/src/document-details/document-property/document-property.html +3 -2
- package/frontend/src/edit-date/edit-date.html +8 -6
- package/frontend/src/edit-date/edit-date.js +0 -20
- package/frontend/src/models/document-search/document-search.html +78 -22
- package/frontend/src/models/document-search/document-search.js +64 -2
- package/package.json +1 -1
package/frontend/public/app.js
CHANGED
|
@@ -60,6 +60,172 @@ module.exports = {
|
|
|
60
60
|
};
|
|
61
61
|
|
|
62
62
|
|
|
63
|
+
/***/ },
|
|
64
|
+
|
|
65
|
+
/***/ "./frontend/src/_util/calendar.js"
|
|
66
|
+
/*!****************************************!*\
|
|
67
|
+
!*** ./frontend/src/_util/calendar.js ***!
|
|
68
|
+
\****************************************/
|
|
69
|
+
(module) {
|
|
70
|
+
|
|
71
|
+
"use strict";
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
function pad2(n) {
|
|
75
|
+
return String(n).padStart(2, '0');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function pad3(n) {
|
|
79
|
+
return String(n).padStart(3, '0');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {number} value
|
|
84
|
+
* @param {number} min
|
|
85
|
+
* @param {number} max
|
|
86
|
+
*/
|
|
87
|
+
function clampInt(value, min, max) {
|
|
88
|
+
const n = Math.floor(Number(value));
|
|
89
|
+
if (Number.isNaN(n)) {
|
|
90
|
+
return min;
|
|
91
|
+
}
|
|
92
|
+
return Math.min(max, Math.max(min, n));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @param {Date} date
|
|
97
|
+
* @returns {string} YYYY-MM-DDTHH:mm:ss.SSS
|
|
98
|
+
*/
|
|
99
|
+
function dateToDatetimeLocal(date) {
|
|
100
|
+
if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
|
|
101
|
+
return '';
|
|
102
|
+
}
|
|
103
|
+
return [
|
|
104
|
+
date.getFullYear(),
|
|
105
|
+
'-',
|
|
106
|
+
pad2(date.getMonth() + 1),
|
|
107
|
+
'-',
|
|
108
|
+
pad2(date.getDate()),
|
|
109
|
+
'T',
|
|
110
|
+
pad2(date.getHours()),
|
|
111
|
+
':',
|
|
112
|
+
pad2(date.getMinutes()),
|
|
113
|
+
':',
|
|
114
|
+
pad2(date.getSeconds()),
|
|
115
|
+
'.',
|
|
116
|
+
pad3(date.getMilliseconds())
|
|
117
|
+
].join('');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @param {Date} date
|
|
122
|
+
* @returns {string} YYYY-MM-DD
|
|
123
|
+
*/
|
|
124
|
+
function dateToDateOnly(date) {
|
|
125
|
+
if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
|
|
126
|
+
return '';
|
|
127
|
+
}
|
|
128
|
+
return [
|
|
129
|
+
date.getFullYear(),
|
|
130
|
+
'-',
|
|
131
|
+
pad2(date.getMonth() + 1),
|
|
132
|
+
'-',
|
|
133
|
+
pad2(date.getDate())
|
|
134
|
+
].join('');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @param {unknown} value
|
|
139
|
+
* @returns {Date|null}
|
|
140
|
+
*/
|
|
141
|
+
function parseValueToDate(value) {
|
|
142
|
+
if (value == null || value === '') {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
const date = new Date(value);
|
|
146
|
+
if (Number.isNaN(date.getTime())) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
return date;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @param {string} localValue - YYYY-MM-DD or YYYY-MM-DDTHH:mm:ss.SSS
|
|
154
|
+
* @returns {Date|null}
|
|
155
|
+
*/
|
|
156
|
+
function localStringToDate(localValue) {
|
|
157
|
+
if (!localValue) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
const date = new Date(localValue);
|
|
161
|
+
if (Number.isNaN(date.getTime())) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
return date;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function isSameDay(a, b) {
|
|
168
|
+
if (!a || !b) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
return (
|
|
172
|
+
a.getFullYear() === b.getFullYear()
|
|
173
|
+
&& a.getMonth() === b.getMonth()
|
|
174
|
+
&& a.getDate() === b.getDate()
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function isSameMonth(date, year, month) {
|
|
179
|
+
return date.getFullYear() === year && date.getMonth() === month;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Build a 6-row calendar grid for the given month (local timezone).
|
|
184
|
+
* @param {number} year
|
|
185
|
+
* @param {number} month - 0-indexed
|
|
186
|
+
* @returns {Array<{ key: string, label: number, date: Date, inMonth: boolean }>}
|
|
187
|
+
*/
|
|
188
|
+
function buildCalendarDays(year, month) {
|
|
189
|
+
const firstOfMonth = new Date(year, month, 1);
|
|
190
|
+
const startOffset = firstOfMonth.getDay();
|
|
191
|
+
const gridStart = new Date(year, month, 1 - startOffset);
|
|
192
|
+
|
|
193
|
+
const days = [];
|
|
194
|
+
for (let i = 0; i < 42; i++) {
|
|
195
|
+
const date = new Date(gridStart.getFullYear(), gridStart.getMonth(), gridStart.getDate() + i);
|
|
196
|
+
days.push({
|
|
197
|
+
key: `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`,
|
|
198
|
+
label: date.getDate(),
|
|
199
|
+
date,
|
|
200
|
+
inMonth: date.getMonth() === month
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return days;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const WEEKDAY_LABELS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
|
|
207
|
+
|
|
208
|
+
const MONTH_LABELS = [
|
|
209
|
+
'January', 'February', 'March', 'April', 'May', 'June',
|
|
210
|
+
'July', 'August', 'September', 'October', 'November', 'December'
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
module.exports = {
|
|
214
|
+
pad2,
|
|
215
|
+
pad3,
|
|
216
|
+
clampInt,
|
|
217
|
+
dateToDatetimeLocal,
|
|
218
|
+
dateToDateOnly,
|
|
219
|
+
parseValueToDate,
|
|
220
|
+
localStringToDate,
|
|
221
|
+
isSameDay,
|
|
222
|
+
isSameMonth,
|
|
223
|
+
buildCalendarDays,
|
|
224
|
+
WEEKDAY_LABELS,
|
|
225
|
+
MONTH_LABELS
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
|
|
63
229
|
/***/ },
|
|
64
230
|
|
|
65
231
|
/***/ "./frontend/src/_util/dateRange.js"
|
|
@@ -228,6 +394,8 @@ module.exports = deepEqual;
|
|
|
228
394
|
"use strict";
|
|
229
395
|
|
|
230
396
|
|
|
397
|
+
const { dateToDatetimeLocal } = __webpack_require__(/*! ./calendar */ "./frontend/src/_util/calendar.js");
|
|
398
|
+
|
|
231
399
|
const { Trie } = __webpack_require__(/*! ../models/trie */ "./frontend/src/models/trie.js");
|
|
232
400
|
|
|
233
401
|
const QUERY_SELECTORS = [
|
|
@@ -277,7 +445,7 @@ function buildAutocompleteTrie(schemaPaths) {
|
|
|
277
445
|
trie.bulkInsert(QUERY_SELECTORS, 5, 'operator');
|
|
278
446
|
trie.bulkInsert(QUERY_SELECTORS, 5, 'fieldName');
|
|
279
447
|
trie.bulkInsert(VALUE_HELPERS, 5, 'value');
|
|
280
|
-
|
|
448
|
+
|
|
281
449
|
if (Array.isArray(schemaPaths) && schemaPaths.length > 0) {
|
|
282
450
|
const paths = schemaPaths
|
|
283
451
|
.map(path => path?.path)
|
|
@@ -289,13 +457,13 @@ function buildAutocompleteTrie(schemaPaths) {
|
|
|
289
457
|
}
|
|
290
458
|
trie.bulkInsert(paths, 10, 'fieldName');
|
|
291
459
|
}
|
|
292
|
-
|
|
460
|
+
|
|
293
461
|
return trie;
|
|
294
462
|
}
|
|
295
463
|
|
|
296
464
|
function getAutocompleteContext(searchText, cursorPos) {
|
|
297
465
|
const before = searchText.slice(0, cursorPos);
|
|
298
|
-
|
|
466
|
+
|
|
299
467
|
// Check if we're in a field name context (after { or ,)
|
|
300
468
|
// This takes precedence over value context to handle cases like { _id: { $gt
|
|
301
469
|
const fieldMatch = before.match(/(?:\{|,)\s*([^:\s]*)$/);
|
|
@@ -307,7 +475,7 @@ function getAutocompleteContext(searchText, cursorPos) {
|
|
|
307
475
|
startPos: cursorPos - token.length
|
|
308
476
|
};
|
|
309
477
|
}
|
|
310
|
-
|
|
478
|
+
|
|
311
479
|
// Check if we're in a value context (after a colon)
|
|
312
480
|
// Match the last colon followed by optional whitespace and capture everything after
|
|
313
481
|
const valueMatch = before.match(/:\s*(\{?\s*)([^\s,\}\]:]*)$/);
|
|
@@ -319,19 +487,19 @@ function getAutocompleteContext(searchText, cursorPos) {
|
|
|
319
487
|
startPos: cursorPos - token.length
|
|
320
488
|
};
|
|
321
489
|
}
|
|
322
|
-
|
|
490
|
+
|
|
323
491
|
return null;
|
|
324
492
|
}
|
|
325
493
|
|
|
326
494
|
function getAutocompleteSuggestions(trie, searchText, cursorPos, schemaPaths) {
|
|
327
495
|
const context = getAutocompleteContext(searchText, cursorPos);
|
|
328
|
-
|
|
496
|
+
|
|
329
497
|
if (!context) {
|
|
330
498
|
return [];
|
|
331
499
|
}
|
|
332
|
-
|
|
500
|
+
|
|
333
501
|
const { token, role } = context;
|
|
334
|
-
|
|
502
|
+
|
|
335
503
|
// Extract the actual term without quotes
|
|
336
504
|
const leadingQuoteMatch = token.match(/^["']/);
|
|
337
505
|
const trailingQuoteMatch = token.length > 1 && /["']$/.test(token)
|
|
@@ -341,14 +509,14 @@ function getAutocompleteSuggestions(trie, searchText, cursorPos, schemaPaths) {
|
|
|
341
509
|
.replace(/^["']/, '')
|
|
342
510
|
.replace(trailingQuoteMatch ? new RegExp(`[${trailingQuoteMatch}]$`) : '', '')
|
|
343
511
|
.trim();
|
|
344
|
-
|
|
512
|
+
|
|
345
513
|
if (!term) {
|
|
346
514
|
return [];
|
|
347
515
|
}
|
|
348
|
-
|
|
516
|
+
|
|
349
517
|
const primarySuggestions = trie.getSuggestions(term, 10, role);
|
|
350
518
|
const suggestionsSet = new Set(primarySuggestions);
|
|
351
|
-
|
|
519
|
+
|
|
352
520
|
// Add schema path suggestions for field names
|
|
353
521
|
if (role === 'fieldName' && Array.isArray(schemaPaths) && schemaPaths.length > 0) {
|
|
354
522
|
for (const schemaPath of schemaPaths) {
|
|
@@ -365,9 +533,9 @@ function getAutocompleteSuggestions(trie, searchText, cursorPos, schemaPaths) {
|
|
|
365
533
|
}
|
|
366
534
|
}
|
|
367
535
|
}
|
|
368
|
-
|
|
536
|
+
|
|
369
537
|
let suggestions = Array.from(suggestionsSet);
|
|
370
|
-
|
|
538
|
+
|
|
371
539
|
// Preserve quotes if present
|
|
372
540
|
if (leadingQuoteMatch) {
|
|
373
541
|
const leadingQuote = leadingQuoteMatch[0];
|
|
@@ -378,14 +546,14 @@ function getAutocompleteSuggestions(trie, searchText, cursorPos, schemaPaths) {
|
|
|
378
546
|
suggestion.endsWith(trailingQuoteMatch) ? suggestion : `${suggestion}${trailingQuoteMatch}`
|
|
379
547
|
);
|
|
380
548
|
}
|
|
381
|
-
|
|
549
|
+
|
|
382
550
|
return suggestions;
|
|
383
551
|
}
|
|
384
552
|
|
|
385
553
|
function applySuggestion(searchText, cursorPos, suggestion) {
|
|
386
554
|
const before = searchText.slice(0, cursorPos);
|
|
387
555
|
const after = searchText.slice(cursorPos);
|
|
388
|
-
|
|
556
|
+
|
|
389
557
|
// Check if we're in a value context
|
|
390
558
|
const valueMatch = before.match(/:\s*(\{?\s*)([^\s,\}\]:]*)$/);
|
|
391
559
|
if (valueMatch) {
|
|
@@ -393,30 +561,30 @@ function applySuggestion(searchText, cursorPos, suggestion) {
|
|
|
393
561
|
const start = cursorPos - token.length;
|
|
394
562
|
let replacement = suggestion;
|
|
395
563
|
let cursorOffset = replacement.length;
|
|
396
|
-
|
|
564
|
+
|
|
397
565
|
// Add parentheses for function helpers and position cursor inside
|
|
398
566
|
if (FUNCTION_HELPERS.has(suggestion)) {
|
|
399
567
|
replacement = `${suggestion}()`;
|
|
400
568
|
cursorOffset = suggestion.length + 1; // Position cursor between ()
|
|
401
569
|
}
|
|
402
|
-
|
|
570
|
+
|
|
403
571
|
return {
|
|
404
572
|
text: searchText.slice(0, start) + replacement + after,
|
|
405
573
|
newCursorPos: start + cursorOffset
|
|
406
574
|
};
|
|
407
575
|
}
|
|
408
|
-
|
|
576
|
+
|
|
409
577
|
// Check if we're in a field name context
|
|
410
578
|
const fieldMatch = before.match(/(?:\{|,)\s*([^:\s]*)$/);
|
|
411
579
|
if (fieldMatch) {
|
|
412
580
|
const token = fieldMatch[1];
|
|
413
581
|
const start = cursorPos - token.length;
|
|
414
582
|
let replacement = suggestion;
|
|
415
|
-
|
|
583
|
+
|
|
416
584
|
const leadingQuote = token.startsWith('"') || token.startsWith('\'') ? token[0] : '';
|
|
417
585
|
const trailingQuote = token.length > 1 && (token.endsWith('"') || token.endsWith('\'')) ? token[token.length - 1] : '';
|
|
418
586
|
const colonNeeded = !/^\s*:/.test(after);
|
|
419
|
-
|
|
587
|
+
|
|
420
588
|
// If suggestion already has quotes, use it as-is
|
|
421
589
|
const suggestionHasQuotes = (suggestion.startsWith('"') || suggestion.startsWith('\'')) &&
|
|
422
590
|
(suggestion.endsWith('"') || suggestion.endsWith('\''));
|
|
@@ -430,27 +598,107 @@ function applySuggestion(searchText, cursorPos, suggestion) {
|
|
|
430
598
|
replacement = `${replacement}${trailingQuote}`;
|
|
431
599
|
}
|
|
432
600
|
}
|
|
433
|
-
|
|
601
|
+
|
|
434
602
|
// Only insert : if we know the user isn't entering in a nested path
|
|
435
603
|
// If suggestion has full quotes or user typed both quotes, add colon
|
|
436
604
|
if (colonNeeded && (suggestionHasQuotes || !leadingQuote || trailingQuote)) {
|
|
437
605
|
replacement = `${replacement}:`;
|
|
438
606
|
}
|
|
439
|
-
|
|
607
|
+
|
|
440
608
|
return {
|
|
441
609
|
text: searchText.slice(0, start) + replacement + after,
|
|
442
610
|
newCursorPos: start + replacement.length
|
|
443
611
|
};
|
|
444
612
|
}
|
|
445
|
-
|
|
613
|
+
|
|
446
614
|
return null;
|
|
447
615
|
}
|
|
448
616
|
|
|
617
|
+
/**
|
|
618
|
+
* When the cursor is inside a Date(…) or new Date(…) argument, returns the
|
|
619
|
+
* slice indices of that argument so a picker can replace it with a quoted ISO string.
|
|
620
|
+
*/
|
|
621
|
+
function getDatePickerInsertionRange(searchText, cursorPos) {
|
|
622
|
+
const before = searchText.slice(0, cursorPos);
|
|
623
|
+
const re = /((?:new\s+)?Date\s*\(\s*)([^)]*)$/i;
|
|
624
|
+
const m = before.match(re);
|
|
625
|
+
if (!m) {
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
628
|
+
const innerStart = m.index + m[1].length;
|
|
629
|
+
const after = searchText.slice(cursorPos);
|
|
630
|
+
let closeIdx = -1;
|
|
631
|
+
let parenDepth = 0;
|
|
632
|
+
for (let k = 0; k < after.length; k++) {
|
|
633
|
+
const ch = after[k];
|
|
634
|
+
if (ch === '(') {
|
|
635
|
+
parenDepth++;
|
|
636
|
+
} else if (ch === ')') {
|
|
637
|
+
if (parenDepth === 0) {
|
|
638
|
+
closeIdx = cursorPos + k;
|
|
639
|
+
break;
|
|
640
|
+
}
|
|
641
|
+
parenDepth--;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
const innerEnd = closeIdx >= 0 ? closeIdx : cursorPos;
|
|
645
|
+
const needsClosingParen = closeIdx < 0;
|
|
646
|
+
return { innerStart, innerEnd, needsClosingParen };
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function dateArgumentSliceToDatetimeLocal(slice) {
|
|
650
|
+
const t = slice.trim();
|
|
651
|
+
if (!t) {
|
|
652
|
+
return '';
|
|
653
|
+
}
|
|
654
|
+
const unquoted = (t.startsWith('"') && t.endsWith('"')) || (t.startsWith('\'') && t.endsWith('\''))
|
|
655
|
+
? t.slice(1, -1)
|
|
656
|
+
: t;
|
|
657
|
+
const d = new Date(unquoted);
|
|
658
|
+
if (Number.isNaN(d.getTime())) {
|
|
659
|
+
return '';
|
|
660
|
+
}
|
|
661
|
+
return dateToDatetimeLocal(d);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* @param {Date} date
|
|
666
|
+
* @param {'quoted'|'timestamp'} format
|
|
667
|
+
*/
|
|
668
|
+
function formatDateArgumentValue(date, format) {
|
|
669
|
+
if (format === 'quoted') {
|
|
670
|
+
return JSON.stringify(date.toISOString());
|
|
671
|
+
}
|
|
672
|
+
return String(date.getTime());
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Inserts a Date argument value as timestamp or quoted ISO string.
|
|
677
|
+
* @param {string} searchText
|
|
678
|
+
* @param {{ innerStart: number, innerEnd: number, needsClosingParen?: boolean }} range
|
|
679
|
+
* @param {Date} date
|
|
680
|
+
* @param {'quoted'|'timestamp'} [format] - when omitted, inferred from the existing argument
|
|
681
|
+
*/
|
|
682
|
+
function insertDateInDateArgument(searchText, range, date, format) {
|
|
683
|
+
const slice = searchText.slice(range.innerStart, range.innerEnd);
|
|
684
|
+
const insertion = formatDateArgumentValue(date, format);
|
|
685
|
+
const { innerStart, innerEnd } = range;
|
|
686
|
+
const closing = range.needsClosingParen === true ? ')' : '';
|
|
687
|
+
return {
|
|
688
|
+
text: searchText.slice(0, innerStart) + insertion + closing + searchText.slice(innerEnd),
|
|
689
|
+
newCursorPos: innerStart + insertion.length + closing.length
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
|
|
449
693
|
module.exports = {
|
|
450
694
|
buildAutocompleteTrie,
|
|
451
695
|
getAutocompleteContext,
|
|
452
696
|
getAutocompleteSuggestions,
|
|
453
697
|
applySuggestion,
|
|
698
|
+
getDatePickerInsertionRange,
|
|
699
|
+
dateArgumentSliceToDatetimeLocal,
|
|
700
|
+
formatDateArgumentValue,
|
|
701
|
+
insertDateInDateArgument,
|
|
454
702
|
QUERY_SELECTORS,
|
|
455
703
|
VALUE_HELPERS,
|
|
456
704
|
FUNCTION_HELPERS
|
|
@@ -2464,6 +2712,424 @@ module.exports = app => app.component('create-document', {
|
|
|
2464
2712
|
});
|
|
2465
2713
|
|
|
2466
2714
|
|
|
2715
|
+
/***/ },
|
|
2716
|
+
|
|
2717
|
+
/***/ "./frontend/src/custom-date-picker/custom-date-picker.js"
|
|
2718
|
+
/*!***************************************************************!*\
|
|
2719
|
+
!*** ./frontend/src/custom-date-picker/custom-date-picker.js ***!
|
|
2720
|
+
\***************************************************************/
|
|
2721
|
+
(module, __unused_webpack_exports, __webpack_require__) {
|
|
2722
|
+
|
|
2723
|
+
"use strict";
|
|
2724
|
+
|
|
2725
|
+
|
|
2726
|
+
const template = __webpack_require__(/*! ./custom-date-picker.html */ "./frontend/src/custom-date-picker/custom-date-picker.html");
|
|
2727
|
+
const {
|
|
2728
|
+
dateToDatetimeLocal,
|
|
2729
|
+
dateToDateOnly,
|
|
2730
|
+
parseValueToDate,
|
|
2731
|
+
buildCalendarDays,
|
|
2732
|
+
clampInt,
|
|
2733
|
+
isSameDay,
|
|
2734
|
+
pad2,
|
|
2735
|
+
WEEKDAY_LABELS,
|
|
2736
|
+
MONTH_LABELS
|
|
2737
|
+
} = __webpack_require__(/*! ../_util/calendar */ "./frontend/src/_util/calendar.js");
|
|
2738
|
+
|
|
2739
|
+
const WHEEL_PAD = 2;
|
|
2740
|
+
const HOUR_OPTIONS = Array.from({ length: 24 }, (_, i) => i);
|
|
2741
|
+
const MINUTE_OPTIONS = Array.from({ length: 60 }, (_, i) => i);
|
|
2742
|
+
const SECOND_OPTIONS = Array.from({ length: 60 }, (_, i) => i);
|
|
2743
|
+
|
|
2744
|
+
function timePartsFromDate(date) {
|
|
2745
|
+
if (!date) {
|
|
2746
|
+
return { hour: 0, minute: 0, second: 0, millisecond: 0 };
|
|
2747
|
+
}
|
|
2748
|
+
return {
|
|
2749
|
+
hour: date.getHours(),
|
|
2750
|
+
minute: date.getMinutes(),
|
|
2751
|
+
second: date.getSeconds(),
|
|
2752
|
+
millisecond: date.getMilliseconds()
|
|
2753
|
+
};
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
module.exports = app => app.component('custom-date-picker', {
|
|
2757
|
+
template,
|
|
2758
|
+
props: {
|
|
2759
|
+
value: {
|
|
2760
|
+
default: null
|
|
2761
|
+
},
|
|
2762
|
+
mode: {
|
|
2763
|
+
type: String,
|
|
2764
|
+
default: 'datetime' // 'date' | 'datetime'
|
|
2765
|
+
},
|
|
2766
|
+
compact: {
|
|
2767
|
+
type: Boolean,
|
|
2768
|
+
default: false
|
|
2769
|
+
},
|
|
2770
|
+
mini: {
|
|
2771
|
+
type: Boolean,
|
|
2772
|
+
default: false
|
|
2773
|
+
},
|
|
2774
|
+
immediate: {
|
|
2775
|
+
type: Boolean,
|
|
2776
|
+
default: true
|
|
2777
|
+
},
|
|
2778
|
+
horizontal: {
|
|
2779
|
+
type: Boolean,
|
|
2780
|
+
default: false
|
|
2781
|
+
},
|
|
2782
|
+
embedded: {
|
|
2783
|
+
type: Boolean,
|
|
2784
|
+
default: false
|
|
2785
|
+
}
|
|
2786
|
+
},
|
|
2787
|
+
emits: ['input', 'commit'],
|
|
2788
|
+
data() {
|
|
2789
|
+
const selected = parseValueToDate(this.value);
|
|
2790
|
+
const anchor = selected || new Date();
|
|
2791
|
+
const time = timePartsFromDate(anchor);
|
|
2792
|
+
return {
|
|
2793
|
+
viewYear: anchor.getFullYear(),
|
|
2794
|
+
viewMonth: anchor.getMonth(),
|
|
2795
|
+
selectedDate: anchor,
|
|
2796
|
+
hour: time.hour,
|
|
2797
|
+
minute: time.minute,
|
|
2798
|
+
second: time.second,
|
|
2799
|
+
millisecond: time.millisecond,
|
|
2800
|
+
pendingValue: null,
|
|
2801
|
+
wheelScrollLock: false,
|
|
2802
|
+
timeApplyTimer: null,
|
|
2803
|
+
hourOptions: HOUR_OPTIONS,
|
|
2804
|
+
minuteOptions: MINUTE_OPTIONS,
|
|
2805
|
+
secondOptions: SECOND_OPTIONS
|
|
2806
|
+
};
|
|
2807
|
+
},
|
|
2808
|
+
watch: {
|
|
2809
|
+
value() {
|
|
2810
|
+
this.syncFromValue();
|
|
2811
|
+
},
|
|
2812
|
+
horizontal() {
|
|
2813
|
+
this.$nextTick(() => this.scrollWheelsToValues());
|
|
2814
|
+
}
|
|
2815
|
+
},
|
|
2816
|
+
computed: {
|
|
2817
|
+
rootClasses() {
|
|
2818
|
+
const sizeClass = this.mini
|
|
2819
|
+
? 'p-0.5 text-[10px]'
|
|
2820
|
+
: (this.compact ? 'p-2 text-xs' : 'p-3 text-sm');
|
|
2821
|
+
const chromeClass = this.embedded
|
|
2822
|
+
? ''
|
|
2823
|
+
: (this.mini ? '' : 'bg-surface border border-edge-strong rounded-md shadow-sm');
|
|
2824
|
+
return [sizeClass, chromeClass];
|
|
2825
|
+
},
|
|
2826
|
+
timeSectionClasses() {
|
|
2827
|
+
if (this.horizontal) {
|
|
2828
|
+
return 'flex-1 min-w-0 border-l border-edge-strong pl-2';
|
|
2829
|
+
}
|
|
2830
|
+
return [
|
|
2831
|
+
'border-t border-edge-strong',
|
|
2832
|
+
this.mini ? 'mt-1 pt-1' : 'mt-3 pt-3'
|
|
2833
|
+
];
|
|
2834
|
+
},
|
|
2835
|
+
wheelPadCount() {
|
|
2836
|
+
return 2;
|
|
2837
|
+
},
|
|
2838
|
+
wheelVisibleRows() {
|
|
2839
|
+
return 5;
|
|
2840
|
+
},
|
|
2841
|
+
weekdayLabels() {
|
|
2842
|
+
return WEEKDAY_LABELS;
|
|
2843
|
+
},
|
|
2844
|
+
monthLabel() {
|
|
2845
|
+
return `${MONTH_LABELS[this.viewMonth]} ${this.viewYear}`;
|
|
2846
|
+
},
|
|
2847
|
+
calendarDays() {
|
|
2848
|
+
return buildCalendarDays(this.viewYear, this.viewMonth);
|
|
2849
|
+
},
|
|
2850
|
+
today() {
|
|
2851
|
+
const now = new Date();
|
|
2852
|
+
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
2853
|
+
},
|
|
2854
|
+
showTime() {
|
|
2855
|
+
return this.mode === 'datetime';
|
|
2856
|
+
},
|
|
2857
|
+
dayCellClass() {
|
|
2858
|
+
if (this.mini) {
|
|
2859
|
+
return 'h-6';
|
|
2860
|
+
}
|
|
2861
|
+
if (this.compact) {
|
|
2862
|
+
return 'h-7';
|
|
2863
|
+
}
|
|
2864
|
+
return 'aspect-square';
|
|
2865
|
+
},
|
|
2866
|
+
calendarSectionClass() {
|
|
2867
|
+
if (this.mini) return 'w-36';
|
|
2868
|
+
if (this.compact) return 'w-44';
|
|
2869
|
+
return 'w-48';
|
|
2870
|
+
},
|
|
2871
|
+
wheelItemPx() {
|
|
2872
|
+
if (this.mini) {
|
|
2873
|
+
return 32;
|
|
2874
|
+
}
|
|
2875
|
+
if (this.compact) {
|
|
2876
|
+
return 36;
|
|
2877
|
+
}
|
|
2878
|
+
return 40;
|
|
2879
|
+
},
|
|
2880
|
+
wheelListClass() {
|
|
2881
|
+
return [
|
|
2882
|
+
'overflow-y-auto overscroll-y-contain snap-y snap-mandatory',
|
|
2883
|
+
'[-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden'
|
|
2884
|
+
];
|
|
2885
|
+
},
|
|
2886
|
+
wheelListStyle() {
|
|
2887
|
+
return {
|
|
2888
|
+
height: `${this.wheelItemPx * this.wheelVisibleRows}px`
|
|
2889
|
+
};
|
|
2890
|
+
},
|
|
2891
|
+
wheelItemClass() {
|
|
2892
|
+
return [
|
|
2893
|
+
'snap-center shrink-0 w-full flex items-center justify-center font-mono transition-colors',
|
|
2894
|
+
this.mini ? 'h-8 text-sm' : (this.compact ? 'h-9 text-sm' : 'h-10 text-base')
|
|
2895
|
+
];
|
|
2896
|
+
},
|
|
2897
|
+
wheelHighlightClass() {
|
|
2898
|
+
return this.mini ? 'h-8' : (this.compact ? 'h-9' : 'h-10');
|
|
2899
|
+
},
|
|
2900
|
+
wheelColumnClass() {
|
|
2901
|
+
return this.mini ? 'w-11' : (this.compact ? 'w-12' : 'w-14');
|
|
2902
|
+
}
|
|
2903
|
+
},
|
|
2904
|
+
mounted() {
|
|
2905
|
+
this.$nextTick(() => this.scrollWheelsToValues());
|
|
2906
|
+
},
|
|
2907
|
+
beforeUnmount() {
|
|
2908
|
+
if (this.timeApplyTimer) {
|
|
2909
|
+
clearTimeout(this.timeApplyTimer);
|
|
2910
|
+
}
|
|
2911
|
+
},
|
|
2912
|
+
methods: {
|
|
2913
|
+
syncFromValue() {
|
|
2914
|
+
const selected = parseValueToDate(this.value);
|
|
2915
|
+
const anchor = selected || new Date();
|
|
2916
|
+
this.selectedDate = anchor;
|
|
2917
|
+
this.viewYear = anchor.getFullYear();
|
|
2918
|
+
this.viewMonth = anchor.getMonth();
|
|
2919
|
+
const time = timePartsFromDate(anchor);
|
|
2920
|
+
this.hour = time.hour;
|
|
2921
|
+
this.minute = time.minute;
|
|
2922
|
+
this.second = time.second;
|
|
2923
|
+
this.millisecond = time.millisecond;
|
|
2924
|
+
this.$nextTick(() => this.scrollWheelsToValues());
|
|
2925
|
+
},
|
|
2926
|
+
clampedTimeParts() {
|
|
2927
|
+
return {
|
|
2928
|
+
hour: clampInt(this.hour, 0, 23),
|
|
2929
|
+
minute: clampInt(this.minute, 0, 59),
|
|
2930
|
+
second: clampInt(this.second, 0, 59),
|
|
2931
|
+
millisecond: clampInt(this.millisecond, 0, 999)
|
|
2932
|
+
};
|
|
2933
|
+
},
|
|
2934
|
+
formatWheelValue(value) {
|
|
2935
|
+
return pad2(value);
|
|
2936
|
+
},
|
|
2937
|
+
wheelItemClasses(value, selectedValue) {
|
|
2938
|
+
return value === selectedValue
|
|
2939
|
+
? 'text-content-primary font-semibold'
|
|
2940
|
+
: 'text-content-tertiary hover:text-content-secondary';
|
|
2941
|
+
},
|
|
2942
|
+
wheelIndexFromScroll(el, max) {
|
|
2943
|
+
return clampInt(Math.round(el.scrollTop / this.wheelItemPx), 0, max);
|
|
2944
|
+
},
|
|
2945
|
+
scrollWheelTo(el, index) {
|
|
2946
|
+
if (!el) {
|
|
2947
|
+
return;
|
|
2948
|
+
}
|
|
2949
|
+
el.scrollTop = index * this.wheelItemPx;
|
|
2950
|
+
},
|
|
2951
|
+
scrollWheelsToValues() {
|
|
2952
|
+
this.wheelScrollLock = true;
|
|
2953
|
+
this.scrollWheelTo(this.$refs.hourWheel, this.hour);
|
|
2954
|
+
this.scrollWheelTo(this.$refs.minuteWheel, this.minute);
|
|
2955
|
+
this.scrollWheelTo(this.$refs.secondWheel, this.second);
|
|
2956
|
+
requestAnimationFrame(() => {
|
|
2957
|
+
this.wheelScrollLock = false;
|
|
2958
|
+
});
|
|
2959
|
+
},
|
|
2960
|
+
scheduleTimeApply() {
|
|
2961
|
+
if (this.timeApplyTimer) {
|
|
2962
|
+
clearTimeout(this.timeApplyTimer);
|
|
2963
|
+
}
|
|
2964
|
+
this.timeApplyTimer = setTimeout(() => {
|
|
2965
|
+
this.timeApplyTimer = null;
|
|
2966
|
+
this.applyTimeToSelection();
|
|
2967
|
+
}, 120);
|
|
2968
|
+
},
|
|
2969
|
+
onHourWheelScroll(event) {
|
|
2970
|
+
if (this.wheelScrollLock) {
|
|
2971
|
+
return;
|
|
2972
|
+
}
|
|
2973
|
+
const hour = this.wheelIndexFromScroll(event.target, 23);
|
|
2974
|
+
if (hour === this.hour) {
|
|
2975
|
+
return;
|
|
2976
|
+
}
|
|
2977
|
+
this.hour = hour;
|
|
2978
|
+
this.scheduleTimeApply();
|
|
2979
|
+
},
|
|
2980
|
+
onMinuteWheelScroll(event) {
|
|
2981
|
+
if (this.wheelScrollLock) {
|
|
2982
|
+
return;
|
|
2983
|
+
}
|
|
2984
|
+
const minute = this.wheelIndexFromScroll(event.target, 59);
|
|
2985
|
+
if (minute === this.minute) {
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2988
|
+
this.minute = minute;
|
|
2989
|
+
this.scheduleTimeApply();
|
|
2990
|
+
},
|
|
2991
|
+
onSecondWheelScroll(event) {
|
|
2992
|
+
if (this.wheelScrollLock) {
|
|
2993
|
+
return;
|
|
2994
|
+
}
|
|
2995
|
+
const second = this.wheelIndexFromScroll(event.target, 59);
|
|
2996
|
+
if (second === this.second) {
|
|
2997
|
+
return;
|
|
2998
|
+
}
|
|
2999
|
+
this.second = second;
|
|
3000
|
+
this.scheduleTimeApply();
|
|
3001
|
+
},
|
|
3002
|
+
selectHour(hour) {
|
|
3003
|
+
this.hour = hour;
|
|
3004
|
+
this.scrollWheelTo(this.$refs.hourWheel, hour);
|
|
3005
|
+
this.applyTimeToSelection();
|
|
3006
|
+
},
|
|
3007
|
+
selectMinute(minute) {
|
|
3008
|
+
this.minute = minute;
|
|
3009
|
+
this.scrollWheelTo(this.$refs.minuteWheel, minute);
|
|
3010
|
+
this.applyTimeToSelection();
|
|
3011
|
+
},
|
|
3012
|
+
selectSecond(second) {
|
|
3013
|
+
this.second = second;
|
|
3014
|
+
this.scrollWheelTo(this.$refs.secondWheel, second);
|
|
3015
|
+
this.applyTimeToSelection();
|
|
3016
|
+
},
|
|
3017
|
+
dayClasses(day) {
|
|
3018
|
+
const selected = this.selectedDate;
|
|
3019
|
+
const isSelected = selected && isSameDay(day.date, selected);
|
|
3020
|
+
const isToday = isSameDay(day.date, this.today);
|
|
3021
|
+
return {
|
|
3022
|
+
'text-content-tertiary': !day.inMonth,
|
|
3023
|
+
'bg-merlot-700 text-white hover:bg-merlot-800': isSelected,
|
|
3024
|
+
'hover:bg-surface-hover': day.inMonth && !isSelected,
|
|
3025
|
+
'ring-1 ring-merlot-700 ring-inset': isToday && !isSelected && day.inMonth,
|
|
3026
|
+
'font-medium': day.inMonth
|
|
3027
|
+
};
|
|
3028
|
+
},
|
|
3029
|
+
prevMonth() {
|
|
3030
|
+
if (this.viewMonth === 0) {
|
|
3031
|
+
this.viewMonth = 11;
|
|
3032
|
+
this.viewYear -= 1;
|
|
3033
|
+
} else {
|
|
3034
|
+
this.viewMonth -= 1;
|
|
3035
|
+
}
|
|
3036
|
+
},
|
|
3037
|
+
nextMonth() {
|
|
3038
|
+
if (this.viewMonth === 11) {
|
|
3039
|
+
this.viewMonth = 0;
|
|
3040
|
+
this.viewYear += 1;
|
|
3041
|
+
} else {
|
|
3042
|
+
this.viewMonth += 1;
|
|
3043
|
+
}
|
|
3044
|
+
},
|
|
3045
|
+
goToToday() {
|
|
3046
|
+
const now = new Date();
|
|
3047
|
+
this.viewYear = now.getFullYear();
|
|
3048
|
+
this.viewMonth = now.getMonth();
|
|
3049
|
+
this.selectDay({
|
|
3050
|
+
date: new Date(now.getFullYear(), now.getMonth(), now.getDate()),
|
|
3051
|
+
inMonth: true
|
|
3052
|
+
});
|
|
3053
|
+
},
|
|
3054
|
+
selectDay(day) {
|
|
3055
|
+
const { hour, minute, second, millisecond } = this.clampedTimeParts();
|
|
3056
|
+
const next = new Date(day.date);
|
|
3057
|
+
if (this.showTime) {
|
|
3058
|
+
next.setHours(hour, minute, second, millisecond);
|
|
3059
|
+
} else {
|
|
3060
|
+
next.setHours(0, 0, 0, 0);
|
|
3061
|
+
}
|
|
3062
|
+
this.selectedDate = next;
|
|
3063
|
+
if (!day.inMonth) {
|
|
3064
|
+
this.viewYear = next.getFullYear();
|
|
3065
|
+
this.viewMonth = next.getMonth();
|
|
3066
|
+
}
|
|
3067
|
+
this.emitValue(next);
|
|
3068
|
+
},
|
|
3069
|
+
applyTimeToSelection() {
|
|
3070
|
+
const { hour, minute, second, millisecond } = this.clampedTimeParts();
|
|
3071
|
+
this.hour = hour;
|
|
3072
|
+
this.minute = minute;
|
|
3073
|
+
this.second = second;
|
|
3074
|
+
this.millisecond = millisecond;
|
|
3075
|
+
|
|
3076
|
+
if (!this.selectedDate) {
|
|
3077
|
+
const now = new Date();
|
|
3078
|
+
this.selectedDate = new Date(
|
|
3079
|
+
now.getFullYear(),
|
|
3080
|
+
now.getMonth(),
|
|
3081
|
+
now.getDate(),
|
|
3082
|
+
hour,
|
|
3083
|
+
minute,
|
|
3084
|
+
second,
|
|
3085
|
+
millisecond
|
|
3086
|
+
);
|
|
3087
|
+
this.viewYear = this.selectedDate.getFullYear();
|
|
3088
|
+
this.viewMonth = this.selectedDate.getMonth();
|
|
3089
|
+
} else {
|
|
3090
|
+
this.selectedDate = new Date(
|
|
3091
|
+
this.selectedDate.getFullYear(),
|
|
3092
|
+
this.selectedDate.getMonth(),
|
|
3093
|
+
this.selectedDate.getDate(),
|
|
3094
|
+
hour,
|
|
3095
|
+
minute,
|
|
3096
|
+
second,
|
|
3097
|
+
millisecond
|
|
3098
|
+
);
|
|
3099
|
+
}
|
|
3100
|
+
this.emitValue(this.selectedDate);
|
|
3101
|
+
},
|
|
3102
|
+
valuePayload(date) {
|
|
3103
|
+
if (this.mode === 'date') {
|
|
3104
|
+
return dateToDateOnly(date);
|
|
3105
|
+
}
|
|
3106
|
+
return dateToDatetimeLocal(date);
|
|
3107
|
+
},
|
|
3108
|
+
emitValue(date) {
|
|
3109
|
+
const payload = this.valuePayload(date);
|
|
3110
|
+
if (!this.immediate) {
|
|
3111
|
+
this.pendingValue = payload;
|
|
3112
|
+
return;
|
|
3113
|
+
}
|
|
3114
|
+
this.$emit('input', payload);
|
|
3115
|
+
},
|
|
3116
|
+
commit() {
|
|
3117
|
+
const payload = this.pendingValue != null
|
|
3118
|
+
? this.pendingValue
|
|
3119
|
+
: (this.selectedDate ? this.valuePayload(this.selectedDate) : null);
|
|
3120
|
+
if (payload == null) {
|
|
3121
|
+
return;
|
|
3122
|
+
}
|
|
3123
|
+
this.$emit('commit', payload);
|
|
3124
|
+
this.pendingValue = null;
|
|
3125
|
+
},
|
|
3126
|
+
isSelectedDay(day) {
|
|
3127
|
+
return this.selectedDate && isSameDay(day.date, this.selectedDate);
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
});
|
|
3131
|
+
|
|
3132
|
+
|
|
2467
3133
|
/***/ },
|
|
2468
3134
|
|
|
2469
3135
|
/***/ "./frontend/src/dashboard-result/dashboard-chart/dashboard-chart.js"
|
|
@@ -4880,7 +5546,7 @@ const template = __webpack_require__(/*! ./date-view-mode-picker.html */ "./fron
|
|
|
4880
5546
|
|
|
4881
5547
|
module.exports = app => app.component('date-view-mode-picker', {
|
|
4882
5548
|
template,
|
|
4883
|
-
props: ['viewMode', 'path'],
|
|
5549
|
+
props: ['viewMode', 'path', 'editting'],
|
|
4884
5550
|
emits: ['update:viewMode'],
|
|
4885
5551
|
computed: {
|
|
4886
5552
|
format() {
|
|
@@ -4937,20 +5603,12 @@ appendCSS(__webpack_require__(/*! ./document-details.css */ "./frontend/src/docu
|
|
|
4937
5603
|
|
|
4938
5604
|
module.exports = app => app.component('document-details', {
|
|
4939
5605
|
template,
|
|
4940
|
-
props: ['document', 'schemaPaths', 'virtualPaths', 'editting', 'changes', 'invalid', 'viewMode'
|
|
5606
|
+
props: ['document', 'schemaPaths', 'virtualPaths', 'editting', 'changes', 'invalid', 'viewMode'],
|
|
4941
5607
|
data() {
|
|
4942
5608
|
return {
|
|
4943
5609
|
searchQuery: '',
|
|
4944
5610
|
selectedType: '',
|
|
4945
|
-
collapsedVirtuals: new Set()
|
|
4946
|
-
showAddFieldModal: false,
|
|
4947
|
-
fieldData: {
|
|
4948
|
-
name: '',
|
|
4949
|
-
type: '',
|
|
4950
|
-
value: ''
|
|
4951
|
-
},
|
|
4952
|
-
fieldErrors: {},
|
|
4953
|
-
isSubmittingField: false
|
|
5611
|
+
collapsedVirtuals: new Set()
|
|
4954
5612
|
};
|
|
4955
5613
|
},
|
|
4956
5614
|
mounted() {
|
|
@@ -5020,20 +5678,6 @@ module.exports = app => app.component('document-details', {
|
|
|
5020
5678
|
|
|
5021
5679
|
return Array.from(types).sort();
|
|
5022
5680
|
},
|
|
5023
|
-
allFieldTypes() {
|
|
5024
|
-
const schemaTypes = this.availableTypes;
|
|
5025
|
-
const commonTypes = ['String', 'Number', 'Boolean', 'Date', 'Array', 'Object'];
|
|
5026
|
-
|
|
5027
|
-
// Combine schema types with common types, avoiding duplicates
|
|
5028
|
-
const allTypes = new Set([...schemaTypes, ...commonTypes]);
|
|
5029
|
-
return Array.from(allTypes).sort();
|
|
5030
|
-
},
|
|
5031
|
-
shouldUseAce() {
|
|
5032
|
-
return ['Array', 'Object', 'Embedded'].includes(this.fieldData.type);
|
|
5033
|
-
},
|
|
5034
|
-
shouldUseDatePicker() {
|
|
5035
|
-
return this.fieldData.type === 'Date';
|
|
5036
|
-
},
|
|
5037
5681
|
typeFilteredSchemaPaths() {
|
|
5038
5682
|
let paths = this.schemaPaths || [];
|
|
5039
5683
|
|
|
@@ -5245,120 +5889,6 @@ module.exports = app => app.component('document-details', {
|
|
|
5245
5889
|
isVirtualFieldCollapsed(fieldName) {
|
|
5246
5890
|
return this.collapsedVirtuals.has(fieldName);
|
|
5247
5891
|
},
|
|
5248
|
-
openAddFieldModal() {
|
|
5249
|
-
this.showAddFieldModal = true;
|
|
5250
|
-
},
|
|
5251
|
-
closeAddFieldModal() {
|
|
5252
|
-
this.showAddFieldModal = false;
|
|
5253
|
-
this.resetFieldForm();
|
|
5254
|
-
},
|
|
5255
|
-
async addNewField(fieldData) {
|
|
5256
|
-
// Emit event to parent component to handle the field addition
|
|
5257
|
-
this.$emit('add-field', fieldData);
|
|
5258
|
-
this.closeAddFieldModal();
|
|
5259
|
-
},
|
|
5260
|
-
validateFieldForm() {
|
|
5261
|
-
this.fieldErrors = {};
|
|
5262
|
-
|
|
5263
|
-
// Validate field name
|
|
5264
|
-
const trimmedName = this.fieldData.name.trim();
|
|
5265
|
-
if (!trimmedName) {
|
|
5266
|
-
this.fieldErrors.name = 'Field name is required';
|
|
5267
|
-
} else {
|
|
5268
|
-
const transformedName = this.getTransformedFieldName();
|
|
5269
|
-
if (!transformedName) {
|
|
5270
|
-
this.fieldErrors.name = 'Field name contains only invalid characters';
|
|
5271
|
-
} else if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(transformedName)) {
|
|
5272
|
-
this.fieldErrors.name = 'Field name must start with a letter, underscore, or $ and contain only letters, numbers, underscores, and $';
|
|
5273
|
-
}
|
|
5274
|
-
}
|
|
5275
|
-
|
|
5276
|
-
// Validate field type
|
|
5277
|
-
if (!this.fieldData.type) {
|
|
5278
|
-
this.fieldErrors.type = 'Field type is required';
|
|
5279
|
-
}
|
|
5280
|
-
|
|
5281
|
-
// Validate field value if provided
|
|
5282
|
-
if (this.fieldData.value && this.fieldData.value.trim()) {
|
|
5283
|
-
if (['Object', 'Array'].includes(this.fieldData.type)) {
|
|
5284
|
-
try {
|
|
5285
|
-
JSON.parse(this.fieldData.value);
|
|
5286
|
-
} catch (e) {
|
|
5287
|
-
this.fieldErrors.value = 'Invalid JSON format for object/array type';
|
|
5288
|
-
}
|
|
5289
|
-
} else if (this.fieldData.type === 'Number') {
|
|
5290
|
-
if (isNaN(Number(this.fieldData.value))) {
|
|
5291
|
-
this.fieldErrors.value = 'Invalid number format';
|
|
5292
|
-
}
|
|
5293
|
-
} else if (this.fieldData.type === 'Boolean') {
|
|
5294
|
-
const lowerValue = this.fieldData.value.toLowerCase();
|
|
5295
|
-
if (!['true', 'false', '1', '0', 'yes', 'no'].includes(lowerValue)) {
|
|
5296
|
-
this.fieldErrors.value = 'Invalid boolean value (use true/false, 1/0, yes/no)';
|
|
5297
|
-
}
|
|
5298
|
-
} else if (this.fieldData.type === 'Date') {
|
|
5299
|
-
// Date picker provides YYYY-MM-DD format, validate it
|
|
5300
|
-
const dateValue = new Date(this.fieldData.value);
|
|
5301
|
-
if (isNaN(dateValue.getTime())) {
|
|
5302
|
-
this.fieldErrors.value = 'Invalid date format';
|
|
5303
|
-
}
|
|
5304
|
-
}
|
|
5305
|
-
}
|
|
5306
|
-
|
|
5307
|
-
return Object.keys(this.fieldErrors).length === 0;
|
|
5308
|
-
},
|
|
5309
|
-
parseFieldValue(value, type) {
|
|
5310
|
-
if (!value || !value.trim()) {
|
|
5311
|
-
return null;
|
|
5312
|
-
}
|
|
5313
|
-
|
|
5314
|
-
switch (type) {
|
|
5315
|
-
case 'Number':
|
|
5316
|
-
return Number(value);
|
|
5317
|
-
case 'Boolean':
|
|
5318
|
-
const lowerValue = value.toLowerCase();
|
|
5319
|
-
return ['true', '1', 'yes'].includes(lowerValue);
|
|
5320
|
-
case 'Date':
|
|
5321
|
-
// For date picker, value is already in YYYY-MM-DD format
|
|
5322
|
-
return new Date(value);
|
|
5323
|
-
case 'Object':
|
|
5324
|
-
case 'Array':
|
|
5325
|
-
return JSON.parse(value);
|
|
5326
|
-
default:
|
|
5327
|
-
return value;
|
|
5328
|
-
}
|
|
5329
|
-
},
|
|
5330
|
-
async handleAddFieldSubmit() {
|
|
5331
|
-
if (!this.validateFieldForm()) {
|
|
5332
|
-
return;
|
|
5333
|
-
}
|
|
5334
|
-
|
|
5335
|
-
this.isSubmittingField = true;
|
|
5336
|
-
|
|
5337
|
-
try {
|
|
5338
|
-
const fieldData = {
|
|
5339
|
-
name: this.fieldData.name,
|
|
5340
|
-
type: this.fieldData.type,
|
|
5341
|
-
value: this.parseFieldValue(this.fieldData.value, this.fieldData.type)
|
|
5342
|
-
};
|
|
5343
|
-
|
|
5344
|
-
this.$emit('add-field', fieldData);
|
|
5345
|
-
this.closeAddFieldModal();
|
|
5346
|
-
} catch (error) {
|
|
5347
|
-
console.error('Error adding field:', error);
|
|
5348
|
-
this.fieldErrors.value = 'Error processing field value';
|
|
5349
|
-
} finally {
|
|
5350
|
-
this.isSubmittingField = false;
|
|
5351
|
-
}
|
|
5352
|
-
},
|
|
5353
|
-
resetFieldForm() {
|
|
5354
|
-
this.fieldData = {
|
|
5355
|
-
name: '',
|
|
5356
|
-
type: '',
|
|
5357
|
-
value: ''
|
|
5358
|
-
};
|
|
5359
|
-
this.fieldErrors = {};
|
|
5360
|
-
this.isSubmittingField = false;
|
|
5361
|
-
},
|
|
5362
5892
|
getVirtualFieldType(virtual) {
|
|
5363
5893
|
const value = virtual.value;
|
|
5364
5894
|
if (value === null || value === undefined) {
|
|
@@ -5941,7 +6471,15 @@ module.exports = app => app.component('document', {
|
|
|
5941
6471
|
shouldShowDeleteModal: false,
|
|
5942
6472
|
shouldShowCloneModal: false,
|
|
5943
6473
|
shouldShowValidationModal: false,
|
|
6474
|
+
showAddFieldModal: false,
|
|
5944
6475
|
validationResult: null,
|
|
6476
|
+
fieldData: {
|
|
6477
|
+
name: '',
|
|
6478
|
+
type: '',
|
|
6479
|
+
value: ''
|
|
6480
|
+
},
|
|
6481
|
+
fieldErrors: {},
|
|
6482
|
+
isSubmittingField: false,
|
|
5945
6483
|
previousQuery: null,
|
|
5946
6484
|
lastUpdatedAt: null,
|
|
5947
6485
|
isRefreshing: false,
|
|
@@ -6003,6 +6541,33 @@ module.exports = app => app.component('document', {
|
|
|
6003
6541
|
}
|
|
6004
6542
|
return shortcuts;
|
|
6005
6543
|
},
|
|
6544
|
+
allFieldTypes() {
|
|
6545
|
+
const types = new Set(['String', 'Number', 'Boolean', 'Date', 'Array', 'Object']);
|
|
6546
|
+
|
|
6547
|
+
(this.schemaPaths || []).forEach(path => {
|
|
6548
|
+
if (path.instance) {
|
|
6549
|
+
types.add(path.instance);
|
|
6550
|
+
}
|
|
6551
|
+
});
|
|
6552
|
+
|
|
6553
|
+
Object.keys(this.document || {}).forEach(key => {
|
|
6554
|
+
if ((this.schemaPaths || []).some(path => path.path === key)) {
|
|
6555
|
+
return;
|
|
6556
|
+
}
|
|
6557
|
+
const fieldType = this.getFieldType(this.document[key]);
|
|
6558
|
+
if (fieldType && fieldType !== 'unknown' && fieldType !== 'null') {
|
|
6559
|
+
types.add(fieldType);
|
|
6560
|
+
}
|
|
6561
|
+
});
|
|
6562
|
+
|
|
6563
|
+
return Array.from(types).sort();
|
|
6564
|
+
},
|
|
6565
|
+
shouldUseAce() {
|
|
6566
|
+
return ['Array', 'Object', 'Embedded'].includes(this.fieldData.type);
|
|
6567
|
+
},
|
|
6568
|
+
shouldUseDatePicker() {
|
|
6569
|
+
return this.fieldData.type === 'Date';
|
|
6570
|
+
},
|
|
6006
6571
|
isLambda() {
|
|
6007
6572
|
return !!window?.MONGOOSE_STUDIO_CONFIG?.isLambda;
|
|
6008
6573
|
}
|
|
@@ -6199,16 +6764,143 @@ module.exports = app => app.component('document', {
|
|
|
6199
6764
|
showClonedDocument(doc) {
|
|
6200
6765
|
this.$router.push({ path: `/model/${this.model}/document/${doc._id}` });
|
|
6201
6766
|
},
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6767
|
+
addField() {
|
|
6768
|
+
this.showAddFieldModal = true;
|
|
6769
|
+
},
|
|
6770
|
+
closeAddFieldModal() {
|
|
6771
|
+
this.showAddFieldModal = false;
|
|
6772
|
+
this.resetFieldForm();
|
|
6773
|
+
},
|
|
6774
|
+
resetFieldForm() {
|
|
6775
|
+
this.fieldData = {
|
|
6776
|
+
name: '',
|
|
6777
|
+
type: '',
|
|
6778
|
+
value: ''
|
|
6779
|
+
};
|
|
6780
|
+
this.fieldErrors = {};
|
|
6781
|
+
this.isSubmittingField = false;
|
|
6782
|
+
},
|
|
6783
|
+
getTransformedFieldName() {
|
|
6784
|
+
const trimmedName = this.fieldData.name.trim();
|
|
6785
|
+
return trimmedName
|
|
6786
|
+
.replace(/[^a-zA-Z0-9_$]/g, '_')
|
|
6787
|
+
.replace(/^[^a-zA-Z_$]+/, '');
|
|
6788
|
+
},
|
|
6789
|
+
validateFieldForm() {
|
|
6790
|
+
this.fieldErrors = {};
|
|
6210
6791
|
|
|
6211
|
-
|
|
6792
|
+
const trimmedName = this.fieldData.name.trim();
|
|
6793
|
+
if (!trimmedName) {
|
|
6794
|
+
this.fieldErrors.name = 'Field name is required';
|
|
6795
|
+
} else {
|
|
6796
|
+
const transformedName = this.getTransformedFieldName();
|
|
6797
|
+
if (!transformedName) {
|
|
6798
|
+
this.fieldErrors.name = 'Field name contains only invalid characters';
|
|
6799
|
+
} else if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(transformedName)) {
|
|
6800
|
+
this.fieldErrors.name = 'Field name must start with a letter, underscore, or $ and contain only letters, numbers, underscores, and $';
|
|
6801
|
+
}
|
|
6802
|
+
}
|
|
6803
|
+
|
|
6804
|
+
if (!this.fieldData.type) {
|
|
6805
|
+
this.fieldErrors.type = 'Field type is required';
|
|
6806
|
+
}
|
|
6807
|
+
|
|
6808
|
+
if (this.fieldData.value && this.fieldData.value.trim()) {
|
|
6809
|
+
if (['Object', 'Array'].includes(this.fieldData.type)) {
|
|
6810
|
+
try {
|
|
6811
|
+
JSON.parse(this.fieldData.value);
|
|
6812
|
+
} catch (err) {
|
|
6813
|
+
this.fieldErrors.value = 'Invalid JSON format for object/array type';
|
|
6814
|
+
}
|
|
6815
|
+
} else if (this.fieldData.type === 'Number') {
|
|
6816
|
+
if (isNaN(Number(this.fieldData.value))) {
|
|
6817
|
+
this.fieldErrors.value = 'Invalid number format';
|
|
6818
|
+
}
|
|
6819
|
+
} else if (this.fieldData.type === 'Boolean') {
|
|
6820
|
+
const lowerValue = this.fieldData.value.toLowerCase();
|
|
6821
|
+
if (!['true', 'false', '1', '0', 'yes', 'no'].includes(lowerValue)) {
|
|
6822
|
+
this.fieldErrors.value = 'Invalid boolean value (use true/false, 1/0, yes/no)';
|
|
6823
|
+
}
|
|
6824
|
+
} else if (this.fieldData.type === 'Date') {
|
|
6825
|
+
const dateValue = new Date(this.fieldData.value);
|
|
6826
|
+
if (isNaN(dateValue.getTime())) {
|
|
6827
|
+
this.fieldErrors.value = 'Invalid date format';
|
|
6828
|
+
}
|
|
6829
|
+
}
|
|
6830
|
+
}
|
|
6831
|
+
|
|
6832
|
+
return Object.keys(this.fieldErrors).length === 0;
|
|
6833
|
+
},
|
|
6834
|
+
parseFieldValue(value, type) {
|
|
6835
|
+
if (!value || !value.trim()) {
|
|
6836
|
+
return null;
|
|
6837
|
+
}
|
|
6838
|
+
|
|
6839
|
+
switch (type) {
|
|
6840
|
+
case 'Number':
|
|
6841
|
+
return Number(value);
|
|
6842
|
+
case 'Boolean':
|
|
6843
|
+
return ['true', '1', 'yes'].includes(value.toLowerCase());
|
|
6844
|
+
case 'Date':
|
|
6845
|
+
return new Date(value);
|
|
6846
|
+
case 'Object':
|
|
6847
|
+
case 'Array':
|
|
6848
|
+
return JSON.parse(value);
|
|
6849
|
+
default:
|
|
6850
|
+
return value;
|
|
6851
|
+
}
|
|
6852
|
+
},
|
|
6853
|
+
async handleAddFieldSubmit() {
|
|
6854
|
+
if (!this.validateFieldForm()) {
|
|
6855
|
+
return;
|
|
6856
|
+
}
|
|
6857
|
+
|
|
6858
|
+
this.isSubmittingField = true;
|
|
6859
|
+
|
|
6860
|
+
try {
|
|
6861
|
+
const fieldName = this.getTransformedFieldName();
|
|
6862
|
+
const fieldValue = this.parseFieldValue(this.fieldData.value, this.fieldData.type);
|
|
6863
|
+
|
|
6864
|
+
const { doc } = await api.Model.addField({
|
|
6865
|
+
model: this.model,
|
|
6866
|
+
_id: this.document._id,
|
|
6867
|
+
fieldName,
|
|
6868
|
+
fieldValue
|
|
6869
|
+
});
|
|
6870
|
+
this.document = doc;
|
|
6871
|
+
this.closeAddFieldModal();
|
|
6872
|
+
|
|
6873
|
+
this.$toast.success(`Field added! Field "${fieldName}" has been added to the document`);
|
|
6874
|
+
} catch (error) {
|
|
6875
|
+
console.error('Error adding field:', error);
|
|
6876
|
+
this.fieldErrors.value = 'Error adding field';
|
|
6877
|
+
} finally {
|
|
6878
|
+
this.isSubmittingField = false;
|
|
6879
|
+
}
|
|
6880
|
+
},
|
|
6881
|
+
getFieldType(value) {
|
|
6882
|
+
if (value === null || value === undefined) {
|
|
6883
|
+
return 'null';
|
|
6884
|
+
}
|
|
6885
|
+
if (Array.isArray(value)) {
|
|
6886
|
+
return 'Array';
|
|
6887
|
+
}
|
|
6888
|
+
if (value instanceof Date) {
|
|
6889
|
+
return 'Date';
|
|
6890
|
+
}
|
|
6891
|
+
if (typeof value === 'object') {
|
|
6892
|
+
return 'Object';
|
|
6893
|
+
}
|
|
6894
|
+
if (typeof value === 'number') {
|
|
6895
|
+
return 'Number';
|
|
6896
|
+
}
|
|
6897
|
+
if (typeof value === 'boolean') {
|
|
6898
|
+
return 'Boolean';
|
|
6899
|
+
}
|
|
6900
|
+
if (typeof value === 'string') {
|
|
6901
|
+
return 'String';
|
|
6902
|
+
}
|
|
6903
|
+
return 'unknown';
|
|
6212
6904
|
},
|
|
6213
6905
|
updateViewMode(mode) {
|
|
6214
6906
|
this.viewMode = mode;
|
|
@@ -6639,9 +7331,6 @@ module.exports = app => app.component('edit-date', {
|
|
|
6639
7331
|
template: template,
|
|
6640
7332
|
props: ['value', 'format'],
|
|
6641
7333
|
emits: ['input'],
|
|
6642
|
-
data: () => ({
|
|
6643
|
-
inputType: ''
|
|
6644
|
-
}),
|
|
6645
7334
|
methods: {
|
|
6646
7335
|
updateFromISO($event) {
|
|
6647
7336
|
const value = $event.target.value;
|
|
@@ -6661,23 +7350,6 @@ module.exports = app => app.component('edit-date', {
|
|
|
6661
7350
|
}
|
|
6662
7351
|
},
|
|
6663
7352
|
computed: {
|
|
6664
|
-
valueAsLocalString() {
|
|
6665
|
-
if (this.value == null) {
|
|
6666
|
-
return this.value;
|
|
6667
|
-
}
|
|
6668
|
-
const date = new Date(this.value);
|
|
6669
|
-
return [
|
|
6670
|
-
date.getFullYear(),
|
|
6671
|
-
'-',
|
|
6672
|
-
(date.getMonth() + 1).toString().padStart(2, '0'),
|
|
6673
|
-
'-',
|
|
6674
|
-
date.getDate().toString().padStart(2, '0'),
|
|
6675
|
-
'T',
|
|
6676
|
-
date.getHours().toString().padStart(2, '0'),
|
|
6677
|
-
':',
|
|
6678
|
-
date.getMinutes().toString().padStart(2, '0')
|
|
6679
|
-
].join('');
|
|
6680
|
-
},
|
|
6681
7353
|
valueAsISOString() {
|
|
6682
7354
|
if (this.value == null) {
|
|
6683
7355
|
return '' + this.value;
|
|
@@ -8350,7 +9022,10 @@ const template = __webpack_require__(/*! ./document-search.html */ "./frontend/s
|
|
|
8350
9022
|
const {
|
|
8351
9023
|
buildAutocompleteTrie,
|
|
8352
9024
|
getAutocompleteSuggestions,
|
|
8353
|
-
applySuggestion
|
|
9025
|
+
applySuggestion,
|
|
9026
|
+
getDatePickerInsertionRange,
|
|
9027
|
+
dateArgumentSliceToDatetimeLocal,
|
|
9028
|
+
insertDateInDateArgument
|
|
8354
9029
|
} = __webpack_require__(/*! ../../_util/document-search-autocomplete */ "./frontend/src/_util/document-search-autocomplete.js");
|
|
8355
9030
|
|
|
8356
9031
|
module.exports = app => app.component('document-search', {
|
|
@@ -8370,7 +9045,11 @@ module.exports = app => app.component('document-search', {
|
|
|
8370
9045
|
autocompleteSuggestions: [],
|
|
8371
9046
|
autocompleteIndex: 0,
|
|
8372
9047
|
autocompleteTrie: null,
|
|
8373
|
-
searchText: this.value || ''
|
|
9048
|
+
searchText: this.value || '',
|
|
9049
|
+
datePickerContext: null,
|
|
9050
|
+
datePickerLocalValue: '',
|
|
9051
|
+
dateInsertFormat: 'quoted', // 'timestamp' | 'quoted'
|
|
9052
|
+
datePickerMinimized: false
|
|
8374
9053
|
};
|
|
8375
9054
|
},
|
|
8376
9055
|
watch: {
|
|
@@ -8412,10 +9091,31 @@ module.exports = app => app.component('document-search', {
|
|
|
8412
9091
|
});
|
|
8413
9092
|
}
|
|
8414
9093
|
},
|
|
9094
|
+
onSearchKeyup(ev) {
|
|
9095
|
+
if (
|
|
9096
|
+
this.autocompleteSuggestions.length > 0 &&
|
|
9097
|
+
(ev.key === 'ArrowUp' || ev.key === 'ArrowDown')
|
|
9098
|
+
) {
|
|
9099
|
+
return;
|
|
9100
|
+
}
|
|
9101
|
+
this.updateAutocomplete();
|
|
9102
|
+
},
|
|
8415
9103
|
updateAutocomplete() {
|
|
8416
9104
|
const input = this.$refs.searchInput;
|
|
8417
9105
|
const cursorPos = input ? input.selectionStart : 0;
|
|
8418
9106
|
|
|
9107
|
+
const dateRange = getDatePickerInsertionRange(this.searchText, cursorPos);
|
|
9108
|
+
if (dateRange && !this.datePickerContext) {
|
|
9109
|
+
this.datePickerMinimized = false;
|
|
9110
|
+
}
|
|
9111
|
+
this.datePickerContext = dateRange;
|
|
9112
|
+
if (dateRange) {
|
|
9113
|
+
const argSlice = this.searchText.slice(dateRange.innerStart, dateRange.innerEnd);
|
|
9114
|
+
this.datePickerLocalValue = dateArgumentSliceToDatetimeLocal(argSlice);
|
|
9115
|
+
} else {
|
|
9116
|
+
this.datePickerLocalValue = '';
|
|
9117
|
+
}
|
|
9118
|
+
|
|
8419
9119
|
if (this.autocompleteTrie) {
|
|
8420
9120
|
this.autocompleteSuggestions = getAutocompleteSuggestions(
|
|
8421
9121
|
this.autocompleteTrie,
|
|
@@ -8428,6 +9128,34 @@ module.exports = app => app.component('document-search', {
|
|
|
8428
9128
|
this.autocompleteSuggestions = [];
|
|
8429
9129
|
}
|
|
8430
9130
|
},
|
|
9131
|
+
applyDateFromPicker(localDateTime) {
|
|
9132
|
+
if (!localDateTime || !this.datePickerContext) {
|
|
9133
|
+
return;
|
|
9134
|
+
}
|
|
9135
|
+
const picked = new Date(localDateTime);
|
|
9136
|
+
if (Number.isNaN(picked.getTime())) {
|
|
9137
|
+
if (this.$toast) {
|
|
9138
|
+
this.$toast.error('Invalid date or time. Enter a valid date and time, then try again.');
|
|
9139
|
+
}
|
|
9140
|
+
return;
|
|
9141
|
+
}
|
|
9142
|
+
const result = insertDateInDateArgument(
|
|
9143
|
+
this.searchText,
|
|
9144
|
+
this.datePickerContext,
|
|
9145
|
+
picked,
|
|
9146
|
+
this.dateInsertFormat
|
|
9147
|
+
);
|
|
9148
|
+
const input = this.$refs.searchInput;
|
|
9149
|
+
this.searchText = result.text;
|
|
9150
|
+
this.autocompleteSuggestions = [];
|
|
9151
|
+
this.$nextTick(() => {
|
|
9152
|
+
if (input) {
|
|
9153
|
+
input.focus();
|
|
9154
|
+
input.setSelectionRange(result.newCursorPos, result.newCursorPos);
|
|
9155
|
+
}
|
|
9156
|
+
this.updateAutocomplete();
|
|
9157
|
+
});
|
|
9158
|
+
},
|
|
8431
9159
|
handleKeyDown(ev) {
|
|
8432
9160
|
if (this.autocompleteSuggestions.length === 0) {
|
|
8433
9161
|
return;
|
|
@@ -8462,6 +9190,12 @@ module.exports = app => app.component('document-search', {
|
|
|
8462
9190
|
});
|
|
8463
9191
|
this.autocompleteSuggestions = [];
|
|
8464
9192
|
},
|
|
9193
|
+
onWrapperFocusOut(ev) {
|
|
9194
|
+
if (!this.$el.contains(ev.relatedTarget)) {
|
|
9195
|
+
this.datePickerContext = null;
|
|
9196
|
+
this.datePickerMinimized = false;
|
|
9197
|
+
}
|
|
9198
|
+
},
|
|
8465
9199
|
addPathFilter(path) {
|
|
8466
9200
|
if (this.searchText) {
|
|
8467
9201
|
if (this.searchText.endsWith('}')) {
|
|
@@ -12379,6 +13113,8 @@ var map = {
|
|
|
12379
13113
|
"./": "./frontend/src/index.js",
|
|
12380
13114
|
"./_util/baseComponent": "./frontend/src/_util/baseComponent.js",
|
|
12381
13115
|
"./_util/baseComponent.js": "./frontend/src/_util/baseComponent.js",
|
|
13116
|
+
"./_util/calendar": "./frontend/src/_util/calendar.js",
|
|
13117
|
+
"./_util/calendar.js": "./frontend/src/_util/calendar.js",
|
|
12382
13118
|
"./_util/dateRange": "./frontend/src/_util/dateRange.js",
|
|
12383
13119
|
"./_util/dateRange.js": "./frontend/src/_util/dateRange.js",
|
|
12384
13120
|
"./_util/deepEqual": "./frontend/src/_util/deepEqual.js",
|
|
@@ -12421,6 +13157,9 @@ var map = {
|
|
|
12421
13157
|
"./create-document/create-document.css": "./frontend/src/create-document/create-document.css",
|
|
12422
13158
|
"./create-document/create-document.html": "./frontend/src/create-document/create-document.html",
|
|
12423
13159
|
"./create-document/create-document.js": "./frontend/src/create-document/create-document.js",
|
|
13160
|
+
"./custom-date-picker/custom-date-picker": "./frontend/src/custom-date-picker/custom-date-picker.js",
|
|
13161
|
+
"./custom-date-picker/custom-date-picker.html": "./frontend/src/custom-date-picker/custom-date-picker.html",
|
|
13162
|
+
"./custom-date-picker/custom-date-picker.js": "./frontend/src/custom-date-picker/custom-date-picker.js",
|
|
12424
13163
|
"./dashboard-result/dashboard-chart/dashboard-chart": "./frontend/src/dashboard-result/dashboard-chart/dashboard-chart.js",
|
|
12425
13164
|
"./dashboard-result/dashboard-chart/dashboard-chart.html": "./frontend/src/dashboard-result/dashboard-chart/dashboard-chart.html",
|
|
12426
13165
|
"./dashboard-result/dashboard-chart/dashboard-chart.js": "./frontend/src/dashboard-result/dashboard-chart/dashboard-chart.js",
|
|
@@ -52856,6 +53595,17 @@ module.exports = "<div class=\"px-1\">\n <div class=\"mb-4\">\n <label class
|
|
|
52856
53595
|
|
|
52857
53596
|
/***/ },
|
|
52858
53597
|
|
|
53598
|
+
/***/ "./frontend/src/custom-date-picker/custom-date-picker.html"
|
|
53599
|
+
/*!*****************************************************************!*\
|
|
53600
|
+
!*** ./frontend/src/custom-date-picker/custom-date-picker.html ***!
|
|
53601
|
+
\*****************************************************************/
|
|
53602
|
+
(module) {
|
|
53603
|
+
|
|
53604
|
+
"use strict";
|
|
53605
|
+
module.exports = "<div\n class=\"custom-date-picker\"\n :class=\"rootClasses\"\n>\n <div :class=\"horizontal ? 'flex gap-3 items-start' : ''\">\n <div :class=\"[horizontal ? 'shrink-0' : '', calendarSectionClass]\">\n <div class=\"flex items-center justify-between gap-1 mb-1\" :class=\"mini ? 'mb-0.5' : 'mb-2'\">\n <button\n type=\"button\"\n class=\"shrink-0 flex items-center justify-center rounded border border-edge-strong hover:bg-surface-hover text-content-secondary\"\n :class=\"mini ? 'w-5 h-5 text-[10px]' : 'w-7 h-7'\"\n aria-label=\"Previous month\"\n @click=\"prevMonth\"\n >‹</button>\n <span\n class=\"flex-1 min-w-0 text-center font-medium text-content-primary truncate\"\n :class=\"mini ? 'text-[10px]' : ''\"\n >{{ monthLabel }}</span>\n <button\n type=\"button\"\n class=\"shrink-0 flex items-center justify-center rounded border border-edge-strong hover:bg-surface-hover text-content-secondary\"\n :class=\"mini ? 'w-5 h-5 text-[10px]' : 'w-7 h-7'\"\n aria-label=\"Next month\"\n @click=\"nextMonth\"\n >›</button>\n </div>\n\n <div class=\"grid grid-cols-7 gap-px mb-0.5\">\n <div\n v-for=\"label in weekdayLabels\"\n :key=\"label\"\n class=\"flex items-center justify-center text-center text-content-tertiary font-medium min-w-0\"\n :class=\"[\n dayCellClass,\n mini ? 'text-[9px]' : (compact ? 'text-[10px]' : 'text-xs')\n ]\"\n >{{ mini ? label.charAt(0) : label }}</div>\n </div>\n\n <div class=\"grid grid-cols-7 gap-px\">\n <button\n v-for=\"day in calendarDays\"\n :key=\"day.key\"\n type=\"button\"\n class=\"min-w-0 w-full flex items-center justify-center rounded transition-colors\"\n :class=\"[\n dayCellClass,\n mini ? 'text-[10px]' : (compact ? 'text-xs' : 'text-sm'),\n dayClasses(day)\n ]\"\n :aria-label=\"day.label\"\n :aria-pressed=\"isSelectedDay(day)\"\n @click=\"selectDay(day)\"\n >{{ day.label }}</button>\n </div>\n </div>\n\n <div\n v-if=\"showTime\"\n :class=\"timeSectionClasses\"\n @mousedown.stop\n >\n <div\n class=\"text-content-secondary\"\n :class=\"[\n horizontal ? 'mb-1 text-xs' : 'mb-2',\n mini ? 'text-xs' : (horizontal ? '' : 'text-sm')\n ]\"\n >Time</div>\n <div class=\"relative flex items-center justify-center\">\n <div\n class=\"pointer-events-none absolute inset-x-0 top-1/2 -translate-y-1/2 border-y border-edge-strong bg-surface-hover/40\"\n :class=\"wheelHighlightClass\"\n aria-hidden=\"true\"\n ></div>\n <div class=\"relative flex items-center justify-center gap-1\">\n <div\n ref=\"hourWheel\"\n class=\"wheel-column\"\n :class=\"[wheelListClass, wheelColumnClass]\"\n :style=\"wheelListStyle\"\n @scroll=\"onHourWheelScroll\"\n >\n <div\n v-for=\"n in wheelPadCount\"\n :key=\"'hour-pad-top-' + n\"\n :class=\"wheelItemClass\"\n aria-hidden=\"true\"\n ></div>\n <button\n v-for=\"h in hourOptions\"\n :key=\"'hour-' + h\"\n type=\"button\"\n :class=\"[wheelItemClass, wheelItemClasses(h, hour)]\"\n :aria-label=\"'Hour ' + h\"\n :aria-selected=\"hour === h\"\n @click=\"selectHour(h)\"\n >{{ formatWheelValue(h) }}</button>\n <div\n v-for=\"n in wheelPadCount\"\n :key=\"'hour-pad-bottom-' + n\"\n :class=\"wheelItemClass\"\n aria-hidden=\"true\"\n ></div>\n </div>\n <span class=\"text-content-secondary font-medium\" :class=\"mini ? 'text-sm' : 'text-base'\">:</span>\n <div\n ref=\"minuteWheel\"\n class=\"wheel-column\"\n :class=\"[wheelListClass, wheelColumnClass]\"\n :style=\"wheelListStyle\"\n @scroll=\"onMinuteWheelScroll\"\n >\n <div\n v-for=\"n in wheelPadCount\"\n :key=\"'minute-pad-top-' + n\"\n :class=\"wheelItemClass\"\n aria-hidden=\"true\"\n ></div>\n <button\n v-for=\"m in minuteOptions\"\n :key=\"'minute-' + m\"\n type=\"button\"\n :class=\"[wheelItemClass, wheelItemClasses(m, minute)]\"\n :aria-label=\"'Minute ' + m\"\n :aria-selected=\"minute === m\"\n @click=\"selectMinute(m)\"\n >{{ formatWheelValue(m) }}</button>\n <div\n v-for=\"n in wheelPadCount\"\n :key=\"'minute-pad-bottom-' + n\"\n :class=\"wheelItemClass\"\n aria-hidden=\"true\"\n ></div>\n </div>\n <span class=\"text-content-secondary font-medium\" :class=\"mini ? 'text-sm' : 'text-base'\">:</span>\n <div\n ref=\"secondWheel\"\n class=\"wheel-column\"\n :class=\"[wheelListClass, wheelColumnClass]\"\n :style=\"wheelListStyle\"\n @scroll=\"onSecondWheelScroll\"\n >\n <div\n v-for=\"n in wheelPadCount\"\n :key=\"'second-pad-top-' + n\"\n :class=\"wheelItemClass\"\n aria-hidden=\"true\"\n ></div>\n <button\n v-for=\"s in secondOptions\"\n :key=\"'second-' + s\"\n type=\"button\"\n :class=\"[wheelItemClass, wheelItemClasses(s, second)]\"\n :aria-label=\"'Second ' + s\"\n :aria-selected=\"second === s\"\n @click=\"selectSecond(s)\"\n >{{ formatWheelValue(s) }}</button>\n <div\n v-for=\"n in wheelPadCount\"\n :key=\"'second-pad-bottom-' + n\"\n :class=\"wheelItemClass\"\n aria-hidden=\"true\"\n ></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <div\n class=\"flex justify-end items-center gap-2\"\n :class=\"mini ? 'mt-1' : 'mt-2'\"\n >\n <button\n v-if=\"!immediate\"\n type=\"button\"\n class=\"text-xs font-medium text-white bg-merlot-700 hover:bg-merlot-800 px-2 py-1 rounded\"\n :class=\"mini ? 'py-0.5 px-1.5' : ''\"\n @click=\"commit\"\n >Apply</button>\n </div>\n</div>\n";
|
|
53606
|
+
|
|
53607
|
+
/***/ },
|
|
53608
|
+
|
|
52859
53609
|
/***/ "./frontend/src/dashboard-result/dashboard-chart/dashboard-chart.html"
|
|
52860
53610
|
/*!****************************************************************************!*\
|
|
52861
53611
|
!*** ./frontend/src/dashboard-result/dashboard-chart/dashboard-chart.html ***!
|
|
@@ -53039,7 +53789,7 @@ module.exports = "<pre class=\"w-full whitespace-pre-wrap break-words font-mono
|
|
|
53039
53789
|
(module) {
|
|
53040
53790
|
|
|
53041
53791
|
"use strict";
|
|
53042
|
-
module.exports = "<div class=\"flex items-center gap-2\" @click.stop>\n <select\n :value=\"format\"\n @input=\"onFormatChange($event.target.value)\"\n class=\"text-xs border border-edge rounded-md bg-surface px-2 py-1\"\n >\n <option value=\"utc_iso\">UTC (ISO)</option>\n <option value=\"local_browser\">Local (Browser)</option>\n <option value=\"unix_ms\">Unix (ms)</option>\n <option value=\"unix_seconds\">Unix (seconds)</option>\n <option value=\"duration_relative\">Duration relative to now</option>\n <option value=\"custom_tz\">Custom TZ...</option>\n </select>\n <input\n v-if=\"format === 'custom_tz'\"\n :value=\"timezone\"\n @input=\"onTimezoneChange($event.target.value.trim())\"\n :list=\"timezoneDatalistId\"\n type=\"text\"\n placeholder=\"America/New_York\"\n class=\"text-xs border border-edge rounded-md bg-surface px-2 py-1 w-40\"\n >\n <datalist v-if=\"format === 'custom_tz'\" :id=\"timezoneDatalistId\">\n <option v-for=\"tz in timezones\" :key=\"tz\" :value=\"tz\"></option>\n </datalist>\n</div>\n";
|
|
53792
|
+
module.exports = "<div class=\"flex items-center gap-2\" @click.stop>\n <select\n :value=\"format\"\n @input=\"onFormatChange($event.target.value)\"\n :disabled=\"editting\"\n class=\"text-xs border border-edge rounded-md bg-surface px-2 py-1\"\n >\n <option value=\"utc_iso\">UTC (ISO)</option>\n <option value=\"local_browser\">Local (Browser)</option>\n <option value=\"unix_ms\">Unix (ms)</option>\n <option value=\"unix_seconds\">Unix (seconds)</option>\n <option value=\"duration_relative\">Duration relative to now</option>\n <option value=\"custom_tz\">Custom TZ...</option>\n </select>\n <input\n v-if=\"format === 'custom_tz'\"\n :value=\"timezone\"\n @input=\"onTimezoneChange($event.target.value.trim())\"\n :list=\"timezoneDatalistId\"\n type=\"text\"\n placeholder=\"America/New_York\"\n class=\"text-xs border border-edge rounded-md bg-surface px-2 py-1 w-40\"\n >\n <datalist v-if=\"format === 'custom_tz'\" :id=\"timezoneDatalistId\">\n <option v-for=\"tz in timezones\" :key=\"tz\" :value=\"tz\"></option>\n </datalist>\n</div>\n";
|
|
53043
53793
|
|
|
53044
53794
|
/***/ },
|
|
53045
53795
|
|
|
@@ -53050,7 +53800,7 @@ module.exports = "<div class=\"flex items-center gap-2\" @click.stop>\n <select
|
|
|
53050
53800
|
(module) {
|
|
53051
53801
|
|
|
53052
53802
|
"use strict";
|
|
53053
|
-
module.exports = ".document-details {\n width: 100%;\n}\n\n.document-details .value {\n padding-top: 10px;\n padding-bottom: 10px;\n}\n\n.document-details .path-key {\n background-color: #f0f0f0;\n margin-bottom: 0.5em;\n}\n\n.document-details .path-type {\n color: rgba(0,0,0,.36);\n font-size: 0.8em;\n}\n\n.document-details .date-position {\n float: right;\n margin-top: -7px;\n}\n\n/*
|
|
53803
|
+
module.exports = ".document-details {\n width: 100%;\n}\n\n.document-details .value {\n padding-top: 10px;\n padding-bottom: 10px;\n}\n\n.document-details .path-key {\n background-color: #f0f0f0;\n margin-bottom: 0.5em;\n}\n\n.document-details .path-type {\n color: rgba(0,0,0,.36);\n font-size: 0.8em;\n}\n\n.document-details .date-position {\n float: right;\n margin-top: -7px;\n}\n\n/* JSON View Styles */\n.json-view {\n width: 100%;\n}\n\n.json-view pre {\n margin: 0;\n max-height: 70vh;\n overflow: auto;\n line-height: 1.5;\n}\n\n.json-view pre::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n.json-view pre::-webkit-scrollbar-track {\n background: #f1f1f1;\n border-radius: 4px;\n}\n\n.json-view pre::-webkit-scrollbar-thumb {\n background: #888;\n border-radius: 4px;\n}\n\n.json-view pre::-webkit-scrollbar-thumb:hover {\n background: #555;\n}\n";
|
|
53054
53804
|
|
|
53055
53805
|
/***/ },
|
|
53056
53806
|
|
|
@@ -53061,7 +53811,7 @@ module.exports = ".document-details {\n width: 100%;\n}\n\n.document-details .v
|
|
|
53061
53811
|
(module) {
|
|
53062
53812
|
|
|
53063
53813
|
"use strict";
|
|
53064
|
-
module.exports = "<div class=\"document-details\">\n <!-- View Toggle and Search/Filter Bar -->\n <div class=\"sticky top-[60px] z-40 bg-surface rounded-md p-4 border-b border-edge shadow-sm\">\n\n <!-- Search and Filter Bar (only show in fields view) -->\n <div v-if=\"viewMode === 'fields'\" class=\"flex md:gap-3\">\n <!-- Search Bar -->\n <div class=\"relative flex-1\">\n <input\n ref=\"searchInput\"\n v-model=\"searchQuery\"\n type=\"text\"\n placeholder=\"Search fields...\"\n class=\"w-full px-4 py-2 pl-10 pr-4 text-sm border border-edge-strong rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n <div class=\"absolute inset-y-0 left-0 flex items-center pl-3\">\n <svg class=\"w-4 h-4 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"></path>\n </svg>\n </div>\n </div>\n\n <!-- Data Type Filter -->\n <div class=\"relative hidden md:block\">\n <select\n v-model=\"selectedType\"\n class=\"px-4 py-2 pr-8 text-sm border border-edge-strong rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-surface min-w-[140px] appearance-none\"\n >\n <option value=\"\">All Types</option>\n <option v-for=\"type in availableTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <div class=\"absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none\">\n <svg class=\"w-4 h-4 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Fields View -->\n <div v-if=\"viewMode === 'fields'\">\n <!-- Matched Schema Paths (shown first when searching) -->\n <div\n v-for=\"path in matchedSchemaPaths\"\n :key=\"path.path || path\"\n class=\"value\"\n >\n <document-property\n :path=\"path\"\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :highlight=\"true\"\n :searchQuery=\"searchQuery\"\n :invalid=\"invalid\"></document-property>\n </div>\n\n <!-- Matched Virtual Fields (shown after matched schema paths when searching) -->\n <div\n v-for=\"path in matchedVirtuals\"\n :key=\"path.name\"\n class=\"border border-edge rounded-lg mb-2 transition-all duration-200 ease-in-out mt-4\"\n >\n <!-- Virtual Field Header (Always Visible) -->\n <div\n @click=\"toggleVirtualField(path.name)\"\n class=\"p-3 bg-slate-100 hover:bg-muted cursor-pointer flex items-center justify-between border-b border-edge transition-colors duration-200 ease-in-out\"\n >\n <div class=\"flex items-center\">\n <svg\n :class=\"isVirtualFieldCollapsed(path.name) ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-content-tertiary mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-content\">\n <span\n v-for=\"(part, index) in getSearchMatchParts(path.name)\"\n :key=\"index\"\n :class=\"{ 'font-bold': part.matched }\">{{ part.text }}</span>\n </span>\n <span v-if=\"path.isVirtual\" class=\"ml-2 text-sm text-purple-600\">(virtual - {{getVirtualFieldType(path)}})</span>\n <span v-else class=\"ml-2 text-sm text-blue-600\">(user-added - {{getVirtualFieldType(path)}})</span>\n </div>\n </div>\n\n <!-- Virtual Field Content (Collapsible) -->\n <div v-if=\"!isVirtualFieldCollapsed(path.name)\" class=\"p-3\">\n <div v-if=\"path.value == null\" class=\"text-sky-800\">\n {{'' + path.value}}\n </div>\n <div v-else>\n {{path.value}}\n </div>\n </div>\n </div>\n\n <!-- Unmatched Schema Paths (shown after matched items when searching, or all when not searching) -->\n <div\n v-for=\"path in unmatchedSchemaPaths\"\n :key=\"path.path || path\"\n class=\"value\"\n >\n <document-property\n :path=\"path\"\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :highlight=\"false\"\n :searchQuery=\"searchQuery\"\n :invalid=\"invalid\"></document-property>\n </div>\n\n <!-- Unmatched Virtual Fields (shown last) -->\n <div\n v-for=\"path in unmatchedVirtuals\"\n :key=\"path.name\"\n class=\"border border-edge rounded-lg mb-2 transition-all duration-200 ease-in-out\"\n >\n <!-- Virtual Field Header (Always Visible) -->\n <div\n @click=\"toggleVirtualField(path.name)\"\n class=\"p-3 bg-slate-100 hover:bg-muted cursor-pointer flex items-center justify-between border-b border-edge transition-colors duration-200 ease-in-out\"\n >\n <div class=\"flex items-center\">\n <svg\n :class=\"isVirtualFieldCollapsed(path.name) ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-content-tertiary mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-content\">{{path.name}}</span>\n <span v-if=\"path.isVirtual\" class=\"ml-2 text-sm text-purple-600\">(virtual - {{getVirtualFieldType(path)}})</span>\n <span v-else class=\"ml-2 text-sm text-blue-600\">(user-added - {{getVirtualFieldType(path)}})</span>\n </div>\n </div>\n\n <!-- Virtual Field Content (Collapsible) -->\n <div v-if=\"!isVirtualFieldCollapsed(path.name)\" class=\"p-3\">\n <div v-if=\"path.value == null\" class=\"text-sky-800\">\n {{'' + path.value}}\n </div>\n <div v-else>\n {{path.value}}\n </div>\n </div>\n </div>\n\n <!-- No Results Message -->\n <div v-if=\"searchQuery.trim() && !hasSearchMatches\" class=\"text-center py-8 text-content-tertiary\">\n <svg class=\"w-12 h-12 mx-auto mb-4 text-gray-300\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9.172 16.172a4 4 0 015.656 0M9 12h6m-6-4h6m2 5.291A7.962 7.962 0 0112 15c-2.34 0-4.29-1.009-5.824-2.709M15 6.291A7.962 7.962 0 0012 5c-2.34 0-4.29 1.009-5.824 2.709\"></path>\n </svg>\n <p>No fields found matching \"{{searchQuery}}\"</p>\n </div>\n </div>\n\n <!-- JSON View -->\n <div v-if=\"viewMode === 'json'\" class=\"json-view\">\n <div class=\"border border-edge-strong rounded-lg bg-page p-4 overflow-auto\">\n <pre class=\"text-sm font-mono text-gray-800 whitespace-pre\">{{formattedJson}}</pre>\n </div>\n </div>\n\n <!-- Add Field Modal -->\n <modal v-if=\"showAddFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"closeAddFieldModal\">×</div>\n <div class=\"add-field-modal\">\n <div class=\"mb-6\">\n <h2 class=\"text-xl font-semibold text-content mb-2\">Add New Field</h2>\n <p class=\"text-sm text-gray-600\">Create a new field for this document</p>\n </div>\n\n <form @submit.prevent=\"handleAddFieldSubmit\" class=\"space-y-4\">\n <!-- Field Name -->\n <div>\n <label for=\"fieldName\" class=\"block text-sm font-medium text-content-secondary mb-1\">\n Field Name *\n </label>\n <input\n id=\"fieldName\"\n v-model=\"fieldData.name\"\n type=\"text\"\n required\n placeholder=\"Enter field name...\"\n class=\"w-full px-3 py-2 border border-edge-strong rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.name }\"\n />\n <p v-if=\"fieldErrors.name\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.name}}</p>\n <p v-if=\"fieldData.name && getTransformedFieldName() !== fieldData.name.trim()\" class=\"mt-1 text-sm text-blue-600\">\n Field name will be: <code class=\"bg-blue-50 px-1 py-0.5 rounded text-xs\">{{getTransformedFieldName()}}</code>\n </p>\n </div>\n\n <!-- Field Type -->\n <div>\n <label for=\"fieldType\" class=\"block text-sm font-medium text-content-secondary mb-1\">\n Field Type *\n </label>\n <select\n id=\"fieldType\"\n v-model=\"fieldData.type\"\n required\n class=\"w-full px-3 py-2 border border-edge-strong rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.type }\"\n >\n <option value=\"\">Select field type...</option>\n <option v-for=\"type in allFieldTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <p v-if=\"fieldErrors.type\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.type}}</p>\n </div>\n\n <!-- Field Value -->\n <div>\n <label for=\"fieldValue\" class=\"block text-sm font-medium text-content-secondary mb-1\">\n Initial Value\n </label>\n\n <!-- Date picker for Date type -->\n <input\n v-if=\"shouldUseDatePicker\"\n v-model=\"fieldData.value\"\n type=\"date\"\n id=\"fieldValue\"\n class=\"w-full px-3 py-2 border border-edge-strong rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <!-- Simple input for basic types -->\n <input\n v-else-if=\"!shouldUseAce\"\n v-model=\"fieldData.value\"\n type=\"text\"\n id=\"fieldValue\"\n placeholder=\"Enter initial value (optional)...\"\n class=\"w-full px-3 py-2 border border-edge-strong rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <!-- Ace editor for complex types (Array/Object/Embedded) -->\n <ace-editor\n v-else\n id=\"fieldValue\"\n v-model=\"fieldData.value\"\n mode=\"json\"\n :line-numbers=\"true\"\n :min-lines=\"3\"\n class=\"add-field-ace-editor w-full min-h-[100px]\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <p v-if=\"fieldErrors.value\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.value}}</p>\n <p class=\"mt-1 text-xs text-content-tertiary\">\n <span v-if=\"shouldUseDatePicker\">Select a date or leave empty for null/undefined values.</span>\n <span v-else-if=\"shouldUseAce\">Leave empty for null/undefined values. Use valid JSON format.</span>\n <span v-else>Leave empty for null/undefined values.</span>\n </p>\n </div>\n\n\n <!-- Action Buttons -->\n <div class=\"flex justify-end gap-3 pt-4 border-t border-edge\">\n <button\n type=\"button\"\n @click=\"closeAddFieldModal\"\n class=\"px-4 py-2 text-sm font-medium text-content-secondary bg-surface border border-edge-strong rounded-md hover:bg-page focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n :disabled=\"isSubmittingField\"\n class=\"px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <span v-if=\"isSubmittingField\">Adding...</span>\n <span v-else>Add Field</span>\n </button>\n </div>\n </form>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
53814
|
+
module.exports = "<div class=\"document-details\">\n <!-- View Toggle and Search/Filter Bar -->\n <div class=\"sticky top-[60px] z-40 bg-surface rounded-md p-4 border-b border-edge shadow-sm\">\n\n <!-- Search and Filter Bar (only show in fields view) -->\n <div v-if=\"viewMode === 'fields'\" class=\"flex md:gap-3\">\n <!-- Search Bar -->\n <div class=\"relative flex-1\">\n <input\n ref=\"searchInput\"\n v-model=\"searchQuery\"\n type=\"text\"\n placeholder=\"Search fields...\"\n class=\"w-full px-4 py-2 pl-10 pr-4 text-sm border border-edge-strong rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n <div class=\"absolute inset-y-0 left-0 flex items-center pl-3\">\n <svg class=\"w-4 h-4 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"></path>\n </svg>\n </div>\n </div>\n\n <!-- Data Type Filter -->\n <div class=\"relative hidden md:block\">\n <select\n v-model=\"selectedType\"\n class=\"px-4 py-2 pr-8 text-sm border border-edge-strong rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-surface min-w-[140px] appearance-none\"\n >\n <option value=\"\">All Types</option>\n <option v-for=\"type in availableTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <div class=\"absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none\">\n <svg class=\"w-4 h-4 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Fields View -->\n <div v-if=\"viewMode === 'fields'\">\n <!-- Matched Schema Paths (shown first when searching) -->\n <div\n v-for=\"path in matchedSchemaPaths\"\n :key=\"path.path || path\"\n class=\"value\"\n >\n <document-property\n :path=\"path\"\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :highlight=\"true\"\n :searchQuery=\"searchQuery\"\n :invalid=\"invalid\"></document-property>\n </div>\n\n <!-- Matched Virtual Fields (shown after matched schema paths when searching) -->\n <div\n v-for=\"path in matchedVirtuals\"\n :key=\"path.name\"\n class=\"border border-edge rounded-lg mb-2 transition-all duration-200 ease-in-out mt-4\"\n >\n <!-- Virtual Field Header (Always Visible) -->\n <div\n @click=\"toggleVirtualField(path.name)\"\n class=\"p-3 bg-slate-100 hover:bg-muted cursor-pointer flex items-center justify-between border-b border-edge transition-colors duration-200 ease-in-out\"\n >\n <div class=\"flex items-center\">\n <svg\n :class=\"isVirtualFieldCollapsed(path.name) ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-content-tertiary mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-content\">\n <span\n v-for=\"(part, index) in getSearchMatchParts(path.name)\"\n :key=\"index\"\n :class=\"{ 'font-bold': part.matched }\">{{ part.text }}</span>\n </span>\n <span v-if=\"path.isVirtual\" class=\"ml-2 text-sm text-purple-600\">(virtual - {{getVirtualFieldType(path)}})</span>\n <span v-else class=\"ml-2 text-sm text-blue-600\">(user-added - {{getVirtualFieldType(path)}})</span>\n </div>\n </div>\n\n <!-- Virtual Field Content (Collapsible) -->\n <div v-if=\"!isVirtualFieldCollapsed(path.name)\" class=\"p-3\">\n <div v-if=\"path.value == null\" class=\"text-sky-800\">\n {{'' + path.value}}\n </div>\n <div v-else>\n {{path.value}}\n </div>\n </div>\n </div>\n\n <!-- Unmatched Schema Paths (shown after matched items when searching, or all when not searching) -->\n <div\n v-for=\"path in unmatchedSchemaPaths\"\n :key=\"path.path || path\"\n class=\"value\"\n >\n <document-property\n :path=\"path\"\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :highlight=\"false\"\n :searchQuery=\"searchQuery\"\n :invalid=\"invalid\"></document-property>\n </div>\n\n <!-- Unmatched Virtual Fields (shown last) -->\n <div\n v-for=\"path in unmatchedVirtuals\"\n :key=\"path.name\"\n class=\"border border-edge rounded-lg mb-2 transition-all duration-200 ease-in-out\"\n >\n <!-- Virtual Field Header (Always Visible) -->\n <div\n @click=\"toggleVirtualField(path.name)\"\n class=\"p-3 bg-slate-100 hover:bg-muted cursor-pointer flex items-center justify-between border-b border-edge transition-colors duration-200 ease-in-out\"\n >\n <div class=\"flex items-center\">\n <svg\n :class=\"isVirtualFieldCollapsed(path.name) ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-content-tertiary mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-content\">{{path.name}}</span>\n <span v-if=\"path.isVirtual\" class=\"ml-2 text-sm text-purple-600\">(virtual - {{getVirtualFieldType(path)}})</span>\n <span v-else class=\"ml-2 text-sm text-blue-600\">(user-added - {{getVirtualFieldType(path)}})</span>\n </div>\n </div>\n\n <!-- Virtual Field Content (Collapsible) -->\n <div v-if=\"!isVirtualFieldCollapsed(path.name)\" class=\"p-3\">\n <div v-if=\"path.value == null\" class=\"text-sky-800\">\n {{'' + path.value}}\n </div>\n <div v-else>\n {{path.value}}\n </div>\n </div>\n </div>\n\n <!-- No Results Message -->\n <div v-if=\"searchQuery.trim() && !hasSearchMatches\" class=\"text-center py-8 text-content-tertiary\">\n <svg class=\"w-12 h-12 mx-auto mb-4 text-gray-300\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9.172 16.172a4 4 0 015.656 0M9 12h6m-6-4h6m2 5.291A7.962 7.962 0 0112 15c-2.34 0-4.29-1.009-5.824-2.709M15 6.291A7.962 7.962 0 0012 5c-2.34 0-4.29 1.009-5.824 2.709\"></path>\n </svg>\n <p>No fields found matching \"{{searchQuery}}\"</p>\n </div>\n </div>\n\n <!-- JSON View -->\n <div v-if=\"viewMode === 'json'\" class=\"json-view\">\n <div class=\"border border-edge-strong rounded-lg bg-page p-4 overflow-auto\">\n <pre class=\"text-sm font-mono text-gray-800 whitespace-pre\">{{formattedJson}}</pre>\n </div>\n </div>\n</div>\n";
|
|
53065
53815
|
|
|
53066
53816
|
/***/ },
|
|
53067
53817
|
|
|
@@ -53083,7 +53833,7 @@ module.exports = ".document-details {\n width: 100%;\n }\n \n .document-de
|
|
|
53083
53833
|
(module) {
|
|
53084
53834
|
|
|
53085
53835
|
"use strict";
|
|
53086
|
-
module.exports = "<div class=\"border border-edge bg-surface rounded-lg mb-2\" style=\"overflow: visible;\">\n <!-- Collapsible Header -->\n <div\n @click=\"toggleCollapse\"\n class=\"p-1 cursor-pointer flex items-center justify-between border-b border-edge bg-slate-100 hover:bg-muted transition-colors duration-200 ease-in-out\"\n style=\"overflow: visible; position: relative;\"\n >\n <div class=\"flex items-center\" >\n <svg\n :class=\"isCollapsed ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-content-tertiary mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-content\">\n <span\n v-for=\"(part, index) in pathNameParts\"\n :key=\"index\"\n :class=\"{ 'font-bold': part.matched }\">{{ part.text }}</span>\n </span>\n <span class=\"ml-2 text-sm text-content-tertiary\">({{(path.instance || 'unknown').toLowerCase()}})</span>\n <div v-if=\"path.instance === 'Array' && canUseArrayTableView\" class=\"ml-3 inline-flex items-center rounded-full bg-gray-200 p-0.5 text-xs font-semibold\">\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"arrayDetailViewMode === 'list' ? 'bg-merlot-700 text-white shadow' : 'text-content-secondary hover:text-content'\"\n @click.stop=\"setArrayDetailViewMode('list')\">\n List\n </button>\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"arrayDetailViewMode === 'table' ? 'bg-merlot-700 text-white shadow' : 'text-content-secondary hover:text-content'\"\n @click.stop=\"setArrayDetailViewMode('table')\">\n Table\n </button>\n </div>\n <div v-if=\"isGeoJsonGeometry\" class=\"ml-3 inline-flex items-center gap-2\">\n <div class=\"inline-flex items-center rounded-full bg-gray-200 p-0.5 text-xs font-semibold\">\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"detailViewMode === 'text' ? 'bg-blue-600 text-white shadow' : 'text-content-secondary hover:text-content'\"\n :style=\"detailViewMode === 'text' ? 'color: white !important; background-color: #2563eb !important;' : ''\"\n @click.stop=\"setDetailViewMode('text')\">\n Text\n </button>\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"detailViewMode === 'map' ? 'bg-blue-600 text-white shadow' : 'text-content-secondary hover:text-content'\"\n :style=\"detailViewMode === 'map' ? 'color: white !important; background-color: #2563eb !important;' : ''\"\n @click.stop=\"setDetailViewMode('map')\">\n Map\n </button>\n </div>\n <!-- Info icon with tooltip -->\n <div v-if=\"editting\" class=\"relative inline-block\" style=\"z-index: 10002;\" @mouseenter=\"showTooltip = true\" @mouseleave=\"showTooltip = false\" @click.stop>\n <svg\n ref=\"infoIcon\"\n class=\"w-6 h-6 text-gray-400 hover:text-gray-600 cursor-help\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"></path>\n </svg>\n <div\n v-show=\"showTooltip\"\n ref=\"tooltip\"\n class=\"absolute left-full top-0 ml-2 w-64 p-3 text-white text-xs rounded-lg shadow-xl\"\n style=\"z-index: 99999; pointer-events: none; white-space: normal; position: fixed; background-color: #111827;\"\n :style=\"getTooltipStyle()\"\n >\n <div class=\"font-semibold mb-2\">Map Controls:</div>\n <div v-if=\"isGeoJsonPoint\" class=\"space-y-1\">\n <div>• Drag pin to move location</div>\n </div>\n <div v-else-if=\"isGeoJsonPolygon\" class=\"space-y-1\">\n <div>• Drag vertices to reshape polygon</div>\n <div v-if=\"isMultiPolygon\">• Right-click edge to add new vertex</div>\n <div>• Right-click vertex to delete</div>\n </div>\n <div class=\"absolute top-2 -left-1 w-0 h-0 border-t-4 border-b-4 border-r-4 border-transparent border-r-gray-900\"></div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"flex items-center gap-2\">\n <date-view-mode-picker\n v-if=\"isDatePath\"\n :viewMode=\"dateViewMode\"\n :path=\"path\"\n @update:viewMode=\"dateViewMode = $event\"\n ></date-view-mode-picker>\n <objectid-view-mode-picker\n v-if=\"isObjectIdPath\"\n :viewMode=\"objectIdViewMode\"\n @update:viewMode=\"objectIdViewMode = $event\"\n ></objectid-view-mode-picker>\n <button\n type=\"button\"\n class=\"flex items-center gap-1 text-sm text-gray-600 hover:text-gray-800 px-2 py-1 rounded-md border border-transparent hover:border-edge-strong bg-surface\"\n @click.stop.prevent=\"copyPropertyValue\"\n title=\"Copy value\"\n aria-label=\"Copy property value\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7h8m-8 4h8m-8 4h5m-7-9a2 2 0 012-2h7a2 2 0 012 2v10a2 2 0 01-2 2H8l-4-4V7a2 2 0 012-2z\" />\n </svg>\n {{copyButtonLabel}}\n </button>\n <router-link\n v-if=\"path.ref && getValueForPath(path.path)\"\n :to=\"`/model/${path.ref}/document/${getValueForPath(path.path)}`\"\n class=\"bg-primary hover:bg-primary-hover text-primary-text px-2 py-1 text-sm rounded-md\"\n @click.stop\n >View Document\n </router-link>\n </div>\n </div>\n\n <!-- Collapsible Content -->\n <div v-if=\"!isCollapsed\" class=\"p-2\">\n <!-- Date Type Selector (when editing dates) -->\n <div v-if=\"editting && path.instance === 'Date'\" class=\"mb-3 flex gap-1.5\">\n <div\n @click=\"dateType = 'picker'\"\n :class=\"dateType === 'picker' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'picker' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n Date Picker\n </div>\n </div>\n <div\n @click=\"dateType = 'iso'\"\n :class=\"dateType === 'iso' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'iso' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n ISO String\n </div>\n </div>\n </div>\n\n <!-- Field Content -->\n <div v-if=\"editting && path.path !== '_id'\">\n <!-- Use detail-default with map editing for GeoJSON geometries -->\n <component\n v-if=\"isGeoJsonGeometry\"\n :is=\"getComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :viewMode=\"detailViewMode\"\n :on-change=\"handleInputChange\"\n >\n </component>\n <!-- Use standard edit components for other types -->\n <component\n v-else\n :is=\"getEditComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :format=\"dateType\"\n v-bind=\"getEditComponentProps(path)\"\n @input=\"handleInputChange($event)\"\n @error=\"invalid[path.path] = $event;\"\n >\n </component>\n </div>\n <div v-else>\n <!-- Arrays own their truncation and array search behavior in detail-array. -->\n <component\n v-if=\"isArray\"\n :is=\"getComponentForPath(path)\"\n :value=\"rawValue\"\n :viewMode=\"propertyDetailViewMode\"\n :truncate=\"needsTruncation\"\n @updated=\"renderedValue = $event\"></component>\n <!-- Non-array truncated view -->\n <div v-else-if=\"shouldShowTruncated && !isArray\" class=\"relative\">\n <div class=\"text-content-secondary whitespace-pre-wrap break-words font-mono text-sm\">{{truncatedString}}</div>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-merlot-700 hover:text-merlot-900 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show more ({{valueAsString.length}} characters)\n </button>\n </div>\n <!-- Expanded view -->\n <div v-else-if=\"needsTruncation && isValueExpanded\" class=\"relative\">\n <component\n :is=\"getComponentForPath(path)\"\n :value=\"rawValue\"\n :viewMode=\"propertyDetailViewMode\"\n @updated=\"renderedValue = $event\"></component>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-merlot-700 hover:text-merlot-900 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4 rotate-180\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show less\n </button>\n </div>\n <!-- Full view (no truncation needed) -->\n <div v-else>\n <component\n :is=\"getComponentForPath(path)\"\n :value=\"rawValue\"\n :viewMode=\"propertyDetailViewMode\"\n @updated=\"renderedValue = $event\"></component>\n </div>\n </div>\n </div>\n</div>\n";
|
|
53836
|
+
module.exports = "<div class=\"border border-edge bg-surface rounded-lg mb-2\" style=\"overflow: visible;\">\n <!-- Collapsible Header -->\n <div\n @click=\"toggleCollapse\"\n class=\"p-1 cursor-pointer flex items-center justify-between border-b border-edge bg-slate-100 hover:bg-muted transition-colors duration-200 ease-in-out\"\n style=\"overflow: visible; position: relative;\"\n >\n <div class=\"flex items-center\" >\n <svg\n :class=\"isCollapsed ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-content-tertiary mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-content\">\n <span\n v-for=\"(part, index) in pathNameParts\"\n :key=\"index\"\n :class=\"{ 'font-bold': part.matched }\">{{ part.text }}</span>\n </span>\n <span class=\"ml-2 text-sm text-content-tertiary\">({{(path.instance || 'unknown').toLowerCase()}})</span>\n <div v-if=\"path.instance === 'Array' && canUseArrayTableView\" class=\"ml-3 inline-flex items-center rounded-full bg-gray-200 p-0.5 text-xs font-semibold\">\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"arrayDetailViewMode === 'list' ? 'bg-merlot-700 text-white shadow' : 'text-content-secondary hover:text-content'\"\n @click.stop=\"setArrayDetailViewMode('list')\">\n List\n </button>\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"arrayDetailViewMode === 'table' ? 'bg-merlot-700 text-white shadow' : 'text-content-secondary hover:text-content'\"\n @click.stop=\"setArrayDetailViewMode('table')\">\n Table\n </button>\n </div>\n <div v-if=\"isGeoJsonGeometry\" class=\"ml-3 inline-flex items-center gap-2\">\n <div class=\"inline-flex items-center rounded-full bg-gray-200 p-0.5 text-xs font-semibold\">\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"detailViewMode === 'text' ? 'bg-blue-600 text-white shadow' : 'text-content-secondary hover:text-content'\"\n :style=\"detailViewMode === 'text' ? 'color: white !important; background-color: #2563eb !important;' : ''\"\n @click.stop=\"setDetailViewMode('text')\">\n Text\n </button>\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"detailViewMode === 'map' ? 'bg-blue-600 text-white shadow' : 'text-content-secondary hover:text-content'\"\n :style=\"detailViewMode === 'map' ? 'color: white !important; background-color: #2563eb !important;' : ''\"\n @click.stop=\"setDetailViewMode('map')\">\n Map\n </button>\n </div>\n <!-- Info icon with tooltip -->\n <div v-if=\"editting\" class=\"relative inline-block\" style=\"z-index: 10002;\" @mouseenter=\"showTooltip = true\" @mouseleave=\"showTooltip = false\" @click.stop>\n <svg\n ref=\"infoIcon\"\n class=\"w-6 h-6 text-gray-400 hover:text-gray-600 cursor-help\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"></path>\n </svg>\n <div\n v-show=\"showTooltip\"\n ref=\"tooltip\"\n class=\"absolute left-full top-0 ml-2 w-64 p-3 text-white text-xs rounded-lg shadow-xl\"\n style=\"z-index: 99999; pointer-events: none; white-space: normal; position: fixed; background-color: #111827;\"\n :style=\"getTooltipStyle()\"\n >\n <div class=\"font-semibold mb-2\">Map Controls:</div>\n <div v-if=\"isGeoJsonPoint\" class=\"space-y-1\">\n <div>• Drag pin to move location</div>\n </div>\n <div v-else-if=\"isGeoJsonPolygon\" class=\"space-y-1\">\n <div>• Drag vertices to reshape polygon</div>\n <div v-if=\"isMultiPolygon\">• Right-click edge to add new vertex</div>\n <div>• Right-click vertex to delete</div>\n </div>\n <div class=\"absolute top-2 -left-1 w-0 h-0 border-t-4 border-b-4 border-r-4 border-transparent border-r-gray-900\"></div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"flex items-center gap-2\">\n <date-view-mode-picker\n v-if=\"isDatePath\"\n :viewMode=\"dateViewMode\"\n :path=\"path\"\n :editting=\"editting\"\n @update:viewMode=\"dateViewMode = $event\"\n ></date-view-mode-picker>\n <objectid-view-mode-picker\n v-if=\"isObjectIdPath\"\n :viewMode=\"objectIdViewMode\"\n @update:viewMode=\"objectIdViewMode = $event\"\n ></objectid-view-mode-picker>\n <button\n type=\"button\"\n class=\"flex items-center gap-1 text-sm text-gray-600 hover:text-gray-800 px-2 py-1 rounded-md border border-transparent hover:border-edge-strong bg-surface\"\n @click.stop.prevent=\"copyPropertyValue\"\n title=\"Copy value\"\n aria-label=\"Copy property value\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7h8m-8 4h8m-8 4h5m-7-9a2 2 0 012-2h7a2 2 0 012 2v10a2 2 0 01-2 2H8l-4-4V7a2 2 0 012-2z\" />\n </svg>\n {{copyButtonLabel}}\n </button>\n <router-link\n v-if=\"path.ref && getValueForPath(path.path)\"\n :to=\"`/model/${path.ref}/document/${getValueForPath(path.path)}`\"\n class=\"bg-primary hover:bg-primary-hover text-primary-text px-2 py-1 text-sm rounded-md\"\n @click.stop\n >View Document\n </router-link>\n </div>\n </div>\n\n <!-- Collapsible Content -->\n <div v-if=\"!isCollapsed\" class=\"p-2\">\n <!-- Date Type Selector (when editing dates) -->\n <div v-if=\"editting && path.instance === 'Date'\" class=\"mb-3 flex gap-1.5\">\n <div\n @click=\"dateType = 'picker'\"\n :class=\"dateType === 'picker' ? 'bg-merlot-700' : ''\"\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-merlot-700' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'iso' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n ISO String\n </div>\n </div>\n </div>\n\n <!-- Field Content -->\n <div v-if=\"editting && path.path !== '_id'\">\n <!-- Use detail-default with map editing for GeoJSON geometries -->\n <component\n v-if=\"isGeoJsonGeometry\"\n :is=\"getComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :viewMode=\"detailViewMode\"\n :on-change=\"handleInputChange\"\n >\n </component>\n <!-- Use standard edit components for other types -->\n <component\n v-else\n :is=\"getEditComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :format=\"dateType\"\n v-bind=\"getEditComponentProps(path)\"\n @input=\"handleInputChange($event)\"\n @error=\"invalid[path.path] = $event;\"\n >\n </component>\n </div>\n <div v-else>\n <!-- Arrays own their truncation and array search behavior in detail-array. -->\n <component\n v-if=\"isArray\"\n :is=\"getComponentForPath(path)\"\n :value=\"rawValue\"\n :viewMode=\"propertyDetailViewMode\"\n :truncate=\"needsTruncation\"\n @updated=\"renderedValue = $event\"></component>\n <!-- Non-array truncated view -->\n <div v-else-if=\"shouldShowTruncated && !isArray\" class=\"relative\">\n <div class=\"text-content-secondary whitespace-pre-wrap break-words font-mono text-sm\">{{truncatedString}}</div>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-merlot-700 hover:text-merlot-900 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show more ({{valueAsString.length}} characters)\n </button>\n </div>\n <!-- Expanded view -->\n <div v-else-if=\"needsTruncation && isValueExpanded\" class=\"relative\">\n <component\n :is=\"getComponentForPath(path)\"\n :value=\"rawValue\"\n :viewMode=\"propertyDetailViewMode\"\n @updated=\"renderedValue = $event\"></component>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-merlot-700 hover:text-merlot-900 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4 rotate-180\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show less\n </button>\n </div>\n <!-- Full view (no truncation needed) -->\n <div v-else>\n <component\n :is=\"getComponentForPath(path)\"\n :value=\"rawValue\"\n :viewMode=\"propertyDetailViewMode\"\n @updated=\"renderedValue = $event\"></component>\n </div>\n </div>\n </div>\n</div>\n";
|
|
53087
53837
|
|
|
53088
53838
|
/***/ },
|
|
53089
53839
|
|
|
@@ -53127,7 +53877,7 @@ module.exports = "<div>\n <h2>\n Are you sure you want to delete the follo
|
|
|
53127
53877
|
(module) {
|
|
53128
53878
|
|
|
53129
53879
|
"use strict";
|
|
53130
|
-
module.exports = ".document .document-menu {\n display: flex;\n position: sticky;\n top: 0;\n z-index: 100;\n background-color: var(--studio-bg-surface);\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";
|
|
53880
|
+
module.exports = ".document .document-menu {\n display: flex;\n position: sticky;\n top: 0;\n z-index: 100;\n background-color: var(--studio-bg-surface);\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\n/* Add Field Modal Styles */\n.add-field-modal {\n max-width: 500px;\n width: 100%;\n}\n\n.add-field-modal .modal-exit {\n position: absolute;\n top: 15px;\n right: 20px;\n font-size: 24px;\n cursor: pointer;\n color: #6b7280;\n z-index: 10;\n}\n\n.add-field-modal .modal-exit:hover {\n color: #374151;\n}\n\n.add-field-modal form {\n max-height: 70vh;\n overflow-y: auto;\n}\n\n.add-field-modal input[type=\"text\"],\n.add-field-modal input[type=\"email\"],\n.add-field-modal input[type=\"password\"],\n.add-field-modal select,\n.add-field-modal textarea {\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n.add-field-modal input:focus,\n.add-field-modal select:focus,\n.add-field-modal textarea:focus {\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);\n}\n\n.add-field-modal .border-red-500 {\n border-color: #ef4444 !important;\n}\n\n.add-field-modal .border-red-500:focus {\n box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1) !important;\n}\n\n/* Ace editor styling in modal */\n.add-field-modal .add-field-ace-editor.ace_editor {\n border: 1px solid #d1d5db;\n border-radius: 0.375rem;\n font-size: 14px;\n min-height: 100px;\n}\n\n.add-field-modal .add-field-ace-editor:focus-within.ace_editor,\n.add-field-modal .add-field-ace-editor.ace_focus {\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);\n}\n";
|
|
53131
53881
|
|
|
53132
53882
|
/***/ },
|
|
53133
53883
|
|
|
@@ -53138,7 +53888,7 @@ module.exports = ".document .document-menu {\n display: flex;\n position: stic
|
|
|
53138
53888
|
(module) {
|
|
53139
53889
|
|
|
53140
53890
|
"use strict";
|
|
53141
|
-
module.exports = "<div class=\"document px-1 pt-4 pb-16 md:px-0 bg-slate-50 w-full\">\n <div class=\"max-w-7xl mx-auto\">\n <div class=\"flex gap-4 items-center sticky top-0 z-50 bg-surface p-4 border-b border-edge shadow-sm\">\n <div class=\"font-bold overflow-hidden text-ellipsis\">{{model}}: {{documentId}}</div>\n <div class=\"flex grow\">\n <button\n @click=\"viewMode = 'fields'\"\n :class=\"viewMode === 'fields'\n ? 'bg-primary text-white z-10'\n : 'bg-gray-200 text-content-secondary hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-edge-strong border-r-0 rounded-l-lg rounded-r-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 6h16M4 10h16M4 14h16M4 18h16\"></path>\n </svg>\n Fields\n </button>\n <button\n @click=\"viewMode = 'json'\"\n :class=\"viewMode === 'json'\n ? 'bg-primary text-white z-10'\n : 'bg-gray-200 text-content-secondary hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-edge-strong rounded-r-lg rounded-l-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4\"></path>\n </svg>\n JSON\n </button>\n </div>\n\n <div class=\"hidden md:flex items-center gap-3 text-sm text-slate-600\">\n <div class=\"text-right leading-tight\">\n <div class=\"text-xs uppercase tracking-wide text-slate-400 flex items-center gap-2 justify-end\">\n <span>Loaded at</span>\n <span class=\"inline-flex items-center gap-1 text-[10px] font-semibold\" :class=\"autoRefreshEnabled ? 'text-forest-green-600' : 'text-slate-400'\">\n <span class=\"inline-block h-1.5 w-1.5 rounded-full\" :class=\"autoRefreshEnabled ? 'bg-forest-green-500' : 'bg-slate-300'\"></span>\n </span>\n </div>\n <div class=\"font-medium text-slate-700\">{{lastUpdatedLabel}}</div>\n </div>\n </div>\n\n <div class=\"gap-2 hidden md:flex items-center\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true\"\n :disabled=\"!canEdit\"\n :class=\"{'cursor-not-allowed opacity-50': !canEdit}\"\n type=\"button\"\n class=\"rounded-md bg-primary px-2.5 py-1.5 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n <img src=\"images/edit.svg\" class=\"inline\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false\"\n type=\"button\"\n class=\"rounded-md bg-slate-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n × Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n @click=\"shouldShowConfirmModal=true;\"\n type=\"button\"\n class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">\n <img src=\"images/save.svg\" class=\"inline\" /> Save\n </button>\n\n <!-- 3-dot menu -->\n <div class=\"relative\">\n <button\n @click=\"desktopMenuOpen = !desktopMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-2.5 py-1.5 text-sm font-medium text-content-secondary hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"desktopMenuOpen\"\n aria-label=\"More options\"\n >\n <svg class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"5\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"12\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"19\" r=\"2\"></circle>\n </svg>\n </button>\n <div\n v-show=\"desktopMenuOpen\"\n @click.away=\"desktopMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-surface ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n @click=\"refreshDocument({ force: true, source: 'manual' }); desktopMenuOpen = false\"\n :disabled=\"isRefreshing\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', isRefreshing ? 'cursor-not-allowed opacity-50' : 'hover:bg-slate-100']\"\n type=\"button\"\n >\n {{isRefreshing ? 'Refreshing...' : 'Refresh'}}\n </button>\n <button\n @click=\"toggleAutoRefresh(); desktopMenuOpen = false\"\n :disabled=\"isLambda\"\n type=\"button\"\n :class=\"['flex items-center px-4 py-2 text-sm', isLambda ? 'text-gray-400 cursor-not-allowed' : 'text-content-secondary hover:bg-slate-100']\"\n :title=\"isLambda ? 'Auto-refresh only available on Express deployments' : ''\"\n >\n {{autoRefreshEnabled ? 'Disable Auto-Refresh' : 'Enable Auto-Refresh'}}\n </button>\n <button\n @click=\"addField(); desktopMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"copyDocument(); desktopMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-blue-100\"\n >\n Copy Document\n </button>\n <button\n @click=\"openValidationModal()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Validate\n </button>\n <button\n @click=\"openScriptDrawer()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-indigo-100\"\n >\n Run Script\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n <div class=\"md:hidden flex items-center\">\n <div class=\"relative\">\n <button\n @click=\"mobileMenuOpen = !mobileMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-3 py-2 text-sm font-medium text-content-secondary hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"mobileMenuOpen\"\n aria-label=\"Open menu\"\n >\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\n d=\"M4 6h16M4 12h16M4 18h16\"></path>\n </svg>\n </button>\n <div\n v-show=\"mobileMenuOpen\"\n @click.away=\"mobileMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-52 rounded-md shadow-lg bg-surface ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true; mobileMenuOpen = false\"\n :disabled=\"!canEdit\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canEdit ? 'cursor-not-allowed opacity-50' : 'hover:bg-primary-subtle']\"\n type=\"button\"\n >\n Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false; mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-slate-100\"\n >\n Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-green-100']\"\n @click=\"shouldShowConfirmModal=true; mobileMenuOpen = false\"\n type=\"button\"\n >\n Save\n </button>\n <button\n @click=\"refreshDocument({ force: true, source: 'manual' }); mobileMenuOpen = false\"\n :disabled=\"isRefreshing\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', isRefreshing ? 'cursor-not-allowed opacity-50' : 'hover:bg-slate-100']\"\n type=\"button\"\n >\n {{isRefreshing ? 'Refreshing...' : 'Refresh'}}\n </button>\n <button\n @click=\"toggleAutoRefresh(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-slate-100\"\n >\n Auto-Refresh {{autoRefreshEnabled ? 'ON' : 'OFF'}}\n </button>\n <button\n @click=\"addField(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"copyDocument(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-blue-100\"\n >\n Copy Document\n </button>\n <button\n @click=\"openValidationModal()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Validate\n </button>\n <button\n @click=\"openScriptDrawer()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-indigo-100\"\n >\n Run Script\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div v-if=\"status === 'loaded'\">\n <document-details\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :virtualPaths=\"virtualPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :invalid=\"invalid\"\n :viewMode=\"viewMode\"\n :model=\"model\"\n @add-field=\"addField\"\n @view-mode-change=\"updateViewMode\"></document-details>\n <modal v-if=\"shouldShowConfirmModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowConfirmModal = false;\">×</div>\n <confirm-changes @close=\"shouldShowConfirmModal = false;\" @save=\"save\" :value=\"changes\"></confirm-changes>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteModal = false;\">×</div>\n <confirm-delete @close=\"shouldShowDeleteModal = false;\" @remove=\"remove\" :value=\"document\"></confirm-delete>\n </template>\n </modal>\n <modal v-if=\"shouldShowCloneModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCloneModal = false;\">×</div>\n <clone-document :currentModel=\"model\" :doc=\"document\" :schemaPaths=\"schemaPaths\" @close=\"showClonedDocument\"></clone-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowValidationModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowValidationModal = false;\">×</div>\n <div class=\"text-xl font-bold mb-2\">Validate Document</div>\n <div v-if=\"!validationResult\" class=\"space-y-3\">\n <div class=\"h-4 w-32 animate-pulse rounded bg-muted\"></div>\n <div class=\"h-20 w-full animate-pulse rounded bg-muted\"></div>\n </div>\n <div v-else class=\"space-y-3\">\n <div\n class=\"rounded px-3 py-2 text-sm font-semibold\"\n :class=\"validationResult.valid ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-700'\"\n >\n {{ validationResult.valid ? 'Document is valid' : 'Document is invalid' }}\n </div>\n <div v-if=\"!validationResult.valid\" class=\"rounded border border-edge bg-muted p-3 text-sm text-content-secondary\">\n {{ validationResult.error }}\n </div>\n <div v-if=\"validationResult?.errors && Object.keys(validationResult.errors).length > 0\" class=\"rounded border border-edge\">\n <div class=\"border-b border-edge px-3 py-2 text-sm font-semibold text-content-secondary\">Invalid Paths</div>\n <div class=\"max-h-72 overflow-auto\">\n <div\n v-for=\"[path, pathError] in Object.entries(validationResult.errors)\"\n :key=\"path\"\n class=\"grid gap-2 border-b border-edge px-3 py-2 text-sm last:border-b-0 md:grid-cols-[minmax(160px,220px)_1fr]\"\n >\n <div class=\"font-mono text-content\">{{ path }}</div>\n <div class=\"text-content-secondary\">\n <div>{{ pathError.message }}</div>\n <div v-if=\"pathError.kind\" class=\"mt-1 text-xs text-content-tertiary\">Kind: {{ pathError.kind }}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n </div>\n <execute-script\n :visible=\"scriptDrawerOpen\"\n :model=\"model\"\n :document-id=\"documentId\"\n :editting=\"editting\"\n @close=\"closeScriptDrawer\"\n @refresh=\"handleScriptRefresh\"\n ></execute-script>\n <div\n v-if=\"keyboardShortcuts.length > 0\"\n class=\"fixed bottom-4 left-4 z-40 group\"\n >\n <button\n type=\"button\"\n aria-label=\"Keyboard shortcuts\"\n title=\"Keyboard shortcuts\"\n class=\"rounded-full border border-slate-300 bg-surface p-2 text-slate-700 shadow-sm transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\">\n <rect x=\"2.5\" y=\"6.5\" width=\"19\" height=\"11\" rx=\"2\" stroke-width=\"1.5\"></rect>\n <path d=\"M6 10.5h1.5M9 10.5h1.5M12 10.5h1.5M15 10.5h1.5M18 10.5h.01M6 13.5h1.5M9 13.5h6M16.5 13.5h1.5\" stroke-width=\"1.5\" stroke-linecap=\"round\"></path>\n </svg>\n </button>\n <div class=\"pointer-events-none absolute left-0 bottom-full mb-2 min-w-[220px] translate-y-1 rounded-lg bg-slate-900 px-3 py-2.5 text-slate-200 opacity-0 shadow-xl transition-all duration-150 group-hover:pointer-events-auto group-hover:translate-y-0 group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:translate-y-0 group-focus-within:opacity-100\">\n <div\n v-for=\"shortcut in keyboardShortcuts\"\n :key=\"shortcut.command + shortcut.description\"\n class=\"flex items-center justify-between gap-3 text-xs\"\n >\n <span class=\"font-mono font-semibold text-slate-50\">{{shortcut.command}}</span>\n <span class=\"text-slate-300\">{{shortcut.description}}</span>\n </div>\n </div>\n </div>\n </div>\n</div>\n";
|
|
53891
|
+
module.exports = "<div class=\"document px-1 pt-4 pb-16 md:px-0 bg-slate-50 w-full\">\n <div class=\"max-w-7xl mx-auto\">\n <div class=\"flex gap-4 items-center sticky top-0 z-50 bg-surface p-4 border-b border-edge shadow-sm\">\n <div class=\"font-bold overflow-hidden text-ellipsis\">{{model}}: {{documentId}}</div>\n <div class=\"flex grow\">\n <button\n @click=\"viewMode = 'fields'\"\n :class=\"viewMode === 'fields'\n ? 'bg-primary text-white z-10'\n : 'bg-gray-200 text-content-secondary hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-edge-strong border-r-0 rounded-l-lg rounded-r-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 6h16M4 10h16M4 14h16M4 18h16\"></path>\n </svg>\n Fields\n </button>\n <button\n @click=\"viewMode = 'json'\"\n :class=\"viewMode === 'json'\n ? 'bg-primary text-white z-10'\n : 'bg-gray-200 text-content-secondary hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-edge-strong rounded-r-lg rounded-l-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4\"></path>\n </svg>\n JSON\n </button>\n </div>\n\n <div class=\"hidden md:flex items-center gap-3 text-sm text-slate-600\">\n <div class=\"text-right leading-tight\">\n <div class=\"text-xs uppercase tracking-wide text-slate-400 flex items-center gap-2 justify-end\">\n <span>Loaded at</span>\n <span class=\"inline-flex items-center gap-1 text-[10px] font-semibold\" :class=\"autoRefreshEnabled ? 'text-forest-green-600' : 'text-slate-400'\">\n <span class=\"inline-block h-1.5 w-1.5 rounded-full\" :class=\"autoRefreshEnabled ? 'bg-forest-green-500' : 'bg-slate-300'\"></span>\n </span>\n </div>\n <div class=\"font-medium text-slate-700\">{{lastUpdatedLabel}}</div>\n </div>\n </div>\n\n <div class=\"gap-2 hidden md:flex items-center\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true\"\n :disabled=\"!canEdit\"\n :class=\"{'cursor-not-allowed opacity-50': !canEdit}\"\n type=\"button\"\n class=\"rounded-md bg-primary px-2.5 py-1.5 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n <img src=\"images/edit.svg\" class=\"inline\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false\"\n type=\"button\"\n class=\"rounded-md bg-slate-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n × Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n @click=\"shouldShowConfirmModal=true;\"\n type=\"button\"\n class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">\n <img src=\"images/save.svg\" class=\"inline\" /> Save\n </button>\n\n <!-- 3-dot menu -->\n <div class=\"relative\">\n <button\n @click=\"desktopMenuOpen = !desktopMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-2.5 py-1.5 text-sm font-medium text-content-secondary hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"desktopMenuOpen\"\n aria-label=\"More options\"\n >\n <svg class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"5\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"12\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"19\" r=\"2\"></circle>\n </svg>\n </button>\n <div\n v-show=\"desktopMenuOpen\"\n @click.away=\"desktopMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-surface ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n @click=\"refreshDocument({ force: true, source: 'manual' }); desktopMenuOpen = false\"\n :disabled=\"isRefreshing\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', isRefreshing ? 'cursor-not-allowed opacity-50' : 'hover:bg-slate-100']\"\n type=\"button\"\n >\n {{isRefreshing ? 'Refreshing...' : 'Refresh'}}\n </button>\n <button\n @click=\"toggleAutoRefresh(); desktopMenuOpen = false\"\n :disabled=\"isLambda\"\n type=\"button\"\n :class=\"['flex items-center px-4 py-2 text-sm', isLambda ? 'text-gray-400 cursor-not-allowed' : 'text-content-secondary hover:bg-slate-100']\"\n :title=\"isLambda ? 'Auto-refresh only available on Express deployments' : ''\"\n >\n {{autoRefreshEnabled ? 'Disable Auto-Refresh' : 'Enable Auto-Refresh'}}\n </button>\n <button\n @click=\"addField(); desktopMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"copyDocument(); desktopMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-blue-100\"\n >\n Copy Document\n </button>\n <button\n @click=\"openValidationModal()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Validate\n </button>\n <button\n @click=\"openScriptDrawer()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-indigo-100\"\n >\n Run Script\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n <div class=\"md:hidden flex items-center\">\n <div class=\"relative\">\n <button\n @click=\"mobileMenuOpen = !mobileMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-3 py-2 text-sm font-medium text-content-secondary hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"mobileMenuOpen\"\n aria-label=\"Open menu\"\n >\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\n d=\"M4 6h16M4 12h16M4 18h16\"></path>\n </svg>\n </button>\n <div\n v-show=\"mobileMenuOpen\"\n @click.away=\"mobileMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-52 rounded-md shadow-lg bg-surface ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true; mobileMenuOpen = false\"\n :disabled=\"!canEdit\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canEdit ? 'cursor-not-allowed opacity-50' : 'hover:bg-primary-subtle']\"\n type=\"button\"\n >\n Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false; mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-slate-100\"\n >\n Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-green-100']\"\n @click=\"shouldShowConfirmModal=true; mobileMenuOpen = false\"\n type=\"button\"\n >\n Save\n </button>\n <button\n @click=\"refreshDocument({ force: true, source: 'manual' }); mobileMenuOpen = false\"\n :disabled=\"isRefreshing\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', isRefreshing ? 'cursor-not-allowed opacity-50' : 'hover:bg-slate-100']\"\n type=\"button\"\n >\n {{isRefreshing ? 'Refreshing...' : 'Refresh'}}\n </button>\n <button\n @click=\"toggleAutoRefresh(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-slate-100\"\n >\n Auto-Refresh {{autoRefreshEnabled ? 'ON' : 'OFF'}}\n </button>\n <button\n @click=\"addField(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"copyDocument(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-blue-100\"\n >\n Copy Document\n </button>\n <button\n @click=\"openValidationModal()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Validate\n </button>\n <button\n @click=\"openScriptDrawer()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-indigo-100\"\n >\n Run Script\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div v-if=\"status === 'loaded'\">\n <document-details\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :virtualPaths=\"virtualPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :invalid=\"invalid\"\n :viewMode=\"viewMode\"\n @view-mode-change=\"updateViewMode\"></document-details>\n <modal v-if=\"showAddFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"closeAddFieldModal\">×</div>\n <div class=\"add-field-modal\">\n <div class=\"mb-6\">\n <h2 class=\"text-xl font-semibold text-content mb-2\">Add New Field</h2>\n <p class=\"text-sm text-gray-600\">Create a new field for this document</p>\n </div>\n\n <form @submit.prevent=\"handleAddFieldSubmit\" class=\"space-y-4\">\n <div>\n <label for=\"fieldName\" class=\"block text-sm font-medium text-content-secondary mb-1\">\n Field Name *\n </label>\n <input\n id=\"fieldName\"\n v-model=\"fieldData.name\"\n type=\"text\"\n required\n placeholder=\"Enter field name...\"\n class=\"w-full px-3 py-2 border border-edge-strong rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.name }\"\n />\n <p v-if=\"fieldErrors.name\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.name}}</p>\n <p v-if=\"fieldData.name && getTransformedFieldName() !== fieldData.name.trim()\" class=\"mt-1 text-sm text-blue-600\">\n Field name will be: <code class=\"bg-blue-50 px-1 py-0.5 rounded text-xs\">{{getTransformedFieldName()}}</code>\n </p>\n </div>\n\n <div>\n <label for=\"fieldType\" class=\"block text-sm font-medium text-content-secondary mb-1\">\n Field Type *\n </label>\n <select\n id=\"fieldType\"\n v-model=\"fieldData.type\"\n required\n class=\"w-full px-3 py-2 border border-edge-strong rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.type }\"\n >\n <option value=\"\">Select field type...</option>\n <option v-for=\"type in allFieldTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <p v-if=\"fieldErrors.type\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.type}}</p>\n </div>\n\n <div>\n <label for=\"fieldValue\" class=\"block text-sm font-medium text-content-secondary mb-1\">\n Initial Value\n </label>\n\n <custom-date-picker\n v-if=\"shouldUseDatePicker\"\n id=\"fieldValue\"\n mode=\"date\"\n :value=\"fieldData.value\"\n :class=\"{ 'border-red-500': fieldErrors.value, 'max-w-[400px]': true }\"\n :horizontal=\"true\"\n @input=\"fieldData.value = $event\"\n />\n\n <input\n v-else-if=\"!shouldUseAce\"\n v-model=\"fieldData.value\"\n type=\"text\"\n id=\"fieldValue\"\n placeholder=\"Enter initial value (optional)...\"\n class=\"w-full px-3 py-2 border border-edge-strong rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <ace-editor\n v-else\n id=\"fieldValue\"\n v-model=\"fieldData.value\"\n mode=\"json\"\n :line-numbers=\"true\"\n :min-lines=\"3\"\n class=\"add-field-ace-editor w-full min-h-[100px]\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <p v-if=\"fieldErrors.value\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.value}}</p>\n <p class=\"mt-1 text-xs text-content-tertiary\">\n <span v-if=\"shouldUseDatePicker\">Select a date or leave empty for null/undefined values.</span>\n <span v-else-if=\"shouldUseAce\">Leave empty for null/undefined values. Use valid JSON format.</span>\n <span v-else>Leave empty for null/undefined values.</span>\n </p>\n </div>\n\n <div class=\"flex justify-end gap-3 pt-4 border-t border-edge\">\n <button\n type=\"button\"\n @click=\"closeAddFieldModal\"\n class=\"px-4 py-2 text-sm font-medium text-content-secondary bg-surface border border-edge-strong rounded-md hover:bg-page focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n :disabled=\"isSubmittingField\"\n class=\"px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <span v-if=\"isSubmittingField\">Adding...</span>\n <span v-else>Add Field</span>\n </button>\n </div>\n </form>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowConfirmModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowConfirmModal = false;\">×</div>\n <confirm-changes @close=\"shouldShowConfirmModal = false;\" @save=\"save\" :value=\"changes\"></confirm-changes>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteModal = false;\">×</div>\n <confirm-delete @close=\"shouldShowDeleteModal = false;\" @remove=\"remove\" :value=\"document\"></confirm-delete>\n </template>\n </modal>\n <modal v-if=\"shouldShowCloneModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCloneModal = false;\">×</div>\n <clone-document :currentModel=\"model\" :doc=\"document\" :schemaPaths=\"schemaPaths\" @close=\"showClonedDocument\"></clone-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowValidationModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowValidationModal = false;\">×</div>\n <div class=\"text-xl font-bold mb-2\">Validate Document</div>\n <div v-if=\"!validationResult\" class=\"space-y-3\">\n <div class=\"h-4 w-32 animate-pulse rounded bg-muted\"></div>\n <div class=\"h-20 w-full animate-pulse rounded bg-muted\"></div>\n </div>\n <div v-else class=\"space-y-3\">\n <div\n class=\"rounded px-3 py-2 text-sm font-semibold\"\n :class=\"validationResult.valid ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-700'\"\n >\n {{ validationResult.valid ? 'Document is valid' : 'Document is invalid' }}\n </div>\n <div v-if=\"!validationResult.valid\" class=\"rounded border border-edge bg-muted p-3 text-sm text-content-secondary\">\n {{ validationResult.error }}\n </div>\n <div v-if=\"validationResult?.errors && Object.keys(validationResult.errors).length > 0\" class=\"rounded border border-edge\">\n <div class=\"border-b border-edge px-3 py-2 text-sm font-semibold text-content-secondary\">Invalid Paths</div>\n <div class=\"max-h-72 overflow-auto\">\n <div\n v-for=\"[path, pathError] in Object.entries(validationResult.errors)\"\n :key=\"path\"\n class=\"grid gap-2 border-b border-edge px-3 py-2 text-sm last:border-b-0 md:grid-cols-[minmax(160px,220px)_1fr]\"\n >\n <div class=\"font-mono text-content\">{{ path }}</div>\n <div class=\"text-content-secondary\">\n <div>{{ pathError.message }}</div>\n <div v-if=\"pathError.kind\" class=\"mt-1 text-xs text-content-tertiary\">Kind: {{ pathError.kind }}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n </div>\n <execute-script\n :visible=\"scriptDrawerOpen\"\n :model=\"model\"\n :document-id=\"documentId\"\n :editting=\"editting\"\n @close=\"closeScriptDrawer\"\n @refresh=\"handleScriptRefresh\"\n ></execute-script>\n <div\n v-if=\"keyboardShortcuts.length > 0\"\n class=\"fixed bottom-4 left-4 z-40 group\"\n >\n <button\n type=\"button\"\n aria-label=\"Keyboard shortcuts\"\n title=\"Keyboard shortcuts\"\n class=\"rounded-full border border-slate-300 bg-surface p-2 text-slate-700 shadow-sm transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\">\n <rect x=\"2.5\" y=\"6.5\" width=\"19\" height=\"11\" rx=\"2\" stroke-width=\"1.5\"></rect>\n <path d=\"M6 10.5h1.5M9 10.5h1.5M12 10.5h1.5M15 10.5h1.5M18 10.5h.01M6 13.5h1.5M9 13.5h6M16.5 13.5h1.5\" stroke-width=\"1.5\" stroke-linecap=\"round\"></path>\n </svg>\n </button>\n <div class=\"pointer-events-none absolute left-0 bottom-full mb-2 min-w-[220px] translate-y-1 rounded-lg bg-slate-900 px-3 py-2.5 text-slate-200 opacity-0 shadow-xl transition-all duration-150 group-hover:pointer-events-auto group-hover:translate-y-0 group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:translate-y-0 group-focus-within:opacity-100\">\n <div\n v-for=\"shortcut in keyboardShortcuts\"\n :key=\"shortcut.command + shortcut.description\"\n class=\"flex items-center justify-between gap-3 text-xs\"\n >\n <span class=\"font-mono font-semibold text-slate-50\">{{shortcut.command}}</span>\n <span class=\"text-slate-300\">{{shortcut.description}}</span>\n </div>\n </div>\n </div>\n </div>\n</div>\n";
|
|
53142
53892
|
|
|
53143
53893
|
/***/ },
|
|
53144
53894
|
|
|
@@ -53193,7 +53943,7 @@ module.exports = "<div class=\"edit-boolean\">\n <div class=\"flex flex-wrap ga
|
|
|
53193
53943
|
(module) {
|
|
53194
53944
|
|
|
53195
53945
|
"use strict";
|
|
53196
|
-
module.exports = "<div>\n <
|
|
53946
|
+
module.exports = "<div>\n <custom-date-picker\n v-if=\"dateSelection == 'picker'\"\n :value=\"value\"\n mode=\"datetime\"\n :horizontal=\"true\"\n class=\"max-w-[420px]\"\n @input=\"$emit('input', $event)\"\n />\n <input\n v-if=\"dateSelection == 'iso'\"\n type=\"text\"\n class=\"w-64 h-8 border border-edge-strong outline-0\"\n :value=\"valueAsISOString\"\n @input=\"updateFromISO\">\n</div>\n";
|
|
53197
53947
|
|
|
53198
53948
|
/***/ },
|
|
53199
53949
|
|
|
@@ -53435,7 +54185,7 @@ module.exports = "<div>\n <div class=\"modal-exit\" @click=\"close\">×</d
|
|
|
53435
54185
|
(module) {
|
|
53436
54186
|
|
|
53437
54187
|
"use strict";
|
|
53438
|
-
module.exports = "<form @submit.prevent=\"emitSearch\" class=\"
|
|
54188
|
+
module.exports = "<div class=\"relative flex-grow m-0\" @focusout=\"onWrapperFocusOut\">\n <form @submit.prevent=\"emitSearch\" class=\"w-full mb-0\">\n <input\n ref=\"searchInput\"\n class=\"w-full font-mono rounded-md p-1 border border-edge-strong outline-edge-strong text-lg focus:ring-1 focus:ring-primary-subtle 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($event); updateAutocomplete()\"\n @input=\"updateAutocomplete\"\n @keyup=\"onSearchKeyup\"\n @keydown=\"handleKeyDown\"\n />\n <div\n v-if=\"autocompleteSuggestions.length\"\n ref=\"autocompleteDropdown\"\n class=\"absolute z-[9999] bg-surface border border-edge-strong rounded mt-1 w-full shadow\"\n >\n <ul class=\"max-h-40 overflow-y-auto\">\n <li\n v-for=\"(suggestion, index) in autocompleteSuggestions\"\n :key=\"suggestion\"\n class=\"px-2 py-1 cursor-pointer\"\n :class=\"{ 'bg-primary-subtle': index === autocompleteIndex }\"\n @mousedown.prevent=\"applySuggestion(index)\"\n >\n {{ suggestion }}\n </li>\n </ul>\n </div>\n </form>\n\n <div\n v-if=\"datePickerContext\"\n class=\"absolute top-full left-0 z-[10000] mt-0 w-[400px] max-w-[calc(100vw-2rem)] bg-surface border border-edge-strong rounded-lg shadow-xl\"\n role=\"dialog\"\n aria-label=\"Date picker\"\n @mousedown.stop\n >\n <div class=\"flex items-center justify-between gap-2 px-3 py-2 border-b border-edge-strong\">\n <span class=\"text-sm font-medium text-content-primary\">Insert date into filter</span>\n <div class=\"flex items-center gap-2 shrink-0\">\n <select\n v-if=\"!datePickerMinimized\"\n v-model=\"dateInsertFormat\"\n class=\"text-xs border border-edge rounded-md bg-surface px-2 py-1\"\n @mousedown.stop\n >\n <option value=\"timestamp\">Milliseconds</option>\n <option value=\"quoted\">Date string</option>\n </select>\n <button\n type=\"button\"\n class=\"flex items-center justify-center w-5 h-5 rounded hover:bg-surface-hover text-content-secondary\"\n :aria-label=\"datePickerMinimized ? 'Expand date picker' : 'Minimize date picker'\"\n @click.stop=\"datePickerMinimized = !datePickerMinimized\"\n >\n <svg v-if=\"datePickerMinimized\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" class=\"w-3.5 h-3.5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m19.5 8.25-7.5 7.5-7.5-7.5\" />\n </svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" class=\"w-3.5 h-3.5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m4.5 15.75 7.5-7.5 7.5 7.5\" />\n </svg>\n </button>\n </div>\n </div>\n <div v-if=\"!datePickerMinimized\" class=\"p-2\">\n <custom-date-picker\n compact\n horizontal\n embedded\n :immediate=\"false\"\n mode=\"datetime\"\n :value=\"datePickerLocalValue\"\n @commit=\"applyDateFromPicker\"\n />\n </div>\n </div>\n</div>\n";
|
|
53439
54189
|
|
|
53440
54190
|
/***/ },
|
|
53441
54191
|
|
|
@@ -64975,7 +65725,7 @@ var src_default = VueToastificationPlugin;
|
|
|
64975
65725
|
(module) {
|
|
64976
65726
|
|
|
64977
65727
|
"use strict";
|
|
64978
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.5.
|
|
65728
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.5.8","description":"AI-Powered Data Workspace for MongoDB: Turn your MongoDB data into dashboards, maps, and interactive workflows - powered by your Mongoose models and AI.","homepage":"https://mongoosestudio.app/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"@ai-sdk/anthropic":"2.x","@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","ace-builds":"^1.43.6","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","mpath":"^0.9.0","node-inspect-extracted":"3.x","regexp.escape":"^2.0.1","tailwindcss":"3.4.0","time-commando":"1.0.1","typescript":"^6.0.2","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x","xss":"^1.0.15"},"peerDependencies":{"@nestjs/common":"10.x || 11.x","mongoose":"7.x || 8.x || ^9.0.0"},"peerDependenciesMeta":{"@nestjs/common":{"optional":true}},"optionalPeerDependencies":{"@mongoosejs/task":"0.5.x || 0.6.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","@nestjs/common":"^11.1.9","express":"4.x","mocha":"10.2.0","mongodb-memory-server":"^11.0.1","mongoose":"9.x","sinon":"^21.0.1"},"scripts":{"lint":"eslint .","seed":"node seed/index.js","start":"node ./local.js","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js","test:frontend":"mocha test/frontend/*.test.js","eval:agent":"node eval/agent/run.js"}}');
|
|
64979
65729
|
|
|
64980
65730
|
/***/ }
|
|
64981
65731
|
|