@mongoosejs/studio 0.1.19 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/backend/actions/ChatMessage/executeScript.js +4 -1
  2. package/backend/actions/ChatThread/createChatMessage.js +28 -22
  3. package/backend/actions/Model/getDocument.js +2 -1
  4. package/backend/actions/Model/getDocuments.js +3 -2
  5. package/backend/actions/Model/getDocumentsStream.js +3 -2
  6. package/backend/helpers/evaluateFilter.js +38 -1
  7. package/backend/helpers/getRefFromSchemaType.js +5 -0
  8. package/express.js +4 -2
  9. package/frontend/public/app.js +704 -405
  10. package/frontend/public/index.html +1 -1
  11. package/frontend/public/style.css +1 -1
  12. package/frontend/public/tw.css +81 -62
  13. package/frontend/src/_util/document-search-autocomplete.js +229 -0
  14. package/frontend/src/chat/chat-message-script/chat-message-script.html +27 -20
  15. package/frontend/src/chat/chat.html +20 -17
  16. package/frontend/src/chat/chat.js +2 -0
  17. package/frontend/src/document/document.css +1 -8
  18. package/frontend/src/document/document.html +202 -164
  19. package/frontend/src/document/document.js +1 -0
  20. package/frontend/src/document-details/document-details.html +1 -11
  21. package/frontend/src/document-details/document-details.js +43 -1
  22. package/frontend/src/document-details/document-property/document-property.html +4 -4
  23. package/frontend/src/index.js +36 -15
  24. package/frontend/src/json-node/json-node.html +118 -0
  25. package/frontend/src/json-node/json-node.js +272 -0
  26. package/frontend/src/list-array/list-array.html +15 -3
  27. package/frontend/src/list-array/list-array.js +21 -3
  28. package/frontend/src/list-default/list-default.js +2 -2
  29. package/frontend/src/list-json/json-node.html +1 -1
  30. package/frontend/src/list-json/list-json.html +1 -1
  31. package/frontend/src/list-json/list-json.js +11 -248
  32. package/frontend/src/list-subdocument/list-subdocument.html +13 -4
  33. package/frontend/src/list-subdocument/list-subdocument.js +11 -6
  34. package/frontend/src/models/document-search/document-search.html +1 -1
  35. package/frontend/src/models/document-search/document-search.js +22 -116
  36. package/frontend/src/models/models.css +5 -15
  37. package/frontend/src/models/models.html +34 -34
  38. package/frontend/src/models/models.js +1 -1
  39. package/frontend/src/navbar/navbar.html +15 -6
  40. package/package.json +3 -3
@@ -1,5 +1,8 @@
1
1
  <div class="flex" style="height: calc(100vh - 55px); height: calc(100dvh - 55px)">
2
- <div class="fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10" @click="hideSidebar = false">
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="bg-gray-50 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' : ''">
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-4 font-bold text-lg">Chat Threads</div>
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-64">
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-4 hover:bg-gray-200 cursor-pointer w-64"
45
- :class="{ 'bg-gray-300': thread._id === chatThreadId }"
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
- <ul role="list" class="space-y-4">
56
- <div v-if="true">
57
- <div class="flex items-center justify-center py-3 mb-4">
58
- <div class="bg-gray-300 h-px flex-grow max-w-xs"></div>
59
- <p class="mx-4 text-sm font-medium text-gray-500">This is the beginning of the message thread</p>
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 something...'"
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="1"
81
+ rows="2"
79
82
  ref="messageInput"
80
83
  @input="adjustTextareaHeight"
81
84
  @keydown.enter.exact.prevent="handleEnter"
@@ -181,5 +181,7 @@ module.exports = app => app.component('chat', {
181
181
  });
182
182
  });
183
183
  }
184
+
185
+ this.$refs.messageInput.focus();
184
186
  }
185
187
  });
