@mongoosejs/studio 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/backend/actions/ChatThread/streamChatMessage.js +12 -12
- package/backend/chatAgent/agentSystemPrompt.js +22 -10
- package/backend/chatAgent/getAgentTools.js +19 -8
- package/backend/chatAgent/runChatAgent.js +17 -0
- package/backend/index.js +3 -0
- package/debugging.js +11 -0
- package/eval/agent/cases.js +168 -0
- package/eval/agent/fixtures/carsharing.js +147 -0
- package/eval/agent/fixtures/ecommerce.js +152 -0
- package/eval/agent/run.js +323 -0
- package/frontend/public/app.js +346 -9
- package/frontend/public/tw.css +52 -0
- package/frontend/src/_util/recent-pages-history.js +116 -0
- package/frontend/src/chat/chat-message-script/chat-message-script.html +1 -6
- package/frontend/src/document/document.html +2 -2
- package/frontend/src/index.js +143 -1
- package/frontend/src/modal/modal.css +26 -1
- package/frontend/src/modal/modal.html +2 -2
- package/frontend/src/modal/modal.js +5 -1
- package/frontend/src/navbar/navbar.html +73 -2
- package/frontend/src/navbar/navbar.js +63 -1
- package/index.js +2 -1
- package/package.json +3 -2
package/frontend/public/tw.css
CHANGED
|
@@ -1192,6 +1192,10 @@ video {
|
|
|
1192
1192
|
width: 90vw !important;
|
|
1193
1193
|
}
|
|
1194
1194
|
|
|
1195
|
+
.\!w-\[min\(94vw\2c 28rem\)\] {
|
|
1196
|
+
width: min(94vw,28rem) !important;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1195
1199
|
.w-0 {
|
|
1196
1200
|
width: 0px;
|
|
1197
1201
|
}
|
|
@@ -1676,6 +1680,10 @@ video {
|
|
|
1676
1680
|
overflow: auto;
|
|
1677
1681
|
}
|
|
1678
1682
|
|
|
1683
|
+
.\!overflow-hidden {
|
|
1684
|
+
overflow: hidden !important;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1679
1687
|
.overflow-hidden {
|
|
1680
1688
|
overflow: hidden;
|
|
1681
1689
|
}
|
|
@@ -1718,6 +1726,10 @@ video {
|
|
|
1718
1726
|
word-break: break-all;
|
|
1719
1727
|
}
|
|
1720
1728
|
|
|
1729
|
+
.\!rounded-lg {
|
|
1730
|
+
border-radius: 0.5rem !important;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1721
1733
|
.rounded {
|
|
1722
1734
|
border-radius: 0.25rem;
|
|
1723
1735
|
}
|
|
@@ -1772,6 +1784,10 @@ video {
|
|
|
1772
1784
|
border-bottom-right-radius: 0px;
|
|
1773
1785
|
}
|
|
1774
1786
|
|
|
1787
|
+
.\!border {
|
|
1788
|
+
border-width: 1px !important;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1775
1791
|
.border {
|
|
1776
1792
|
border-width: 1px;
|
|
1777
1793
|
}
|
|
@@ -1836,6 +1852,10 @@ video {
|
|
|
1836
1852
|
border-style: none;
|
|
1837
1853
|
}
|
|
1838
1854
|
|
|
1855
|
+
.\!border-edge {
|
|
1856
|
+
border-color: var(--color-edge) !important;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1839
1859
|
.border-black\/10 {
|
|
1840
1860
|
border-color: rgb(0 0 0 / 0.1);
|
|
1841
1861
|
}
|
|
@@ -1921,6 +1941,10 @@ video {
|
|
|
1921
1941
|
border-right-color: rgb(17 24 39 / var(--tw-border-opacity));
|
|
1922
1942
|
}
|
|
1923
1943
|
|
|
1944
|
+
.\!bg-surface {
|
|
1945
|
+
background-color: var(--color-surface) !important;
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1924
1948
|
.bg-amber-100 {
|
|
1925
1949
|
--tw-bg-opacity: 1;
|
|
1926
1950
|
background-color: rgb(254 243 199 / var(--tw-bg-opacity));
|
|
@@ -2205,6 +2229,10 @@ video {
|
|
|
2205
2229
|
--tw-gradient-to: #dc2626 var(--tw-gradient-to-position);
|
|
2206
2230
|
}
|
|
2207
2231
|
|
|
2232
|
+
.\!p-0 {
|
|
2233
|
+
padding: 0px !important;
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2208
2236
|
.p-0 {
|
|
2209
2237
|
padding: 0px;
|
|
2210
2238
|
}
|
|
@@ -2396,6 +2424,14 @@ video {
|
|
|
2396
2424
|
padding-left: 2rem;
|
|
2397
2425
|
}
|
|
2398
2426
|
|
|
2427
|
+
.pr-12 {
|
|
2428
|
+
padding-right: 3rem;
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
.pr-2 {
|
|
2432
|
+
padding-right: 0.5rem;
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2399
2435
|
.pr-3 {
|
|
2400
2436
|
padding-right: 0.75rem;
|
|
2401
2437
|
}
|
|
@@ -2810,6 +2846,12 @@ video {
|
|
|
2810
2846
|
opacity: 0.75;
|
|
2811
2847
|
}
|
|
2812
2848
|
|
|
2849
|
+
.\!shadow-xl {
|
|
2850
|
+
--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1) !important;
|
|
2851
|
+
--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color) !important;
|
|
2852
|
+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow) !important;
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2813
2855
|
.shadow {
|
|
2814
2856
|
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
|
2815
2857
|
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
|
|
@@ -3851,6 +3893,11 @@ video {
|
|
|
3851
3893
|
}
|
|
3852
3894
|
}
|
|
3853
3895
|
|
|
3896
|
+
:is(:where(.dark) .dark\:\!bg-shark-900) {
|
|
3897
|
+
--tw-bg-opacity: 1 !important;
|
|
3898
|
+
background-color: rgb(21 25 30 / var(--tw-bg-opacity)) !important;
|
|
3899
|
+
}
|
|
3900
|
+
|
|
3854
3901
|
:is(:where(.dark) .dark\:bg-shark-800) {
|
|
3855
3902
|
--tw-bg-opacity: 1;
|
|
3856
3903
|
background-color: rgb(33 37 41 / var(--tw-bg-opacity));
|
|
@@ -3864,6 +3911,11 @@ video {
|
|
|
3864
3911
|
background-color: rgb(33 37 41 / 0.8);
|
|
3865
3912
|
}
|
|
3866
3913
|
|
|
3914
|
+
:is(:where(.dark) .dark\:bg-shark-900) {
|
|
3915
|
+
--tw-bg-opacity: 1;
|
|
3916
|
+
background-color: rgb(21 25 30 / var(--tw-bg-opacity));
|
|
3917
|
+
}
|
|
3918
|
+
|
|
3867
3919
|
:is(:where(.dark) .dark\:text-gray-400) {
|
|
3868
3920
|
--tw-text-opacity: 1;
|
|
3869
3921
|
color: rgb(156 163 175 / var(--tw-text-opacity));
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const RECENT_PAGES_STORAGE_KEY = 'studio:recent-pages-history';
|
|
4
|
+
const RECENT_PAGES_CHANGED_EVENT = 'studio-recent-pages-changed';
|
|
5
|
+
const RECENT_PAGES_LOCK_NAME = `studio-lock:${RECENT_PAGES_STORAGE_KEY}`;
|
|
6
|
+
const MAX_RECENT_PAGES = 50;
|
|
7
|
+
|
|
8
|
+
/** Collapses hash-style prefixes (e.g. `/#/`) to a normal path; keeps query string. */
|
|
9
|
+
function normalizeTrackedPath(fullPath) {
|
|
10
|
+
if (!fullPath || typeof fullPath !== 'string') {
|
|
11
|
+
return '/';
|
|
12
|
+
}
|
|
13
|
+
const qIndex = fullPath.indexOf('?');
|
|
14
|
+
const rawMain = qIndex === -1 ? fullPath : fullPath.slice(0, qIndex);
|
|
15
|
+
const qs = qIndex === -1 ? '' : fullPath.slice(qIndex);
|
|
16
|
+
|
|
17
|
+
let pathOnly = rawMain.trim().replace(/#/g, '');
|
|
18
|
+
pathOnly = pathOnly.replace(/^\/+/u, '/');
|
|
19
|
+
if (pathOnly !== '/' && /\/$/u.test(pathOnly)) {
|
|
20
|
+
pathOnly = pathOnly.replace(/\/+$/u, '');
|
|
21
|
+
}
|
|
22
|
+
if (pathOnly === '' || pathOnly === '/') {
|
|
23
|
+
pathOnly = '/';
|
|
24
|
+
} else if (!pathOnly.startsWith('/')) {
|
|
25
|
+
pathOnly = `/${pathOnly}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const out = qs ? pathOnly + qs : pathOnly;
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function safeReadRecentPages() {
|
|
33
|
+
if (typeof window === 'undefined' || !window.localStorage) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const raw = window.localStorage.getItem(RECENT_PAGES_STORAGE_KEY);
|
|
38
|
+
if (!raw) {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
const parsed = JSON.parse(raw);
|
|
42
|
+
if (!Array.isArray(parsed)) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
return parsed
|
|
46
|
+
.map(entry => {
|
|
47
|
+
if (!entry || typeof entry.path !== 'string') {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return { ...entry, path: normalizeTrackedPath(entry.path) };
|
|
51
|
+
})
|
|
52
|
+
.filter(
|
|
53
|
+
entry =>
|
|
54
|
+
entry &&
|
|
55
|
+
typeof entry.label === 'string' &&
|
|
56
|
+
typeof entry.visitedAt === 'number' &&
|
|
57
|
+
entry.path !== ''
|
|
58
|
+
)
|
|
59
|
+
.slice(0, MAX_RECENT_PAGES);
|
|
60
|
+
} catch {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function notifyRecentPagesChangedFromThisTab() {
|
|
66
|
+
if (typeof window === 'undefined') {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
window.dispatchEvent(new CustomEvent(RECENT_PAGES_CHANGED_EVENT));
|
|
71
|
+
} catch {
|
|
72
|
+
/* ignore */
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function saveRecentPages(entries) {
|
|
77
|
+
if (typeof window === 'undefined' || !window.localStorage) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
window.localStorage.setItem(
|
|
81
|
+
RECENT_PAGES_STORAGE_KEY,
|
|
82
|
+
JSON.stringify(entries.slice(0, MAX_RECENT_PAGES))
|
|
83
|
+
);
|
|
84
|
+
notifyRecentPagesChangedFromThisTab();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function withRecentPagesStorageLock(syncFn) {
|
|
88
|
+
if (typeof window === 'undefined' || !window.localStorage) {
|
|
89
|
+
syncFn();
|
|
90
|
+
return Promise.resolve();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const locksApi =
|
|
95
|
+
typeof navigator !== 'undefined' && navigator.locks !== undefined ? navigator.locks : null;
|
|
96
|
+
|
|
97
|
+
if (locksApi !== null && typeof locksApi.request === 'function') {
|
|
98
|
+
return locksApi.request(RECENT_PAGES_LOCK_NAME, syncFn);
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
/* fall through */
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
syncFn();
|
|
105
|
+
return Promise.resolve();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
RECENT_PAGES_STORAGE_KEY,
|
|
110
|
+
RECENT_PAGES_CHANGED_EVENT,
|
|
111
|
+
MAX_RECENT_PAGES,
|
|
112
|
+
normalizeTrackedPath,
|
|
113
|
+
safeReadRecentPages,
|
|
114
|
+
saveRecentPages,
|
|
115
|
+
withRecentPagesStorageLock
|
|
116
|
+
};
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5" />
|
|
50
50
|
</svg>
|
|
51
51
|
</button>
|
|
52
|
-
<div
|
|
52
|
+
<div class="relative mr-1" ref="runDropdown">
|
|
53
53
|
<div class="flex items-center">
|
|
54
54
|
<button
|
|
55
55
|
class="px-2 py-1 text-xs bg-primary hover:bg-primary-hover text-primary-text border-none rounded-l-md cursor-pointer transition-colors disabled:bg-gray-400 disabled:cursor-not-allowed"
|
|
@@ -146,11 +146,6 @@
|
|
|
146
146
|
@click.stop="cancelEditing">
|
|
147
147
|
Cancel
|
|
148
148
|
</button>
|
|
149
|
-
<async-button
|
|
150
|
-
class="px-2 py-1 text-xs bg-blue-600 text-white border-none rounded cursor-pointer hover:bg-blue-700 transition-colors disabled:bg-gray-400"
|
|
151
|
-
@click="executeScript(false)">
|
|
152
|
-
Execute
|
|
153
|
-
</async-button>
|
|
154
149
|
</div>
|
|
155
150
|
</div>
|
|
156
151
|
<pre v-else class="whitespace-pre-wrap !my-0 bg-muted"><code v-text="script" ref="code" :class="'language-' + language"></code></pre>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<button
|
|
7
7
|
@click="viewMode = 'fields'"
|
|
8
8
|
:class="viewMode === 'fields'
|
|
9
|
-
? 'bg-
|
|
9
|
+
? 'bg-primary text-white z-10'
|
|
10
10
|
: 'bg-gray-200 text-content-secondary hover:bg-gray-300'"
|
|
11
11
|
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"
|
|
12
12
|
>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<button
|
|
19
19
|
@click="viewMode = 'json'"
|
|
20
20
|
:class="viewMode === 'json'
|
|
21
|
-
? 'bg-
|
|
21
|
+
? 'bg-primary text-white z-10'
|
|
22
22
|
: 'bg-gray-200 text-content-secondary hover:bg-gray-300'"
|
|
23
23
|
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"
|
|
24
24
|
>
|
package/frontend/src/index.js
CHANGED
|
@@ -15,12 +15,143 @@ const { routes, hasAccess } = require('./routes');
|
|
|
15
15
|
const Toast = require('vue-toastification').default;
|
|
16
16
|
const { useToast } = require('vue-toastification');
|
|
17
17
|
const appendCSS = require('./appendCSS');
|
|
18
|
+
const {
|
|
19
|
+
safeReadRecentPages,
|
|
20
|
+
saveRecentPages,
|
|
21
|
+
MAX_RECENT_PAGES,
|
|
22
|
+
normalizeTrackedPath,
|
|
23
|
+
withRecentPagesStorageLock
|
|
24
|
+
} = require('./_util/recent-pages-history');
|
|
18
25
|
appendCSS(require('vue-toastification/dist/index.css'));
|
|
19
26
|
|
|
27
|
+
const TRACKED_RECENT_PAGE_ROUTE_NAMES = new Set(['model', 'document', 'dashboard', 'chat']);
|
|
28
|
+
|
|
29
|
+
function formatHistoryLabel(route) {
|
|
30
|
+
if (!route || typeof route.path !== 'string') {
|
|
31
|
+
return 'Unknown page';
|
|
32
|
+
}
|
|
33
|
+
if (route.name === 'model') {
|
|
34
|
+
return route.params?.model ? `Model: ${route.params.model}` : 'Model';
|
|
35
|
+
}
|
|
36
|
+
if (route.name === 'document') {
|
|
37
|
+
const model = route.params?.model ? `${route.params.model} ` : '';
|
|
38
|
+
const documentId = route.params?.documentId ? String(route.params.documentId) : '';
|
|
39
|
+
const shortDocumentId = documentId ? documentId.slice(0, 24) : '';
|
|
40
|
+
return `Document: ${model}${shortDocumentId}`.trim();
|
|
41
|
+
}
|
|
42
|
+
if (route.name === 'dashboard') {
|
|
43
|
+
return route.params?.dashboardId ? `Dashboard: ${route.params.dashboardId}` : 'Dashboard';
|
|
44
|
+
}
|
|
45
|
+
if (route.name === 'chat') {
|
|
46
|
+
return route.params?.threadId ? `Chat: ${route.params.threadId}` : 'Chat';
|
|
47
|
+
}
|
|
48
|
+
const normalizedPath = route.path.replace(/^\//, '');
|
|
49
|
+
return normalizedPath || 'Home';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function trackRecentPage(route) {
|
|
53
|
+
if (!route || typeof route.path !== 'string') {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (!TRACKED_RECENT_PAGE_ROUTE_NAMES.has(route.name)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (typeof route.path === 'string' && route.path.includes('code=')) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const path = normalizeTrackedPath(route.fullPath || route.path || '/');
|
|
63
|
+
const label = formatHistoryLabel(route);
|
|
64
|
+
const visitedAt = Date.now();
|
|
65
|
+
|
|
66
|
+
void withRecentPagesStorageLock(() => {
|
|
67
|
+
const existing = safeReadRecentPages();
|
|
68
|
+
const deduped = existing.filter(entry => normalizeTrackedPath(entry.path) !== path);
|
|
69
|
+
const next = [...deduped, { path, label, visitedAt }]
|
|
70
|
+
.sort((a, b) => b.visitedAt - a.visitedAt)
|
|
71
|
+
.slice(0, MAX_RECENT_PAGES);
|
|
72
|
+
saveRecentPages(next);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
20
76
|
const app = Vue.createApp({
|
|
21
77
|
template: '<app-component />'
|
|
22
78
|
});
|
|
23
79
|
|
|
80
|
+
window.drawArrowTo = function drawArrowTo(elementOrSelector) {
|
|
81
|
+
const element = typeof elementOrSelector === 'string' ?
|
|
82
|
+
document.querySelector(elementOrSelector) :
|
|
83
|
+
elementOrSelector;
|
|
84
|
+
|
|
85
|
+
if (!element || typeof element.getBoundingClientRect !== 'function') {
|
|
86
|
+
throw new Error('window.drawArrowTo() requires a DOM element or selector');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const existingArrow = document.getElementById('mongoose-studio-screenshot-arrow');
|
|
90
|
+
if (existingArrow) {
|
|
91
|
+
existingArrow.remove();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const rect = element.getBoundingClientRect();
|
|
95
|
+
const arrowLength = 140;
|
|
96
|
+
const arrowRise = 80;
|
|
97
|
+
const centerX = rect.left + rect.width / 2;
|
|
98
|
+
const centerY = rect.top + rect.height / 2;
|
|
99
|
+
const startX = Math.max(24, Math.min(window.innerWidth - 24, centerX - arrowLength));
|
|
100
|
+
const startY = Math.max(24, Math.min(window.innerHeight - 24, centerY - arrowRise));
|
|
101
|
+
const targetX = Math.max(rect.left, Math.min(rect.right, startX));
|
|
102
|
+
const targetY = Math.max(rect.top, Math.min(rect.bottom, startY));
|
|
103
|
+
const controlX = startX + (targetX - startX) * 0.6;
|
|
104
|
+
const controlY = startY + (targetY - startY) * 0.15;
|
|
105
|
+
const svgNS = 'http://www.w3.org/2000/svg';
|
|
106
|
+
const svg = document.createElementNS(svgNS, 'svg');
|
|
107
|
+
const defs = document.createElementNS(svgNS, 'defs');
|
|
108
|
+
const marker = document.createElementNS(svgNS, 'marker');
|
|
109
|
+
const markerPath = document.createElementNS(svgNS, 'path');
|
|
110
|
+
const path = document.createElementNS(svgNS, 'path');
|
|
111
|
+
|
|
112
|
+
svg.setAttribute('id', 'mongoose-studio-screenshot-arrow');
|
|
113
|
+
svg.setAttribute('viewBox', `0 0 ${window.innerWidth} ${window.innerHeight}`);
|
|
114
|
+
svg.setAttribute('width', '100vw');
|
|
115
|
+
svg.setAttribute('height', '100vh');
|
|
116
|
+
svg.style.position = 'fixed';
|
|
117
|
+
svg.style.left = '0';
|
|
118
|
+
svg.style.top = '0';
|
|
119
|
+
svg.style.zIndex = '2147483647';
|
|
120
|
+
svg.style.pointerEvents = 'none';
|
|
121
|
+
|
|
122
|
+
marker.setAttribute('id', 'mongoose-studio-screenshot-arrow-head');
|
|
123
|
+
marker.setAttribute('markerWidth', '14');
|
|
124
|
+
marker.setAttribute('markerHeight', '14');
|
|
125
|
+
marker.setAttribute('refX', '12');
|
|
126
|
+
marker.setAttribute('refY', '7');
|
|
127
|
+
marker.setAttribute('orient', 'auto');
|
|
128
|
+
marker.setAttribute('markerUnits', 'userSpaceOnUse');
|
|
129
|
+
|
|
130
|
+
markerPath.setAttribute('d', 'M 0 0 L 14 7 L 0 14 z');
|
|
131
|
+
markerPath.setAttribute('fill', '#dc2626');
|
|
132
|
+
|
|
133
|
+
path.setAttribute('d', `M ${startX} ${startY} Q ${controlX} ${controlY} ${targetX} ${targetY}`);
|
|
134
|
+
path.setAttribute('fill', 'none');
|
|
135
|
+
path.setAttribute('stroke', '#dc2626');
|
|
136
|
+
path.setAttribute('stroke-width', '5');
|
|
137
|
+
path.setAttribute('stroke-linecap', 'round');
|
|
138
|
+
path.setAttribute('stroke-linejoin', 'round');
|
|
139
|
+
path.setAttribute('marker-end', 'url(#mongoose-studio-screenshot-arrow-head)');
|
|
140
|
+
|
|
141
|
+
marker.appendChild(markerPath);
|
|
142
|
+
defs.appendChild(marker);
|
|
143
|
+
svg.appendChild(defs);
|
|
144
|
+
svg.appendChild(path);
|
|
145
|
+
document.body.appendChild(svg);
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
element: svg,
|
|
149
|
+
remove() {
|
|
150
|
+
svg.remove();
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
|
|
24
155
|
// https://github.com/Maronato/vue-toastification/tree/main?tab=readme-ov-file#toast-types
|
|
25
156
|
app.use(Toast, { position: 'bottom-right', timeout: 3000 });
|
|
26
157
|
|
|
@@ -169,7 +300,14 @@ app.component('app-component', {
|
|
|
169
300
|
const authError = Vue.ref(null);
|
|
170
301
|
const modelSchemaPaths = Vue.ref(null);
|
|
171
302
|
|
|
172
|
-
const state = Vue.reactive({
|
|
303
|
+
const state = Vue.reactive({
|
|
304
|
+
user,
|
|
305
|
+
roles,
|
|
306
|
+
status,
|
|
307
|
+
nodeEnv,
|
|
308
|
+
authError,
|
|
309
|
+
modelSchemaPaths
|
|
310
|
+
});
|
|
173
311
|
Vue.provide('state', state);
|
|
174
312
|
|
|
175
313
|
return state;
|
|
@@ -233,6 +371,10 @@ router.beforeEach((to, from, next) => {
|
|
|
233
371
|
}
|
|
234
372
|
});
|
|
235
373
|
|
|
374
|
+
router.afterEach((to) => {
|
|
375
|
+
trackRecentPage(to);
|
|
376
|
+
});
|
|
377
|
+
|
|
236
378
|
app.config.globalProperties = { format, arrayUtils, $toast: toast };
|
|
237
379
|
app.use(router);
|
|
238
380
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
.modal-mask {
|
|
4
4
|
position: fixed;
|
|
5
|
-
z-index:
|
|
5
|
+
z-index: 10050;
|
|
6
6
|
top: 0;
|
|
7
7
|
left: 0;
|
|
8
8
|
width: 100%;
|
|
@@ -30,6 +30,11 @@
|
|
|
30
30
|
position: relative;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
.modal-container.modal-container--flush {
|
|
34
|
+
padding: 0;
|
|
35
|
+
padding-bottom: 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
33
38
|
.modal-header {
|
|
34
39
|
margin-top: 0;
|
|
35
40
|
font-size: 18px;
|
|
@@ -50,6 +55,25 @@
|
|
|
50
55
|
overflow: auto;
|
|
51
56
|
}
|
|
52
57
|
|
|
58
|
+
/* Recent pages: one scroll region — inner .modal-recent-pages-scroll only (avoids double scrollbars with .modal-body overflow). */
|
|
59
|
+
.modal-body.modal-recent-pages-body {
|
|
60
|
+
box-sizing: border-box;
|
|
61
|
+
display: flex;
|
|
62
|
+
flex-direction: column;
|
|
63
|
+
margin: 0;
|
|
64
|
+
height: min(60vh, 22.5rem);
|
|
65
|
+
max-height: min(60vh, 22.5rem);
|
|
66
|
+
overflow: hidden;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.modal-body.modal-recent-pages-body .modal-recent-pages-scroll {
|
|
70
|
+
flex: 1 1 0;
|
|
71
|
+
min-height: 0;
|
|
72
|
+
overflow-x: hidden;
|
|
73
|
+
overflow-y: auto;
|
|
74
|
+
-webkit-overflow-scrolling: touch;
|
|
75
|
+
}
|
|
76
|
+
|
|
53
77
|
.modal__button--default {
|
|
54
78
|
float: right;
|
|
55
79
|
}
|
|
@@ -79,6 +103,7 @@
|
|
|
79
103
|
|
|
80
104
|
.modal-container .modal-exit {
|
|
81
105
|
position: absolute;
|
|
106
|
+
z-index: 2;
|
|
82
107
|
right: 0.25em;
|
|
83
108
|
top: 0.25em;
|
|
84
109
|
cursor: pointer;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<transition name="modal">
|
|
2
2
|
<div class="modal-mask">
|
|
3
3
|
<div class="modal-wrapper">
|
|
4
|
-
<div class="modal-container" :class="containerClass">
|
|
5
|
-
<div class="modal-body">
|
|
4
|
+
<div class="modal-container" :class="[containerClass, { 'modal-container--flush': flushContainer }]">
|
|
5
|
+
<div class="modal-body" :class="bodyClass">
|
|
6
6
|
<slot name="body">
|
|
7
7
|
</slot>
|
|
8
8
|
</div>
|
|
@@ -7,7 +7,11 @@ appendCSS(require('./modal.css'));
|
|
|
7
7
|
|
|
8
8
|
module.exports = app => app.component('modal', {
|
|
9
9
|
template,
|
|
10
|
-
props:
|
|
10
|
+
props: {
|
|
11
|
+
containerClass: String,
|
|
12
|
+
bodyClass: String,
|
|
13
|
+
flushContainer: Boolean
|
|
14
|
+
},
|
|
11
15
|
mounted() {
|
|
12
16
|
window.addEventListener('keydown', this.onEscape);
|
|
13
17
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
<div>
|
|
1
2
|
<div class="navbar w-full bg-page border-b border-edge !h-[55px] hidden md:grid grid-cols-[1fr_auto_1fr] items-stretch px-4">
|
|
2
3
|
<!-- Left: Logo + Brand -->
|
|
3
4
|
<div class="flex items-center gap-2 self-center">
|
|
@@ -67,6 +68,19 @@
|
|
|
67
68
|
</nav>
|
|
68
69
|
<!-- Right: Env Badge + User -->
|
|
69
70
|
<div class="flex items-center justify-end gap-3 self-center">
|
|
71
|
+
<button
|
|
72
|
+
type="button"
|
|
73
|
+
class="inline-flex items-center justify-center rounded-md p-2 text-content-tertiary hover:bg-muted hover:text-content-secondary"
|
|
74
|
+
title="Recent pages"
|
|
75
|
+
aria-label="Recent pages"
|
|
76
|
+
aria-haspopup="dialog"
|
|
77
|
+
:aria-expanded="showRecentPagesModal ? 'true' : 'false'"
|
|
78
|
+
@click="openRecentPagesModal"
|
|
79
|
+
>
|
|
80
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-5 w-5" aria-hidden="true">
|
|
81
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
82
|
+
</svg>
|
|
83
|
+
</button>
|
|
70
84
|
<div
|
|
71
85
|
v-if="!!state.nodeEnv"
|
|
72
86
|
title="NODE_ENV"
|
|
@@ -149,9 +163,22 @@
|
|
|
149
163
|
<span class="text-base font-semibold text-content">Mongoose Studio</span>
|
|
150
164
|
</router-link>
|
|
151
165
|
</div>
|
|
152
|
-
<div class="flex items-center">
|
|
166
|
+
<div class="flex items-center gap-1 pr-2">
|
|
167
|
+
<button
|
|
168
|
+
type="button"
|
|
169
|
+
class="rounded-md p-2 text-content-tertiary hover:bg-muted hover:text-content-secondary md:hidden"
|
|
170
|
+
title="Recent pages"
|
|
171
|
+
aria-label="Recent pages"
|
|
172
|
+
aria-haspopup="dialog"
|
|
173
|
+
:aria-expanded="showRecentPagesModal ? 'true' : 'false'"
|
|
174
|
+
@click="openRecentPagesModal"
|
|
175
|
+
>
|
|
176
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-6 w-6" aria-hidden="true">
|
|
177
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
178
|
+
</svg>
|
|
179
|
+
</button>
|
|
153
180
|
<!-- Mobile menu toggle, controls the 'mobileMenuOpen' state. -->
|
|
154
|
-
<button type="button" id="open-mobile-menu" class="-ml-2 rounded-md p-2
|
|
181
|
+
<button type="button" id="open-mobile-menu" class="-ml-2 rounded-md p-2 text-gray-400">
|
|
155
182
|
<span class="sr-only">Open menu</span>
|
|
156
183
|
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
|
157
184
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
|
@@ -266,3 +293,47 @@
|
|
|
266
293
|
</nav>
|
|
267
294
|
</div>
|
|
268
295
|
</div>
|
|
296
|
+
|
|
297
|
+
<modal
|
|
298
|
+
v-if="showRecentPagesModal"
|
|
299
|
+
flush-container
|
|
300
|
+
containerClass="!max-w-md !w-[min(94vw,28rem)] !overflow-hidden !rounded-lg !border !border-edge !bg-surface !shadow-xl dark:!bg-shark-900 text-content"
|
|
301
|
+
bodyClass="modal-recent-pages-body !p-0"
|
|
302
|
+
>
|
|
303
|
+
<template v-slot:body>
|
|
304
|
+
<div class="modal-exit" @click="closeRecentPagesModal" role="button" aria-label="Close recent pages">×</div>
|
|
305
|
+
<div
|
|
306
|
+
class="shrink-0 flex items-center gap-3 border-b border-edge bg-surface px-4 py-4 pr-12 dark:bg-shark-900"
|
|
307
|
+
>
|
|
308
|
+
<h2 class="m-0 text-lg font-semibold text-content">Recent pages</h2>
|
|
309
|
+
<button
|
|
310
|
+
type="button"
|
|
311
|
+
class="rounded px-2 py-1 text-sm font-medium text-content-secondary hover:bg-muted disabled:opacity-50"
|
|
312
|
+
@click="clearRecentPagesHistory"
|
|
313
|
+
:disabled="recentPagesList.length === 0"
|
|
314
|
+
>
|
|
315
|
+
Clear
|
|
316
|
+
</button>
|
|
317
|
+
</div>
|
|
318
|
+
<div class="modal-recent-pages-scroll">
|
|
319
|
+
<div
|
|
320
|
+
v-if="recentPagesList.length === 0"
|
|
321
|
+
class="m-4 rounded border border-dashed border-edge px-3 py-6 text-center text-sm text-content-tertiary"
|
|
322
|
+
>
|
|
323
|
+
No recent pages yet.
|
|
324
|
+
</div>
|
|
325
|
+
<button
|
|
326
|
+
v-for="entry in recentPagesList"
|
|
327
|
+
:key="entry.path + '-' + entry.visitedAt"
|
|
328
|
+
type="button"
|
|
329
|
+
class="w-full px-4 py-2.5 text-left hover:bg-muted"
|
|
330
|
+
:class="entry.path === $route.fullPath ? 'bg-primary-subtle' : ''"
|
|
331
|
+
@click="goToRecentPage(entry)"
|
|
332
|
+
>
|
|
333
|
+
<div class="truncate text-sm font-medium text-content">{{ entry.label }}</div>
|
|
334
|
+
<div class="truncate font-mono text-xs text-content-tertiary">{{ entry.path }}</div>
|
|
335
|
+
</button>
|
|
336
|
+
</div>
|
|
337
|
+
</template>
|
|
338
|
+
</modal>
|
|
339
|
+
</div>
|