@mongoosejs/studio 0.2.13 → 0.3.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 (81) hide show
  1. package/backend/actions/ChatMessage/executeScript.js +5 -1
  2. package/backend/actions/ChatThread/createChatMessage.js +2 -1
  3. package/backend/actions/ChatThread/streamChatMessage.js +2 -2
  4. package/eslint.config.js +4 -1
  5. package/frontend/public/app.js +24642 -543
  6. package/frontend/public/dark-theme.css +365 -0
  7. package/frontend/public/images/mongoose-studio.svg +4 -0
  8. package/frontend/public/index.html +21 -1
  9. package/frontend/public/style.css +5 -7
  10. package/frontend/public/theme-variables.css +294 -0
  11. package/frontend/public/tw.css +305 -252
  12. package/frontend/src/ace-editor/ace-editor.html +4 -0
  13. package/frontend/src/ace-editor/ace-editor.js +89 -0
  14. package/frontend/src/aceEditor.js +69 -0
  15. package/frontend/src/chat/chat-message/chat-message.html +1 -1
  16. package/frontend/src/chat/chat-message/chat-message.js +1 -1
  17. package/frontend/src/chat/chat-message-script/chat-message-script.html +51 -34
  18. package/frontend/src/chat/chat-message-script/chat-message-script.js +12 -55
  19. package/frontend/src/chat/chat.html +68 -39
  20. package/frontend/src/chat/chat.js +26 -2
  21. package/frontend/src/clone-document/clone-document.html +7 -2
  22. package/frontend/src/clone-document/clone-document.js +1 -8
  23. package/frontend/src/create-dashboard/create-dashboard.html +11 -6
  24. package/frontend/src/create-dashboard/create-dashboard.js +0 -7
  25. package/frontend/src/create-document/create-document.html +15 -9
  26. package/frontend/src/create-document/create-document.js +5 -12
  27. package/frontend/src/dashboard/dashboard.html +14 -12
  28. package/frontend/src/dashboard/dashboard.js +12 -4
  29. package/frontend/src/dashboard/edit-dashboard/edit-dashboard.html +13 -7
  30. package/frontend/src/dashboard/edit-dashboard/edit-dashboard.js +13 -21
  31. package/frontend/src/dashboard-result/dashboard-chart/dashboard-chart.html +19 -17
  32. package/frontend/src/dashboard-result/dashboard-chart/dashboard-chart.js +97 -2
  33. package/frontend/src/dashboard-result/dashboard-map/dashboard-map.js +27 -3
  34. package/frontend/src/dashboard-result/dashboard-result.html +3 -3
  35. package/frontend/src/dashboards/dashboards.html +101 -109
  36. package/frontend/src/dashboards/dashboards.js +25 -1
  37. package/frontend/src/detail-default/detail-default.html +2 -2
  38. package/frontend/src/detail-default/detail-default.js +24 -3
  39. package/frontend/src/document/confirm-changes/confirm-changes.html +1 -1
  40. package/frontend/src/document/confirm-delete/confirm-delete.html +1 -1
  41. package/frontend/src/document/document.css +1 -1
  42. package/frontend/src/document/document.html +28 -28
  43. package/frontend/src/document/execute-script/execute-script.html +20 -21
  44. package/frontend/src/document/execute-script/execute-script.js +1 -43
  45. package/frontend/src/document-details/document-details.css +4 -9
  46. package/frontend/src/document-details/document-details.html +34 -33
  47. package/frontend/src/document-details/document-details.js +2 -53
  48. package/frontend/src/document-details/document-property/document-property.html +12 -12
  49. package/frontend/src/edit-array/edit-array.html +7 -6
  50. package/frontend/src/edit-array/edit-array.js +10 -50
  51. package/frontend/src/edit-boolean/edit-boolean.html +12 -12
  52. package/frontend/src/edit-date/edit-date.html +2 -2
  53. package/frontend/src/edit-default/edit-default.html +1 -1
  54. package/frontend/src/edit-string/edit-string.html +3 -3
  55. package/frontend/src/edit-subdocument/edit-subdocument.html +5 -3
  56. package/frontend/src/edit-subdocument/edit-subdocument.js +1 -15
  57. package/frontend/src/export-query-results/export-query-results.html +3 -3
  58. package/frontend/src/json-node/json-node.html +3 -3
  59. package/frontend/src/list-json/json-node.html +1 -1
  60. package/frontend/src/models/document-search/document-search.html +3 -3
  61. package/frontend/src/models/model-switcher/model-switcher.html +53 -0
  62. package/frontend/src/models/model-switcher/model-switcher.js +123 -0
  63. package/frontend/src/models/models.css +3 -10
  64. package/frontend/src/models/models.html +146 -80
  65. package/frontend/src/models/models.js +108 -4
  66. package/frontend/src/navbar/navbar.html +157 -97
  67. package/frontend/src/navbar/navbar.js +31 -12
  68. package/frontend/src/routes.js +1 -1
  69. package/frontend/src/splash/splash.html +5 -5
  70. package/frontend/src/task-single/task-single.html +29 -29
  71. package/frontend/src/task-single/task-single.js +10 -10
  72. package/frontend/src/tasks/task-details/task-details.html +38 -38
  73. package/frontend/src/tasks/task-details/task-details.js +7 -2
  74. package/frontend/src/tasks/tasks.html +36 -35
  75. package/frontend/src/tasks/tasks.js +2 -25
  76. package/frontend/src/team/new-invitation/new-invitation.html +8 -8
  77. package/frontend/src/team/team.html +27 -27
  78. package/frontend/src/update-document/update-document.html +7 -2
  79. package/frontend/src/update-document/update-document.js +2 -11
  80. package/package.json +2 -1
  81. package/tailwind.config.js +75 -11