@@ -1,10 +1,3 @@
1
- .document {
2
- max-width: 1200px;
3
- margin-left: auto;
4
- margin-right: auto;
5
- padding-top: 25px;
6
- }
7
-
8
1
  .document .document-menu {
9
2
  display: flex;
10
3
  position: sticky;
@@ -32,4 +25,4 @@
32
25
 
33
26
  .document button img {
34
27
  height: 1em;
35
- }
28
+ }
@@ -1,175 +1,213 @@
1
- <div class="document px-1 md:px-0">
2
- <div class="flex justify-between sticky top-0 z-50 bg-white p-4 border-b border-gray-200 shadow-sm">
3
- <div class="flex">
4
- <button
5
- @click="goBack"
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
- &lsaquo; 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
- &times; 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="mobileMenuOpen = !mobileMenuOpen"
82
- type="button"
83
- 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"
84
- aria-expanded="mobileMenuOpen"
85
- aria-label="Open menu"
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-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
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
- <div
93
- v-show="mobileMenuOpen"
94
- @click.away="mobileMenuOpen = false"
95
- 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"
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
- <div class="py-1 flex flex-col">
98
- <button
99
- v-if="!editting"
100
- @click="editting = true; mobileMenuOpen = false"
101
- :disabled="!canEdit"
102
- :class="['flex items-center px-4 py-2 text-sm text-gray-700', !canEdit ? 'cursor-not-allowed opacity-50' : 'hover:bg-ultramarine-100']"
103
- type="button"
104
- >
105
- <img src="images/edit.svg" class="inline mr-2" /> Edit
106
- </button>
107
- <button
108
- v-if="editting"
109
- @click="editting = false; mobileMenuOpen = false"
110
- type="button"
111
- class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-slate-100"
112
- >
113
- &times; Cancel
114
- </button>
115
- <button
116
- v-if="editting"
117
- :disabled="!canManipulate"
118
- :class="['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-green-100']"
119
- @click="shouldShowConfirmModal=true; mobileMenuOpen = false"
120
- type="button"
121
- >
122
- <img src="images/save.svg" class="inline mr-2" /> Save
123
- </button>
124
- <button
125
- @click="shouldShowDeleteModal=true; mobileMenuOpen = false"
126
- :disabled="!canManipulate"
127
- :class="['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']"
128
- type="button"
129
- >
130
- <img src="images/delete.svg" class="inline mr-2" /> Delete
131
- </button>
132
- <button
133
- @click="shouldShowCloneModal=true; mobileMenuOpen = false"
134
- :disabled="!canManipulate"
135
- :class="['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']"
136
- type="button"
137
- >
138
- <img src="images/duplicate.svg" class="inline mr-2" /> Clone
139
- </button>
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
+ &times; 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
- </div>
145
- <div v-if="status === 'loaded'">
146
- <document-details
147
- :document="document"
148
- :schemaPaths="schemaPaths"
149
- :virtualPaths="virtualPaths"
150
- :editting="editting"
151
- :changes="changes"
152
- :invalid="invalid"
153
- :viewMode="viewMode"
154
- @add-field="addField"
155
- @view-mode-change="updateViewMode"></document-details>
156
- <modal v-if="shouldShowConfirmModal">
157
- <template v-slot:body>
158
- <div class="modal-exit" @click="shouldShowConfirmModal = false;">&times;</div>
159
- <confirm-changes @close="shouldShowConfirmModal = false;" @save="save" :value="changes"></confirm-changes>
160
- </template>
161
- </modal>
162
- <modal v-if="shouldShowDeleteModal">
163
- <template v-slot:body>
164
- <div class="modal-exit" @click="shouldShowDeleteModal = false;">&times;</div>
165
- <confirm-delete @close="shouldShowDeleteModal = false;" @remove="remove" :value="document"></confirm-delete>
166
- </template>
167
- </modal>
168
- <modal v-if="shouldShowCloneModal">
169
- <template v-slot:body>
170
- <div class="modal-exit" @click="shouldShowCloneModal = false;">&times;</div>
171
- <clone-document :currentModel="model" :doc="document" :schemaPaths="schemaPaths" @close="showClonedDocument"></clone-document>
172
- </template>
173
- </modal>
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;">&times;</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;">&times;</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;">&times;</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>
@@ -21,6 +21,7 @@ module.exports = app => app.component('document', {
21
21
  virtuals: [],
22
22
  virtualPaths: [],
23
23
  mobileMenuOpen: false,
24
+ desktopMenuOpen: false,
24
25
  viewMode: 'fields',
25
26
  shouldShowConfirmModal: false,
26
27
  shouldShowDeleteModal: false,
@@ -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-3 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-gray-50 hover:bg-gray-100': !highlight }"
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-3">
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