@mongoosejs/studio 0.1.20 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/backend/actions/ChatMessage/executeScript.js +4 -1
- package/backend/actions/ChatThread/createChatMessage.js +28 -22
- package/backend/helpers/evaluateFilter.js +38 -1
- package/express.js +4 -2
- package/frontend/public/app.js +702 -428
- package/frontend/public/index.html +1 -1
- package/frontend/public/style.css +1 -1
- package/frontend/public/tw.css +81 -62
- package/frontend/src/_util/document-search-autocomplete.js +229 -0
- package/frontend/src/chat/chat-message-script/chat-message-script.html +27 -20
- package/frontend/src/chat/chat.html +20 -17
- package/frontend/src/chat/chat.js +2 -0
- package/frontend/src/document/document.css +1 -8
- package/frontend/src/document/document.html +202 -164
- package/frontend/src/document/document.js +1 -0
- package/frontend/src/document-details/document-details.html +1 -11
- package/frontend/src/document-details/document-details.js +43 -1
- package/frontend/src/document-details/document-property/document-property.html +4 -4
- package/frontend/src/index.js +36 -15
- package/frontend/src/json-node/json-node.html +118 -0
- package/frontend/src/json-node/json-node.js +272 -0
- package/frontend/src/list-array/list-array.html +15 -3
- package/frontend/src/list-array/list-array.js +21 -3
- package/frontend/src/list-default/list-default.js +2 -2
- package/frontend/src/list-json/list-json.html +1 -1
- package/frontend/src/list-json/list-json.js +11 -273
- package/frontend/src/list-subdocument/list-subdocument.html +13 -4
- package/frontend/src/list-subdocument/list-subdocument.js +11 -6
- package/frontend/src/models/document-search/document-search.html +1 -1
- package/frontend/src/models/document-search/document-search.js +22 -116
- package/frontend/src/models/models.css +5 -15
- package/frontend/src/models/models.html +34 -34
- package/frontend/src/navbar/navbar.html +15 -6
- package/package.json +2 -2
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
<div class="flex" style="height: calc(100vh - 55px); height: calc(100dvh - 55px)">
|
|
2
|
-
<div
|
|
2
|
+
<div
|
|
3
|
+
class="fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10"
|
|
4
|
+
@click="hideSidebar = false"
|
|
5
|
+
v-show="hideSidebar">
|
|
3
6
|
<svg xmlns="http://www.w3.org/2000/svg" style="h-5 w-5" viewBox="0 -960 960 960" class="w-5" fill="#5f6368"><path d="M360-120v-720h80v720h-80Zm160-160v-400l200 200-200 200Z"/></svg>
|
|
4
7
|
</div>
|
|
5
8
|
<button
|
|
@@ -14,9 +17,9 @@
|
|
|
14
17
|
<svg v-else xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 1a5 5 0 00-5 5v3H6a2 2 0 00-2 2v9a2 2 0 002 2h12a2 2 0 002-2v-9a2 2 0 00-2-2h-1V6a5 5 0 00-5-5zm-3 8V6a3 3 0 016 0v3H9zm9 2v9H6v-9h12z"/></svg>
|
|
15
18
|
</button>
|
|
16
19
|
<!-- Sidebar: Chat Threads -->
|
|
17
|
-
<aside class="
|
|
20
|
+
<aside class="border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-64 fixed lg:relative" :class="hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-64' : ''">
|
|
18
21
|
<div class="flex items-center border-b border-gray-100 w-64 overflow-x-hidden">
|
|
19
|
-
<div class="p-
|
|
22
|
+
<div class="p-1 ml-2 font-bold">Chat Threads</div>
|
|
20
23
|
<button
|
|
21
24
|
@click="hideSidebar = true"
|
|
22
25
|
class="ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none"
|
|
@@ -36,13 +39,13 @@
|
|
|
36
39
|
<div v-if="status === 'loaded' && chatThreads.length === 0" class="p-4 text-sm text-gray-700">
|
|
37
40
|
No threads yet
|
|
38
41
|
</div>
|
|
39
|
-
<ul v-if="status === 'loaded'" class="w-
|
|
42
|
+
<ul v-if="status === 'loaded'" class="w-full">
|
|
40
43
|
<li
|
|
41
44
|
v-for="thread in chatThreads"
|
|
42
45
|
:key="thread._id"
|
|
43
46
|
@click="selectThread(thread._id)"
|
|
44
|
-
class="p-
|
|
45
|
-
:class="{ 'bg-gray-
|
|
47
|
+
class="w-full p-2 hover:bg-ultramarine-100 cursor-pointer text-sm text-gray-700"
|
|
48
|
+
:class="{ 'bg-ultramarine-200 text-gray-900': thread._id === chatThreadId }"
|
|
46
49
|
>
|
|
47
50
|
{{ thread.title || 'Untitled Thread' }}
|
|
48
51
|
</li>
|
|
@@ -50,16 +53,16 @@
|
|
|
50
53
|
</aside>
|
|
51
54
|
|
|
52
55
|
<!-- Main Chat Area -->
|
|
53
|
-
<main class="flex-1 flex flex-col">
|
|
56
|
+
<main class="flex-1 flex flex-col bg-slate-50">
|
|
54
57
|
<div class="flex-1 overflow-y-auto p-6 space-y-4" ref="messagesContainer">
|
|
55
|
-
<
|
|
56
|
-
<div
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<div class="bg-gray-300 h-px flex-grow max-w-xs"></div>
|
|
61
|
-
</div>
|
|
58
|
+
<div v-if="chatMessages?.length === 0">
|
|
59
|
+
<div class="flex items-center w-full h-full justify-center py-3 mb-4">
|
|
60
|
+
<p class="mx-4 font-bold text-gray-900">
|
|
61
|
+
Ask Mongoose Studio to analyze your data or generate a script
|
|
62
|
+
</p>
|
|
62
63
|
</div>
|
|
64
|
+
</div>
|
|
65
|
+
<ul role="list" class="space-y-4" v-else>
|
|
63
66
|
<li v-for="message in chatMessages" :key="message._id">
|
|
64
67
|
<chat-message :message="message" :target-dashboard-id="currentThread?.dashboardId"></chat-message>
|
|
65
68
|
</li>
|
|
@@ -72,10 +75,10 @@
|
|
|
72
75
|
<form @submit.prevent="sendMessage" :disabled="sendingMessage" class="flex gap-2 items-end justify-end">
|
|
73
76
|
<textarea
|
|
74
77
|
v-model="newMessage"
|
|
75
|
-
:placeholder="sendingMessage ? 'Sending...' : 'Ask
|
|
76
|
-
class="flex-1 border rounded px-4 py-2 resize-none overflow-y-auto"
|
|
78
|
+
:placeholder="sendingMessage ? 'Sending...' : 'Ask about your data, generate a query, or build a chart…'"
|
|
79
|
+
class="flex-1 border rounded px-4 py-2 resize-none overflow-y-auto shadow-sm"
|
|
77
80
|
:disabled="sendingMessage"
|
|
78
|
-
rows="
|
|
81
|
+
rows="2"
|
|
79
82
|
ref="messageInput"
|
|
80
83
|
@input="adjustTextareaHeight"
|
|
81
84
|
@keydown.enter.exact.prevent="handleEnter"
|
|
@@ -1,175 +1,213 @@
|
|
|
1
|
-
<div class="document px-1 md:px-0">
|
|
2
|
-
<div class="
|
|
3
|
-
<div class="flex">
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
class="mr-2 rounded-md bg-gray-400 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600">
|
|
7
|
-
‹ Back
|
|
8
|
-
</button>
|
|
9
|
-
<button
|
|
10
|
-
@click="viewMode = 'fields'"
|
|
11
|
-
:class="viewMode === 'fields'
|
|
12
|
-
? 'bg-blue-600 text-white z-10'
|
|
13
|
-
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'"
|
|
14
|
-
class="px-4 py-2 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-gray-300 border-r-0 rounded-l-lg rounded-r-none"
|
|
15
|
-
>
|
|
16
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
17
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"></path>
|
|
18
|
-
</svg>
|
|
19
|
-
Fields
|
|
20
|
-
</button>
|
|
21
|
-
<button
|
|
22
|
-
@click="viewMode = 'json'"
|
|
23
|
-
:class="viewMode === 'json'
|
|
24
|
-
? 'bg-blue-600 text-white z-10'
|
|
25
|
-
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'"
|
|
26
|
-
class="px-4 py-2 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-gray-300 rounded-r-lg rounded-l-none"
|
|
27
|
-
>
|
|
28
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
29
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path>
|
|
30
|
-
</svg>
|
|
31
|
-
JSON
|
|
32
|
-
</button>
|
|
33
|
-
</div>
|
|
34
|
-
|
|
35
|
-
<div class="gap-2 hidden md:flex">
|
|
36
|
-
<button
|
|
37
|
-
v-if="!editting"
|
|
38
|
-
@click="editting = true"
|
|
39
|
-
:disabled="!canEdit"
|
|
40
|
-
:class="{'cursor-not-allowed opacity-50': !canEdit}"
|
|
41
|
-
type="button"
|
|
42
|
-
class="rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600">
|
|
43
|
-
<img src="images/edit.svg" class="inline" /> Edit
|
|
44
|
-
</button>
|
|
45
|
-
<button
|
|
46
|
-
v-if="editting"
|
|
47
|
-
@click="editting = false"
|
|
48
|
-
type="button"
|
|
49
|
-
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">
|
|
50
|
-
× Cancel
|
|
51
|
-
</button>
|
|
52
|
-
<button
|
|
53
|
-
v-if="editting"
|
|
54
|
-
:disabled="!canManipulate"
|
|
55
|
-
:class="{'cursor-not-allowed opacity-50': !canManipulate}"
|
|
56
|
-
@click="shouldShowConfirmModal=true;"
|
|
57
|
-
type="button"
|
|
58
|
-
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">
|
|
59
|
-
<img src="images/save.svg" class="inline" /> Save
|
|
60
|
-
</button>
|
|
61
|
-
<button
|
|
62
|
-
@click="shouldShowDeleteModal=true;"
|
|
63
|
-
:disabled="!canManipulate"
|
|
64
|
-
:class="{'cursor-not-allowed opacity-50': !canManipulate}"
|
|
65
|
-
type="button"
|
|
66
|
-
class="rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600">
|
|
67
|
-
<img src="images/delete.svg" class="inline" /> Delete
|
|
68
|
-
</button>
|
|
69
|
-
<button
|
|
70
|
-
@click="shouldShowCloneModal=true;"
|
|
71
|
-
:disabled="!canManipulate"
|
|
72
|
-
:class="{'cursor-not-allowed opacity-50': !canManipulate}"
|
|
73
|
-
type="button"
|
|
74
|
-
class="rounded-md bg-pink-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600">
|
|
75
|
-
<img src="images/duplicate.svg" class="inline" /> Clone
|
|
76
|
-
</button>
|
|
77
|
-
</div>
|
|
78
|
-
<div class="md:hidden flex items-center">
|
|
79
|
-
<div class="relative">
|
|
1
|
+
<div class="document px-1 pt-4 md:px-0 bg-slate-50 w-full">
|
|
2
|
+
<div class="max-w-7xl mx-auto">
|
|
3
|
+
<div class="flex gap-4 items-center sticky top-0 z-50 bg-white p-4 border-b border-gray-200 shadow-sm">
|
|
4
|
+
<div class="font-bold overflow-hidden text-ellipsis">{{model}}: {{documentId}}</div>
|
|
5
|
+
<div class="flex grow">
|
|
80
6
|
<button
|
|
81
|
-
@click="
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
7
|
+
@click="viewMode = 'fields'"
|
|
8
|
+
:class="viewMode === 'fields'
|
|
9
|
+
? 'bg-blue-600 text-white z-10'
|
|
10
|
+
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'"
|
|
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-gray-300 border-r-0 rounded-l-lg rounded-r-none"
|
|
86
12
|
>
|
|
87
|
-
<svg class="w-
|
|
88
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
89
|
-
d="M4 6h16M4 12h16M4 18h16"></path>
|
|
13
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
14
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"></path>
|
|
90
15
|
</svg>
|
|
16
|
+
Fields
|
|
91
17
|
</button>
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
18
|
+
<button
|
|
19
|
+
@click="viewMode = 'json'"
|
|
20
|
+
:class="viewMode === 'json'
|
|
21
|
+
? 'bg-blue-600 text-white z-10'
|
|
22
|
+
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'"
|
|
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-gray-300 rounded-r-lg rounded-l-none"
|
|
96
24
|
>
|
|
97
|
-
<
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
25
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
26
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path>
|
|
27
|
+
</svg>
|
|
28
|
+
JSON
|
|
29
|
+
</button>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class="gap-2 hidden md:flex items-center">
|
|
33
|
+
<button
|
|
34
|
+
v-if="!editting"
|
|
35
|
+
@click="editting = true"
|
|
36
|
+
:disabled="!canEdit"
|
|
37
|
+
:class="{'cursor-not-allowed opacity-50': !canEdit}"
|
|
38
|
+
type="button"
|
|
39
|
+
class="rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600">
|
|
40
|
+
<img src="images/edit.svg" class="inline" /> Edit
|
|
41
|
+
</button>
|
|
42
|
+
<button
|
|
43
|
+
v-if="editting"
|
|
44
|
+
@click="editting = false"
|
|
45
|
+
type="button"
|
|
46
|
+
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">
|
|
47
|
+
× Cancel
|
|
48
|
+
</button>
|
|
49
|
+
<button
|
|
50
|
+
v-if="editting"
|
|
51
|
+
:disabled="!canManipulate"
|
|
52
|
+
:class="{'cursor-not-allowed opacity-50': !canManipulate}"
|
|
53
|
+
@click="shouldShowConfirmModal=true;"
|
|
54
|
+
type="button"
|
|
55
|
+
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">
|
|
56
|
+
<img src="images/save.svg" class="inline" /> Save
|
|
57
|
+
</button>
|
|
58
|
+
|
|
59
|
+
<!-- 3-dot menu -->
|
|
60
|
+
<div class="relative">
|
|
61
|
+
<button
|
|
62
|
+
@click="desktopMenuOpen = !desktopMenuOpen"
|
|
63
|
+
type="button"
|
|
64
|
+
class="inline-flex items-center justify-center rounded-md bg-gray-200 px-2.5 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|
65
|
+
aria-expanded="desktopMenuOpen"
|
|
66
|
+
aria-label="More options"
|
|
67
|
+
>
|
|
68
|
+
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
|
69
|
+
<circle cx="12" cy="5" r="2"></circle>
|
|
70
|
+
<circle cx="12" cy="12" r="2"></circle>
|
|
71
|
+
<circle cx="12" cy="19" r="2"></circle>
|
|
72
|
+
</svg>
|
|
73
|
+
</button>
|
|
74
|
+
<div
|
|
75
|
+
v-show="desktopMenuOpen"
|
|
76
|
+
@click.away="desktopMenuOpen = false"
|
|
77
|
+
class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50"
|
|
78
|
+
>
|
|
79
|
+
<div class="py-1 flex flex-col">
|
|
80
|
+
<button
|
|
81
|
+
@click="addField(); desktopMenuOpen = false"
|
|
82
|
+
type="button"
|
|
83
|
+
class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-green-100"
|
|
84
|
+
>
|
|
85
|
+
Add Field
|
|
86
|
+
</button>
|
|
87
|
+
<button
|
|
88
|
+
@click="shouldShowDeleteModal=true; desktopMenuOpen = false"
|
|
89
|
+
:disabled="!canManipulate"
|
|
90
|
+
:class="['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']"
|
|
91
|
+
type="button"
|
|
92
|
+
>
|
|
93
|
+
Delete
|
|
94
|
+
</button>
|
|
95
|
+
<button
|
|
96
|
+
@click="shouldShowCloneModal=true; desktopMenuOpen = false"
|
|
97
|
+
:disabled="!canManipulate"
|
|
98
|
+
:class="['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']"
|
|
99
|
+
type="button"
|
|
100
|
+
>
|
|
101
|
+
Clone
|
|
102
|
+
</button>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
<div class="md:hidden flex items-center">
|
|
108
|
+
<div class="relative">
|
|
109
|
+
<button
|
|
110
|
+
@click="mobileMenuOpen = !mobileMenuOpen"
|
|
111
|
+
type="button"
|
|
112
|
+
class="inline-flex items-center justify-center rounded-md bg-gray-200 px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|
113
|
+
aria-expanded="mobileMenuOpen"
|
|
114
|
+
aria-label="Open menu"
|
|
115
|
+
>
|
|
116
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
117
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
118
|
+
d="M4 6h16M4 12h16M4 18h16"></path>
|
|
119
|
+
</svg>
|
|
120
|
+
</button>
|
|
121
|
+
<div
|
|
122
|
+
v-show="mobileMenuOpen"
|
|
123
|
+
@click.away="mobileMenuOpen = false"
|
|
124
|
+
class="origin-top-right absolute right-0 mt-2 w-52 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50"
|
|
125
|
+
>
|
|
126
|
+
<div class="py-1 flex flex-col">
|
|
127
|
+
<button
|
|
128
|
+
v-if="!editting"
|
|
129
|
+
@click="editting = true; mobileMenuOpen = false"
|
|
130
|
+
:disabled="!canEdit"
|
|
131
|
+
:class="['flex items-center px-4 py-2 text-sm text-gray-700', !canEdit ? 'cursor-not-allowed opacity-50' : 'hover:bg-ultramarine-100']"
|
|
132
|
+
type="button"
|
|
133
|
+
>
|
|
134
|
+
Edit
|
|
135
|
+
</button>
|
|
136
|
+
<button
|
|
137
|
+
v-if="editting"
|
|
138
|
+
@click="editting = false; mobileMenuOpen = false"
|
|
139
|
+
type="button"
|
|
140
|
+
class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-slate-100"
|
|
141
|
+
>
|
|
142
|
+
Cancel
|
|
143
|
+
</button>
|
|
144
|
+
<button
|
|
145
|
+
v-if="editting"
|
|
146
|
+
:disabled="!canManipulate"
|
|
147
|
+
:class="['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-green-100']"
|
|
148
|
+
@click="shouldShowConfirmModal=true; mobileMenuOpen = false"
|
|
149
|
+
type="button"
|
|
150
|
+
>
|
|
151
|
+
Save
|
|
152
|
+
</button>
|
|
153
|
+
<button
|
|
154
|
+
@click="addField(); mobileMenuOpen = false"
|
|
155
|
+
type="button"
|
|
156
|
+
class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-green-100"
|
|
157
|
+
>
|
|
158
|
+
Add Field
|
|
159
|
+
</button>
|
|
160
|
+
<button
|
|
161
|
+
@click="shouldShowDeleteModal=true; mobileMenuOpen = false"
|
|
162
|
+
:disabled="!canManipulate"
|
|
163
|
+
:class="['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']"
|
|
164
|
+
type="button"
|
|
165
|
+
>
|
|
166
|
+
Delete
|
|
167
|
+
</button>
|
|
168
|
+
<button
|
|
169
|
+
@click="shouldShowCloneModal=true; mobileMenuOpen = false"
|
|
170
|
+
:disabled="!canManipulate"
|
|
171
|
+
:class="['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']"
|
|
172
|
+
type="button"
|
|
173
|
+
>
|
|
174
|
+
Clone
|
|
175
|
+
</button>
|
|
176
|
+
</div>
|
|
140
177
|
</div>
|
|
141
178
|
</div>
|
|
142
179
|
</div>
|
|
143
180
|
</div>
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
181
|
+
<div v-if="status === 'loaded'">
|
|
182
|
+
<document-details
|
|
183
|
+
:document="document"
|
|
184
|
+
:schemaPaths="schemaPaths"
|
|
185
|
+
:virtualPaths="virtualPaths"
|
|
186
|
+
:editting="editting"
|
|
187
|
+
:changes="changes"
|
|
188
|
+
:invalid="invalid"
|
|
189
|
+
:viewMode="viewMode"
|
|
190
|
+
:model="model"
|
|
191
|
+
@add-field="addField"
|
|
192
|
+
@view-mode-change="updateViewMode"></document-details>
|
|
193
|
+
<modal v-if="shouldShowConfirmModal">
|
|
194
|
+
<template v-slot:body>
|
|
195
|
+
<div class="modal-exit" @click="shouldShowConfirmModal = false;">×</div>
|
|
196
|
+
<confirm-changes @close="shouldShowConfirmModal = false;" @save="save" :value="changes"></confirm-changes>
|
|
197
|
+
</template>
|
|
198
|
+
</modal>
|
|
199
|
+
<modal v-if="shouldShowDeleteModal">
|
|
200
|
+
<template v-slot:body>
|
|
201
|
+
<div class="modal-exit" @click="shouldShowDeleteModal = false;">×</div>
|
|
202
|
+
<confirm-delete @close="shouldShowDeleteModal = false;" @remove="remove" :value="document"></confirm-delete>
|
|
203
|
+
</template>
|
|
204
|
+
</modal>
|
|
205
|
+
<modal v-if="shouldShowCloneModal">
|
|
206
|
+
<template v-slot:body>
|
|
207
|
+
<div class="modal-exit" @click="shouldShowCloneModal = false;">×</div>
|
|
208
|
+
<clone-document :currentModel="model" :doc="document" :schemaPaths="schemaPaths" @close="showClonedDocument"></clone-document>
|
|
209
|
+
</template>
|
|
210
|
+
</modal>
|
|
211
|
+
</div>
|
|
174
212
|
</div>
|
|
175
213
|
</div>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div class="document-details">
|
|
2
2
|
<!-- View Toggle and Search/Filter Bar -->
|
|
3
|
-
<div class="sticky top-[60px] z-40 bg-white p-4 border-b border-gray-200 shadow-sm">
|
|
3
|
+
<div class="sticky top-[60px] z-40 bg-white rounded-md p-4 border-b border-gray-200 shadow-sm">
|
|
4
4
|
|
|
5
5
|
<!-- Search and Filter Bar (only show in fields view) -->
|
|
6
6
|
<div v-if="viewMode === 'fields'" class="flex md:gap-3">
|
|
@@ -37,16 +37,6 @@
|
|
|
37
37
|
</svg>
|
|
38
38
|
</div>
|
|
39
39
|
</div>
|
|
40
|
-
|
|
41
|
-
<!-- Add Field Button -->
|
|
42
|
-
<button
|
|
43
|
-
@click="openAddFieldModal"
|
|
44
|
-
class="hidden md:flex px-4 py-2 text-sm font-medium text-white bg-green-600 hover:bg-green-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 items-center gap-2"
|
|
45
|
-
>
|
|
46
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
47
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
|
48
|
-
</svg> Add Field
|
|
49
|
-
</button>
|
|
50
40
|
</div>
|
|
51
41
|
</div>
|
|
52
42
|
|
|
@@ -9,7 +9,7 @@ appendCSS(require('./document-details.css'));
|
|
|
9
9
|
|
|
10
10
|
module.exports = app => app.component('document-details', {
|
|
11
11
|
template,
|
|
12
|
-
props: ['document', 'schemaPaths', 'virtualPaths', 'editting', 'changes', 'invalid', 'viewMode'],
|
|
12
|
+
props: ['document', 'schemaPaths', 'virtualPaths', 'editting', 'changes', 'invalid', 'viewMode', 'model'],
|
|
13
13
|
data() {
|
|
14
14
|
return {
|
|
15
15
|
searchQuery: '',
|
|
@@ -37,6 +37,8 @@ module.exports = app => app.component('document-details', {
|
|
|
37
37
|
this.initializeFieldValueEditor();
|
|
38
38
|
}
|
|
39
39
|
});
|
|
40
|
+
|
|
41
|
+
this.searchQuery = this.getSearchQueryFromRoute();
|
|
40
42
|
},
|
|
41
43
|
beforeDestroy() {
|
|
42
44
|
this.destroyFieldValueEditor();
|
|
@@ -55,6 +57,17 @@ module.exports = app => app.component('document-details', {
|
|
|
55
57
|
});
|
|
56
58
|
}
|
|
57
59
|
}
|
|
60
|
+
},
|
|
61
|
+
searchQuery(newValue) {
|
|
62
|
+
this.syncSearchQueryToUrl(newValue);
|
|
63
|
+
},
|
|
64
|
+
'$route.query.fieldSearch': {
|
|
65
|
+
handler(newValue) {
|
|
66
|
+
const nextValue = typeof newValue === 'string' ? newValue : '';
|
|
67
|
+
if (nextValue !== this.searchQuery) {
|
|
68
|
+
this.searchQuery = nextValue;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
58
71
|
}
|
|
59
72
|
},
|
|
60
73
|
computed: {
|
|
@@ -245,6 +258,35 @@ module.exports = app => app.component('document-details', {
|
|
|
245
258
|
}
|
|
246
259
|
},
|
|
247
260
|
methods: {
|
|
261
|
+
getSearchQueryFromRoute() {
|
|
262
|
+
return this.$route?.query?.fieldSearch || '';
|
|
263
|
+
},
|
|
264
|
+
syncSearchQueryToUrl(value) {
|
|
265
|
+
if (typeof window === 'undefined') {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const normalizedValue = typeof value === 'string' ? value : '';
|
|
270
|
+
const shouldStore = normalizedValue.trim().length > 0;
|
|
271
|
+
const hash = window.location.hash.replace(/^#?/, '');
|
|
272
|
+
const [hashPath, hashQueryString = ''] = hash.split('?');
|
|
273
|
+
const params = new URLSearchParams(hashQueryString);
|
|
274
|
+
const currentValue = params.get('fieldSearch') || '';
|
|
275
|
+
|
|
276
|
+
if (normalizedValue === currentValue || (!shouldStore && !currentValue)) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (shouldStore) {
|
|
281
|
+
params.set('fieldSearch', normalizedValue);
|
|
282
|
+
} else {
|
|
283
|
+
params.delete('fieldSearch');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const nextQueryString = params.toString();
|
|
287
|
+
const nextHash = nextQueryString ? `${hashPath}?${nextQueryString}` : hashPath;
|
|
288
|
+
window.history.replaceState(window.history.state, '', `#${nextHash}`);
|
|
289
|
+
},
|
|
248
290
|
toggleVirtualField(fieldName) {
|
|
249
291
|
if (this.collapsedVirtuals.has(fieldName)) {
|
|
250
292
|
this.collapsedVirtuals.delete(fieldName);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
<div class="border border-gray-200 rounded-lg mb-2">
|
|
1
|
+
<div class="border border-gray-200 bg-white rounded-lg mb-2">
|
|
2
2
|
<!-- Collapsible Header -->
|
|
3
3
|
<div
|
|
4
4
|
@click="toggleCollapse"
|
|
5
|
-
class="p-
|
|
6
|
-
:class="{ 'bg-amber-100 hover:bg-amber-200': highlight, 'bg-
|
|
5
|
+
class="p-1 cursor-pointer flex items-center justify-between border-b border-gray-200 transition-colors duration-200 ease-in-out"
|
|
6
|
+
:class="{ 'bg-amber-100 hover:bg-amber-200': highlight, 'bg-slate-100 hover:bg-gray-100': !highlight }"
|
|
7
7
|
>
|
|
8
8
|
<div class="flex items-center" >
|
|
9
9
|
<svg
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
</div>
|
|
43
43
|
|
|
44
44
|
<!-- Collapsible Content -->
|
|
45
|
-
<div v-if="!isCollapsed" class="p-
|
|
45
|
+
<div v-if="!isCollapsed" class="p-2">
|
|
46
46
|
<!-- Date Type Selector (when editing dates) -->
|
|
47
47
|
<div v-if="editting && path.instance === 'Date'" class="mb-3 flex gap-1.5">
|
|
48
48
|
<div
|