@@ -0,0 +1,123 @@
1
+ 'use strict';
2
+
3
+ const template = require('./model-switcher.html');
4
+ const xss = require('xss');
5
+
6
+ module.exports = app => app.component('model-switcher', {
7
+ template: template,
8
+ props: ['show', 'models', 'recentlyViewedModels', 'modelDocumentCounts'],
9
+ emits: ['close', 'select'],
10
+ data: () => ({
11
+ search: '',
12
+ selectedIndex: 0
13
+ }),
14
+ mounted() {
15
+ this.lastMouseMove = 0;
16
+ this.trackLastMouseMove = () => {
17
+ this.lastMouseMove = Date.now();
18
+ };
19
+
20
+ window.addEventListener('mousemove', this.trackLastMouseMove);
21
+ },
22
+ beforeUnmount() {
23
+ window.removeEventListener('mousemove', this.trackLastMouseMove);
24
+ },
25
+ watch: {
26
+ search() {
27
+ this.selectedIndex = 0;
28
+ },
29
+ show(val) {
30
+ if (val) {
31
+ this.search = '';
32
+ this.selectedIndex = 0;
33
+ this.$nextTick(() => {
34
+ if (this.$refs.searchInput) this.$refs.searchInput.focus();
35
+ });
36
+ }
37
+ }
38
+ },
39
+ computed: {
40
+ items() {
41
+ const items = [];
42
+ const search = this.search.trim().toLowerCase();
43
+ const recent = (this.recentlyViewedModels || []).filter(m => this.models.includes(m));
44
+
45
+ if (!search && recent.length > 0) {
46
+ for (const model of recent) {
47
+ items.push({ model, section: 'Recently Viewed' });
48
+ }
49
+ }
50
+
51
+ const filtered = search
52
+ ? this.models.filter(m => m.toLowerCase().includes(search))
53
+ : this.models;
54
+
55
+ for (const model of filtered) {
56
+ items.push({ model, section: search ? 'Search Results' : 'All Models' });
57
+ }
58
+
59
+ return items;
60
+ }
61
+ },
62
+ methods: {
63
+ selectModel(model) {
64
+ this.$emit('select', model);
65
+ },
66
+ onMouseEnter(index) {
67
+ // If user hasn't moved mouse recently, then this event was likely triggered by scroll
68
+ if (this.lastMouseMove <= Date.now() - 500) {
69
+ return;
70
+ }
71
+ this.selectedIndex = index;
72
+ },
73
+ handleKeydown(event) {
74
+ const items = this.items;
75
+ if (event.key === 'ArrowDown') {
76
+ event.preventDefault();
77
+ this.selectedIndex = Math.min(this.selectedIndex + 1, items.length - 1);
78
+ this.scrollSelectedIntoView();
79
+ } else if (event.key === 'ArrowUp') {
80
+ event.preventDefault();
81
+ this.selectedIndex = Math.max(this.selectedIndex - 1, 0);
82
+ this.scrollSelectedIntoView();
83
+ } else if (event.key === 'Enter') {
84
+ event.preventDefault();
85
+ if (items.length > 0 && items[this.selectedIndex]) {
86
+ this.selectModel(items[this.selectedIndex].model);
87
+ }
88
+ } else if (event.key === 'Escape') {
89
+ event.preventDefault();
90
+ this.$emit('close');
91
+ }
92
+ },
93
+ scrollSelectedIntoView() {
94
+ this.$nextTick(() => {
95
+ const container = this.$refs.list;
96
+ if (!container) return;
97
+ const selected = container.querySelector('[data-selected="true"]');
98
+ if (selected) selected.scrollIntoView({ block: 'nearest' });
99
+ });
100
+ },
101
+ highlightMatch(model) {
102
+ const search = this.search.trim();
103
+ if (!search) return model;
104
+ const idx = model.toLowerCase().indexOf(search.toLowerCase());
105
+ if (idx === -1) return model;
106
+ const before = model.slice(0, idx);
107
+ const match = model.slice(idx, idx + search.length);
108
+ const after = model.slice(idx + search.length);
109
+ return `${xss(before)}<strong>${xss(match)}</strong>${xss(after)}`;
110
+ },
111
+ formatCompactCount(value) {
112
+ if (typeof value !== 'number') return '—';
113
+ if (value < 1000) return `${value}`;
114
+ const formatValue = (number, suffix) => {
115
+ const rounded = (Math.round(number * 10) / 10).toFixed(1).replace(/\.0$/, '');
116
+ return `${rounded}${suffix}`;
117
+ };
118
+ if (value < 1000000) return formatValue(value / 1000, 'k');
119
+ if (value < 1000000000) return formatValue(value / 1000000, 'M');
120
+ return formatValue(value / 1000000000, 'B');
121
+ }
122
+ }
123
+ });
@@ -11,7 +11,6 @@
11
11
  }
12
12
 
13
13
  .models .model-selector {
14
- background-color: #eee;
15
14
  flex-grow: 0;
16
15
  padding: 15px;
17
16
  padding-top: 0px;
@@ -82,17 +81,11 @@
82
81
  }
83
82
 
84
83
  .models .documents-menu {
85
- position: fixed;
86
- z-index: 1;
84
+ position: sticky;
85
+ top: 0;
86
+ z-index: 2;
87
87
  padding: 4px;
88
88
  display: flex;
89
- width: 100vw;
90
- }
91
-
92
- @media (min-width: 1024px) {
93
- .models .documents-menu {
94
- width: calc(100vw - 12rem);
95
- }
96
89
  }
97
90
 
98
91
  .models .documents-menu .search-input {
@@ -1,48 +1,107 @@
1
1
  <div class="models 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">
3
- <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
- </div>
5
- <aside class="bg-white border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-48 fixed lg:relative shrink-0" :class="hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-48' : ''">
6
- <div class="flex items-center border-b border-gray-100 w-48 overflow-x-hidden">
7
- <div class="p-1 ml-2 font-bold">Models</div>
8
- <button
9
- @click="hideSidebar = true"
10
- class="ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none"
11
- aria-label="Close sidebar"
12
- >
13
- <svg xmlns="http://www.w3.org/2000/svg" style="h-5 w-5" viewBox="0 -960 960 960" class="w-5" fill="currentColor"><path d="M660-320v-320L500-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z"/></svg>
14
- </button>
2
+ <aside class="bg-page border-r overflow-hidden transition-all duration-300 ease-in-out z-20 w-0 lg:w-64 fixed lg:relative shrink-0 flex flex-col top-[55px] bottom-0 lg:top-auto lg:bottom-auto lg:h-full" :class="hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-64' : ''">
3
+ <!-- Search -->
4
+ <div class="p-3 shrink-0">
5
+ <div class="relative">
6
+ <svg class="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
7
+ <path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
8
+ </svg>
9
+ <input
10
+ v-model="modelSearch"
11
+ type="text"
12
+ placeholder="Find model..."
13
+ @keydown.esc="modelSearch = ''"
14
+ class="w-full rounded-md border border-edge bg-surface py-1.5 pl-8 pr-3 text-sm text-content placeholder:text-gray-400 focus:border-edge-strong focus:outline-none focus:ring-1 focus:ring-gray-300"
15
+ />
16
+ </div>
15
17
  </div>
16
- <nav class="flex flex-1 flex-col">
17
- <ul role="list" class="flex flex-1 flex-col gap-y-7 p-1">
18
- <li>
19
- <ul role="list">
20
- <li v-for="model in models">
21
- <router-link
22
- :to="'/model/' + model"
23
- class="flex items-center gap-2 rounded-md py-2 pr-2 pl-2 text-sm text-gray-700"
24
- :class="model === currentModel ? 'bg-ultramarine-100 font-bold text-gray-900' : 'hover:bg-ultramarine-50'">
25
- <span class="truncate">{{model}}</span>
26
- <span
27
- v-if="modelDocumentCounts && modelDocumentCounts[model] !== undefined && model !== currentModel"
28
- class="ml-auto text-xs text-gray-500 bg-gray-100 rounded-md px-1 py-[2px]"
29
- >
30
- {{formatCompactCount(modelDocumentCounts[model])}}
31
- </span>
32
- </router-link>
33
- </li>
34
- </ul>
18
+ <!-- Model list (scrollable) -->
19
+ <nav class="flex-1 overflow-y-auto px-2 pb-2">
20
+ <!-- Recently Viewed -->
21
+ <div v-if="filteredRecentModels.length > 0 && !modelSearch.trim()">
22
+ <div class="px-2 py-1 text-xs font-semibold text-gray-400 uppercase tracking-wider">Recently Viewed</div>
23
+ <ul role="list">
24
+ <li v-for="model in filteredRecentModels" :key="'recent-' + model">
25
+ <router-link
26
+ :to="'/model/' + model"
27
+ class="flex items-center rounded-md py-1.5 px-2 text-sm text-content-secondary"
28
+ :class="model === currentModel ? 'bg-gray-200 font-semibold text-content' : 'hover:bg-muted'">
29
+ <span class="truncate" v-html="highlightMatch(model)"></span>
30
+ <span
31
+ v-if="modelDocumentCounts && modelDocumentCounts[model] !== undefined && model !== currentModel"
32
+ class="ml-auto text-xs text-gray-400 bg-muted rounded px-1.5 py-[1px]"
33
+ >
34
+ {{formatCompactCount(modelDocumentCounts[model])}}
35
+ </span>
36
+ </router-link>
37
+ </li>
38
+ </ul>
39
+ <div class="border-b border-edge my-2"></div>
40
+ </div>
41
+ <!-- All Models / Search Results -->
42
+ <div class="px-2 py-1 text-xs font-semibold text-gray-400 uppercase tracking-wider">{{ modelSearch.trim() ? 'Search Results' : 'All Models' }}</div>
43
+ <ul role="list">
44
+ <li v-for="model in filteredModels" :key="'all-' + model">
45
+ <router-link
46
+ :to="'/model/' + model"
47
+ class="flex items-center rounded-md py-1.5 px-2 text-sm text-content-secondary"
48
+ :class="model === currentModel ? 'bg-gray-200 font-semibold text-content' : 'hover:bg-muted'">
49
+ <span class="truncate" v-html="highlightMatch(model)"></span>
50
+ <span
51
+ v-if="modelDocumentCounts && modelDocumentCounts[model] !== undefined && model !== currentModel"
52
+ class="ml-auto text-xs text-gray-400 bg-muted rounded px-1.5 py-[1px]"
53
+ >
54
+ {{formatCompactCount(modelDocumentCounts[model])}}
55
+ </span>
56
+ </router-link>
35
57
  </li>
36
58
  </ul>
37
- <div v-if="models.length === 0 && status === 'loaded'" class="p-2 bg-red-100">
59
+ <div v-if="filteredModels.length === 0 && modelSearch.trim()" class="px-2 py-2 text-sm text-content-tertiary">
60
+ No models match "{{modelSearch}}"
61
+ </div>
62
+ <div v-if="models.length === 0 && status === 'loaded'" class="p-2 bg-red-100 rounded-md">
38
63
  No models found
39
64
  </div>
40
65
  </nav>
66
+ <!-- Bottom toolbar -->
67
+ <div class="shrink-0 border-t border-edge bg-page px-2 py-1.5 flex items-center gap-1">
68
+ <button
69
+ type="button"
70
+ @click="hideSidebar = true"
71
+ class="rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted"
72
+ title="Hide sidebar"
73
+ >
74
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
75
+ <path stroke-linecap="round" stroke-linejoin="round" d="m18.75 4.5-7.5 7.5 7.5 7.5m-6-15L5.25 12l7.5 7.5" />
76
+ </svg>
77
+ </button>
78
+ <button
79
+ type="button"
80
+ @click="openModelSwitcher"
81
+ class="rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted"
82
+ title="Quick switch (Ctrl+P)"
83
+ >
84
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
85
+ <path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
86
+ </svg>
87
+ </button>
88
+ </div>
41
89
  </aside>
42
- <div class="documents bg-slate-50" ref="documentsList">
43
- <div class="relative h-[42px]" style="z-index: 1000">
90
+ <div class="documents bg-slate-50 min-w-0" ref="documentsList">
44
91
  <div class="documents-menu bg-slate-50">
45
92
  <div class="flex flex-row items-center w-full gap-2">
93
+ <button
94
+ v-if="hideSidebar === true || hideSidebar === null"
95
+ type="button"
96
+ @click="hideSidebar = false"
97
+ class="shrink-0 rounded-md p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted"
98
+ :class="hideSidebar === null ? 'lg:hidden' : ''"
99
+ title="Show sidebar"
100
+ >
101
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
102
+ <path stroke-linecap="round" stroke-linejoin="round" d="m5.25 4.5 7.5 7.5-7.5 7.5m6-15 7.5 7.5-7.5 7.5" />
103
+ </svg>
104
+ </button>
46
105
  <document-search
47
106
  ref="documentSearch"
48
107
  :value="searchText"
@@ -57,8 +116,8 @@
57
116
  <button
58
117
  @click="stagingSelect"
59
118
  type="button"
60
- :class="{ 'bg-gray-500 ring-inset ring-2 ring-gray-300 hover:bg-gray-600': selectMultiple, 'bg-ultramarine-600 hover:bg-ultramarine-500' : !selectMultiple }"
61
- class="rounded px-2 py-2 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600"
119
+ :class="{ 'bg-page0 ring-inset ring-2 ring-gray-300 hover:bg-gray-600': selectMultiple, 'bg-primary hover:bg-primary-hover' : !selectMultiple }"
120
+ class="rounded px-2 py-2 text-sm font-semibold text-primary-text shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
62
121
  >
63
122
  {{ selectMultiple ? 'Cancel' : 'Select' }}
64
123
  </button>
@@ -83,55 +142,55 @@
83
142
  @click="toggleActionsMenu"
84
143
  type="button"
85
144
  aria-label="More actions"
86
- class="rounded bg-white px-2 py-2 text-sm font-semibold text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600">
145
+ class="rounded bg-surface px-2 py-2 text-sm font-semibold text-content-secondary shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-page focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary">
87
146
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
88
147
  <path stroke-linecap="round" stroke-linejoin="round" d="M12 6.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Zm0 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Zm0 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Z" />
89
148
  </svg>
90
149
  </button>
91
150
  <div
92
151
  v-if="showActionsMenu"
93
- class="absolute right-0 mt-2 w-48 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none z-20"
152
+ class="absolute right-0 mt-2 w-48 origin-top-right rounded-md bg-surface shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none z-20"
94
153
  >
95
154
  <div class="py-1">
96
155
  <button
97
156
  @click="shouldShowExportModal = true; showActionsMenu = false"
98
157
  type="button"
99
- class="block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100"
158
+ class="block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted"
100
159
  >
101
160
  Export
102
161
  </button>
103
162
  <button
104
163
  @click="shouldShowCreateModal = true; showActionsMenu = false"
105
164
  type="button"
106
- class="block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100"
165
+ class="block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted"
107
166
  >
108
167
  Create
109
168
  </button>
110
169
  <button
111
170
  @click="openFieldSelection(); showActionsMenu = false"
112
171
  type="button"
113
- class="block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100"
172
+ class="block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted"
114
173
  >
115
174
  Projection
116
175
  </button>
117
176
  <button
118
177
  @click="openIndexModal"
119
178
  type="button"
120
- class="block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100"
179
+ class="block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted"
121
180
  >
122
181
  Indexes
123
182
  </button>
124
183
  <button
125
184
  @click="openCollectionInfo"
126
185
  type="button"
127
- class="block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100"
186
+ class="block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted"
128
187
  >
129
188
  Collection Info
130
189
  </button>
131
190
  <button
132
191
  @click="findOldestDocument"
133
192
  type="button"
134
- class="block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100"
193
+ class="block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted"
135
194
  >
136
195
  Find oldest document
137
196
  </button>
@@ -142,15 +201,15 @@
142
201
  <button
143
202
  @click="setOutputType('table')"
144
203
  type="button"
145
- class="relative inline-flex items-center rounded-none rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
146
- :class="outputType === 'table' ? 'bg-gray-200' : 'bg-white'">
204
+ class="relative inline-flex items-center rounded-none rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-page focus:z-10"
205
+ :class="outputType === 'table' ? 'bg-gray-200' : 'bg-surface'">
147
206
  <img class="h-5 w-5" src="images/table.svg">
148
207
  </button>
149
208
  <button
150
209
  @click="setOutputType('json')"
151
210
  type="button"
152
- class="relative -ml-px inline-flex items-center px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
153
- :class="outputType === 'json' ? 'bg-gray-200' : 'bg-white'">
211
+ class="relative -ml-px inline-flex items-center px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-page focus:z-10"
212
+ :class="outputType === 'json' ? 'bg-gray-200' : 'bg-surface'">
154
213
  <img class="h-5 w-5" src="images/json.svg">
155
214
  </button>
156
215
  <button
@@ -160,8 +219,8 @@
160
219
  :title="geoJsonFields.length > 0 ? 'Map view' : 'No GeoJSON fields detected'"
161
220
  class="relative -ml-px inline-flex items-center rounded-none rounded-r-md px-2 py-2 ring-1 ring-inset ring-gray-300 focus:z-10"
162
221
  :class="[
163
- geoJsonFields.length === 0 ? 'text-gray-300 cursor-not-allowed bg-gray-100' : 'text-gray-400 hover:bg-gray-50',
164
- outputType === 'map' ? 'bg-gray-200' : (geoJsonFields.length > 0 ? 'bg-white' : '')
222
+ geoJsonFields.length === 0 ? 'text-gray-300 cursor-not-allowed bg-muted' : 'text-gray-400 hover:bg-page',
223
+ outputType === 'map' ? 'bg-gray-200' : (geoJsonFields.length > 0 ? 'bg-surface' : '')
165
224
  ]">
166
225
  <svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
167
226
  <path stroke-linecap="round" stroke-linejoin="round" d="M9 6.75V15m6-6v8.25m.503 3.498 4.875-2.437c.381-.19.622-.58.622-1.006V4.82c0-.836-.88-1.38-1.628-1.006l-3.869 1.934c-.317.159-.69.159-1.006 0L9.503 3.252a1.125 1.125 0 0 0-1.006 0L3.622 5.689C3.24 5.88 3 6.27 3 6.695V19.18c0 .836.88 1.38 1.628 1.006l3.869-1.934c.317-.159.69-.159 1.006 0l4.994 2.497c.317.158.69.158 1.006 0Z" />
@@ -170,7 +229,6 @@
170
229
  </span>
171
230
  </div>
172
231
  </div>
173
- </div>
174
232
  <div class="documents-container relative">
175
233
  <div v-if="error">
176
234
  <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 relative m-4 rounded-md" role="alert">
@@ -180,7 +238,7 @@
180
238
  </div>
181
239
  <table v-else-if="outputType === 'table'">
182
240
  <thead class="bg-slate-50">
183
- <th v-for="path in filteredPaths" @click="addPathFilter(path.path)" class="cursor-pointer p-3">
241
+ <th v-for="path in filteredPaths" @click="addPathFilter(path.path)" class="bg-slate-50 cursor-pointer p-3">
184
242
  {{path.path}}
185
243
  <span class="path-type">
186
244
  ({{(path.instance || 'unknown')}})
@@ -190,7 +248,7 @@
190
248
  </th>
191
249
  </thead>
192
250
  <tbody>
193
- <tr v-for="document in documents" @click="handleDocumentClick(document, $event)" :key="document._id" class="bg-white hover:bg-slate-50">
251
+ <tr v-for="document in documents" @click="handleDocumentClick(document, $event)" :key="document._id" class="bg-surface hover:bg-slate-50">
194
252
  <td v-for="schemaPath in filteredPaths" class="p-3 cursor-pointer" :class="{ 'bg-blue-200': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }">
195
253
  <component
196
254
  :is="getComponentForPath(schemaPath)"
@@ -208,12 +266,12 @@
208
266
  @click="handleDocumentContainerClick(document, $event)"
209
267
  :class="[
210
268
  'group relative transition-colors rounded-md border border-slate-100',
211
- selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:shadow-sm hover:border-slate-300 bg-white'
269
+ selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:shadow-sm hover:border-slate-300 bg-surface'
212
270
  ]"
213
271
  >
214
272
  <button
215
273
  type="button"
216
- class="absolute top-2 right-2 z-10 inline-flex items-center rounded bg-ultramarine-600 px-2 py-1 text-xs font-semibold text-white shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600"
274
+ class="absolute top-2 right-2 z-10 inline-flex items-center rounded bg-primary px-2 py-1 text-xs font-semibold text-primary-text shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
217
275
  @click.stop="openDocument(document)"
218
276
  >
219
277
  Open this Document
@@ -223,12 +281,12 @@
223
281
  </div>
224
282
  </div>
225
283
  <div v-else-if="outputType === 'map'" class="flex flex-col h-full">
226
- <div class="p-2 bg-white border-b flex items-center gap-2">
227
- <label class="text-sm font-medium text-gray-700">GeoJSON Field:</label>
284
+ <div class="p-2 bg-surface border-b flex items-center gap-2">
285
+ <label class="text-sm font-medium text-content-secondary">GeoJSON Field:</label>
228
286
  <select
229
287
  :value="selectedGeoField"
230
288
  @change="setSelectedGeoField($event.target.value)"
231
- class="rounded-md border border-gray-300 py-1 px-2 text-sm focus:border-ultramarine-500 focus:ring-ultramarine-500"
289
+ class="rounded-md border border-edge-strong py-1 px-2 text-sm focus:border-primary focus:ring-primary"
232
290
  >
233
291
  <option v-for="field in geoJsonFields" :key="field.path" :value="field.path">
234
292
  {{ field.label }}
@@ -238,8 +296,8 @@
238
296
  @click="loadMoreDocuments"
239
297
  :disabled="loadedAllDocs"
240
298
  type="button"
241
- class="rounded px-2 py-1 text-xs font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600"
242
- :class="loadedAllDocs ? 'bg-gray-400 cursor-not-allowed' : 'bg-ultramarine-600 hover:bg-ultramarine-500'"
299
+ class="rounded px-2 py-1 text-xs font-semibold text-primary-text shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
300
+ :class="loadedAllDocs ? 'bg-gray-400 cursor-not-allowed' : 'bg-primary hover:bg-primary-hover'"
243
301
  >
244
302
  Load more
245
303
  </async-button>
@@ -271,7 +329,7 @@
271
329
  <div>
272
330
  <div class="font-bold flex items-center gap-2">
273
331
  <div>{{ index.name }}</div>
274
- <div v-if="isTTLIndex(index)" class="rounded-full bg-ultramarine-100 px-2 py-0.5 text-xs font-semibold text-ultramarine-700">
332
+ <div v-if="isTTLIndex(index)" class="rounded-full bg-primary-subtle px-2 py-0.5 text-xs font-semibold text-primary">
275
333
  TTL: {{ formatTTL(index.expireAfterSeconds) }}
276
334
  </div>
277
335
  </div>
@@ -296,33 +354,33 @@
296
354
  <div v-if="!collectionInfo" class="text-gray-600">Loading collection details...</div>
297
355
  <div v-else class="space-y-3">
298
356
  <div class="flex justify-between gap-4">
299
- <div class="font-semibold text-gray-700">Documents</div>
300
- <div class="text-gray-900">{{ formatNumber(collectionInfo.documentCount) }}</div>
357
+ <div class="font-semibold text-content-secondary">Documents</div>
358
+ <div class="text-content">{{ formatNumber(collectionInfo.documentCount) }}</div>
301
359
  </div>
302
360
  <div class="flex justify-between gap-4">
303
- <div class="font-semibold text-gray-700">Indexes</div>
304
- <div class="text-gray-900">{{ formatNumber(collectionInfo.indexCount) }}</div>
361
+ <div class="font-semibold text-content-secondary">Indexes</div>
362
+ <div class="text-content">{{ formatNumber(collectionInfo.indexCount) }}</div>
305
363
  </div>
306
364
  <div class="flex justify-between gap-4">
307
- <div class="font-semibold text-gray-700">Total Index Size</div>
308
- <div class="text-gray-900">{{ formatCollectionSize(collectionInfo.totalIndexSize) }}</div>
365
+ <div class="font-semibold text-content-secondary">Total Index Size</div>
366
+ <div class="text-content">{{ formatCollectionSize(collectionInfo.totalIndexSize) }}</div>
309
367
  </div>
310
368
  <div class="flex justify-between gap-4">
311
- <div class="font-semibold text-gray-700">Total Storage Size</div>
312
- <div class="text-gray-900">{{ formatCollectionSize(collectionInfo.size) }}</div>
369
+ <div class="font-semibold text-content-secondary">Total Storage Size</div>
370
+ <div class="text-content">{{ formatCollectionSize(collectionInfo.size) }}</div>
313
371
  </div>
314
372
  <div class="flex flex-col gap-1">
315
373
  <div class="flex justify-between gap-4">
316
- <div class="font-semibold text-gray-700">Collation</div>
317
- <div class="text-gray-900">{{ collectionInfo.hasCollation ? 'Yes' : 'No' }}</div>
374
+ <div class="font-semibold text-content-secondary">Collation</div>
375
+ <div class="text-content">{{ collectionInfo.hasCollation ? 'Yes' : 'No' }}</div>
318
376
  </div>
319
- <div v-if="collectionInfo.hasCollation" class="rounded bg-gray-100 p-3 text-sm text-gray-800 overflow-x-auto">
377
+ <div v-if="collectionInfo.hasCollation" class="rounded bg-muted p-3 text-sm text-gray-800 overflow-x-auto">
320
378
  <pre class="whitespace-pre-wrap">{{ JSON.stringify(collectionInfo.collation, null, 2) }}</pre>
321
379
  </div>
322
380
  </div>
323
381
  <div class="flex justify-between gap-4">
324
- <div class="font-semibold text-gray-700">Capped</div>
325
- <div class="text-gray-900">{{ collectionInfo.capped ? 'Yes' : 'No' }}</div>
382
+ <div class="font-semibold text-content-secondary">Capped</div>
383
+ <div class="text-content">{{ collectionInfo.capped ? 'Yes' : 'No' }}</div>
326
384
  </div>
327
385
  </div>
328
386
  </template>
@@ -331,16 +389,16 @@
331
389
  <template v-slot:body>
332
390
  <div class="modal-exit" @click="shouldShowFieldModal = false; selectedPaths = [...filteredPaths];">&times;</div>
333
391
  <div v-for="(path, index) in schemaPaths" :key="index" class="w-5 flex items-center">
334
- <input class="mt-0 h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-600 accent-sky-600" type="checkbox" :id="'path.path'+index" @change="addOrRemove(path)" :value="path.path" :checked="isSelected(path.path)" />
335
- <div class="ml-2 text-gray-700 grow shrink text-left">
392
+ <input class="mt-0 h-4 w-4 rounded border-edge-strong text-sky-600 focus:ring-sky-600 accent-sky-600" type="checkbox" :id="'path.path'+index" @change="addOrRemove(path)" :value="path.path" :checked="isSelected(path.path)" />
393
+ <div class="ml-2 text-content-secondary grow shrink text-left">
336
394
  <label :for="'path.path' + index">{{path.path}}</label>
337
395
  </div>
338
396
  </div>
339
397
  <div class="mt-4 flex gap-2">
340
- <button type="button" @click="filterDocuments()" class="rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600">Filter Selection</button>
398
+ <button type="button" @click="filterDocuments()" 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">Filter Selection</button>
341
399
  <button type="button" @click="selectAll()" class="rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600">Select All</button>
342
400
  <button type="button" @click="deselectAll()" class="rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600">Deselect All</button>
343
- <button type="button" @click="resetDocuments()" class="rounded-md bg-gray-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600" >Cancel</button>
401
+ <button type="button" @click="resetDocuments()" class="rounded-md bg-gray-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-page0 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600" >Cancel</button>
344
402
  </div>
345
403
  </template>
346
404
  </modal>
@@ -367,10 +425,18 @@
367
425
  <async-button @click="deleteDocuments" class="rounded bg-red-500 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600">
368
426
  Confirm
369
427
  </async-button>
370
- <button @click="shouldShowDeleteMultipleModal = false;" class="rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500">
428
+ <button @click="shouldShowDeleteMultipleModal = false;" class="rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-page0 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500">
371
429
  Cancel
372
430
  </button>
373
431
  </div>
374
432
  </template>
375
433
  </modal>
434
+ <model-switcher
435
+ :show="showModelSwitcher"
436
+ :models="models"
437
+ :recently-viewed-models="recentlyViewedModels"
438
+ :model-document-counts="modelDocumentCounts"
439
+ @close="showModelSwitcher = false"
440
+ @select="selectSwitcherModel"
441
+ ></model-switcher>
376
442
  </div>