@mongoosejs/studio 0.3.11 → 0.4.1
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/README.md +9 -0
- package/backend/actions/ChatThread/createChatMessage.js +15 -3
- package/backend/actions/ChatThread/createChatThread.js +8 -1
- package/backend/actions/ChatThread/index.js +1 -0
- package/backend/actions/ChatThread/streamChatMessage.js +49 -14
- package/backend/actions/ChatThread/toggleAgentMode.js +37 -0
- package/backend/actions/Model/getDocuments.js +7 -4
- package/backend/actions/Model/getDocumentsStream.js +7 -4
- package/backend/actions/Model/streamChatMessage.js +15 -4
- package/backend/authorize.js +1 -0
- package/backend/chatAgent/agentSystemPrompt.js +45 -0
- package/backend/chatAgent/agentToolMetadata.js +20 -0
- package/backend/chatAgent/getAgentTools.js +140 -0
- package/backend/db/chatMessageSchema.js +5 -0
- package/backend/db/chatThreadSchema.js +4 -0
- package/backend/helpers/isBindIPConnection.js +8 -0
- package/backend/helpers/isLocalhostConnection.js +8 -0
- package/backend/helpers/normalizeBindIPOption.js +16 -0
- package/backend/helpers/parseProjectionParam.js +180 -0
- package/backend/integrations/callLLM.js +4 -2
- package/backend/integrations/streamLLM.js +12 -4
- package/eslint.config.js +2 -1
- package/express.js +16 -0
- package/frontend/public/app.js +913 -110
- package/frontend/public/tw.css +34 -8
- package/frontend/src/api.js +6 -0
- package/frontend/src/chat/chat-message/chat-message.html +12 -1
- package/frontend/src/chat/chat-message/chat-message.js +8 -0
- package/frontend/src/chat/chat-message-script/chat-message-script.html +1 -1
- package/frontend/src/chat/chat.html +91 -44
- package/frontend/src/chat/chat.js +119 -19
- package/frontend/src/create-document/create-document.js +3 -0
- package/frontend/src/detail-objectid/detail-objectid.html +1 -0
- package/frontend/src/detail-objectid/detail-objectid.js +67 -0
- package/frontend/src/document-details/document-property/document-property.html +8 -3
- package/frontend/src/document-details/document-property/document-property.js +7 -0
- package/frontend/src/document-details/objectid-view-mode-picker/objectid-view-mode-picker.html +12 -0
- package/frontend/src/document-details/objectid-view-mode-picker/objectid-view-mode-picker.js +19 -0
- package/frontend/src/models/document-search/document-search.js +6 -0
- package/frontend/src/models/models.html +6 -8
- package/frontend/src/models/models.js +257 -85
- package/frontend/src/models/projection-search/projection-search.html +27 -0
- package/frontend/src/models/projection-search/projection-search.js +226 -0
- package/index.d.ts +1 -1
- package/package.json +3 -2
package/frontend/public/app.js
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
/******/ (() => { // webpackBootstrap
|
|
2
2
|
/******/ var __webpack_modules__ = ({
|
|
3
3
|
|
|
4
|
+
/***/ "./backend/chatAgent/agentToolMetadata.js"
|
|
5
|
+
/*!************************************************!*\
|
|
6
|
+
!*** ./backend/chatAgent/agentToolMetadata.js ***!
|
|
7
|
+
\************************************************/
|
|
8
|
+
(module) {
|
|
9
|
+
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
module.exports = [
|
|
14
|
+
{
|
|
15
|
+
name: 'estimatedDocumentCount',
|
|
16
|
+
description: 'Count the estimated number of documents in a collection. Times out after 5 seconds.'
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: 'find',
|
|
20
|
+
description: 'Run a find() query on a Mongoose model and return an array of documents. Times out after 5 seconds.'
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'findOne',
|
|
24
|
+
description: 'Run a findOne() query on a Mongoose model and return a single document or null. Times out after 5 seconds.'
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'typeCheck',
|
|
28
|
+
description: 'Type-checks the script and validates syntax using TypeScript and node --check'
|
|
29
|
+
}
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
/***/ },
|
|
34
|
+
|
|
4
35
|
/***/ "./frontend/src/_util/baseComponent.js"
|
|
5
36
|
/*!*********************************************!*\
|
|
6
37
|
!*** ./frontend/src/_util/baseComponent.js ***!
|
|
@@ -685,6 +716,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
685
716
|
shareChatThread(params) {
|
|
686
717
|
return client.post('', { action: 'ChatThread.shareChatThread', ...params }).then(res => res.data);
|
|
687
718
|
},
|
|
719
|
+
toggleAgentMode(params) {
|
|
720
|
+
return client.post('', { action: 'ChatThread.toggleAgentMode', ...params }).then(res => res.data);
|
|
721
|
+
},
|
|
688
722
|
streamChatMessage: async function* streamChatMessage(params) {
|
|
689
723
|
// Don't stream on Next.js or Netlify for now.
|
|
690
724
|
const data = await client.post('', { action: 'ChatThread.createChatMessage', ...params }).then(res => res.data);
|
|
@@ -861,6 +895,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
861
895
|
shareChatThread: function shareChatThread(params) {
|
|
862
896
|
return client.post('/ChatThread/shareChatThread', params).then(res => res.data);
|
|
863
897
|
},
|
|
898
|
+
toggleAgentMode: function toggleAgentMode(params) {
|
|
899
|
+
return client.post('/ChatThread/toggleAgentMode', params).then(res => res.data);
|
|
900
|
+
},
|
|
864
901
|
streamChatMessage: async function* streamChatMessage(params) {
|
|
865
902
|
const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
|
|
866
903
|
const url = window.MONGOOSE_STUDIO_CONFIG.baseURL + '/ChatThread/streamChatMessage?' + new URLSearchParams(params).toString();
|
|
@@ -1593,6 +1630,14 @@ module.exports = app => app.component('chat-message', {
|
|
|
1593
1630
|
marked(text) {
|
|
1594
1631
|
return marked(text);
|
|
1595
1632
|
},
|
|
1633
|
+
formatToolInput(input) {
|
|
1634
|
+
if (!input) return '';
|
|
1635
|
+
const parts = [];
|
|
1636
|
+
if (input.modelName) parts.push(input.modelName);
|
|
1637
|
+
if (input.filter && Object.keys(input.filter).length) parts.push(JSON.stringify(input.filter));
|
|
1638
|
+
if (input.limit && input.limit !== 10) parts.push('limit=' + input.limit);
|
|
1639
|
+
return parts.length ? '(' + parts.join(', ') + ')' : '';
|
|
1640
|
+
},
|
|
1596
1641
|
async executeScript(message, script) {
|
|
1597
1642
|
const { chatMessage } = await api.ChatMessage.executeScript({
|
|
1598
1643
|
chatMessageId: message._id,
|
|
@@ -1643,9 +1688,12 @@ module.exports = app => app.component('chat-message', {
|
|
|
1643
1688
|
|
|
1644
1689
|
|
|
1645
1690
|
const api = __webpack_require__(/*! ../api */ "./frontend/src/api.js");
|
|
1691
|
+
const agentToolMetadata = __webpack_require__(/*! ../../../backend/chatAgent/agentToolMetadata */ "./backend/chatAgent/agentToolMetadata.js");
|
|
1646
1692
|
const getCurrentDateTimeContext = __webpack_require__(/*! ../getCurrentDateTimeContext */ "./frontend/src/getCurrentDateTimeContext.js");
|
|
1647
1693
|
const template = __webpack_require__(/*! ./chat.html */ "./frontend/src/chat/chat.html");
|
|
1648
1694
|
|
|
1695
|
+
const AGENT_MODE_STORAGE_KEY = '_mongooseStudioAgentMode';
|
|
1696
|
+
|
|
1649
1697
|
module.exports = {
|
|
1650
1698
|
template: template,
|
|
1651
1699
|
props: ['threadId'],
|
|
@@ -1654,12 +1702,15 @@ module.exports = {
|
|
|
1654
1702
|
sendingMessage: false,
|
|
1655
1703
|
newMessage: '',
|
|
1656
1704
|
chatThreadId: null,
|
|
1705
|
+
draftAgentMode: false,
|
|
1657
1706
|
chatThreads: [],
|
|
1658
1707
|
chatMessages: [],
|
|
1659
1708
|
hideSidebar: null,
|
|
1709
|
+
showAgentSidebar: false,
|
|
1660
1710
|
sharingThread: false,
|
|
1661
1711
|
threadSearch: '',
|
|
1662
|
-
showProUpgradeModal: false
|
|
1712
|
+
showProUpgradeModal: false,
|
|
1713
|
+
agentTools: agentToolMetadata
|
|
1663
1714
|
}),
|
|
1664
1715
|
methods: {
|
|
1665
1716
|
async sendMessage() {
|
|
@@ -1668,7 +1719,9 @@ module.exports = {
|
|
|
1668
1719
|
const content = this.newMessage;
|
|
1669
1720
|
this.newMessage = '';
|
|
1670
1721
|
if (!this.chatThreadId) {
|
|
1671
|
-
const { chatThread } = await api.ChatThread.createChatThread(
|
|
1722
|
+
const { chatThread } = await api.ChatThread.createChatThread({
|
|
1723
|
+
agentMode: this.draftAgentMode
|
|
1724
|
+
});
|
|
1672
1725
|
this.chatThreads.unshift(chatThread);
|
|
1673
1726
|
this.chatThreadId = chatThread._id;
|
|
1674
1727
|
this.chatMessages = [];
|
|
@@ -1682,11 +1735,7 @@ module.exports = {
|
|
|
1682
1735
|
role: 'user'
|
|
1683
1736
|
});
|
|
1684
1737
|
|
|
1685
|
-
this
|
|
1686
|
-
if (this.$refs.messagesContainer) {
|
|
1687
|
-
this.$refs.messagesContainer.scrollTop = this.$refs.messagesContainer.scrollHeight;
|
|
1688
|
-
}
|
|
1689
|
-
});
|
|
1738
|
+
this.scrollToBottom();
|
|
1690
1739
|
|
|
1691
1740
|
const params = {
|
|
1692
1741
|
chatThreadId: this.chatThreadId,
|
|
@@ -1695,6 +1744,7 @@ module.exports = {
|
|
|
1695
1744
|
};
|
|
1696
1745
|
let userChatMessage = null;
|
|
1697
1746
|
let assistantChatMessage = null;
|
|
1747
|
+
const toolCalls = [];
|
|
1698
1748
|
for await (const event of api.ChatThread.streamChatMessage(params)) {
|
|
1699
1749
|
if (event.chatMessage) {
|
|
1700
1750
|
if (!userChatMessage) {
|
|
@@ -1703,6 +1753,7 @@ module.exports = {
|
|
|
1703
1753
|
} else {
|
|
1704
1754
|
const assistantChatMessageIndex = this.chatMessages.indexOf(assistantChatMessage);
|
|
1705
1755
|
assistantChatMessage = event.chatMessage;
|
|
1756
|
+
assistantChatMessage.toolCalls = toolCalls;
|
|
1706
1757
|
if (assistantChatMessageIndex !== -1) {
|
|
1707
1758
|
this.chatMessages.splice(assistantChatMessageIndex, 1, assistantChatMessage);
|
|
1708
1759
|
} else {
|
|
@@ -1715,27 +1766,44 @@ module.exports = {
|
|
|
1715
1766
|
thread.title = event.chatThread.title;
|
|
1716
1767
|
}
|
|
1717
1768
|
}
|
|
1769
|
+
} else if (event.toolCall) {
|
|
1770
|
+
toolCalls.push({ toolName: event.toolCall.toolName, input: event.toolCall.input, status: 'running' });
|
|
1771
|
+
if (!assistantChatMessage) {
|
|
1772
|
+
assistantChatMessage = {
|
|
1773
|
+
_id: Math.random().toString(36).substr(2, 9),
|
|
1774
|
+
content: '',
|
|
1775
|
+
role: 'assistant',
|
|
1776
|
+
toolCalls: [...toolCalls]
|
|
1777
|
+
};
|
|
1778
|
+
this.chatMessages.push(assistantChatMessage);
|
|
1779
|
+
assistantChatMessage = this.chatMessages[this.chatMessages.length - 1];
|
|
1780
|
+
} else {
|
|
1781
|
+
assistantChatMessage.toolCalls = [...toolCalls];
|
|
1782
|
+
}
|
|
1783
|
+
this.scrollToBottom();
|
|
1784
|
+
} else if (event.toolResult) {
|
|
1785
|
+
const tc = toolCalls.find(t => t.toolName === event.toolResult.toolName && t.status === 'running');
|
|
1786
|
+
if (tc) {
|
|
1787
|
+
tc.status = 'done';
|
|
1788
|
+
}
|
|
1789
|
+
if (assistantChatMessage) {
|
|
1790
|
+
assistantChatMessage.toolCalls = [...toolCalls];
|
|
1791
|
+
}
|
|
1792
|
+
this.scrollToBottom();
|
|
1718
1793
|
} else if (event.textPart) {
|
|
1719
1794
|
if (!assistantChatMessage) {
|
|
1720
1795
|
assistantChatMessage = {
|
|
1721
1796
|
_id: Math.random().toString(36).substr(2, 9),
|
|
1722
1797
|
content: event.textPart,
|
|
1723
|
-
role: 'assistant'
|
|
1798
|
+
role: 'assistant',
|
|
1799
|
+
toolCalls: [...toolCalls]
|
|
1724
1800
|
};
|
|
1725
1801
|
this.chatMessages.push(assistantChatMessage);
|
|
1726
1802
|
assistantChatMessage = this.chatMessages[this.chatMessages.length - 1];
|
|
1727
|
-
this
|
|
1728
|
-
if (this.$refs.messagesContainer) {
|
|
1729
|
-
this.$refs.messagesContainer.scrollTop = this.$refs.messagesContainer.scrollHeight;
|
|
1730
|
-
}
|
|
1731
|
-
});
|
|
1803
|
+
this.scrollToBottom();
|
|
1732
1804
|
} else {
|
|
1733
1805
|
assistantChatMessage.content += event.textPart;
|
|
1734
|
-
this
|
|
1735
|
-
if (this.$refs.messagesContainer) {
|
|
1736
|
-
this.$refs.messagesContainer.scrollTop = this.$refs.messagesContainer.scrollHeight;
|
|
1737
|
-
}
|
|
1738
|
-
});
|
|
1806
|
+
this.scrollToBottom();
|
|
1739
1807
|
}
|
|
1740
1808
|
} else if (event.message) {
|
|
1741
1809
|
throw new Error(event.message);
|
|
@@ -1754,10 +1822,38 @@ module.exports = {
|
|
|
1754
1822
|
this.$refs.messagesContainer.scrollTop = this.$refs.messagesContainer.scrollHeight;
|
|
1755
1823
|
}
|
|
1756
1824
|
});
|
|
1825
|
+
} catch (err) {
|
|
1826
|
+
this.$toast.error(err?.message || 'Failed to send message.');
|
|
1757
1827
|
} finally {
|
|
1758
1828
|
this.sendingMessage = false;
|
|
1759
1829
|
}
|
|
1760
1830
|
},
|
|
1831
|
+
async toggleAgentMode() {
|
|
1832
|
+
const wasEnabled = this.isAgentModeEnabled;
|
|
1833
|
+
const newValue = !this.isAgentModeEnabled;
|
|
1834
|
+
this.draftAgentMode = newValue;
|
|
1835
|
+
this.persistAgentModePreference(newValue);
|
|
1836
|
+
if (!this.chatThreadId) {
|
|
1837
|
+
this.maybeOpenAgentSidebar({ wasEnabled, isEnabled: newValue });
|
|
1838
|
+
return;
|
|
1839
|
+
}
|
|
1840
|
+
const { chatThread } = await api.ChatThread.toggleAgentMode({
|
|
1841
|
+
chatThreadId: this.chatThreadId,
|
|
1842
|
+
agentMode: newValue
|
|
1843
|
+
});
|
|
1844
|
+
const idx = this.chatThreads.findIndex(t => t._id === chatThread._id);
|
|
1845
|
+
if (idx !== -1) {
|
|
1846
|
+
this.chatThreads.splice(idx, 1, chatThread);
|
|
1847
|
+
}
|
|
1848
|
+
this.maybeOpenAgentSidebar({ wasEnabled, isEnabled: newValue });
|
|
1849
|
+
},
|
|
1850
|
+
scrollToBottom() {
|
|
1851
|
+
this.$nextTick(() => {
|
|
1852
|
+
if (this.$refs.messagesContainer) {
|
|
1853
|
+
this.$refs.messagesContainer.scrollTop = this.$refs.messagesContainer.scrollHeight;
|
|
1854
|
+
}
|
|
1855
|
+
});
|
|
1856
|
+
},
|
|
1761
1857
|
handleEnter(ev) {
|
|
1762
1858
|
if (!ev.shiftKey) {
|
|
1763
1859
|
this.sendMessage();
|
|
@@ -1777,7 +1873,9 @@ module.exports = {
|
|
|
1777
1873
|
return message.role === 'user' ? 'bg-muted' : '';
|
|
1778
1874
|
},
|
|
1779
1875
|
async createNewThread() {
|
|
1780
|
-
const { chatThread } = await api.ChatThread.createChatThread(
|
|
1876
|
+
const { chatThread } = await api.ChatThread.createChatThread({
|
|
1877
|
+
agentMode: this.draftAgentMode
|
|
1878
|
+
});
|
|
1781
1879
|
this.$toast.success('Chat thread created!');
|
|
1782
1880
|
this.$router.push('/chat/' + chatThread._id);
|
|
1783
1881
|
},
|
|
@@ -1819,12 +1917,57 @@ module.exports = {
|
|
|
1819
1917
|
} finally {
|
|
1820
1918
|
this.sharingThread = false;
|
|
1821
1919
|
}
|
|
1920
|
+
},
|
|
1921
|
+
isDesktopViewport() {
|
|
1922
|
+
return typeof window !== 'undefined' &&
|
|
1923
|
+
typeof window.matchMedia === 'function' &&
|
|
1924
|
+
window.matchMedia('(min-width: 1024px)').matches;
|
|
1925
|
+
},
|
|
1926
|
+
getAgentModePreference() {
|
|
1927
|
+
if (typeof window === 'undefined') {
|
|
1928
|
+
return false;
|
|
1929
|
+
}
|
|
1930
|
+
return window.localStorage?.getItem(AGENT_MODE_STORAGE_KEY) === 'true';
|
|
1931
|
+
},
|
|
1932
|
+
persistAgentModePreference(agentMode) {
|
|
1933
|
+
if (typeof window !== 'undefined') {
|
|
1934
|
+
window.localStorage?.setItem(AGENT_MODE_STORAGE_KEY, String(agentMode));
|
|
1935
|
+
}
|
|
1936
|
+
},
|
|
1937
|
+
async syncCurrentThreadAgentMode() {
|
|
1938
|
+
if (!this.chatThreadId || !this.currentThread || !this.draftAgentMode || this.currentThread.agentMode) {
|
|
1939
|
+
return;
|
|
1940
|
+
}
|
|
1941
|
+
const { chatThread } = await api.ChatThread.toggleAgentMode({
|
|
1942
|
+
chatThreadId: this.chatThreadId,
|
|
1943
|
+
agentMode: true
|
|
1944
|
+
});
|
|
1945
|
+
const idx = this.chatThreads.findIndex(t => t._id === chatThread._id);
|
|
1946
|
+
if (idx !== -1) {
|
|
1947
|
+
this.chatThreads.splice(idx, 1, chatThread);
|
|
1948
|
+
}
|
|
1949
|
+
},
|
|
1950
|
+
maybeOpenAgentSidebar({ wasEnabled, isEnabled }) {
|
|
1951
|
+
if (!isEnabled) {
|
|
1952
|
+
this.showAgentSidebar = false;
|
|
1953
|
+
return;
|
|
1954
|
+
}
|
|
1955
|
+
if (wasEnabled || !this.isDesktopViewport()) {
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
this.showAgentSidebar = true;
|
|
1959
|
+
},
|
|
1960
|
+
closeAgentSidebar() {
|
|
1961
|
+
this.showAgentSidebar = false;
|
|
1822
1962
|
}
|
|
1823
1963
|
},
|
|
1824
1964
|
computed: {
|
|
1825
1965
|
currentThread() {
|
|
1826
1966
|
return this.chatThreads.find(t => t._id === this.chatThreadId);
|
|
1827
1967
|
},
|
|
1968
|
+
isAgentModeEnabled() {
|
|
1969
|
+
return this.currentThread?.agentMode ?? this.draftAgentMode;
|
|
1970
|
+
},
|
|
1828
1971
|
hasWorkspace() {
|
|
1829
1972
|
return !!window.MONGOOSE_STUDIO_CONFIG.workspace?._id;
|
|
1830
1973
|
},
|
|
@@ -1842,10 +1985,12 @@ module.exports = {
|
|
|
1842
1985
|
async mounted() {
|
|
1843
1986
|
window.pageState = this;
|
|
1844
1987
|
|
|
1988
|
+
this.draftAgentMode = this.getAgentModePreference();
|
|
1845
1989
|
this.chatThreadId = this.threadId;
|
|
1846
1990
|
const { chatThreads } = await api.ChatThread.listChatThreads();
|
|
1847
1991
|
this.chatThreads = chatThreads;
|
|
1848
1992
|
if (this.chatThreadId) {
|
|
1993
|
+
await this.syncCurrentThreadAgentMode();
|
|
1849
1994
|
const { chatMessages } = await api.ChatThread.getChatThread({ chatThreadId: this.chatThreadId });
|
|
1850
1995
|
this.chatMessages = chatMessages;
|
|
1851
1996
|
}
|
|
@@ -2055,6 +2200,9 @@ module.exports = app => app.component('create-document', {
|
|
|
2055
2200
|
documentData: this.aiOriginalDocument,
|
|
2056
2201
|
currentDateTime: getCurrentDateTimeContext()
|
|
2057
2202
|
})) {
|
|
2203
|
+
if (event?.message) {
|
|
2204
|
+
throw new Error(event.message);
|
|
2205
|
+
}
|
|
2058
2206
|
if (event?.textPart) {
|
|
2059
2207
|
this.aiSuggestion += event.textPart;
|
|
2060
2208
|
}
|
|
@@ -4268,6 +4416,84 @@ module.exports = app => app.component('detail-default', {
|
|
|
4268
4416
|
});
|
|
4269
4417
|
|
|
4270
4418
|
|
|
4419
|
+
/***/ },
|
|
4420
|
+
|
|
4421
|
+
/***/ "./frontend/src/detail-objectid/detail-objectid.js"
|
|
4422
|
+
/*!*********************************************************!*\
|
|
4423
|
+
!*** ./frontend/src/detail-objectid/detail-objectid.js ***!
|
|
4424
|
+
\*********************************************************/
|
|
4425
|
+
(module, __unused_webpack_exports, __webpack_require__) {
|
|
4426
|
+
|
|
4427
|
+
"use strict";
|
|
4428
|
+
|
|
4429
|
+
|
|
4430
|
+
const template = __webpack_require__(/*! ./detail-objectid.html */ "./frontend/src/detail-objectid/detail-objectid.html");
|
|
4431
|
+
const ObjectId = __webpack_require__(/*! mongoose/lib/types/objectid */ "./node_modules/mongoose/lib/types/objectid.js");
|
|
4432
|
+
|
|
4433
|
+
module.exports = app => app.component('detail-objectid', {
|
|
4434
|
+
template,
|
|
4435
|
+
props: ['value', 'viewMode'],
|
|
4436
|
+
emits: ['updated'],
|
|
4437
|
+
watch: {
|
|
4438
|
+
displayValue: {
|
|
4439
|
+
immediate: true,
|
|
4440
|
+
handler(val) {
|
|
4441
|
+
this.$emit('updated', val);
|
|
4442
|
+
}
|
|
4443
|
+
}
|
|
4444
|
+
},
|
|
4445
|
+
computed: {
|
|
4446
|
+
format() {
|
|
4447
|
+
return this.viewMode || 'hex';
|
|
4448
|
+
},
|
|
4449
|
+
hexValue() {
|
|
4450
|
+
if (this.value == null) {
|
|
4451
|
+
return null;
|
|
4452
|
+
}
|
|
4453
|
+
if (typeof this.value === 'string') {
|
|
4454
|
+
return this.value;
|
|
4455
|
+
}
|
|
4456
|
+
if (typeof this.value === 'object') {
|
|
4457
|
+
if (typeof this.value.toHexString === 'function') {
|
|
4458
|
+
return this.value.toHexString();
|
|
4459
|
+
}
|
|
4460
|
+
if (typeof this.value.$oid === 'string') {
|
|
4461
|
+
return this.value.$oid;
|
|
4462
|
+
}
|
|
4463
|
+
}
|
|
4464
|
+
return String(this.value);
|
|
4465
|
+
},
|
|
4466
|
+
objectIdDate() {
|
|
4467
|
+
const hex = this.hexValue;
|
|
4468
|
+
return new ObjectId(hex).getTimestamp();
|
|
4469
|
+
},
|
|
4470
|
+
displayValue() {
|
|
4471
|
+
if (this.value == null) {
|
|
4472
|
+
return String(this.value);
|
|
4473
|
+
}
|
|
4474
|
+
|
|
4475
|
+
const hex = this.hexValue;
|
|
4476
|
+
if (this.format === 'object_call') {
|
|
4477
|
+
return `ObjectId('${hex}')`;
|
|
4478
|
+
}
|
|
4479
|
+
if (this.format === 'unix_seconds') {
|
|
4480
|
+
if (!this.objectIdDate) {
|
|
4481
|
+
return 'Invalid ObjectId';
|
|
4482
|
+
}
|
|
4483
|
+
return String(Math.floor(this.objectIdDate.getTime() / 1000));
|
|
4484
|
+
}
|
|
4485
|
+
if (this.format === 'date') {
|
|
4486
|
+
if (!this.objectIdDate) {
|
|
4487
|
+
return 'Invalid ObjectId';
|
|
4488
|
+
}
|
|
4489
|
+
return this.objectIdDate.toISOString();
|
|
4490
|
+
}
|
|
4491
|
+
return hex;
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
});
|
|
4495
|
+
|
|
4496
|
+
|
|
4271
4497
|
/***/ },
|
|
4272
4498
|
|
|
4273
4499
|
/***/ "./frontend/src/document-details/date-view-mode-picker/date-view-mode-picker.js"
|
|
@@ -4792,6 +5018,7 @@ module.exports = app => app.component('document-property', {
|
|
|
4792
5018
|
return {
|
|
4793
5019
|
dateType: 'picker', // picker, iso
|
|
4794
5020
|
dateViewMode: 'utc_iso',
|
|
5021
|
+
objectIdViewMode: 'hex',
|
|
4795
5022
|
renderedValue: UNSET,
|
|
4796
5023
|
isCollapsed: false, // Start uncollapsed by default
|
|
4797
5024
|
isValueExpanded: false, // Track if the value is expanded
|
|
@@ -4812,6 +5039,9 @@ module.exports = app => app.component('document-property', {
|
|
|
4812
5039
|
isDatePath() {
|
|
4813
5040
|
return this.path?.instance === 'Date';
|
|
4814
5041
|
},
|
|
5042
|
+
isObjectIdPath() {
|
|
5043
|
+
return this.path?.instance === 'ObjectID' || this.path?.instance === 'ObjectId';
|
|
5044
|
+
},
|
|
4815
5045
|
rawValue() {
|
|
4816
5046
|
return this.getValueForPath(this.path.path);
|
|
4817
5047
|
},
|
|
@@ -4951,6 +5181,9 @@ module.exports = app => app.component('document-property', {
|
|
|
4951
5181
|
if (schemaPath.instance === 'Date') {
|
|
4952
5182
|
return 'detail-date';
|
|
4953
5183
|
}
|
|
5184
|
+
if (schemaPath.instance === 'ObjectID' || schemaPath.instance === 'ObjectId') {
|
|
5185
|
+
return 'detail-objectid';
|
|
5186
|
+
}
|
|
4954
5187
|
return 'detail-default';
|
|
4955
5188
|
},
|
|
4956
5189
|
getEditComponentForPath(path) {
|
|
@@ -5078,6 +5311,36 @@ module.exports = app => app.component('document-property', {
|
|
|
5078
5311
|
});
|
|
5079
5312
|
|
|
5080
5313
|
|
|
5314
|
+
/***/ },
|
|
5315
|
+
|
|
5316
|
+
/***/ "./frontend/src/document-details/objectid-view-mode-picker/objectid-view-mode-picker.js"
|
|
5317
|
+
/*!**********************************************************************************************!*\
|
|
5318
|
+
!*** ./frontend/src/document-details/objectid-view-mode-picker/objectid-view-mode-picker.js ***!
|
|
5319
|
+
\**********************************************************************************************/
|
|
5320
|
+
(module, __unused_webpack_exports, __webpack_require__) {
|
|
5321
|
+
|
|
5322
|
+
"use strict";
|
|
5323
|
+
|
|
5324
|
+
|
|
5325
|
+
const template = __webpack_require__(/*! ./objectid-view-mode-picker.html */ "./frontend/src/document-details/objectid-view-mode-picker/objectid-view-mode-picker.html");
|
|
5326
|
+
|
|
5327
|
+
module.exports = app => app.component('objectid-view-mode-picker', {
|
|
5328
|
+
template,
|
|
5329
|
+
props: ['viewMode'],
|
|
5330
|
+
emits: ['update:viewMode'],
|
|
5331
|
+
computed: {
|
|
5332
|
+
format() {
|
|
5333
|
+
return this.viewMode || 'hex';
|
|
5334
|
+
}
|
|
5335
|
+
},
|
|
5336
|
+
methods: {
|
|
5337
|
+
onFormatChange(newFormat) {
|
|
5338
|
+
this.$emit('update:viewMode', newFormat);
|
|
5339
|
+
}
|
|
5340
|
+
}
|
|
5341
|
+
});
|
|
5342
|
+
|
|
5343
|
+
|
|
5081
5344
|
/***/ },
|
|
5082
5345
|
|
|
5083
5346
|
/***/ "./frontend/src/document/confirm-changes/confirm-changes.js"
|
|
@@ -7140,6 +7403,12 @@ module.exports = app => app.component('document-search', {
|
|
|
7140
7403
|
this.$refs.searchInput.focus();
|
|
7141
7404
|
},
|
|
7142
7405
|
methods: {
|
|
7406
|
+
focusInput() {
|
|
7407
|
+
const input = this.$refs.searchInput;
|
|
7408
|
+
if (input && typeof input.focus === 'function') {
|
|
7409
|
+
input.focus();
|
|
7410
|
+
}
|
|
7411
|
+
},
|
|
7143
7412
|
emitSearch() {
|
|
7144
7413
|
this.$emit('input', this.searchText);
|
|
7145
7414
|
this.$emit('search', this.searchText);
|
|
@@ -7392,58 +7661,126 @@ const OUTPUT_TYPE_STORAGE_KEY = 'studio:model-output-type';
|
|
|
7392
7661
|
const SELECTED_GEO_FIELD_STORAGE_KEY = 'studio:model-selected-geo-field';
|
|
7393
7662
|
const SHOW_ROW_NUMBERS_STORAGE_KEY = 'studio:model-show-row-numbers';
|
|
7394
7663
|
const PROJECTION_MODE_QUERY_KEY = 'projectionMode';
|
|
7664
|
+
const PROJECTION_INPUT_QUERY_KEY = 'projectionInput';
|
|
7395
7665
|
const RECENTLY_VIEWED_MODELS_KEY = 'studio:recently-viewed-models';
|
|
7396
7666
|
const MAX_RECENT_MODELS = 4;
|
|
7667
|
+
const FOCUS_AFTER_MODELS_REMOUNT_KEY = '__studioModelsFocusAfterRemount';
|
|
7397
7668
|
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
return [];
|
|
7402
|
-
}
|
|
7403
|
-
const s = typeof fields === 'string' ? fields : String(fields);
|
|
7404
|
-
const trimmed = s.trim();
|
|
7405
|
-
if (!trimmed) {
|
|
7406
|
-
return [];
|
|
7407
|
-
}
|
|
7408
|
-
let parsed;
|
|
7409
|
-
try {
|
|
7410
|
-
parsed = JSON.parse(trimmed);
|
|
7411
|
-
} catch (e) {
|
|
7412
|
-
return [];
|
|
7669
|
+
function setModelsRemountFocusIntent(target) {
|
|
7670
|
+
if (typeof window === 'undefined') {
|
|
7671
|
+
return;
|
|
7413
7672
|
}
|
|
7414
|
-
|
|
7415
|
-
|
|
7673
|
+
window[FOCUS_AFTER_MODELS_REMOUNT_KEY] = target;
|
|
7674
|
+
}
|
|
7675
|
+
|
|
7676
|
+
function takeModelsRemountFocusIntent() {
|
|
7677
|
+
if (typeof window === 'undefined') {
|
|
7678
|
+
return null;
|
|
7416
7679
|
}
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7680
|
+
const v = window[FOCUS_AFTER_MODELS_REMOUNT_KEY] || null;
|
|
7681
|
+
delete window[FOCUS_AFTER_MODELS_REMOUNT_KEY];
|
|
7682
|
+
return v;
|
|
7683
|
+
}
|
|
7684
|
+
|
|
7685
|
+
/**
|
|
7686
|
+
* Split projection list tokens on commas/whitespace, merging a lone `-` or `+` with the next
|
|
7687
|
+
* chunk so `- password` and `-password` both exclude `password`.
|
|
7688
|
+
* @returns {string[]|null} null when a dangling `+`/`-` has no following field name.
|
|
7689
|
+
*/
|
|
7690
|
+
function normalizeProjectionTokens(trimmed) {
|
|
7691
|
+
const rawTokens = trimmed.split(/[,\s]+/).filter(Boolean);
|
|
7692
|
+
const tokens = [];
|
|
7693
|
+
for (let i = 0; i < rawTokens.length; i++) {
|
|
7694
|
+
const t = rawTokens[i];
|
|
7695
|
+
if (t === '-' || t === '+') {
|
|
7696
|
+
if (i + 1 >= rawTokens.length) {
|
|
7697
|
+
return null;
|
|
7698
|
+
}
|
|
7699
|
+
tokens.push(t + rawTokens[++i]);
|
|
7700
|
+
} else {
|
|
7701
|
+
tokens.push(t);
|
|
7702
|
+
}
|
|
7421
7703
|
}
|
|
7422
|
-
return
|
|
7704
|
+
return tokens;
|
|
7423
7705
|
}
|
|
7424
7706
|
|
|
7425
|
-
/**
|
|
7426
|
-
|
|
7427
|
-
|
|
7707
|
+
/**
|
|
7708
|
+
* Parse projection object syntax like:
|
|
7709
|
+
* { deletedAt: 1 }
|
|
7710
|
+
* { "deletedAt": true, email: 1 }
|
|
7711
|
+
* { password: 0 }
|
|
7712
|
+
*
|
|
7713
|
+
* Supported values are 1/0/true/false (quoted or unquoted).
|
|
7714
|
+
* Returns include paths, exclude-derived include paths, or null when invalid.
|
|
7715
|
+
*/
|
|
7716
|
+
function parseProjectionObjectNotation(trimmed, schemaPaths) {
|
|
7717
|
+
if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
|
|
7428
7718
|
return null;
|
|
7429
7719
|
}
|
|
7430
|
-
const
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
return null;
|
|
7720
|
+
const body = trimmed.slice(1, -1).trim();
|
|
7721
|
+
if (!body) {
|
|
7722
|
+
return [];
|
|
7434
7723
|
}
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7724
|
+
|
|
7725
|
+
const pairRe = /(?:^|,)\s*(?:"([^"]+)"|'([^']+)'|([a-zA-Z_$][\w.$]*))\s*:\s*("[^"]*"|'[^']*'|[^,]+?)\s*(?=,|$)/g;
|
|
7726
|
+
const includeKeys = [];
|
|
7727
|
+
const excludeKeys = [];
|
|
7728
|
+
let matchedChars = '';
|
|
7729
|
+
let match;
|
|
7730
|
+
|
|
7731
|
+
while ((match = pairRe.exec(body)) !== null) {
|
|
7732
|
+
matchedChars += match[0];
|
|
7733
|
+
const key = (match[1] || match[2] || match[3] || '').trim();
|
|
7734
|
+
const rawValue = (match[4] || '').trim();
|
|
7735
|
+
if (!key || !rawValue) {
|
|
7736
|
+
return null;
|
|
7439
7737
|
}
|
|
7440
|
-
|
|
7441
|
-
|
|
7738
|
+
|
|
7739
|
+
const valueLower = rawValue.replace(/^['"]|['"]$/g, '').trim().toLowerCase();
|
|
7740
|
+
const isInclude = valueLower === '1' || valueLower === 'true';
|
|
7741
|
+
const isExclude = valueLower === '0' || valueLower === 'false';
|
|
7742
|
+
if (!isInclude && !isExclude) {
|
|
7743
|
+
return null;
|
|
7442
7744
|
}
|
|
7443
|
-
|
|
7745
|
+
if (isInclude) {
|
|
7746
|
+
includeKeys.push(key);
|
|
7747
|
+
} else {
|
|
7748
|
+
excludeKeys.push(key);
|
|
7749
|
+
}
|
|
7750
|
+
}
|
|
7751
|
+
|
|
7752
|
+
// Reject malformed object strings where regex skipped segments.
|
|
7753
|
+
const normalizedBody = body.replace(/\s+/g, '');
|
|
7754
|
+
const normalizedMatched = matchedChars.replace(/\s+/g, '').replace(/^,/, '');
|
|
7755
|
+
if (!normalizedMatched || normalizedMatched !== normalizedBody) {
|
|
7444
7756
|
return null;
|
|
7445
7757
|
}
|
|
7446
|
-
|
|
7758
|
+
|
|
7759
|
+
const normalizeKey = (key) => String(key).trim();
|
|
7760
|
+
if (includeKeys.length > 0 && excludeKeys.length > 0) {
|
|
7761
|
+
const includeSet = new Set(includeKeys.map(normalizeKey));
|
|
7762
|
+
for (const path of excludeKeys) {
|
|
7763
|
+
const ex = normalizeKey(path);
|
|
7764
|
+
for (const k of Array.from(includeSet)) {
|
|
7765
|
+
if (k.toLowerCase() === ex.toLowerCase()) {
|
|
7766
|
+
includeSet.delete(k);
|
|
7767
|
+
}
|
|
7768
|
+
}
|
|
7769
|
+
}
|
|
7770
|
+
if (includeSet.size === 0) {
|
|
7771
|
+
return null;
|
|
7772
|
+
}
|
|
7773
|
+
return Array.from(includeSet);
|
|
7774
|
+
}
|
|
7775
|
+
|
|
7776
|
+
if (excludeKeys.length > 0) {
|
|
7777
|
+
const excludeNorm = excludeKeys.map(normalizeKey);
|
|
7778
|
+
return schemaPaths
|
|
7779
|
+
.map(p => p.path)
|
|
7780
|
+
.filter(p => !excludeNorm.some(ex => ex === p || p.toLowerCase() === ex.toLowerCase()));
|
|
7781
|
+
}
|
|
7782
|
+
|
|
7783
|
+
return includeKeys.map(normalizeKey);
|
|
7447
7784
|
}
|
|
7448
7785
|
|
|
7449
7786
|
module.exports = app => app.component('models', {
|
|
@@ -7588,10 +7925,19 @@ module.exports = app => app.component('models', {
|
|
|
7588
7925
|
this.setOutputType('json');
|
|
7589
7926
|
}
|
|
7590
7927
|
this.$nextTick(() => {
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
|
|
7594
|
-
|
|
7928
|
+
const focusIntent = takeModelsRemountFocusIntent();
|
|
7929
|
+
if (focusIntent === 'documentSearch') {
|
|
7930
|
+
const ds = this.$refs.documentSearch;
|
|
7931
|
+
if (ds && typeof ds.focusInput === 'function') {
|
|
7932
|
+
ds.focusInput();
|
|
7933
|
+
}
|
|
7934
|
+
return;
|
|
7935
|
+
}
|
|
7936
|
+
if (focusIntent === 'projection' && this.isProjectionMenuSelected) {
|
|
7937
|
+
const projectionSearch = this.$refs.projectionSearch;
|
|
7938
|
+
if (projectionSearch && typeof projectionSearch.focusInput === 'function') {
|
|
7939
|
+
projectionSearch.focusInput();
|
|
7940
|
+
}
|
|
7595
7941
|
}
|
|
7596
7942
|
});
|
|
7597
7943
|
},
|
|
@@ -7998,21 +8344,21 @@ module.exports = app => app.component('models', {
|
|
|
7998
8344
|
params.searchText = this.searchText;
|
|
7999
8345
|
}
|
|
8000
8346
|
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
|
|
8347
|
+
const projectionInput = typeof this.query?.[PROJECTION_INPUT_QUERY_KEY] === 'string' ?
|
|
8348
|
+
this.query[PROJECTION_INPUT_QUERY_KEY].trim() :
|
|
8349
|
+
'';
|
|
8350
|
+
if (projectionInput) {
|
|
8351
|
+
params.projectionInput = projectionInput;
|
|
8352
|
+
}
|
|
8353
|
+
|
|
8354
|
+
if (!projectionInput && this.isProjectionMenuSelected === true) {
|
|
8006
8355
|
const fieldPaths = this.filteredPaths && this.filteredPaths.length > 0
|
|
8007
8356
|
? this.filteredPaths.map(p => p.path).filter(Boolean)
|
|
8008
8357
|
: null;
|
|
8009
8358
|
if (fieldPaths && fieldPaths.length > 0) {
|
|
8010
|
-
|
|
8359
|
+
params.projectionInput = fieldPaths.join(' ');
|
|
8011
8360
|
}
|
|
8012
8361
|
}
|
|
8013
|
-
if (fieldsParam) {
|
|
8014
|
-
params.fields = fieldsParam;
|
|
8015
|
-
}
|
|
8016
8362
|
|
|
8017
8363
|
return params;
|
|
8018
8364
|
},
|
|
@@ -8047,13 +8393,16 @@ module.exports = app => app.component('models', {
|
|
|
8047
8393
|
if (this.currentModel != null) {
|
|
8048
8394
|
await this.getDocuments();
|
|
8049
8395
|
}
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
this.
|
|
8056
|
-
|
|
8396
|
+
const routeProjectionInput = this.$route.query?.[PROJECTION_INPUT_QUERY_KEY];
|
|
8397
|
+
if (typeof routeProjectionInput === 'string' && routeProjectionInput.trim().length > 0) {
|
|
8398
|
+
const urlPaths = this.normalizeProjectionPathsForDisplay(routeProjectionInput);
|
|
8399
|
+
if (urlPaths !== null) {
|
|
8400
|
+
const nextFiltered = urlPaths
|
|
8401
|
+
.map(path => this.schemaPaths.find(p => p.path === path))
|
|
8402
|
+
.filter(Boolean);
|
|
8403
|
+
this.filteredPaths = nextFiltered;
|
|
8404
|
+
this.selectedPaths = [...this.filteredPaths];
|
|
8405
|
+
this.syncProjectionInputFromQueryOrPaths();
|
|
8057
8406
|
}
|
|
8058
8407
|
}
|
|
8059
8408
|
this.status = 'loaded';
|
|
@@ -8162,6 +8511,7 @@ module.exports = app => app.component('models', {
|
|
|
8162
8511
|
} else {
|
|
8163
8512
|
delete this.query.search;
|
|
8164
8513
|
}
|
|
8514
|
+
setModelsRemountFocusIntent('documentSearch');
|
|
8165
8515
|
const query = this.query;
|
|
8166
8516
|
this.$router.push({ query });
|
|
8167
8517
|
this.documents = [];
|
|
@@ -8196,11 +8546,13 @@ module.exports = app => app.component('models', {
|
|
|
8196
8546
|
// Persist projection UI state in the URL so Reset/Suggest don't turn the mode off.
|
|
8197
8547
|
if (next) {
|
|
8198
8548
|
this.query[PROJECTION_MODE_QUERY_KEY] = '1';
|
|
8549
|
+
setModelsRemountFocusIntent('projection');
|
|
8199
8550
|
if (this.outputType === 'map') {
|
|
8200
8551
|
this.setOutputType('json');
|
|
8201
8552
|
}
|
|
8202
8553
|
} else {
|
|
8203
8554
|
delete this.query[PROJECTION_MODE_QUERY_KEY];
|
|
8555
|
+
delete this.query[PROJECTION_INPUT_QUERY_KEY];
|
|
8204
8556
|
delete this.query.fields;
|
|
8205
8557
|
this.filteredPaths = [];
|
|
8206
8558
|
this.selectedPaths = [];
|
|
@@ -8346,14 +8698,25 @@ module.exports = app => app.component('models', {
|
|
|
8346
8698
|
}
|
|
8347
8699
|
const isProjectionModeOn = this.isProjectionMenuSelected === true;
|
|
8348
8700
|
if (isProjectionModeOn) {
|
|
8349
|
-
this.
|
|
8701
|
+
const routeProjectionInput = this.$route.query?.[PROJECTION_INPUT_QUERY_KEY];
|
|
8702
|
+
if (typeof routeProjectionInput === 'string' && routeProjectionInput.trim().length > 0) {
|
|
8703
|
+
const projectionPaths = this.normalizeProjectionPathsForDisplay(routeProjectionInput);
|
|
8704
|
+
const fromProjectionInput = Array.isArray(projectionPaths) ?
|
|
8705
|
+
projectionPaths
|
|
8706
|
+
.map(path => this.schemaPaths.find(p => p.path === path))
|
|
8707
|
+
.filter(Boolean) :
|
|
8708
|
+
[];
|
|
8709
|
+
this.filteredPaths = fromProjectionInput;
|
|
8710
|
+
} else {
|
|
8711
|
+
this.filteredPaths = [];
|
|
8712
|
+
}
|
|
8350
8713
|
} else {
|
|
8351
8714
|
this.filteredPaths = [];
|
|
8352
8715
|
this.selectedPaths = [];
|
|
8353
8716
|
this.projectionText = '';
|
|
8354
8717
|
}
|
|
8355
8718
|
this.selectedPaths = [...this.filteredPaths];
|
|
8356
|
-
this.
|
|
8719
|
+
this.syncProjectionInputFromQueryOrPaths();
|
|
8357
8720
|
schemaPathsReceived = true;
|
|
8358
8721
|
}
|
|
8359
8722
|
if (event.numDocs !== undefined) {
|
|
@@ -8434,6 +8797,10 @@ module.exports = app => app.component('models', {
|
|
|
8434
8797
|
// Keep current filter input in sync with the URL so projection reset
|
|
8435
8798
|
// does not unintentionally wipe the filter on remount.
|
|
8436
8799
|
this.syncFilterToQuery();
|
|
8800
|
+
if (this.$route.query?.fields) {
|
|
8801
|
+
delete this.query.fields;
|
|
8802
|
+
}
|
|
8803
|
+
delete this.query[PROJECTION_INPUT_QUERY_KEY];
|
|
8437
8804
|
this.filteredPaths = [];
|
|
8438
8805
|
this.selectedPaths = [];
|
|
8439
8806
|
this.projectionText = '';
|
|
@@ -8456,18 +8823,9 @@ module.exports = app => app.component('models', {
|
|
|
8456
8823
|
this.applyDefaultProjection(pathNames.slice(0, DEFAULT_FIRST_N_FIELDS));
|
|
8457
8824
|
this.selectedPaths = [...this.filteredPaths];
|
|
8458
8825
|
this.syncProjectionFromPaths();
|
|
8826
|
+
setModelsRemountFocusIntent('projection');
|
|
8459
8827
|
this.updateProjectionQuery();
|
|
8460
8828
|
},
|
|
8461
|
-
initProjection(ev) {
|
|
8462
|
-
if (!this.projectionText || !this.projectionText.trim()) {
|
|
8463
|
-
this.projectionText = '';
|
|
8464
|
-
this.$nextTick(() => {
|
|
8465
|
-
if (ev && ev.target) {
|
|
8466
|
-
ev.target.setSelectionRange(0, 0);
|
|
8467
|
-
}
|
|
8468
|
-
});
|
|
8469
|
-
}
|
|
8470
|
-
},
|
|
8471
8829
|
syncProjectionFromPaths() {
|
|
8472
8830
|
if (this.filteredPaths.length === 0) {
|
|
8473
8831
|
this.projectionText = '';
|
|
@@ -8477,6 +8835,29 @@ module.exports = app => app.component('models', {
|
|
|
8477
8835
|
// Since `filteredPaths` represents the final include set, we serialize as space-separated fields.
|
|
8478
8836
|
this.projectionText = this.filteredPaths.map(p => p.path).join(' ');
|
|
8479
8837
|
},
|
|
8838
|
+
syncProjectionInputFromQueryOrPaths() {
|
|
8839
|
+
const fromQuery = this.$route.query?.[PROJECTION_INPUT_QUERY_KEY];
|
|
8840
|
+
if (typeof fromQuery === 'string' && fromQuery.trim().length > 0) {
|
|
8841
|
+
this.projectionText = fromQuery;
|
|
8842
|
+
return;
|
|
8843
|
+
}
|
|
8844
|
+
this.syncProjectionFromPaths();
|
|
8845
|
+
},
|
|
8846
|
+
normalizeProjectionPathsForDisplay(text) {
|
|
8847
|
+
const paths = this.parseProjectionInput(text);
|
|
8848
|
+
if (paths == null) {
|
|
8849
|
+
return null;
|
|
8850
|
+
}
|
|
8851
|
+
const excludesId = this.projectionExplicitlyExcludesId(text);
|
|
8852
|
+
const hasIdSchemaPath = this.schemaPaths.some(p => p.path === '_id');
|
|
8853
|
+
if (hasIdSchemaPath && !excludesId) {
|
|
8854
|
+
const hasIdAlready = paths.some(p => String(p).toLowerCase() === '_id');
|
|
8855
|
+
if (!hasIdAlready) {
|
|
8856
|
+
paths.unshift('_id');
|
|
8857
|
+
}
|
|
8858
|
+
}
|
|
8859
|
+
return paths;
|
|
8860
|
+
},
|
|
8480
8861
|
parseProjectionInput(text) {
|
|
8481
8862
|
if (!text || typeof text !== 'string') {
|
|
8482
8863
|
return [];
|
|
@@ -8487,17 +8868,21 @@ module.exports = app => app.component('models', {
|
|
|
8487
8868
|
}
|
|
8488
8869
|
const normalizeKey = (key) => String(key).trim();
|
|
8489
8870
|
|
|
8490
|
-
// String
|
|
8871
|
+
// String projection syntax:
|
|
8491
8872
|
// name email
|
|
8492
8873
|
// -password (exclusion-only)
|
|
8493
8874
|
// +email (inclusion-only)
|
|
8875
|
+
// { deletedAt: 1 } (object notation)
|
|
8494
8876
|
//
|
|
8495
|
-
//
|
|
8496
|
-
if (trimmed.startsWith('{')
|
|
8497
|
-
return
|
|
8877
|
+
// For object notation, return include paths based on 1/0/true/false values.
|
|
8878
|
+
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
|
|
8879
|
+
return parseProjectionObjectNotation(trimmed, this.schemaPaths);
|
|
8498
8880
|
}
|
|
8499
8881
|
|
|
8500
|
-
const tokens = trimmed
|
|
8882
|
+
const tokens = normalizeProjectionTokens(trimmed);
|
|
8883
|
+
if (tokens === null) {
|
|
8884
|
+
return null;
|
|
8885
|
+
}
|
|
8501
8886
|
if (tokens.length === 0) return [];
|
|
8502
8887
|
|
|
8503
8888
|
const includeKeys = [];
|
|
@@ -8526,22 +8911,78 @@ module.exports = app => app.component('models', {
|
|
|
8526
8911
|
// `name email createdAt -email` -> `name createdAt`.
|
|
8527
8912
|
const includeSet = new Set(includeKeys.map(normalizeKey));
|
|
8528
8913
|
for (const path of excludeKeys) {
|
|
8529
|
-
|
|
8914
|
+
const ex = normalizeKey(path);
|
|
8915
|
+
for (const k of Array.from(includeSet)) {
|
|
8916
|
+
if (k.toLowerCase() === ex.toLowerCase()) {
|
|
8917
|
+
includeSet.delete(k);
|
|
8918
|
+
}
|
|
8919
|
+
}
|
|
8920
|
+
}
|
|
8921
|
+
if (includeSet.size === 0) {
|
|
8922
|
+
return null;
|
|
8530
8923
|
}
|
|
8531
8924
|
return Array.from(includeSet);
|
|
8532
8925
|
}
|
|
8533
8926
|
|
|
8534
8927
|
if (excludeKeys.length > 0) {
|
|
8535
|
-
const
|
|
8536
|
-
return this.schemaPaths
|
|
8928
|
+
const excludeNorm = excludeKeys.map(normalizeKey);
|
|
8929
|
+
return this.schemaPaths
|
|
8930
|
+
.map(p => p.path)
|
|
8931
|
+
.filter(p => !excludeNorm.some(ex => ex === p || p.toLowerCase() === ex.toLowerCase()));
|
|
8537
8932
|
}
|
|
8538
8933
|
|
|
8539
8934
|
return includeKeys.map(normalizeKey);
|
|
8540
8935
|
},
|
|
8936
|
+
projectionExplicitlyExcludesId(text) {
|
|
8937
|
+
if (!text || typeof text !== 'string') {
|
|
8938
|
+
return false;
|
|
8939
|
+
}
|
|
8940
|
+
const trimmed = text.trim();
|
|
8941
|
+
if (!trimmed) {
|
|
8942
|
+
return false;
|
|
8943
|
+
}
|
|
8944
|
+
|
|
8945
|
+
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
|
|
8946
|
+
const body = trimmed.slice(1, -1).trim();
|
|
8947
|
+
if (!body) {
|
|
8948
|
+
return false;
|
|
8949
|
+
}
|
|
8950
|
+
const pairRe = /(?:^|,)\s*(?:"([^"]+)"|'([^']+)'|([a-zA-Z_$][\w.$]*))\s*:\s*("[^"]*"|'[^']*'|[^,]+?)\s*(?=,|$)/g;
|
|
8951
|
+
let match;
|
|
8952
|
+
while ((match = pairRe.exec(body)) !== null) {
|
|
8953
|
+
const key = (match[1] || match[2] || match[3] || '').trim();
|
|
8954
|
+
if (!key || key.toLowerCase() !== '_id') {
|
|
8955
|
+
continue;
|
|
8956
|
+
}
|
|
8957
|
+
const rawValue = (match[4] || '').trim();
|
|
8958
|
+
const valueLower = rawValue.replace(/^['"]|['"]$/g, '').trim().toLowerCase();
|
|
8959
|
+
if (valueLower === '0' || valueLower === 'false') {
|
|
8960
|
+
return true;
|
|
8961
|
+
}
|
|
8962
|
+
}
|
|
8963
|
+
return false;
|
|
8964
|
+
}
|
|
8965
|
+
|
|
8966
|
+
const tokens = normalizeProjectionTokens(trimmed);
|
|
8967
|
+
if (!tokens) {
|
|
8968
|
+
return false;
|
|
8969
|
+
}
|
|
8970
|
+
return tokens.some((rawToken) => {
|
|
8971
|
+
const token = rawToken.trim();
|
|
8972
|
+
return token.toLowerCase() === '-_id';
|
|
8973
|
+
});
|
|
8974
|
+
},
|
|
8541
8975
|
applyProjectionFromInput() {
|
|
8542
|
-
|
|
8543
|
-
|
|
8544
|
-
this.
|
|
8976
|
+
if (typeof this.projectionText === 'string' && this.projectionText.trim().length === 0) {
|
|
8977
|
+
this.filteredPaths = [];
|
|
8978
|
+
this.selectedPaths = [];
|
|
8979
|
+
setModelsRemountFocusIntent('projection');
|
|
8980
|
+
this.updateProjectionQuery();
|
|
8981
|
+
return;
|
|
8982
|
+
}
|
|
8983
|
+
|
|
8984
|
+
const paths = this.normalizeProjectionPathsForDisplay(this.projectionText);
|
|
8985
|
+
if (paths == null) {
|
|
8545
8986
|
return;
|
|
8546
8987
|
}
|
|
8547
8988
|
if (paths.length === 0) {
|
|
@@ -8563,16 +9004,16 @@ module.exports = app => app.component('models', {
|
|
|
8563
9004
|
}
|
|
8564
9005
|
}
|
|
8565
9006
|
this.selectedPaths = [...this.filteredPaths];
|
|
8566
|
-
|
|
9007
|
+
setModelsRemountFocusIntent('projection');
|
|
8567
9008
|
this.updateProjectionQuery();
|
|
8568
9009
|
},
|
|
8569
9010
|
updateProjectionQuery() {
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
this.query.fields = JSON.stringify(paths);
|
|
9011
|
+
if (typeof this.projectionText === 'string' && this.projectionText.trim().length > 0) {
|
|
9012
|
+
this.query[PROJECTION_INPUT_QUERY_KEY] = this.projectionText;
|
|
8573
9013
|
} else {
|
|
8574
|
-
delete this.query
|
|
9014
|
+
delete this.query[PROJECTION_INPUT_QUERY_KEY];
|
|
8575
9015
|
}
|
|
9016
|
+
delete this.query.fields;
|
|
8576
9017
|
this.$router.push({ query: this.query });
|
|
8577
9018
|
},
|
|
8578
9019
|
removeField(schemaPath) {
|
|
@@ -8801,6 +9242,243 @@ module.exports = app => app.component('models', {
|
|
|
8801
9242
|
});
|
|
8802
9243
|
|
|
8803
9244
|
|
|
9245
|
+
/***/ },
|
|
9246
|
+
|
|
9247
|
+
/***/ "./frontend/src/models/projection-search/projection-search.js"
|
|
9248
|
+
/*!********************************************************************!*\
|
|
9249
|
+
!*** ./frontend/src/models/projection-search/projection-search.js ***!
|
|
9250
|
+
\********************************************************************/
|
|
9251
|
+
(module, __unused_webpack_exports, __webpack_require__) {
|
|
9252
|
+
|
|
9253
|
+
"use strict";
|
|
9254
|
+
|
|
9255
|
+
|
|
9256
|
+
const template = __webpack_require__(/*! ./projection-search.html */ "./frontend/src/models/projection-search/projection-search.html");
|
|
9257
|
+
|
|
9258
|
+
function getBraceDepth(text, pos) {
|
|
9259
|
+
let depth = 0;
|
|
9260
|
+
for (let i = 0; i < pos; i++) {
|
|
9261
|
+
const ch = text[i];
|
|
9262
|
+
if (ch === "{") {
|
|
9263
|
+
depth++;
|
|
9264
|
+
} else if (ch === "}") {
|
|
9265
|
+
depth--;
|
|
9266
|
+
}
|
|
9267
|
+
}
|
|
9268
|
+
return depth;
|
|
9269
|
+
}
|
|
9270
|
+
|
|
9271
|
+
function collectObjectKeysBefore(text, endPos) {
|
|
9272
|
+
const before = text.slice(0, endPos);
|
|
9273
|
+
const keys = new Set();
|
|
9274
|
+
const re = /([a-zA-Z_][\w.]*)\s*:/g;
|
|
9275
|
+
let m;
|
|
9276
|
+
while ((m = re.exec(before)) !== null) {
|
|
9277
|
+
keys.add(m[1]);
|
|
9278
|
+
}
|
|
9279
|
+
return keys;
|
|
9280
|
+
}
|
|
9281
|
+
|
|
9282
|
+
function getProjectionTokenContext(fullText, cursorPos) {
|
|
9283
|
+
const before = fullText.slice(0, cursorPos);
|
|
9284
|
+
const depth = getBraceDepth(before, before.length);
|
|
9285
|
+
|
|
9286
|
+
if (depth > 0) {
|
|
9287
|
+
const m = before.match(/(?:\{|,)\s*([+-]?)([^\s:},]*)$/);
|
|
9288
|
+
if (!m) {
|
|
9289
|
+
return null;
|
|
9290
|
+
}
|
|
9291
|
+
const prefix = m[1] || '';
|
|
9292
|
+
const raw = m[2] || '';
|
|
9293
|
+
const term = raw.trim().toLowerCase();
|
|
9294
|
+
if (!term) {
|
|
9295
|
+
return null;
|
|
9296
|
+
}
|
|
9297
|
+
const token = `${prefix}${raw}`;
|
|
9298
|
+
const start = cursorPos - token.length;
|
|
9299
|
+
const selectedPaths = collectObjectKeysBefore(before, before.length);
|
|
9300
|
+
if (raw) {
|
|
9301
|
+
selectedPaths.delete(raw.trim());
|
|
9302
|
+
}
|
|
9303
|
+
return { mode: 'object', prefix, term, start, selectedPaths };
|
|
9304
|
+
}
|
|
9305
|
+
|
|
9306
|
+
const tokenMatch = before.match(/(?:^|[,\s])([+-]?)([^\s,]*)$/);
|
|
9307
|
+
if (!tokenMatch) {
|
|
9308
|
+
return null;
|
|
9309
|
+
}
|
|
9310
|
+
const prefix = tokenMatch[1] || '';
|
|
9311
|
+
const raw = tokenMatch[2] || '';
|
|
9312
|
+
const term = raw.trim().toLowerCase();
|
|
9313
|
+
if (!term) {
|
|
9314
|
+
return null;
|
|
9315
|
+
}
|
|
9316
|
+
|
|
9317
|
+
const token = `${prefix}${raw}`;
|
|
9318
|
+
const after = fullText.slice(cursorPos);
|
|
9319
|
+
const start = cursorPos - token.length;
|
|
9320
|
+
const normalizedText = `${fullText.slice(0, start)}${token}${after}`;
|
|
9321
|
+
const tokenCandidates = normalizedText.split(/[,\s]+/).map(s => s.trim()).filter(Boolean);
|
|
9322
|
+
const selectedPaths = new Set(tokenCandidates.map(t => t.replace(/^[+-]/, '')).filter(Boolean));
|
|
9323
|
+
if (raw) {
|
|
9324
|
+
selectedPaths.delete(raw);
|
|
9325
|
+
}
|
|
9326
|
+
|
|
9327
|
+
return { mode: 'list', prefix, term, start, selectedPaths };
|
|
9328
|
+
}
|
|
9329
|
+
|
|
9330
|
+
module.exports = app => app.component('projection-search', {
|
|
9331
|
+
template,
|
|
9332
|
+
props: {
|
|
9333
|
+
value: {
|
|
9334
|
+
type: String,
|
|
9335
|
+
default: ''
|
|
9336
|
+
},
|
|
9337
|
+
schemaPaths: {
|
|
9338
|
+
type: Array,
|
|
9339
|
+
default: () => []
|
|
9340
|
+
}
|
|
9341
|
+
},
|
|
9342
|
+
data() {
|
|
9343
|
+
return {
|
|
9344
|
+
projectionText: this.value || '',
|
|
9345
|
+
autocompleteSuggestions: [],
|
|
9346
|
+
autocompleteIndex: 0
|
|
9347
|
+
};
|
|
9348
|
+
},
|
|
9349
|
+
watch: {
|
|
9350
|
+
value(val) {
|
|
9351
|
+
const nextValue = val || '';
|
|
9352
|
+
if (nextValue === this.projectionText) {
|
|
9353
|
+
return;
|
|
9354
|
+
}
|
|
9355
|
+
this.projectionText = nextValue;
|
|
9356
|
+
this.clearAutocomplete();
|
|
9357
|
+
}
|
|
9358
|
+
},
|
|
9359
|
+
methods: {
|
|
9360
|
+
focusInput() {
|
|
9361
|
+
const input = this.$refs.projectionInput;
|
|
9362
|
+
if (input && typeof input.focus === 'function') {
|
|
9363
|
+
input.focus();
|
|
9364
|
+
}
|
|
9365
|
+
},
|
|
9366
|
+
emitInput() {
|
|
9367
|
+
this.$emit('projection-input', this.projectionText);
|
|
9368
|
+
},
|
|
9369
|
+
initProjection(ev) {
|
|
9370
|
+
if (!this.projectionText || !this.projectionText.trim()) {
|
|
9371
|
+
this.projectionText = '';
|
|
9372
|
+
this.emitInput();
|
|
9373
|
+
this.$nextTick(() => {
|
|
9374
|
+
if (ev && ev.target) {
|
|
9375
|
+
ev.target.setSelectionRange(0, 0);
|
|
9376
|
+
}
|
|
9377
|
+
});
|
|
9378
|
+
}
|
|
9379
|
+
},
|
|
9380
|
+
handleInput() {
|
|
9381
|
+
this.emitInput();
|
|
9382
|
+
this.updateAutocomplete();
|
|
9383
|
+
},
|
|
9384
|
+
updateAutocomplete() {
|
|
9385
|
+
const input = this.$refs.projectionInput;
|
|
9386
|
+
const cursorPos = input ? input.selectionStart : 0;
|
|
9387
|
+
const ctx = getProjectionTokenContext(this.projectionText, cursorPos);
|
|
9388
|
+
if (!ctx) {
|
|
9389
|
+
this.clearAutocomplete();
|
|
9390
|
+
return;
|
|
9391
|
+
}
|
|
9392
|
+
|
|
9393
|
+
const { prefix, term, selectedPaths } = ctx;
|
|
9394
|
+
|
|
9395
|
+
this.autocompleteSuggestions = this.schemaPaths
|
|
9396
|
+
.map(path => path.path)
|
|
9397
|
+
.filter(path => typeof path === 'string' && path.toLowerCase().includes(term) && !selectedPaths.has(path))
|
|
9398
|
+
.slice(0, 10)
|
|
9399
|
+
.map(path => (ctx.mode === 'object' ? path : `${prefix}${path}`));
|
|
9400
|
+
this.autocompleteIndex = 0;
|
|
9401
|
+
},
|
|
9402
|
+
handleKeyDown(ev) {
|
|
9403
|
+
if (this.autocompleteSuggestions.length > 0) {
|
|
9404
|
+
if (ev.key === 'Tab') {
|
|
9405
|
+
ev.preventDefault();
|
|
9406
|
+
this.applySuggestion(this.autocompleteIndex);
|
|
9407
|
+
return;
|
|
9408
|
+
}
|
|
9409
|
+
if (ev.key === 'Enter') {
|
|
9410
|
+
ev.preventDefault();
|
|
9411
|
+
this.applySuggestion(this.autocompleteIndex);
|
|
9412
|
+
// Enter should both accept autocomplete and apply projection.
|
|
9413
|
+
this.$nextTick(() => this.$emit('apply'));
|
|
9414
|
+
return;
|
|
9415
|
+
}
|
|
9416
|
+
if (ev.key === 'ArrowDown') {
|
|
9417
|
+
ev.preventDefault();
|
|
9418
|
+
this.autocompleteIndex = (this.autocompleteIndex + 1) % this.autocompleteSuggestions.length;
|
|
9419
|
+
return;
|
|
9420
|
+
}
|
|
9421
|
+
if (ev.key === 'ArrowUp') {
|
|
9422
|
+
ev.preventDefault();
|
|
9423
|
+
this.autocompleteIndex = (this.autocompleteIndex + this.autocompleteSuggestions.length - 1) %
|
|
9424
|
+
this.autocompleteSuggestions.length;
|
|
9425
|
+
return;
|
|
9426
|
+
}
|
|
9427
|
+
}
|
|
9428
|
+
|
|
9429
|
+
if (ev.key === 'Enter') {
|
|
9430
|
+
ev.preventDefault();
|
|
9431
|
+
this.$emit('apply');
|
|
9432
|
+
} else if (ev.key === 'Escape') {
|
|
9433
|
+
this.clearAutocomplete();
|
|
9434
|
+
}
|
|
9435
|
+
},
|
|
9436
|
+
applySuggestion(index) {
|
|
9437
|
+
const suggestion = this.autocompleteSuggestions[index];
|
|
9438
|
+
if (!suggestion) {
|
|
9439
|
+
return;
|
|
9440
|
+
}
|
|
9441
|
+
const input = this.$refs.projectionInput;
|
|
9442
|
+
if (!input) {
|
|
9443
|
+
return;
|
|
9444
|
+
}
|
|
9445
|
+
|
|
9446
|
+
const cursorPos = input.selectionStart;
|
|
9447
|
+
const after = this.projectionText.slice(cursorPos);
|
|
9448
|
+
const ctx = getProjectionTokenContext(this.projectionText, cursorPos);
|
|
9449
|
+
if (!ctx) {
|
|
9450
|
+
return;
|
|
9451
|
+
}
|
|
9452
|
+
|
|
9453
|
+
const { mode, start } = ctx;
|
|
9454
|
+
let replacement;
|
|
9455
|
+
if (mode === 'object') {
|
|
9456
|
+
const colon = /^\s*:/.test(after) ? '' : ': ';
|
|
9457
|
+
replacement = `${suggestion}${colon}`;
|
|
9458
|
+
} else {
|
|
9459
|
+
const needsSpace = after.length === 0 || !/^[,\s]/.test(after);
|
|
9460
|
+
replacement = needsSpace ? `${suggestion} ` : suggestion;
|
|
9461
|
+
}
|
|
9462
|
+
|
|
9463
|
+
this.projectionText = this.projectionText.slice(0, start) + replacement + after;
|
|
9464
|
+
this.emitInput();
|
|
9465
|
+
this.$nextTick(() => {
|
|
9466
|
+
const newCursorPos = start + replacement.length;
|
|
9467
|
+
input.setSelectionRange(newCursorPos, newCursorPos);
|
|
9468
|
+
});
|
|
9469
|
+
this.clearAutocomplete();
|
|
9470
|
+
},
|
|
9471
|
+
hideAutocomplete() {
|
|
9472
|
+
window.setTimeout(() => this.clearAutocomplete(), 100);
|
|
9473
|
+
},
|
|
9474
|
+
clearAutocomplete() {
|
|
9475
|
+
this.autocompleteSuggestions = [];
|
|
9476
|
+
this.autocompleteIndex = 0;
|
|
9477
|
+
}
|
|
9478
|
+
}
|
|
9479
|
+
});
|
|
9480
|
+
|
|
9481
|
+
|
|
8804
9482
|
/***/ },
|
|
8805
9483
|
|
|
8806
9484
|
/***/ "./frontend/src/models/trie.js"
|
|
@@ -10713,6 +11391,9 @@ var map = {
|
|
|
10713
11391
|
"./detail-default/detail-default": "./frontend/src/detail-default/detail-default.js",
|
|
10714
11392
|
"./detail-default/detail-default.html": "./frontend/src/detail-default/detail-default.html",
|
|
10715
11393
|
"./detail-default/detail-default.js": "./frontend/src/detail-default/detail-default.js",
|
|
11394
|
+
"./detail-objectid/detail-objectid": "./frontend/src/detail-objectid/detail-objectid.js",
|
|
11395
|
+
"./detail-objectid/detail-objectid.html": "./frontend/src/detail-objectid/detail-objectid.html",
|
|
11396
|
+
"./detail-objectid/detail-objectid.js": "./frontend/src/detail-objectid/detail-objectid.js",
|
|
10716
11397
|
"./document-details/date-view-mode-picker/date-view-mode-picker": "./frontend/src/document-details/date-view-mode-picker/date-view-mode-picker.js",
|
|
10717
11398
|
"./document-details/date-view-mode-picker/date-view-mode-picker.html": "./frontend/src/document-details/date-view-mode-picker/date-view-mode-picker.html",
|
|
10718
11399
|
"./document-details/date-view-mode-picker/date-view-mode-picker.js": "./frontend/src/document-details/date-view-mode-picker/date-view-mode-picker.js",
|
|
@@ -10724,6 +11405,9 @@ var map = {
|
|
|
10724
11405
|
"./document-details/document-property/document-property.css": "./frontend/src/document-details/document-property/document-property.css",
|
|
10725
11406
|
"./document-details/document-property/document-property.html": "./frontend/src/document-details/document-property/document-property.html",
|
|
10726
11407
|
"./document-details/document-property/document-property.js": "./frontend/src/document-details/document-property/document-property.js",
|
|
11408
|
+
"./document-details/objectid-view-mode-picker/objectid-view-mode-picker": "./frontend/src/document-details/objectid-view-mode-picker/objectid-view-mode-picker.js",
|
|
11409
|
+
"./document-details/objectid-view-mode-picker/objectid-view-mode-picker.html": "./frontend/src/document-details/objectid-view-mode-picker/objectid-view-mode-picker.html",
|
|
11410
|
+
"./document-details/objectid-view-mode-picker/objectid-view-mode-picker.js": "./frontend/src/document-details/objectid-view-mode-picker/objectid-view-mode-picker.js",
|
|
10727
11411
|
"./document/confirm-changes/confirm-changes": "./frontend/src/document/confirm-changes/confirm-changes.js",
|
|
10728
11412
|
"./document/confirm-changes/confirm-changes.html": "./frontend/src/document/confirm-changes/confirm-changes.html",
|
|
10729
11413
|
"./document/confirm-changes/confirm-changes.js": "./frontend/src/document/confirm-changes/confirm-changes.js",
|
|
@@ -10809,6 +11493,9 @@ var map = {
|
|
|
10809
11493
|
"./models/models.css": "./frontend/src/models/models.css",
|
|
10810
11494
|
"./models/models.html": "./frontend/src/models/models.html",
|
|
10811
11495
|
"./models/models.js": "./frontend/src/models/models.js",
|
|
11496
|
+
"./models/projection-search/projection-search": "./frontend/src/models/projection-search/projection-search.js",
|
|
11497
|
+
"./models/projection-search/projection-search.html": "./frontend/src/models/projection-search/projection-search.html",
|
|
11498
|
+
"./models/projection-search/projection-search.js": "./frontend/src/models/projection-search/projection-search.js",
|
|
10812
11499
|
"./models/trie": "./frontend/src/models/trie.js",
|
|
10813
11500
|
"./models/trie.js": "./frontend/src/models/trie.js",
|
|
10814
11501
|
"./mothership": "./frontend/src/mothership.js",
|
|
@@ -50814,7 +51501,7 @@ module.exports = "<button v-bind=\"attrsToBind\" :disabled=\"isDisabled\" @click
|
|
|
50814
51501
|
(module) {
|
|
50815
51502
|
|
|
50816
51503
|
"use strict";
|
|
50817
|
-
module.exports = "<div class=\"chat-message-script relative border rounded bg-surface my-1 text-content text-sm overflow-hidden\">\n <div class=\"flex border-b py-1 pl-1 text-xs font-medium bg-surface\">\n <button\n class=\"px-4 py-1 border-r border-edge-strong text-content-secondary font-semibold transition-colors duration-200 focus:outline-none\"\n :class=\"[\n 'rounded-l-md',\n activeTab === 'code'\n ? 'bg-gray-700 text-white shadow'\n : 'bg-muted hover:bg-muted text-gray-600'\n ]\"\n @click=\"activeTab = 'code'\">\n Code\n </button>\n <button\n class=\"px-4 py-1 text-content-secondary font-semibold transition-colors duration-200 focus:outline-none\"\n :class=\"[\n 'rounded-r-md',\n activeTab === 'output'\n ? 'bg-gray-700 text-white shadow'\n : 'bg-muted hover:bg-muted text-gray-600'\n ]\"\n @click=\"activeTab = 'output'\">\n Output\n </button>\n <div class=\"ml-auto mr-1 flex items-center\">\n <button\n v-if=\"activeTab === 'output'\"\n class=\"px-2 py-1 mr-1 text-xs bg-gray-700 text-white border-none rounded cursor-pointer hover:bg-primary-hover transition-colors flex items-center\"\n @click=\"openDetailModal\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"height: 1.2em;\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <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\" />\n </svg>\n </button>\n <button\n v-if=\"activeTab === 'code' && !isEditing\"\n class=\"px-2 py-1 mr-1 text-xs bg-gray-700 text-white border-none rounded cursor-pointer hover:bg-gray-600 transition-colors flex items-center\"\n @click.stop=\"startEditing\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM4 13.5V16h2.5l7.086-7.086-2.828-2.828L4 13.5z\" />\n </svg>\n </button>\n <async-button\n v-if=\"!isEditing\"\n class=\"px-2 py-1 text-xs bg-primary hover:bg-primary-hover text-primary-text border-none rounded cursor-pointer transition-colors disabled:bg-gray-400\"\n @click=\"executeScript\">\n Run\n </async-button>\n <div class=\"relative ml-1\" ref=\"dropdown\">\n <button\n @click.stop=\"toggleDropdown\"\n class=\"px-1 py-1 text-xs hover:bg-gray-300 rounded flex items-center\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4z\" />\n </svg>\n </button>\n <div\n v-if=\"showDropdown\"\n class=\"absolute right-0 z-10 mt-1 w-64 origin-top-right rounded-md bg-surface py-1 shadow-lg ring-1 ring-black/5\">\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted\"\n @click=\"openCreateDashboardModal(); showDropdown = false\">\n Create Dashboard\n </button>\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted\"\n @click=\"copyOutput(); showDropdown = false\">\n Copy Output As Text\n </button>\n <button\n v-if=\"canOverwriteDashboard\"\n class=\"block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted\"\n @click=\"openOverwriteDashboardConfirmation(); showDropdown = false\">\n Overwrite Dashboard\n </button>\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted\"\n @click=\"$emit('copyMessage'); showDropdown = false\">\n Copy Full Message\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"p-0 max-h-[50vh] max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] overflow-y-auto\" v-show=\"activeTab === 'code'\">\n <div v-if=\"isEditing\" class=\"flex flex-col space-y-2 chat-script-editor-wrap\">\n <div class=\"border border-edge\">\n <ace-editor\n v-model=\"editedScript\"\n mode=\"javascript\"\n :line-numbers=\"true\"\n class=\"w-full h-[45vh] min-h-[200px]\"\n />\n </div>\n <div class=\"flex justify-end gap-2 pb-2\">\n <button\n class=\"px-2 py-1 text-xs bg-gray-300 text-gray-800 border-none rounded cursor-pointer hover:bg-gray-400 transition-colors\"\n @click.stop=\"cancelEditing\">\n Cancel\n </button>\n <async-button\n 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\"\n @click=\"executeScript\">\n Execute\n </async-button>\n </div>\n </div>\n <pre v-else class=\"whitespace-pre-wrap !my-0 bg-muted\"><code v-text=\"script\" ref=\"code\" :class=\"'language-' + language\"></code></pre>\n </div>\n\n <div class=\"p-3 whitespace-pre-wrap max-h-[50vh] overflow-y-auto bg-surface border-t max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] relative\" v-show=\"activeTab === 'output'\">\n <dashboard-result\n v-if=\"message.executionResult?.output != null\"\n :result=\"message.executionResult.output\">\n </dashboard-result>\n <pre v-else>No output</pre>\n\n <div v-if=\"message.executionResult?.logs?.length\" class=\"mt-3 pt-3 border-t border-edge\">\n <div class=\"text-xs font-semibold text-gray-600 uppercase tracking-wide\">Console</div>\n <pre class=\"mt-1 bg-muted text-content p-3 rounded whitespace-pre-wrap overflow-x-auto max-h-[280px]\">{{ message.executionResult.logs }}</pre>\n </div>\n </div>\n\n <modal ref=\"outputModal\" v-if=\"showDetailModal\" containerClass=\"!h-[90vh] !w-[90vw]\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showDetailModal = false;\">×</div>\n <div class=\"h-full overflow-auto\">\n <dashboard-result\n v-if=\"message.executionResult?.output != null\"\n :result=\"message.executionResult.output\"\n :fullscreen=\"true\">\n </dashboard-result>\n <pre v-else class=\"whitespace-pre-wrap\">No output</pre>\n </div>\n </template>\n </modal>\n <modal v-if=\"showCreateDashboardModal\">\n <template #body>\n <div class=\"modal-exit\" @click=\"showCreateDashboardModal = false\">×</div>\n <div>\n <div class=\"mt-4 text-content font-semibold\">Create Dashboard</div>\n <div class=\"mt-4\">\n <label class=\"block text-sm font-medium leading-6 text-content\">Title</label>\n <div class=\"mt-2\">\n <div class=\"w-full flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-teal-600\">\n <input type=\"text\" v-model=\"newDashboardTitle\" class=\"outline-none block flex-1 border-0 bg-transparent py-1.5 pl-1 text-content placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6\" placeholder=\"My Dashboard\">\n </div>\n </div>\n </div>\n <div class=\"my-4\">\n <label class=\"block text-sm font-medium leading-6 text-content\">Code</label>\n <div class=\"border border-edge\">\n <ace-editor\n v-model=\"dashboardCode\"\n mode=\"javascript\"\n :line-numbers=\"true\"\n class=\"h-[300px] w-full\"\n />\n </div>\n </div>\n <async-button\n @click=\"createDashboardFromScript\"\n class=\"rounded-md bg-teal-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n Submit\n </async-button>\n <div v-if=\"createError\" class=\"rounded-md bg-red-50 p-4 mt-1\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">Error</h3>\n <div class=\"mt-2 text-sm text-red-700\">\n {{createError}}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"showOverwriteDashboardConfirmationModal\">\n <template #body>\n <div class=\"modal-exit\" @click=\"showOverwriteDashboardConfirmationModal = false\">×</div>\n <div>\n <div class=\"mt-4 text-content font-semibold\">Overwrite Dashboard</div>\n <p class=\"mt-2 text-sm text-content-secondary\">\n This will replace the linked dashboard's code with the script below.\n </p>\n <p class=\"mt-1 text-xs text-gray-600 break-all\" v-if=\"targetDashboardId\">\n Dashboard ID: {{ targetDashboardId }}\n </p>\n <div class=\"my-4 border border-edge bg-page rounded\">\n <pre class=\"p-2 h-[300px] overflow-auto whitespace-pre-wrap text-xs\">{{ overwriteDashboardCode }}</pre>\n </div>\n <div class=\"flex items-center gap-2\">\n <async-button\n @click=\"confirmOverwriteDashboard\"\n class=\"rounded-md bg-teal-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n Confirm Overwrite\n </async-button>\n <button\n class=\"px-2.5 py-1.5 rounded-md text-sm font-semibold text-content-secondary bg-gray-200 hover:bg-gray-300\"\n @click=\"showOverwriteDashboardConfirmationModal = false\">\n Cancel\n </button>\n </div>\n <div v-if=\"overwriteError\" class=\"rounded-md bg-red-50 p-4 mt-3\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">Error</h3>\n <div class=\"mt-2 text-sm text-red-700\">\n {{overwriteError}}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
51504
|
+
module.exports = "<div class=\"chat-message-script relative border rounded bg-surface my-1 text-content text-sm overflow-hidden\">\n <div class=\"flex border-b py-1 pl-1 text-xs font-medium bg-surface\">\n <button\n class=\"px-4 py-1 border-r border-edge-strong text-content-secondary font-semibold transition-colors duration-200 focus:outline-none\"\n :class=\"[\n 'rounded-l-md',\n activeTab === 'code'\n ? 'bg-gray-700 text-white shadow'\n : 'bg-muted hover:bg-muted text-gray-600'\n ]\"\n @click=\"activeTab = 'code'\">\n Code\n </button>\n <button\n class=\"px-4 py-1 text-content-secondary font-semibold transition-colors duration-200 focus:outline-none\"\n :class=\"[\n 'rounded-r-md',\n activeTab === 'output'\n ? 'bg-gray-700 text-white shadow'\n : 'bg-muted hover:bg-muted text-gray-600'\n ]\"\n @click=\"activeTab = 'output'\">\n Output\n </button>\n <div class=\"ml-auto mr-1 flex items-center\">\n <button\n v-if=\"activeTab === 'output'\"\n class=\"px-2 py-1 mr-1 text-xs bg-gray-700 text-white border-none rounded cursor-pointer hover:bg-primary-hover transition-colors flex items-center\"\n @click=\"openDetailModal\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"height: 1.2em;\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <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\" />\n </svg>\n </button>\n <button\n v-if=\"activeTab === 'code' && !isEditing\"\n class=\"px-2 py-1 mr-1 text-xs bg-gray-700 text-white border-none rounded cursor-pointer hover:bg-gray-600 transition-colors flex items-center\"\n @click.stop=\"startEditing\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM4 13.5V16h2.5l7.086-7.086-2.828-2.828L4 13.5z\" />\n </svg>\n </button>\n <async-button\n v-if=\"!isEditing\"\n class=\"px-2 py-1 text-xs bg-primary hover:bg-primary-hover text-primary-text border-none rounded cursor-pointer transition-colors disabled:bg-gray-400\"\n @click=\"executeScript\">\n Run\n </async-button>\n <div class=\"relative ml-1\" ref=\"dropdown\">\n <button\n @click.stop=\"toggleDropdown\"\n class=\"px-1 py-1 text-xs hover:bg-gray-300 rounded flex items-center\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4z\" />\n </svg>\n </button>\n <div\n v-if=\"showDropdown\"\n class=\"absolute right-0 z-10 mt-1 w-64 origin-top-right rounded-md bg-surface py-1 shadow-lg ring-1 ring-black/5\">\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted\"\n @click=\"openCreateDashboardModal(); showDropdown = false\">\n Create Dashboard\n </button>\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted\"\n @click=\"copyOutput(); showDropdown = false\">\n Copy Output As Text\n </button>\n <button\n v-if=\"canOverwriteDashboard\"\n class=\"block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted\"\n @click=\"openOverwriteDashboardConfirmation(); showDropdown = false\">\n Overwrite Dashboard\n </button>\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted\"\n @click=\"$emit('copyMessage'); showDropdown = false\">\n Copy Full Message\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"p-0 max-h-[50vh] max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] overflow-y-auto\" v-show=\"activeTab === 'code'\">\n <div v-if=\"isEditing\" class=\"flex flex-col space-y-2 chat-script-editor-wrap\">\n <div class=\"border border-edge\">\n <ace-editor\n v-model=\"editedScript\"\n mode=\"javascript\"\n :line-numbers=\"true\"\n class=\"w-full h-[45vh] min-h-[200px]\"\n />\n </div>\n <div class=\"flex justify-end gap-2 pb-2\">\n <button\n class=\"px-2 py-1 text-xs bg-gray-300 text-gray-800 border-none rounded cursor-pointer hover:bg-gray-400 transition-colors\"\n @click.stop=\"cancelEditing\">\n Cancel\n </button>\n <async-button\n 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\"\n @click=\"executeScript\">\n Execute\n </async-button>\n </div>\n </div>\n <pre v-else class=\"whitespace-pre-wrap !my-0 bg-muted\"><code v-text=\"script\" ref=\"code\" :class=\"'language-' + language\"></code></pre>\n </div>\n\n <div class=\"p-3 whitespace-pre-wrap max-h-[50vh] overflow-y-auto bg-surface border-t max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] relative\" v-show=\"activeTab === 'output'\">\n <dashboard-result\n v-if=\"message.executionResult?.output != null\"\n :result=\"message.executionResult.output\">\n </dashboard-result>\n <pre v-else>No output</pre>\n\n <div v-if=\"message.executionResult?.logs?.length\" class=\"mt-3 pt-3 border-t border-edge\">\n <div class=\"text-xs font-semibold text-gray-600 uppercase tracking-wide\">Console</div>\n <pre class=\"mt-1 bg-muted text-content p-3 rounded whitespace-pre-wrap overflow-x-auto max-h-[280px]\">{{ message.executionResult.logs }}</pre>\n </div>\n </div>\n\n <modal ref=\"outputModal\" v-if=\"showDetailModal\" containerClass=\"!h-[90vh] !w-[90vw]\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showDetailModal = false;\">×</div>\n <div class=\"h-full overflow-auto\">\n <dashboard-result\n v-if=\"message.executionResult?.output != null\"\n :result=\"message.executionResult.output\"\n :fullscreen=\"true\">\n </dashboard-result>\n <pre v-else class=\"whitespace-pre-wrap\">No output</pre>\n </div>\n </template>\n </modal>\n <modal v-if=\"showCreateDashboardModal\">\n <template #body>\n <div class=\"modal-exit\" @click=\"showCreateDashboardModal = false\">×</div>\n <div>\n <div class=\"mt-4 text-content font-semibold\">Create Dashboard</div>\n <div class=\"mt-4\">\n <label class=\"block text-sm font-medium leading-6 text-content\">Title</label>\n <div class=\"mt-2\">\n <div class=\"w-full flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-teal-600\">\n <input type=\"text\" v-model=\"newDashboardTitle\" class=\"outline-none block flex-1 border-0 bg-transparent py-1.5 pl-1 text-content placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6\" placeholder=\"My Dashboard\">\n </div>\n </div>\n </div>\n <div class=\"my-4\">\n <label class=\"block text-sm font-medium leading-6 text-content\">Code</label>\n <div class=\"border border-edge\">\n <ace-editor\n v-model=\"dashboardCode\"\n mode=\"javascript\"\n :line-numbers=\"true\"\n class=\"h-[300px] w-full\"\n />\n </div>\n </div>\n <async-button\n @click=\"createDashboardFromScript\"\n class=\"rounded-md bg-teal-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n Submit\n </async-button>\n <div v-if=\"createError\" class=\"rounded-md bg-red-50 p-4 mt-1\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">Error</h3>\n <div class=\"mt-2 text-sm text-red-700\">\n {{createError}}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"showOverwriteDashboardConfirmationModal\">\n <template #body>\n <div class=\"modal-exit\" @click=\"showOverwriteDashboardConfirmationModal = false\">×</div>\n <div>\n <div class=\"mt-4 text-content font-semibold\">Overwrite Dashboard</div>\n <p class=\"mt-2 text-sm text-content-secondary\">\n This will replace the linked dashboard's code with the script below.\n </p>\n <p class=\"mt-1 text-xs text-gray-600 break-all\" v-if=\"targetDashboardId\">\n Dashboard ID: {{ targetDashboardId }}\n </p>\n <div class=\"my-4 border border-edge bg-page rounded\">\n <pre class=\"p-2 h-[300px] overflow-auto whitespace-pre-wrap text-xs\">{{ overwriteDashboardCode }}</pre>\n </div>\n <div class=\"flex items-center gap-2\">\n <async-button\n @click=\"confirmOverwriteDashboard\"\n class=\"rounded-md bg-teal-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n Confirm Overwrite\n </async-button>\n <button\n class=\"px-2.5 py-1.5 rounded-md text-sm font-semibold text-content-secondary bg-gray-200 hover:bg-gray-300\"\n @click=\"showOverwriteDashboardConfirmationModal = false\">\n Cancel\n </button>\n </div>\n <div v-if=\"overwriteError\" class=\"rounded-md bg-red-50 p-4 mt-3\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">Error</h3>\n <div class=\"mt-2 text-sm text-red-700\">\n {{overwriteError}}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
50818
51505
|
|
|
50819
51506
|
/***/ },
|
|
50820
51507
|
|
|
@@ -50825,7 +51512,7 @@ module.exports = "<div class=\"chat-message-script relative border rounded bg-su
|
|
|
50825
51512
|
(module) {
|
|
50826
51513
|
|
|
50827
51514
|
"use strict";
|
|
50828
|
-
module.exports = "<div class=\"relative flex items-start\" :class=\"{'justify-end': message.role === 'user'}\">\n <div\n class=\"min-w-0 max-w-
|
|
51515
|
+
module.exports = "<div class=\"relative flex items-start\" :class=\"{'justify-end': message.role === 'user'}\">\n <div\n class=\"min-w-0 max-w-full\"\n :class=\"{'text-right': message.role === 'user'}\">\n\n <div v-if=\"message.toolCalls && message.toolCalls.length\" class=\"mb-2 text-xs text-gray-500 dark:text-gray-400\">\n <div\n v-for=\"(tc, i) in message.toolCalls\"\n :key=\"i\"\n class=\"flex items-center gap-1.5 py-0.5 font-mono\">\n <svg :class=\"['w-3.5 h-3.5 shrink-0', tc.status === 'running' ? 'animate-spin text-gray-400 dark:text-gray-500' : 'text-gray-500 dark:text-gray-400']\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M21.75 6.75a4.5 4.5 0 0 1-4.884 4.484c-1.076-.091-2.264.071-2.95.904l-7.152 8.684a2.548 2.548 0 1 1-3.586-3.586l8.684-7.152c.833-.686.995-1.874.904-2.95a4.5 4.5 0 0 1 6.336-4.486l-3.276 3.276a3.004 3.004 0 0 0 2.25 2.25l3.276-3.276c.256.565.398 1.192.398 1.852Z\" /><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M4.867 19.125h.008v.008h-.008v-.008Z\" /></svg>\n <span>{{ tc.toolName }}</span>\n <span v-if=\"tc.toolName !== 'typeCheck'\" class=\"text-gray-400 dark:text-gray-500\">{{ formatToolInput(tc.input) }}</span>\n </div>\n </div>\n\n <div class=\"text-sm text-content rounded-md inline-block relative\" :class=\"styleForMessage\">\n <div v-for=\"part in contentSplitByScripts\">\n <div v-if=\"part.type === 'text'\" v-html=\"marked(part.content)\">\n </div>\n <div v-else-if=\"part.type === 'code'\">\n <chat-message-script\n :message=\"message\"\n :script=\"part.content\"\n :language=\"part.language\"\n :target-dashboard-id=\"targetDashboardId\"\n @copyMessage=\"copyMessage\"></chat-message-script>\n </div>\n </div>\n </div>\n </div>\n</div>\n";
|
|
50829
51516
|
|
|
50830
51517
|
/***/ },
|
|
50831
51518
|
|
|
@@ -50836,7 +51523,7 @@ module.exports = "<div class=\"relative flex items-start\" :class=\"{'justify-en
|
|
|
50836
51523
|
(module) {
|
|
50837
51524
|
|
|
50838
51525
|
"use strict";
|
|
50839
|
-
module.exports = "<div class=\"flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <button\n type=\"button\"\n class=\"fixed top-[65px] left-0 cursor-pointer bg-muted rounded-r-md p-1\"\n :class=\"hideSidebar === true ? 'z-[10001]' : hideSidebar === false ? 'z-10' : 'z-30 lg:hidden'\"\n @click=\"hideSidebar = hideSidebar === false ? true : false\"\n aria-label=\"Toggle chat history\"\n title=\"Toggle chat history\">\n <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 text-content-tertiary\">\n <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\" />\n </svg>\n </button>\n <!-- Sidebar: Chat Threads -->\n <aside\n class=\"bg-page border-r overflow-hidden h-full transition-all duration-300 ease-in-out z-20 w-64 fixed lg:relative shrink-0 flex flex-col\"\n :class=\"hideSidebar === false ? 'translate-x-0' : hideSidebar === true ? '-translate-x-full lg:translate-x-0 lg:!w-0 lg:!border-r-0' : '-translate-x-full lg:translate-x-0'\"\n style=\"z-index: 1000\">\n <!-- Search + New Thread -->\n <div class=\"p-3 shrink-0\">\n <div class=\"relative flex items-center gap-2\">\n <div class=\"relative flex-1\">\n <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\">\n <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\" />\n </svg>\n <input\n v-model=\"threadSearch\"\n type=\"text\"\n placeholder=\"Search threads...\"\n @keydown.esc=\"threadSearch = ''\"\n 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\"\n />\n </div>\n <async-button\n @click=\"createNewThread\"\n class=\"shrink-0 rounded-md border border-edge bg-surface p-1.5 text-content-tertiary hover:bg-muted hover:text-content-secondary\"\n title=\"New thread\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" class=\"w-4 h-4\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 4.5v15m7.5-7.5h-15\" />\n </svg>\n </async-button>\n </div>\n </div>\n <!-- Thread list (scrollable) -->\n <nav class=\"flex-1 overflow-y-auto px-2 pb-2\">\n <div class=\"px-2 py-1 text-xs font-semibold text-gray-400 uppercase tracking-wider\">Threads</div>\n <div v-if=\"status === 'loaded' && filteredThreads.length === 0\" class=\"px-2 py-2 text-sm text-content-tertiary\">\n <template v-if=\"threadSearch.trim()\">No threads match \"{{threadSearch}}\"</template>\n <template v-else>No threads yet</template>\n </div>\n <ul role=\"list\">\n <li\n v-for=\"thread in filteredThreads\"\n :key=\"thread._id\"\n @click=\"selectThread(thread._id)\"\n class=\"rounded-md py-2 px-2 cursor-pointer mb-0.5\"\n :class=\"thread._id === chatThreadId ? 'bg-gray-200 text-content' : 'hover:bg-muted text-content-secondary'\"\n >\n <div class=\"text-sm font-medium truncate\">{{ thread.title || 'Untitled Thread' }}</div>\n <div class=\"text-xs text-gray-400 mt-0.5\">{{ formatThreadDate(thread.updatedAt || thread.createdAt) }}</div>\n </li>\n </ul>\n </nav>\n <!-- Bottom toolbar -->\n <div class=\"shrink-0 border-t border-edge bg-page px-2 py-1.5 flex items-center gap-1\">\n <button\n type=\"button\"\n @click=\"hideSidebar = true\"\n class=\"rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n title=\"Hide sidebar\"\n >\n <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\">\n <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\" />\n </svg>\n </button>\n <button\n type=\"button\"\n @click=\"hasWorkspace ? toggleShareThread() : (showProUpgradeModal = true)\"\n class=\"rounded p-1.5\"\n :class=\"hasWorkspace && chatThreadId ? 'text-gray-400 hover:text-gray-600 hover:bg-muted' : !hasWorkspace ? 'text-gray-400 hover:text-gray-600 hover:bg-muted' : 'text-gray-300 cursor-not-allowed'\"\n :disabled=\"hasWorkspace && !chatThreadId\"\n aria-label=\"Share thread with workspace\"\n :title=\"'Share thread with workspace' + (!hasWorkspace ? ' (requires a pro workspace)' : !chatThreadId ? ': Open a thread first!' : '')\"\n >\n <svg v-if=\"hasWorkspace\" xmlns=\"http://www.w3.org/2000/svg\" class=\"w-4 h-4\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><path d=\"M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7a2.48 2.48 0 0 0 0-1.39l7.02-4.11a2.5 2.5 0 1 0-.87-1.37L8.04 9.94a2.5 2.5 0 1 0 0 4.12l7.12 4.16a2.5 2.5 0 1 0 .84-1.34l-7.05-4.12c-.04-.02-.08-.05-.11-.07a2.48 2.48 0 0 0 0-1.39c.03-.02.07-.04.11-.07l7.11-4.16c.52.47 1.2.76 1.94.76a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0-1.94.94L7.97 8.43a2.5 2.5 0 1 0 0 7.14l9.09 5.3c.52-.47 1.2-.76 1.94-.76a2.5 2.5 0 1 0 0-5z\"/></svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" class=\"w-4 h-4\" 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>\n </button>\n </div>\n </aside>\n\n <!-- Main Chat Area -->\n <main class=\"flex-1 flex flex-col bg-slate-50\">\n <div class=\"flex-1 overflow-y-auto p-6 space-y-4\" ref=\"messagesContainer\">\n <div v-if=\"chatMessages?.length === 0\">\n <div class=\"flex items-center w-full h-full justify-center py-3 mb-4\">\n <p class=\"mx-4 font-bold text-content\">\n Ask Mongoose Studio to analyze your data or generate a script\n </p>\n </div>\n </div>\n <ul role=\"list\" class=\"space-y-4\" v-else>\n <li v-for=\"message in chatMessages\" :key=\"message._id\">\n <chat-message :message=\"message\" :target-dashboard-id=\"currentThread?.dashboardId\"></chat-message>\n </li>\n </ul>\n </div>\n\n\n <!-- Input Area -->\n <div class=\"border-t p-4\">\n <form @submit.prevent=\"sendMessage\" :disabled=\"sendingMessage\" class=\"flex gap-2 items-end justify-end\">\n <textarea\n v-model=\"newMessage\"\n :placeholder=\"sendingMessage ? 'Sending...' : 'Ask about your data, generate a query, or build a chart…'\"\n class=\"flex-1 border rounded px-4 py-2 resize-none overflow-y-auto shadow-sm\"\n :disabled=\"sendingMessage\"\n rows=\"2\"\n ref=\"messageInput\"\n @input=\"adjustTextareaHeight\"\n @keydown.enter.exact.prevent=\"handleEnter\"\n ></textarea>\n <button class=\"bg-primary hover:bg-primary-hover text-white px-4 h-[42px] rounded disabled:bg-gray-600\" :disabled=\"sendingMessage\">\n <svg v-if=\"sendingMessage\" style=\"height: 1em\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <g>\n <circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" opacity=\"0.3\" />\n <path d=\"M12 2a10 10 0 0 1 10 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <animateTransform attributeName=\"transform\" type=\"rotate\" from=\"0 12 12\" to=\"360 12 12\" dur=\"1s\" repeatCount=\"indefinite\" />\n </path>\n </g>\n </svg>\n <span v-else>Send</span>\n </button>\n </form>\n </div>\n </main>\n\n <pro-upgrade-modal\n :show=\"showProUpgradeModal\"\n feature-description=\"Sharing threads lets you collaborate with your team by sharing chat threads within your workspace.\"\n @close=\"showProUpgradeModal = false\"\n ></pro-upgrade-modal>\n</div>\n";
|
|
51526
|
+
module.exports = "<div class=\"flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <button\n type=\"button\"\n class=\"fixed top-[65px] left-0 cursor-pointer bg-muted rounded-r-md p-1\"\n :class=\"hideSidebar === true ? 'z-[10001]' : hideSidebar === false ? 'z-10' : 'z-30 lg:hidden'\"\n @click=\"hideSidebar = hideSidebar === false ? true : false\"\n aria-label=\"Toggle chat history\"\n title=\"Toggle chat history\">\n <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 text-content-tertiary\">\n <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\" />\n </svg>\n </button>\n <!-- Sidebar: Chat Threads -->\n <aside\n class=\"bg-page border-r overflow-hidden h-full transition-all duration-300 ease-in-out z-20 w-64 fixed lg:relative shrink-0 flex flex-col\"\n :class=\"hideSidebar === false ? 'translate-x-0' : hideSidebar === true ? '-translate-x-full lg:translate-x-0 lg:!w-0 lg:!border-r-0' : '-translate-x-full lg:translate-x-0'\"\n style=\"z-index: 1000\">\n <!-- Search + New Thread -->\n <div class=\"p-3 shrink-0\">\n <div class=\"relative flex items-center gap-2\">\n <div class=\"relative flex-1\">\n <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\">\n <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\" />\n </svg>\n <input\n v-model=\"threadSearch\"\n type=\"text\"\n placeholder=\"Search threads...\"\n @keydown.esc=\"threadSearch = ''\"\n 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\"\n />\n </div>\n <async-button\n @click=\"createNewThread\"\n class=\"shrink-0 rounded-md border border-edge bg-surface p-1.5 text-content-tertiary hover:bg-muted hover:text-content-secondary\"\n title=\"New thread\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" class=\"w-4 h-4\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 4.5v15m7.5-7.5h-15\" />\n </svg>\n </async-button>\n </div>\n </div>\n <!-- Thread list (scrollable) -->\n <nav class=\"flex-1 overflow-y-auto px-2 pb-2\">\n <div class=\"px-2 py-1 text-xs font-semibold text-gray-400 uppercase tracking-wider\">Threads</div>\n <div v-if=\"status === 'loaded' && filteredThreads.length === 0\" class=\"px-2 py-2 text-sm text-content-tertiary\">\n <template v-if=\"threadSearch.trim()\">No threads match \"{{threadSearch}}\"</template>\n <template v-else>No threads yet</template>\n </div>\n <ul role=\"list\">\n <li\n v-for=\"thread in filteredThreads\"\n :key=\"thread._id\"\n @click=\"selectThread(thread._id)\"\n class=\"rounded-md py-2 px-2 cursor-pointer mb-0.5\"\n :class=\"thread._id === chatThreadId ? 'bg-gray-200 text-content' : 'hover:bg-muted text-content-secondary'\"\n >\n <div class=\"text-sm font-medium truncate\">{{ thread.title || 'Untitled Thread' }}</div>\n <div class=\"text-xs text-gray-400 mt-0.5\">{{ formatThreadDate(thread.updatedAt || thread.createdAt) }}</div>\n </li>\n </ul>\n </nav>\n <!-- Bottom toolbar -->\n <div class=\"shrink-0 border-t border-edge bg-page px-2 py-1.5 flex items-center gap-1\">\n <button\n type=\"button\"\n @click=\"hideSidebar = true\"\n class=\"rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n title=\"Hide sidebar\"\n >\n <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\">\n <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\" />\n </svg>\n </button>\n <button\n type=\"button\"\n @click=\"hasWorkspace ? toggleShareThread() : (showProUpgradeModal = true)\"\n class=\"rounded p-1.5\"\n :class=\"hasWorkspace && chatThreadId ? 'text-gray-400 hover:text-gray-600 hover:bg-muted' : !hasWorkspace ? 'text-gray-400 hover:text-gray-600 hover:bg-muted' : 'text-gray-300 cursor-not-allowed'\"\n :disabled=\"hasWorkspace && !chatThreadId\"\n aria-label=\"Share thread with workspace\"\n :title=\"'Share thread with workspace' + (!hasWorkspace ? ' (requires a pro workspace)' : !chatThreadId ? ': Open a thread first!' : '')\"\n >\n <svg v-if=\"hasWorkspace\" xmlns=\"http://www.w3.org/2000/svg\" class=\"w-6 h-6\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><path d=\"M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7a2.48 2.48 0 0 0 0-1.39l7.02-4.11a2.5 2.5 0 1 0-.87-1.37L8.04 9.94a2.5 2.5 0 1 0 0 4.12l7.12 4.16a2.5 2.5 0 1 0 .84-1.34l-7.05-4.12c-.04-.02-.08-.05-.11-.07a2.48 2.48 0 0 0 0-1.39c.03-.02.07-.04.11-.07l7.11-4.16c.52.47 1.2.76 1.94.76a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0-1.94.94L7.97 8.43a2.5 2.5 0 1 0 0 7.14l9.09 5.3c.52-.47 1.2-.76 1.94-.76a2.5 2.5 0 1 0 0-5z\"/></svg>\n <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>\n </button>\n <button\n type=\"button\"\n @click=\"toggleAgentMode\"\n class=\"rounded p-1.5 transition-colors\"\n :class=\"isAgentModeEnabled ? 'text-primary bg-primary/10' : 'text-gray-400 hover:text-gray-600 hover:bg-muted'\"\n :aria-label=\"isAgentModeEnabled ? 'Turn agent mode off' : 'Turn agent mode on'\"\n :aria-pressed=\"isAgentModeEnabled\"\n :title=\"isAgentModeEnabled ? 'Agent mode on' : 'Agent mode off'\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 200 200\"><path d=\"m187 170.9-38.72-13.73-0.55-0.25-11.24-12.18v-9.98c6.3-6.82 6.43-11.03 8.3-23.22 7.34-1.41 10.2-7.7 12.28-18.9 0.97-5.02 2.72-9.88 0.14-13.92-1.38-2.35-3.49-3.08-5.62-3.38 1.65-11.57 5.47-27.07-2.68-42.38-5.47-9.94-14-14.18-21.8-14.92-3.5-9.15-12.32-13.06-27.11-13.06h-1.06c-27.2 0-48.53 17.96-52.18 39.74-2.05 11.38-0.41 20.41 0.74 30.03-2.23 0.19-4.38 1.02-5.84 3.42-2.21 3.83-1.27 8.84 0.06 14.19 2 8.28 2.26 16.18 12.9 18.96 1.47 6.52 0.58 14.07 8.3 22.84v10.77l-12.54 12.83-38.38 12.85v24.18h57.31c-3.11-14.37-6.53-23.75-5.45-42.72l0.44-0.34c5.71 9.04 16.02 15.97 29.22 25.91l-12.68 17.15h38.07l-13.16-17.2c11.44-9.29 22.31-16.55 29.2-25.38 4.68 9.53-1.48 26.7-4.72 42.58h56.77v-23.89zm-49.56-49.09c-1.43 5.43-2.53 6.16-9.69 14.68l-11.68 13.11c-3.48 3.27-4.8 3.27-11.11 3.27h-10.39c-6.25 0-7.45-0.78-11.01-4.46-6.78-7.7-11.76-13.37-15.09-17.32-5.39-6.33-5.85-7.2-7.16-18.89-0.73-4.86-3.02-14.25-2.94-14.91 3.94 3.43 13.59 3.35 16.89 3.35 12.76 0 15.79-2.74 19.92-17.12 0.68-2.98 1.71-3.53 4-3.53 2.04 0 3.04 0.98 3.86 3.71 3.9 13.69 5.2 16.52 16.15 16.76h2.99c8.1 0 15.01-1.52 18.58-2.93-0.5 5.06-2.28 16.7-3.32 24.28zm4.72-39.02c-1.97 10.98-2.94 12.3-17.69 12.91-9.05 0.38-11.35-0.71-14.17-8.69-3.69-11.25-2.17-13 5.55-13.83 6.36-0.83 11.74-0.79 15.92-0.79 10.01 0 12.11 0.27 10.39 10.4zm-84.38-32c0-11.99 9.26-21.63 15.54-23.52 4.46-1.14 7.99 3.61 15.25 5.74 5.06 1.54 10.71 1.32 11.84 1.32 11.49 0 16.71-2.62 21.55-6.91 1.66-1.28 3.22-1.57 4.86-0.67 10.89 6.36 15.34 16.28 14.74 26.07-0.36 5.5-2.18 9.03-1.38 12.23l1.14 3.62c-3.83-0.82-6.2-0.92-10.69-0.92-9.44 0-17.61 1.24-22.14 2.5-3.66 1.24-5.2 1.44-8.35 1.44-2.98 0-4.92-0.2-7.56-1.44-4.97-1.54-14.49-2.5-21.47-2.5-3.69 0-7 0.01-10.9 0.62l-1.84 0.3 0.42-3.07c0.82-3.01-1.01-7.56-1.01-14.81z\"/><path d=\"m116.6 125.4-12.18-2.58c-2.29-0.68-3.7 0.76-5.4 0.76-1.66 0-2.79-1.35-5.4-0.66l-10.88 2.36c-1.35 0.12-1.55 1.44-0.79 3.32 0.38 1.02 1.26 0.7 3.07 0.36 3.25-0.46 4.65-0.84 8.11-1.04 1.85-0.17 2.59 0.41 5.06 0.41h1.14c1.72 0 2.62-0.55 4.15-0.31 3.81 0.31 5.64 0.72 11.64 1.22 2.08 0.38 2.4-1.15 2.21-2.52-0.07-0.65-0.24-1.1-0.73-1.32z\"/><path d=\"m94.08 133.5c-1.96 0-2.49 1.23-2.4 2.39 0.06 1.38 0.93 2.27 2.57 2.27h10.06c2.13 0 2.7-1.25 2.7-2.56 0-1.13-0.65-2.1-2.53-2.1h-10.4z\"/><path d=\"m91.31 106.6c-2.13 0-2.51 3.32-1.24 4.55 1.11 1.08 7.39 3.76 9.34 3.76s8.38-3.24 9.4-3.87c1.9-1.2 0.85-4.82-1.7-4.44-1.84 0.3-6.01 3.39-7.96 3.01-1.53-0.32-6.08-3.01-7.84-3.01z\"/><path d=\"m85.62 73.71c-5.3-1.1-10.53-1.15-14.51-1.15-10.96 0-14.08-0.44-14.33 5.26-0.47 11.15 1.82 15.96 6.69 17.19 4.7 1.26 6.84 1.08 11.71 1.08 8.88 0 10.9-1.18 14.23-10.67 2.38-6.81 1.87-10.59-3.79-11.71z\"/><path d=\"m114.5 73.51c-7.72 0.83-7.35 2.71-6.39 9.73 1.78 7.47 3.64 11.94 10.97 12.49 3.07 0.14 4.97-0.05 8-0.32 10.72-0.96 12.6-2.48 14.45-12.22 1.88-10.29-0.54-10.77-9.79-10.77-4.18 0-10.45 0.26-17.24 1.09z\"/></svg>\n </button>\n </div>\n </aside>\n\n <div class=\"flex-1 min-w-0 flex\">\n <!-- Main Chat Area -->\n <main class=\"flex-1 min-w-0 flex flex-col bg-slate-50\">\n <div class=\"flex-1 overflow-y-auto p-6 space-y-4\" ref=\"messagesContainer\">\n <div v-if=\"chatMessages?.length === 0\">\n <div class=\"flex items-center w-full h-full justify-center py-3 mb-4\">\n <p class=\"mx-4 font-bold text-content\">\n Ask Mongoose Studio to analyze your data or generate a script\n </p>\n </div>\n </div>\n <ul role=\"list\" class=\"space-y-4\" v-else>\n <li v-for=\"message in chatMessages\" :key=\"message._id\">\n <chat-message :message=\"message\" :target-dashboard-id=\"currentThread?.dashboardId\"></chat-message>\n </li>\n </ul>\n </div>\n\n\n <!-- Input Area -->\n <div class=\"border-t p-4\">\n <form @submit.prevent=\"sendMessage\" :disabled=\"sendingMessage\" class=\"flex gap-2 items-end justify-end\">\n <textarea\n v-model=\"newMessage\"\n :placeholder=\"sendingMessage ? 'Sending...' : 'Ask about your data, generate a query, or build a chart…'\"\n class=\"flex-1 border rounded px-4 py-2 resize-none overflow-y-auto shadow-sm\"\n :disabled=\"sendingMessage\"\n rows=\"2\"\n ref=\"messageInput\"\n @input=\"adjustTextareaHeight\"\n @keydown.enter.exact.prevent=\"handleEnter\"\n ></textarea>\n <button class=\"bg-primary hover:bg-primary-hover text-white px-4 h-[42px] rounded disabled:bg-gray-600\" :disabled=\"sendingMessage\">\n <svg v-if=\"sendingMessage\" style=\"height: 1em\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <g>\n <circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" opacity=\"0.3\" />\n <path d=\"M12 2a10 10 0 0 1 10 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <animateTransform attributeName=\"transform\" type=\"rotate\" from=\"0 12 12\" to=\"360 12 12\" dur=\"1s\" repeatCount=\"indefinite\" />\n </path>\n </g>\n </svg>\n <span v-else>Send</span>\n </button>\n </form>\n </div>\n </main>\n\n <aside\n v-if=\"isAgentModeEnabled\"\n class=\"hidden lg:flex shrink-0 border-l border-edge bg-page transition-all duration-300 ease-in-out overflow-hidden\"\n :class=\"showAgentSidebar ? 'w-80' : 'w-0 border-l-0'\"\n >\n <div v-if=\"showAgentSidebar\" class=\"flex h-full w-80 flex-col\">\n <div class=\"flex items-center justify-between border-b border-edge px-4 py-3\">\n <div>\n <h2 class=\"text-sm font-semibold text-content\">Agent Mode</h2>\n <p class=\"mt-1 text-xs text-content-tertiary\">Agent mode lets Mongoose Studio inspect your data and validate scripts before it responds.</p>\n </div>\n <button\n type=\"button\"\n @click=\"closeAgentSidebar\"\n class=\"rounded p-1.5 text-gray-400 hover:bg-muted hover:text-gray-600\"\n title=\"Hide agent sidebar\"\n >\n <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\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18 18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <div class=\"flex-1 overflow-y-auto px-4 py-4\">\n <div class=\"mb-3 text-xs font-semibold uppercase tracking-wider text-gray-400\">Available Tools</div>\n <ul role=\"list\" class=\"space-y-3\">\n <li v-for=\"tool in agentTools\" :key=\"tool.name\" class=\"rounded-md border border-edge bg-surface px-3 py-3\">\n <div class=\"text-sm font-medium text-content\">{{ tool.name }}</div>\n <div class=\"mt-1 text-sm text-content-secondary\">{{ tool.description }}</div>\n </li>\n </ul>\n </div>\n </div>\n </aside>\n </div>\n\n <pro-upgrade-modal\n :show=\"showProUpgradeModal\"\n feature-description=\"Sharing threads lets you collaborate with your team by sharing chat threads within your workspace.\"\n @close=\"showProUpgradeModal = false\"\n ></pro-upgrade-modal>\n</div>\n";
|
|
50840
51527
|
|
|
50841
51528
|
/***/ },
|
|
50842
51529
|
|
|
@@ -51060,6 +51747,17 @@ module.exports = "<div class=\"w-full\">\n <pre v-if=\"!isGeoJsonGeometry || !m
|
|
|
51060
51747
|
|
|
51061
51748
|
/***/ },
|
|
51062
51749
|
|
|
51750
|
+
/***/ "./frontend/src/detail-objectid/detail-objectid.html"
|
|
51751
|
+
/*!***********************************************************!*\
|
|
51752
|
+
!*** ./frontend/src/detail-objectid/detail-objectid.html ***!
|
|
51753
|
+
\***********************************************************/
|
|
51754
|
+
(module) {
|
|
51755
|
+
|
|
51756
|
+
"use strict";
|
|
51757
|
+
module.exports = "<pre class=\"w-full whitespace-pre-wrap break-words font-mono text-sm text-content-secondary m-0\">{{displayValue}}</pre>\n";
|
|
51758
|
+
|
|
51759
|
+
/***/ },
|
|
51760
|
+
|
|
51063
51761
|
/***/ "./frontend/src/document-details/date-view-mode-picker/date-view-mode-picker.html"
|
|
51064
51762
|
/*!****************************************************************************************!*\
|
|
51065
51763
|
!*** ./frontend/src/document-details/date-view-mode-picker/date-view-mode-picker.html ***!
|
|
@@ -51111,7 +51809,18 @@ module.exports = ".document-details {\n width: 100%;\n }\n \n .document-de
|
|
|
51111
51809
|
(module) {
|
|
51112
51810
|
|
|
51113
51811
|
"use strict";
|
|
51114
|
-
module.exports = "<div class=\"border border-edge bg-surface rounded-lg mb-2\" style=\"overflow: visible;\">\n <!-- Collapsible Header -->\n <div\n @click=\"toggleCollapse\"\n class=\"p-1 cursor-pointer flex items-center justify-between border-b border-edge transition-colors duration-200 ease-in-out\"\n :class=\"{ 'bg-amber-100 hover:bg-amber-200': highlight, 'bg-slate-100 hover:bg-muted': !highlight }\"\n style=\"overflow: visible; position: relative;\"\n >\n <div class=\"flex items-center\" >\n <svg\n :class=\"isCollapsed ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-content-tertiary mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-content\">{{path.path}}</span>\n <span class=\"ml-2 text-sm text-content-tertiary\">({{(path.instance || 'unknown').toLowerCase()}})</span>\n <div v-if=\"isGeoJsonGeometry\" class=\"ml-3 inline-flex items-center gap-2\">\n <div class=\"inline-flex items-center rounded-full bg-gray-200 p-0.5 text-xs font-semibold\">\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"detailViewMode === 'text' ? 'bg-blue-600 text-white shadow' : 'text-content-secondary hover:text-content'\"\n :style=\"detailViewMode === 'text' ? 'color: white !important; background-color: #2563eb !important;' : ''\"\n @click.stop=\"setDetailViewMode('text')\">\n Text\n </button>\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"detailViewMode === 'map' ? 'bg-blue-600 text-white shadow' : 'text-content-secondary hover:text-content'\"\n :style=\"detailViewMode === 'map' ? 'color: white !important; background-color: #2563eb !important;' : ''\"\n @click.stop=\"setDetailViewMode('map')\">\n Map\n </button>\n </div>\n <!-- Info icon with tooltip -->\n <div v-if=\"editting\" class=\"relative inline-block\" style=\"z-index: 10002;\" @mouseenter=\"showTooltip = true\" @mouseleave=\"showTooltip = false\" @click.stop>\n <svg\n ref=\"infoIcon\"\n class=\"w-6 h-6 text-gray-400 hover:text-gray-600 cursor-help\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"></path>\n </svg>\n <div\n v-show=\"showTooltip\"\n ref=\"tooltip\"\n class=\"absolute left-full top-0 ml-2 w-64 p-3 text-white text-xs rounded-lg shadow-xl\"\n style=\"z-index: 99999; pointer-events: none; white-space: normal; position: fixed; background-color: #111827;\"\n :style=\"getTooltipStyle()\"\n >\n <div class=\"font-semibold mb-2\">Map Controls:</div>\n <div v-if=\"isGeoJsonPoint\" class=\"space-y-1\">\n <div>• Drag pin to move location</div>\n </div>\n <div v-else-if=\"isGeoJsonPolygon\" class=\"space-y-1\">\n <div>• Drag vertices to reshape polygon</div>\n <div v-if=\"isMultiPolygon\">• Right-click edge to add new vertex</div>\n <div>• Right-click vertex to delete</div>\n </div>\n <div class=\"absolute top-2 -left-1 w-0 h-0 border-t-4 border-b-4 border-r-4 border-transparent border-r-gray-900\"></div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"flex items-center gap-2\">\n <date-view-mode-picker\n v-if=\"isDatePath\"\n :viewMode=\"dateViewMode\"\n :path=\"path\"\n @update:viewMode=\"dateViewMode = $event\"\n ></date-view-mode-picker>\n <button\n type=\"button\"\n class=\"flex items-center gap-1 text-sm text-gray-600 hover:text-gray-800 px-2 py-1 rounded-md border border-transparent hover:border-edge-strong bg-surface\"\n @click.stop.prevent=\"copyPropertyValue\"\n title=\"Copy value\"\n aria-label=\"Copy property value\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7h8m-8 4h8m-8 4h5m-7-9a2 2 0 012-2h7a2 2 0 012 2v10a2 2 0 01-2 2H8l-4-4V7a2 2 0 012-2z\" />\n </svg>\n {{copyButtonLabel}}\n </button>\n <router-link\n v-if=\"path.ref && getValueForPath(path.path)\"\n :to=\"`/model/${path.ref}/document/${getValueForPath(path.path)}`\"\n class=\"bg-primary hover:bg-primary-hover text-primary-text px-2 py-1 text-sm rounded-md\"\n @click.stop\n >View Document\n </router-link>\n </div>\n </div>\n\n <!-- Collapsible Content -->\n <div v-if=\"!isCollapsed\" class=\"p-2\">\n <!-- Date Type Selector (when editing dates) -->\n <div v-if=\"editting && path.instance === 'Date'\" class=\"mb-3 flex gap-1.5\">\n <div\n @click=\"dateType = 'picker'\"\n :class=\"dateType === 'picker' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'picker' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n Date Picker\n </div>\n </div>\n <div\n @click=\"dateType = 'iso'\"\n :class=\"dateType === 'iso' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'iso' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n ISO String\n </div>\n </div>\n </div>\n\n <!-- Field Content -->\n <div v-if=\"editting && path.path !== '_id'\">\n <!-- Use detail-default with map editing for GeoJSON geometries -->\n <component\n v-if=\"isGeoJsonGeometry\"\n :is=\"getComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :viewMode=\"detailViewMode\"\n :on-change=\"handleInputChange\"\n >\n </component>\n <!-- Use standard edit components for other types -->\n <component\n v-else\n :is=\"getEditComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :format=\"dateType\"\n v-bind=\"getEditComponentProps(path)\"\n @input=\"handleInputChange($event)\"\n @error=\"invalid[path.path] = $event;\"\n >\n </component>\n </div>\n <div v-else>\n <!-- Show truncated or full value based on needsTruncation and isValueExpanded -->\n <!-- Special handling for truncated arrays -->\n <div v-if=\"isArray && shouldShowTruncated\" class=\"w-full\">\n <div class=\"mt-2\">\n <div\n v-for=\"(item, index) in truncatedArrayItems\"\n :key=\"index\"\n class=\"mb-1.5 py-2.5 px-3 pl-4 bg-transparent border-l-[3px] border-l-blue-500 rounded-none transition-all duration-200 cursor-pointer relative hover:bg-slate-50 hover:border-l-blue-600\">\n <div class=\"absolute -left-2 top-1/2 -translate-y-1/2 w-5 h-5 bg-blue-500 text-white rounded-full flex items-center justify-center text-[10px] font-semibold font-mono z-10 hover:bg-blue-600\">{{ index }}</div>\n <div v-if=\"arrayUtils.isObjectItem(item)\" class=\"flex flex-col gap-1 mt-1 px-2\">\n <div\n v-for=\"key in arrayUtils.getItemKeys(item)\"\n :key=\"key\"\n class=\"flex items-start gap-2 text-xs font-mono\">\n <span class=\"font-semibold text-gray-600 flex-shrink-0 min-w-[80px]\">{{ key }}:</span>\n <span class=\"text-gray-800 break-words whitespace-pre-wrap flex-1\">{{ arrayUtils.formatItemValue(item, key) }}</span>\n </div>\n </div>\n <div v-else class=\"text-xs py-1.5 px-2 font-mono text-gray-800 break-words whitespace-pre-wrap mt-1\">{{ arrayUtils.formatValue(item) }}</div>\n </div>\n <div class=\"mb-1.5 py-2.5 px-3 pl-4 bg-transparent border-none border-l-[3px] border-l-blue-500 rounded-none transition-all duration-200 cursor-pointer relative opacity-70 hover:opacity-100\">\n <div class=\"text-xs py-1.5 px-2 font-mono text-content-tertiary italic break-words whitespace-pre-wrap mt-1\">\n ... and {{ remainingArrayCount }} more item{{ remainingArrayCount !== 1 ? 's' : '' }}\n </div>\n </div>\n </div>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show all {{ arrayValue.length }} items\n </button>\n </div>\n <!-- Non-array truncated view -->\n <div v-else-if=\"shouldShowTruncated && !isArray\" class=\"relative\">\n <div class=\"text-content-secondary whitespace-pre-wrap break-words font-mono text-sm\">{{truncatedString}}</div>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show more ({{valueAsString.length}} characters)\n </button>\n </div>\n <!-- Expanded view -->\n <div v-else-if=\"needsTruncation && isValueExpanded\" class=\"relative\">\n <component\n :is=\"getComponentForPath(path)\"\n :value=\"rawValue\"\n :viewMode=\"isDatePath ? dateViewMode : detailViewMode\"\n @updated=\"renderedValue = $event\"></component>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4 rotate-180\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show less\n </button>\n </div>\n <!-- Full view (no truncation needed) -->\n <div v-else>\n <component\n :is=\"getComponentForPath(path)\"\n :value=\"rawValue\"\n :viewMode=\"isDatePath ? dateViewMode : detailViewMode\"\n @updated=\"renderedValue = $event\"></component>\n </div>\n </div>\n </div>\n</div>\n";
|
|
51812
|
+
module.exports = "<div class=\"border border-edge bg-surface rounded-lg mb-2\" style=\"overflow: visible;\">\n <!-- Collapsible Header -->\n <div\n @click=\"toggleCollapse\"\n class=\"p-1 cursor-pointer flex items-center justify-between border-b border-edge transition-colors duration-200 ease-in-out\"\n :class=\"{ 'bg-amber-100 hover:bg-amber-200': highlight, 'bg-slate-100 hover:bg-muted': !highlight }\"\n style=\"overflow: visible; position: relative;\"\n >\n <div class=\"flex items-center\" >\n <svg\n :class=\"isCollapsed ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-content-tertiary mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-content\">{{path.path}}</span>\n <span class=\"ml-2 text-sm text-content-tertiary\">({{(path.instance || 'unknown').toLowerCase()}})</span>\n <div v-if=\"isGeoJsonGeometry\" class=\"ml-3 inline-flex items-center gap-2\">\n <div class=\"inline-flex items-center rounded-full bg-gray-200 p-0.5 text-xs font-semibold\">\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"detailViewMode === 'text' ? 'bg-blue-600 text-white shadow' : 'text-content-secondary hover:text-content'\"\n :style=\"detailViewMode === 'text' ? 'color: white !important; background-color: #2563eb !important;' : ''\"\n @click.stop=\"setDetailViewMode('text')\">\n Text\n </button>\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"detailViewMode === 'map' ? 'bg-blue-600 text-white shadow' : 'text-content-secondary hover:text-content'\"\n :style=\"detailViewMode === 'map' ? 'color: white !important; background-color: #2563eb !important;' : ''\"\n @click.stop=\"setDetailViewMode('map')\">\n Map\n </button>\n </div>\n <!-- Info icon with tooltip -->\n <div v-if=\"editting\" class=\"relative inline-block\" style=\"z-index: 10002;\" @mouseenter=\"showTooltip = true\" @mouseleave=\"showTooltip = false\" @click.stop>\n <svg\n ref=\"infoIcon\"\n class=\"w-6 h-6 text-gray-400 hover:text-gray-600 cursor-help\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"></path>\n </svg>\n <div\n v-show=\"showTooltip\"\n ref=\"tooltip\"\n class=\"absolute left-full top-0 ml-2 w-64 p-3 text-white text-xs rounded-lg shadow-xl\"\n style=\"z-index: 99999; pointer-events: none; white-space: normal; position: fixed; background-color: #111827;\"\n :style=\"getTooltipStyle()\"\n >\n <div class=\"font-semibold mb-2\">Map Controls:</div>\n <div v-if=\"isGeoJsonPoint\" class=\"space-y-1\">\n <div>• Drag pin to move location</div>\n </div>\n <div v-else-if=\"isGeoJsonPolygon\" class=\"space-y-1\">\n <div>• Drag vertices to reshape polygon</div>\n <div v-if=\"isMultiPolygon\">• Right-click edge to add new vertex</div>\n <div>• Right-click vertex to delete</div>\n </div>\n <div class=\"absolute top-2 -left-1 w-0 h-0 border-t-4 border-b-4 border-r-4 border-transparent border-r-gray-900\"></div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"flex items-center gap-2\">\n <date-view-mode-picker\n v-if=\"isDatePath\"\n :viewMode=\"dateViewMode\"\n :path=\"path\"\n @update:viewMode=\"dateViewMode = $event\"\n ></date-view-mode-picker>\n <objectid-view-mode-picker\n v-if=\"isObjectIdPath\"\n :viewMode=\"objectIdViewMode\"\n @update:viewMode=\"objectIdViewMode = $event\"\n ></objectid-view-mode-picker>\n <button\n type=\"button\"\n class=\"flex items-center gap-1 text-sm text-gray-600 hover:text-gray-800 px-2 py-1 rounded-md border border-transparent hover:border-edge-strong bg-surface\"\n @click.stop.prevent=\"copyPropertyValue\"\n title=\"Copy value\"\n aria-label=\"Copy property value\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7h8m-8 4h8m-8 4h5m-7-9a2 2 0 012-2h7a2 2 0 012 2v10a2 2 0 01-2 2H8l-4-4V7a2 2 0 012-2z\" />\n </svg>\n {{copyButtonLabel}}\n </button>\n <router-link\n v-if=\"path.ref && getValueForPath(path.path)\"\n :to=\"`/model/${path.ref}/document/${getValueForPath(path.path)}`\"\n class=\"bg-primary hover:bg-primary-hover text-primary-text px-2 py-1 text-sm rounded-md\"\n @click.stop\n >View Document\n </router-link>\n </div>\n </div>\n\n <!-- Collapsible Content -->\n <div v-if=\"!isCollapsed\" class=\"p-2\">\n <!-- Date Type Selector (when editing dates) -->\n <div v-if=\"editting && path.instance === 'Date'\" class=\"mb-3 flex gap-1.5\">\n <div\n @click=\"dateType = 'picker'\"\n :class=\"dateType === 'picker' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'picker' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n Date Picker\n </div>\n </div>\n <div\n @click=\"dateType = 'iso'\"\n :class=\"dateType === 'iso' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'iso' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n ISO String\n </div>\n </div>\n </div>\n\n <!-- Field Content -->\n <div v-if=\"editting && path.path !== '_id'\">\n <!-- Use detail-default with map editing for GeoJSON geometries -->\n <component\n v-if=\"isGeoJsonGeometry\"\n :is=\"getComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :viewMode=\"detailViewMode\"\n :on-change=\"handleInputChange\"\n >\n </component>\n <!-- Use standard edit components for other types -->\n <component\n v-else\n :is=\"getEditComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :format=\"dateType\"\n v-bind=\"getEditComponentProps(path)\"\n @input=\"handleInputChange($event)\"\n @error=\"invalid[path.path] = $event;\"\n >\n </component>\n </div>\n <div v-else>\n <!-- Show truncated or full value based on needsTruncation and isValueExpanded -->\n <!-- Special handling for truncated arrays -->\n <div v-if=\"isArray && shouldShowTruncated\" class=\"w-full\">\n <div class=\"mt-2\">\n <div\n v-for=\"(item, index) in truncatedArrayItems\"\n :key=\"index\"\n class=\"mb-1.5 py-2.5 px-3 pl-4 bg-transparent border-l-[3px] border-l-blue-500 rounded-none transition-all duration-200 cursor-pointer relative hover:bg-slate-50 hover:border-l-blue-600\">\n <div class=\"absolute -left-2 top-1/2 -translate-y-1/2 w-5 h-5 bg-blue-500 text-white rounded-full flex items-center justify-center text-[10px] font-semibold font-mono z-10 hover:bg-blue-600\">{{ index }}</div>\n <div v-if=\"arrayUtils.isObjectItem(item)\" class=\"flex flex-col gap-1 mt-1 px-2\">\n <div\n v-for=\"key in arrayUtils.getItemKeys(item)\"\n :key=\"key\"\n class=\"flex items-start gap-2 text-xs font-mono\">\n <span class=\"font-semibold text-gray-600 flex-shrink-0 min-w-[80px]\">{{ key }}:</span>\n <span class=\"text-gray-800 break-words whitespace-pre-wrap flex-1\">{{ arrayUtils.formatItemValue(item, key) }}</span>\n </div>\n </div>\n <div v-else class=\"text-xs py-1.5 px-2 font-mono text-gray-800 break-words whitespace-pre-wrap mt-1\">{{ arrayUtils.formatValue(item) }}</div>\n </div>\n <div class=\"mb-1.5 py-2.5 px-3 pl-4 bg-transparent border-none border-l-[3px] border-l-blue-500 rounded-none transition-all duration-200 cursor-pointer relative opacity-70 hover:opacity-100\">\n <div class=\"text-xs py-1.5 px-2 font-mono text-content-tertiary italic break-words whitespace-pre-wrap mt-1\">\n ... and {{ remainingArrayCount }} more item{{ remainingArrayCount !== 1 ? 's' : '' }}\n </div>\n </div>\n </div>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show all {{ arrayValue.length }} items\n </button>\n </div>\n <!-- Non-array truncated view -->\n <div v-else-if=\"shouldShowTruncated && !isArray\" class=\"relative\">\n <div class=\"text-content-secondary whitespace-pre-wrap break-words font-mono text-sm\">{{truncatedString}}</div>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show more ({{valueAsString.length}} characters)\n </button>\n </div>\n <!-- Expanded view -->\n <div v-else-if=\"needsTruncation && isValueExpanded\" class=\"relative\">\n <component\n :is=\"getComponentForPath(path)\"\n :value=\"rawValue\"\n :viewMode=\"isDatePath ? dateViewMode : (isObjectIdPath ? objectIdViewMode : detailViewMode)\"\n @updated=\"renderedValue = $event\"></component>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4 rotate-180\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show less\n </button>\n </div>\n <!-- Full view (no truncation needed) -->\n <div v-else>\n <component\n :is=\"getComponentForPath(path)\"\n :value=\"rawValue\"\n :viewMode=\"isDatePath ? dateViewMode : (isObjectIdPath ? objectIdViewMode : detailViewMode)\"\n @updated=\"renderedValue = $event\"></component>\n </div>\n </div>\n </div>\n</div>\n";
|
|
51813
|
+
|
|
51814
|
+
/***/ },
|
|
51815
|
+
|
|
51816
|
+
/***/ "./frontend/src/document-details/objectid-view-mode-picker/objectid-view-mode-picker.html"
|
|
51817
|
+
/*!************************************************************************************************!*\
|
|
51818
|
+
!*** ./frontend/src/document-details/objectid-view-mode-picker/objectid-view-mode-picker.html ***!
|
|
51819
|
+
\************************************************************************************************/
|
|
51820
|
+
(module) {
|
|
51821
|
+
|
|
51822
|
+
"use strict";
|
|
51823
|
+
module.exports = "<div class=\"flex items-center gap-2\" @click.stop>\n <select\n :value=\"format\"\n @input=\"onFormatChange($event.target.value)\"\n class=\"text-xs border border-edge rounded-md bg-surface px-2 py-1\"\n >\n <option value=\"hex\">Hex string</option>\n <option value=\"object_call\">ObjectId('...')</option>\n <option value=\"unix_seconds\">Unix timestamp</option>\n <option value=\"date\">Date</option>\n </select>\n</div>\n";
|
|
51115
51824
|
|
|
51116
51825
|
/***/ },
|
|
51117
51826
|
|
|
@@ -51474,7 +52183,18 @@ module.exports = ".models {\n position: relative;\n display: flex;\n flex-dir
|
|
|
51474
52183
|
(module) {
|
|
51475
52184
|
|
|
51476
52185
|
"use strict";
|
|
51477
|
-
module.exports = "<div class=\"models flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <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' : ''\">\n <!-- Search -->\n <div class=\"p-3 shrink-0\">\n <div class=\"relative\">\n <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\">\n <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\" />\n </svg>\n <input\n v-model=\"modelSearch\"\n type=\"text\"\n placeholder=\"Find model...\"\n @keydown.esc=\"modelSearch = ''\"\n 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\"\n />\n </div>\n </div>\n <!-- Model list (scrollable) -->\n <nav class=\"flex-1 overflow-y-auto px-2 pb-2\">\n <!-- Recently Viewed -->\n <div v-if=\"filteredRecentModels.length > 0 && !modelSearch.trim()\">\n <div class=\"px-2 py-1 text-xs font-semibold text-gray-400 uppercase tracking-wider\">Recently Viewed</div>\n <ul role=\"list\">\n <li v-for=\"model in filteredRecentModels\" :key=\"'recent-' + model\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"flex items-center rounded-md py-1.5 px-2 text-sm text-content-secondary\"\n :class=\"model === currentModel ? 'bg-gray-200 font-semibold text-content' : 'hover:bg-muted'\">\n <span class=\"truncate\" v-html=\"highlightMatch(model)\"></span>\n <span\n v-if=\"modelDocumentCounts && modelDocumentCounts[model] !== undefined && model !== currentModel\"\n class=\"ml-auto text-xs text-gray-400 bg-muted rounded px-1.5 py-[1px]\"\n >\n {{formatCompactCount(modelDocumentCounts[model])}}\n </span>\n </router-link>\n </li>\n </ul>\n <div class=\"border-b border-edge my-2\"></div>\n </div>\n <!-- All Models / Search Results -->\n <div class=\"px-2 py-1 text-xs font-semibold text-gray-400 uppercase tracking-wider\">{{ modelSearch.trim() ? 'Search Results' : 'All Models' }}</div>\n <ul role=\"list\">\n <li v-for=\"model in filteredModels\" :key=\"'all-' + model\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"flex items-center rounded-md py-1.5 px-2 text-sm text-content-secondary\"\n :class=\"model === currentModel ? 'bg-gray-200 font-semibold text-content' : 'hover:bg-muted'\">\n <span class=\"truncate\" v-html=\"highlightMatch(model)\"></span>\n <span\n v-if=\"modelDocumentCounts && modelDocumentCounts[model] !== undefined && model !== currentModel\"\n class=\"ml-auto text-xs text-gray-400 bg-muted rounded px-1.5 py-[1px]\"\n >\n {{formatCompactCount(modelDocumentCounts[model])}}\n </span>\n </router-link>\n </li>\n </ul>\n <div v-if=\"filteredModels.length === 0 && modelSearch.trim()\" class=\"px-2 py-2 text-sm text-content-tertiary\">\n No models match \"{{modelSearch}}\"\n </div>\n <div v-if=\"models.length === 0 && status === 'loaded'\" class=\"p-2 bg-red-100 rounded-md\">\n No models found\n </div>\n </nav>\n <!-- Bottom toolbar -->\n <div class=\"shrink-0 border-t border-edge bg-page px-2 py-1.5 flex items-center gap-1\">\n <button\n type=\"button\"\n @click=\"hideSidebar = true\"\n class=\"rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n title=\"Hide sidebar\"\n >\n <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\">\n <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\" />\n </svg>\n </button>\n <button\n type=\"button\"\n @click=\"openModelSwitcher\"\n class=\"rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n title=\"Quick switch (Ctrl+P)\"\n >\n <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\">\n <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\" />\n </svg>\n </button>\n </div>\n </aside>\n <div class=\"documents bg-slate-50 min-w-0\" ref=\"documentsList\">\n <div class=\"documents-menu bg-slate-50\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <button\n v-if=\"hideSidebar === true || hideSidebar === null\"\n type=\"button\"\n @click=\"hideSidebar = false\"\n class=\"shrink-0 rounded-md p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n :class=\"hideSidebar === null ? 'lg:hidden' : ''\"\n title=\"Show sidebar\"\n >\n <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\">\n <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\" />\n </svg>\n </button>\n <document-search\n ref=\"documentSearch\"\n :value=\"searchText\"\n :schema-paths=\"schemaPaths\"\n @search=\"search\"\n >\n </document-search>\n <div>\n <span v-if=\"numDocuments == null\">Loading ...</span>\n <span v-else-if=\"typeof numDocuments === 'number'\">{{documents.length}}/{{numDocuments === 1 ? numDocuments + ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{\n 'bg-gray-700 ring-inset ring-2 ring-gray-300 hover:bg-gray-200 text-content-secondary': selectMultiple,\n 'bg-primary hover:bg-primary-hover text-primary-text': !selectMultiple\n }\"\n class=\"rounded px-2 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {{ selectMultiple ? 'Cancel' : 'Select' }}\n </button>\n <button\n v-show=\"selectMultiple\"\n @click=\"shouldShowUpdateMultipleModal=true;\"\n type=\"button\"\n class=\"rounded bg-green-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\"\n >\n Update\n </button>\n <button\n @click=\"shouldShowDeleteMultipleModal=true;\"\n type=\"button\"\n v-show=\"selectMultiple\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500\"\n >\n Delete\n </button>\n <div class=\"relative\" v-show=\"!selectMultiple\" ref=\"actionsMenuContainer\" @keyup.esc.prevent=\"closeActionsMenu\">\n <button\n @click=\"toggleActionsMenu\"\n type=\"button\"\n aria-label=\"More actions\"\n 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\">\n <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\">\n <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\" />\n </svg>\n </button>\n <div\n v-if=\"showActionsMenu\"\n 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-50\"\n >\n <div class=\"py-1\">\n <button\n @click=\"shouldShowExportModal = true; showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Export\n </button>\n <button\n @click=\"shouldShowCreateModal = true; showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Create\n </button>\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Indexes\n </button>\n <button\n @click=\"openCollectionInfo\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Collection Info\n </button>\n <button\n @click=\"findOldestDocument\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Find oldest document\n </button>\n <button\n @click=\"toggleRowNumbers()\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n {{ showRowNumbers ? 'Hide row numbers' : 'Show row numbers' }}\n </button>\n <button\n @click=\"resetFilter()\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Reset Filter\n </button>\n <button\n @click=\"toggleProjectionMenu(); showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm hover:bg-muted\"\n :class=\"isProjectionMenuSelected ? 'text-primary font-semibold' : 'text-content-secondary'\"\n >\n {{ isProjectionMenuSelected ? 'Projection (On)' : 'Projection' }}\n </button>\n <button\n v-if=\"isProjectionMenuSelected\"\n @click=\"clearProjection()\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Reset Projection\n </button>\n <async-button\n v-if=\"isProjectionMenuSelected\"\n type=\"button\"\n @click=\"applyDefaultProjectionColumns()\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Default Projection\n </async-button>\n </div>\n </div>\n </div>\n <span class=\"isolate inline-flex rounded-md shadow-sm\">\n <button\n @click=\"setOutputType('table')\"\n type=\"button\"\n 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\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-surface'\">\n <img class=\"h-5 w-5\" src=\"images/table.svg\">\n </button>\n <button\n @click=\"setOutputType('json')\"\n type=\"button\"\n 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\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-surface'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n <button\n @click=\"setOutputType('map')\"\n :disabled=\"geoJsonFields.length === 0\"\n type=\"button\"\n :title=\"geoJsonFields.length > 0 ? 'Map view' : 'No GeoJSON fields detected'\"\n 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\"\n :class=\"[\n geoJsonFields.length === 0 ? 'text-gray-300 cursor-not-allowed bg-muted' : 'text-gray-400 hover:bg-page',\n outputType === 'map' ? 'bg-gray-200' : (geoJsonFields.length > 0 ? 'bg-surface' : '')\n ]\">\n <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\">\n <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\" />\n </svg>\n </button>\n </span>\n </div>\n <div v-if=\"isProjectionMenuSelected && (outputType === 'table' || outputType === 'json')\" class=\"flex items-center gap-2 w-full mt-2 flex-shrink-0\">\n <input\n ref=\"projectionInput\"\n v-model=\"projectionText\"\n type=\"text\"\n placeholder=\"Projection: name email, or -password\"\n class=\"flex-1 min-w-0 rounded border border-edge bg-surface px-2 py-1.5 text-sm font-mono placeholder:text-content-tertiary focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\n @focus=\"initProjection($event)\"\n @keydown.enter=\"applyProjectionFromInput()\"\n />\n </div>\n </div>\n <!-- In JSON view, this container is the scrollable element used for infinite scroll. -->\n <div class=\"documents-container relative\" ref=\"documentsContainerScroll\" @scroll=\"checkIfScrolledToBottom\">\n <div v-if=\"error\">\n <div class=\"bg-red-100 border border-red-400 text-red-700 px-4 py-3 relative m-4 rounded-md\" role=\"alert\">\n <span class=\"block font-bold\">Error</span>\n <span class=\"block\">{{ error }}</span>\n <span class=\"block mt-2\">\n Need help?\n <a\n href=\"https://discord.gg/P3YCfKYxpy\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"underline font-medium text-red-800 hover:text-red-900\"\n >Ask in Discord</a>\n or\n <a\n href=\"https://github.com/mongoosejs/studio/issues\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"underline font-medium text-red-800 hover:text-red-900\"\n >\n open a GitHub issue.\n </a>\n </span>\n </div>\n </div>\n <div v-else-if=\"outputType === 'table'\" class=\"flex-1 min-h-0 flex flex-col overflow-hidden\">\n <div\n ref=\"documentsScrollContainer\"\n class=\"overflow-x-auto overflow-y-auto flex-1 min-h-0 border border-edge rounded-lg bg-surface\"\n @scroll=\"checkIfScrolledToBottom\"\n >\n <table class=\"min-w-full border-collapse text-sm\">\n <thead class=\"sticky top-0 z-10 bg-slate-100 dark:bg-shark-800 border-b border-edge\">\n <tr>\n <th v-if=\"showRowNumbers\" class=\"px-3 py-2.5 text-left font-medium text-content border-r border-edge whitespace-nowrap align-middle w-0\">\n #\n </th>\n <th\n v-for=\"path in tableDisplayPaths\"\n :key=\"path.path\"\n class=\"px-3 py-2.5 text-left font-medium text-content border-r border-edge last:border-r-0 whitespace-nowrap align-middle\"\n >\n <div class=\"flex items-center gap-2\">\n <span\n @click=\"addPathFilter(path.path)\"\n class=\"cursor-pointer hover:text-primary truncate min-w-0\"\n :title=\"path.path\"\n >\n {{ path.path }}\n </span>\n <span class=\"text-xs text-content-tertiary shrink-0\">({{ path.instance || 'unknown' }})</span>\n <span class=\"inline-flex shrink-0 gap-0.5 items-center\">\n <button\n type=\"button\"\n @click.stop=\"sortDocs(1, path.path)\"\n class=\"p-0.5 rounded text-content-tertiary hover:text-content hover:bg-muted\"\n :class=\"{ 'text-primary font-semibold': sortBy[path.path] === 1 }\"\n :title=\"sortBy[path.path] === 1 ? 'Clear sort' : 'Sort ascending'\"\n >\n ↑\n </button>\n <button\n type=\"button\"\n @click.stop=\"sortDocs(-1, path.path)\"\n class=\"p-0.5 rounded text-content-tertiary hover:text-content hover:bg-muted\"\n :class=\"{ 'text-primary font-semibold': sortBy[path.path] === -1 }\"\n :title=\"sortBy[path.path] === -1 ? 'Clear sort' : 'Sort descending'\"\n >\n ↓\n </button>\n <button\n v-if=\"filteredPaths.length > 0\"\n type=\"button\"\n @click.stop=\"removeField(path)\"\n class=\"p-1.5 rounded-md border border-transparent text-content-tertiary hover:text-valencia-600 hover:bg-valencia-50 hover:border-valencia-200 focus:outline-none focus:ring-2 focus:ring-valencia-500/30\"\n title=\"Remove column\"\n aria-label=\"Remove column\"\n >\n <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\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18 18 6M6 6l12 12\" />\n </svg>\n </button>\n </span>\n </div>\n </th>\n <th v-if=\"filteredPaths.length > 0\" class=\"px-2 py-2.5 border-r border-edge last:border-r-0 align-middle w-0 bg-slate-50 dark:bg-shark-800/80\">\n <div class=\"relative\" ref=\"addFieldContainer\">\n <button\n type=\"button\"\n @click=\"toggleAddFieldDropdown()\"\n class=\"flex items-center justify-center w-8 h-8 rounded border border-dashed border-edge text-content-tertiary hover:border-primary hover:text-primary hover:bg-primary-subtle/30\"\n title=\"Add column\"\n aria-label=\"Add column\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" class=\"w-5 h-5\"> <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 4.5v15m7.5-7.5h-15\" /> </svg>\n </button>\n <div\n v-if=\"showAddFieldDropdown\"\n class=\"absolute right-0 top-full mt-1 z-[100] min-w-[180px] max-w-[280px] rounded-md border border-edge bg-surface shadow-lg py-1 max-h-48 overflow-y-auto\"\n >\n <input\n v-if=\"availablePathsToAdd.length > 5\"\n ref=\"addFieldFilterInput\"\n v-model=\"addFieldFilterText\"\n type=\"text\"\n placeholder=\"Filter fields...\"\n class=\"mx-2 mb-1 w-[calc(100%-1rem)] rounded border border-edge px-2 py-1 text-sm\"\n @click.stop\n />\n <button\n v-for=\"p in filteredPathsToAdd\"\n :key=\"p.path\"\n type=\"button\"\n class=\"w-full px-3 py-1.5 text-left text-sm hover:bg-muted\"\n @click.stop=\"addField(p)\"\n >\n {{ p.path }}\n </button>\n <p v-if=\"filteredPathsToAdd.length === 0\" class=\"px-3 py-2 text-sm text-content-tertiary\">\n {{ addFieldFilterText.trim() ? 'No matching fields' : 'All fields added' }}\n </p>\n </div>\n </div>\n </th>\n </tr>\n </thead>\n <tbody class=\"bg-surface\">\n <tr\n v-for=\"(document, docIndex) in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentClick(document, $event)\"\n class=\"border-b border-edge cursor-pointer transition-colors hover:bg-muted/60\"\n :class=\"{ 'bg-primary-subtle/50 hover:bg-primary-subtle/70': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }\"\n >\n <td v-if=\"showRowNumbers\" class=\"px-3 py-2 border-r border-edge align-top text-content-tertiary whitespace-nowrap\">\n {{ docIndex + 1 }}\n </td>\n <td\n v-for=\"schemaPath in tableDisplayPaths\"\n :key=\"schemaPath.path\"\n class=\"px-3 py-2 border-r border-edge last:border-r-0 align-top max-w-[280px]\"\n >\n <div class=\"table-cell-content flex items-center gap-1.5 min-w-0 group\">\n <span class=\"min-w-0 overflow-hidden text-ellipsis flex-1\">\n <component\n :is=\"getComponentForPath(schemaPath)\"\n :value=\"getValueForPath(document, schemaPath.path)\"\n :allude=\"getReferenceModel(schemaPath)\"\n />\n </span>\n <button\n type=\"button\"\n class=\"table-cell-copy shrink-0 p-1 rounded text-content-tertiary hover:text-content hover:bg-muted focus:outline-none focus:ring-1 focus:ring-edge\"\n aria-label=\"Copy cell value\"\n @click.stop=\"copyCellValue(getValueForPath(document, schemaPath.path))\"\n >\n <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\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8 5H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1M8 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M8 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m0 0h2a2 2 0 0 1 2 2v3m2 4H10m0 0l3-3m-3 3l3 3\" />\n </svg>\n </button>\n </div>\n </td>\n <td v-if=\"filteredPaths.length > 0\" class=\"w-0 px-0 py-0 border-r border-edge last:border-r-0 bg-slate-50/50 dark:bg-shark-800/30\"></td>\n </tr>\n </tbody>\n </table>\n </div>\n <div v-if=\"outputType === 'table' && (loadingMore || (status === 'loading' && documents.length > 0))\" class=\"flex items-center justify-center gap-2 py-3 text-sm text-content-tertiary border-t border-edge bg-surface\">\n <img src=\"images/loader.gif\" alt=\"\" class=\"h-5 w-5\">\n <span>Loading documents…</span>\n </div>\n <p v-if=\"outputType === 'table' && documents.length === 0 && status === 'loaded'\" class=\"mt-2 text-sm text-content-tertiary px-1\">\n No documents to show. Use Projection in the menu to choose columns.\n </p>\n </div>\n <div v-else-if=\"outputType === 'json'\" class=\"flex flex-col space-y-2 p-1 mt-1\">\n <div\n v-for=\"document in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentContainerClick(document, $event)\"\n :class=\"[\n 'group relative transition-colors rounded-md border border-slate-100',\n selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:shadow-sm hover:border-slate-300 bg-surface'\n ]\"\n >\n <router-link\n 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\"\n :to=\"{ path: '/model/' + currentModel + '/document/' + document._id, query: $route.query }\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n @click.stop\n >\n Open\n </router-link>\n <list-json :value=\"filterDocument(document)\" :references=\"referenceMap\">\n </list-json>\n </div>\n <div v-if=\"outputType === 'json' && (loadingMore || (status === 'loading' && documents.length > 0))\" class=\"flex items-center justify-center gap-2 py-3 text-sm text-content-tertiary\">\n <img src=\"images/loader.gif\" alt=\"\" class=\"h-5 w-5\">\n <span>Loading documents…</span>\n </div>\n </div>\n <div v-else-if=\"outputType === 'map'\" class=\"flex flex-col h-full\">\n <div class=\"p-2 bg-surface border-b flex items-center gap-2\">\n <label class=\"text-sm font-medium text-content-secondary\">GeoJSON Field:</label>\n <select\n :value=\"selectedGeoField\"\n @change=\"setSelectedGeoField($event.target.value)\"\n class=\"rounded-md border border-edge-strong py-1 px-2 text-sm focus:border-primary focus:ring-primary\"\n >\n <option v-for=\"field in geoJsonFields\" :key=\"field.path\" :value=\"field.path\">\n {{ field.label }}\n </option>\n </select>\n <async-button\n @click=\"loadMoreDocuments\"\n :disabled=\"loadedAllDocs\"\n type=\"button\"\n 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\"\n :class=\"loadedAllDocs ? 'bg-gray-400 cursor-not-allowed' : 'bg-primary hover:bg-primary-hover'\"\n >\n Load more\n </async-button>\n </div>\n <div class=\"flex-1 min-h-[400px]\" ref=\"modelsMap\"></div>\n </div>\n <div v-if=\"status === 'loading' && !loadingMore && documents.length === 0\" class=\"loader loader-overlay\" aria-busy=\"true\">\n <img src=\"images/loader.gif\" alt=\"Loading\">\n </div>\n </div>\n </div>\n <modal v-if=\"shouldShowExportModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowExportModal = false\">×</div>\n <export-query-results\n :schemaPaths=\"schemaPaths\"\n :search-text=\"searchText\"\n :currentModel=\"currentModel\"\n @done=\"shouldShowExportModal = false\">\n </export-query-results>\n </template>\n </modal>\n <modal v-if=\"shouldShowIndexModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowIndexModal = false\">×</div>\n <div class=\"text-xl font-bold mb-2\">Indexes</div>\n <div v-for=\"index in mongoDBIndexes\" class=\"w-full flex items-center\">\n <div class=\"grow shrink text-left flex justify-between items-center\" v-if=\"index.name != '_id_'\">\n <div>\n <div class=\"font-bold flex items-center gap-2\">\n <div>{{ index.name }}</div>\n <div v-if=\"isTTLIndex(index)\" class=\"rounded-full bg-primary-subtle px-2 py-0.5 text-xs font-semibold text-primary\">\n TTL: {{ formatTTL(index.expireAfterSeconds) }}\n </div>\n </div>\n <div class=\"text-sm font-mono\">{{ JSON.stringify(index.key) }}</div>\n </div>\n <div>\n <async-button\n type=\"button\"\n @click=\"dropIndex(index.name)\"\n 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 disabled:bg-gray-400 disabled:cursor-not-allowed\">\n Drop\n </async-button>\n </div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCollectionInfoModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCollectionInfoModal = false\">×</div>\n <div class=\"text-xl font-bold mb-2\">Collection Info</div>\n <div v-if=\"!collectionInfo\" class=\"text-gray-600\">Loading collection details...</div>\n <div v-else class=\"space-y-3\">\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Documents</div>\n <div class=\"text-content\">{{ formatNumber(collectionInfo.documentCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Indexes</div>\n <div class=\"text-content\">{{ formatNumber(collectionInfo.indexCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Total Index Size</div>\n <div class=\"text-content\">{{ formatCollectionSize(collectionInfo.totalIndexSize) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Total Storage Size</div>\n <div class=\"text-content\">{{ formatCollectionSize(collectionInfo.size) }}</div>\n </div>\n <div class=\"flex flex-col gap-1\">\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Collation</div>\n <div class=\"text-content\">{{ collectionInfo.hasCollation ? 'Yes' : 'No' }}</div>\n </div>\n <div v-if=\"collectionInfo.hasCollation\" class=\"rounded bg-muted p-3 text-sm text-gray-800 overflow-x-auto\">\n <pre class=\"whitespace-pre-wrap\">{{ JSON.stringify(collectionInfo.collation, null, 2) }}</pre>\n </div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Capped</div>\n <div class=\"text-content\">{{ collectionInfo.capped ? 'Yes' : 'No' }}</div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCreateModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCreateModal = false;\">×</div>\n <create-document :currentModel=\"currentModel\" :paths=\"schemaPaths\" @close=\"closeCreationModal\"></create-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowUpdateMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowUpdateMultipleModal = false;\">×</div>\n <update-document :currentModel=\"currentModel\" :document=\"selectedDocuments\" :multiple=\"true\" @update=\"updateDocuments\" @close=\"shouldShowUpdateMultipleModal=false;\"></update-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteMultipleModal = false;\">×</div>\n <h2>Are you sure you want to delete {{selectedDocuments.length}} documents?</h2>\n <div>\n <list-json :value=\"selectedDocuments\"></list-json>\n </div>\n <div class=\"flex gap-4\">\n <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\">\n Confirm\n </async-button>\n <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\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n <model-switcher\n :show=\"showModelSwitcher\"\n :models=\"models\"\n :recently-viewed-models=\"recentlyViewedModels\"\n :model-document-counts=\"modelDocumentCounts\"\n @close=\"showModelSwitcher = false\"\n @select=\"selectSwitcherModel\"\n ></model-switcher>\n</div>\n";
|
|
52186
|
+
module.exports = "<div class=\"models flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <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' : ''\">\n <!-- Search -->\n <div class=\"p-3 shrink-0\">\n <div class=\"relative\">\n <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\">\n <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\" />\n </svg>\n <input\n v-model=\"modelSearch\"\n type=\"text\"\n placeholder=\"Find model...\"\n @keydown.esc=\"modelSearch = ''\"\n 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\"\n />\n </div>\n </div>\n <!-- Model list (scrollable) -->\n <nav class=\"flex-1 overflow-y-auto px-2 pb-2\">\n <!-- Recently Viewed -->\n <div v-if=\"filteredRecentModels.length > 0 && !modelSearch.trim()\">\n <div class=\"px-2 py-1 text-xs font-semibold text-gray-400 uppercase tracking-wider\">Recently Viewed</div>\n <ul role=\"list\">\n <li v-for=\"model in filteredRecentModels\" :key=\"'recent-' + model\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"flex items-center rounded-md py-1.5 px-2 text-sm text-content-secondary\"\n :class=\"model === currentModel ? 'bg-gray-200 font-semibold text-content' : 'hover:bg-muted'\">\n <span class=\"truncate\" v-html=\"highlightMatch(model)\"></span>\n <span\n v-if=\"modelDocumentCounts && modelDocumentCounts[model] !== undefined && model !== currentModel\"\n class=\"ml-auto text-xs text-gray-400 bg-muted rounded px-1.5 py-[1px]\"\n >\n {{formatCompactCount(modelDocumentCounts[model])}}\n </span>\n </router-link>\n </li>\n </ul>\n <div class=\"border-b border-edge my-2\"></div>\n </div>\n <!-- All Models / Search Results -->\n <div class=\"px-2 py-1 text-xs font-semibold text-gray-400 uppercase tracking-wider\">{{ modelSearch.trim() ? 'Search Results' : 'All Models' }}</div>\n <ul role=\"list\">\n <li v-for=\"model in filteredModels\" :key=\"'all-' + model\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"flex items-center rounded-md py-1.5 px-2 text-sm text-content-secondary\"\n :class=\"model === currentModel ? 'bg-gray-200 font-semibold text-content' : 'hover:bg-muted'\">\n <span class=\"truncate\" v-html=\"highlightMatch(model)\"></span>\n <span\n v-if=\"modelDocumentCounts && modelDocumentCounts[model] !== undefined && model !== currentModel\"\n class=\"ml-auto text-xs text-gray-400 bg-muted rounded px-1.5 py-[1px]\"\n >\n {{formatCompactCount(modelDocumentCounts[model])}}\n </span>\n </router-link>\n </li>\n </ul>\n <div v-if=\"filteredModels.length === 0 && modelSearch.trim()\" class=\"px-2 py-2 text-sm text-content-tertiary\">\n No models match \"{{modelSearch}}\"\n </div>\n <div v-if=\"models.length === 0 && status === 'loaded'\" class=\"p-2 bg-red-100 rounded-md\">\n No models found\n </div>\n </nav>\n <!-- Bottom toolbar -->\n <div class=\"shrink-0 border-t border-edge bg-page px-2 py-1.5 flex items-center gap-1\">\n <button\n type=\"button\"\n @click=\"hideSidebar = true\"\n class=\"rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n title=\"Hide sidebar\"\n >\n <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\">\n <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\" />\n </svg>\n </button>\n <button\n type=\"button\"\n @click=\"openModelSwitcher\"\n class=\"rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n title=\"Quick switch (Ctrl+P)\"\n >\n <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\">\n <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\" />\n </svg>\n </button>\n </div>\n </aside>\n <div class=\"documents bg-slate-50 min-w-0\" ref=\"documentsList\">\n <div class=\"documents-menu bg-slate-50\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <button\n v-if=\"hideSidebar === true || hideSidebar === null\"\n type=\"button\"\n @click=\"hideSidebar = false\"\n class=\"shrink-0 rounded-md p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n :class=\"hideSidebar === null ? 'lg:hidden' : ''\"\n title=\"Show sidebar\"\n >\n <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\">\n <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\" />\n </svg>\n </button>\n <document-search\n ref=\"documentSearch\"\n :value=\"searchText\"\n :schema-paths=\"schemaPaths\"\n @search=\"search\"\n >\n </document-search>\n <div>\n <span v-if=\"numDocuments == null\">Loading ...</span>\n <span v-else-if=\"typeof numDocuments === 'number'\">{{documents.length}}/{{numDocuments === 1 ? numDocuments + ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{\n 'bg-gray-700 ring-inset ring-2 ring-gray-300 hover:bg-gray-200 text-content-secondary': selectMultiple,\n 'bg-primary hover:bg-primary-hover text-primary-text': !selectMultiple\n }\"\n class=\"rounded px-2 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {{ selectMultiple ? 'Cancel' : 'Select' }}\n </button>\n <button\n v-show=\"selectMultiple\"\n @click=\"shouldShowUpdateMultipleModal=true;\"\n type=\"button\"\n class=\"rounded bg-green-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\"\n >\n Update\n </button>\n <button\n @click=\"shouldShowDeleteMultipleModal=true;\"\n type=\"button\"\n v-show=\"selectMultiple\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500\"\n >\n Delete\n </button>\n <div class=\"relative\" v-show=\"!selectMultiple\" ref=\"actionsMenuContainer\" @keyup.esc.prevent=\"closeActionsMenu\">\n <button\n @click=\"toggleActionsMenu\"\n type=\"button\"\n aria-label=\"More actions\"\n 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\">\n <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\">\n <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\" />\n </svg>\n </button>\n <div\n v-if=\"showActionsMenu\"\n 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-50\"\n >\n <div class=\"py-1\">\n <button\n @click=\"shouldShowExportModal = true; showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Export\n </button>\n <button\n @click=\"shouldShowCreateModal = true; showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Create\n </button>\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Indexes\n </button>\n <button\n @click=\"openCollectionInfo\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Collection Info\n </button>\n <button\n @click=\"findOldestDocument\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Find oldest document\n </button>\n <button\n @click=\"toggleRowNumbers()\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n {{ showRowNumbers ? 'Hide row numbers' : 'Show row numbers' }}\n </button>\n <button\n @click=\"resetFilter()\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Reset Filter\n </button>\n <button\n @click=\"toggleProjectionMenu(); showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm hover:bg-muted\"\n :class=\"isProjectionMenuSelected ? 'text-primary font-semibold' : 'text-content-secondary'\"\n >\n {{ isProjectionMenuSelected ? 'Projection (On)' : 'Projection' }}\n </button>\n <button\n v-if=\"isProjectionMenuSelected\"\n @click=\"clearProjection()\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Reset Projection\n </button>\n <async-button\n v-if=\"isProjectionMenuSelected\"\n type=\"button\"\n @click=\"applyDefaultProjectionColumns()\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Default Projection\n </async-button>\n </div>\n </div>\n </div>\n <span class=\"isolate inline-flex rounded-md shadow-sm\">\n <button\n @click=\"setOutputType('table')\"\n type=\"button\"\n 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\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-surface'\">\n <img class=\"h-5 w-5\" src=\"images/table.svg\">\n </button>\n <button\n @click=\"setOutputType('json')\"\n type=\"button\"\n 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\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-surface'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n <button\n @click=\"setOutputType('map')\"\n :disabled=\"geoJsonFields.length === 0\"\n type=\"button\"\n :title=\"geoJsonFields.length > 0 ? 'Map view' : 'No GeoJSON fields detected'\"\n 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\"\n :class=\"[\n geoJsonFields.length === 0 ? 'text-gray-300 cursor-not-allowed bg-muted' : 'text-gray-400 hover:bg-page',\n outputType === 'map' ? 'bg-gray-200' : (geoJsonFields.length > 0 ? 'bg-surface' : '')\n ]\">\n <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\">\n <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\" />\n </svg>\n </button>\n </span>\n </div>\n <div v-if=\"isProjectionMenuSelected && (outputType === 'table' || outputType === 'json')\" class=\"flex items-center gap-2 w-full mt-2 flex-shrink-0\">\n <projection-search\n ref=\"projectionSearch\"\n :value=\"projectionText\"\n :schema-paths=\"schemaPaths\"\n @projection-input=\"projectionText = $event\"\n @apply=\"applyProjectionFromInput\"\n />\n </div>\n </div>\n <!-- In JSON view, this container is the scrollable element used for infinite scroll. -->\n <div class=\"documents-container relative\" ref=\"documentsContainerScroll\" @scroll=\"checkIfScrolledToBottom\">\n <div v-if=\"error\">\n <div class=\"bg-red-100 border border-red-400 text-red-700 px-4 py-3 relative m-4 rounded-md\" role=\"alert\">\n <span class=\"block font-bold\">Error</span>\n <span class=\"block\">{{ error }}</span>\n <span class=\"block mt-2\">\n Need help?\n <a\n href=\"https://discord.gg/P3YCfKYxpy\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"underline font-medium text-red-800 hover:text-red-900\"\n >Ask in Discord</a>\n or\n <a\n href=\"https://github.com/mongoosejs/studio/issues\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"underline font-medium text-red-800 hover:text-red-900\"\n >\n open a GitHub issue.\n </a>\n </span>\n </div>\n </div>\n <div v-else-if=\"outputType === 'table'\" class=\"flex-1 min-h-0 flex flex-col overflow-hidden\">\n <div\n ref=\"documentsScrollContainer\"\n class=\"overflow-x-auto overflow-y-auto flex-1 min-h-0 border border-edge rounded-lg bg-surface\"\n @scroll=\"checkIfScrolledToBottom\"\n >\n <table class=\"min-w-full border-collapse text-sm\">\n <thead class=\"sticky top-0 z-10 bg-slate-100 dark:bg-shark-800 border-b border-edge\">\n <tr>\n <th v-if=\"showRowNumbers\" class=\"px-3 py-2.5 text-left font-medium text-content border-r border-edge whitespace-nowrap align-middle w-0\">\n #\n </th>\n <th\n v-for=\"path in tableDisplayPaths\"\n :key=\"path.path\"\n class=\"px-3 py-2.5 text-left font-medium text-content border-r border-edge last:border-r-0 whitespace-nowrap align-middle\"\n >\n <div class=\"flex items-center gap-2\">\n <span\n @click=\"addPathFilter(path.path)\"\n class=\"cursor-pointer hover:text-primary truncate min-w-0\"\n :title=\"path.path\"\n >\n {{ path.path }}\n </span>\n <span class=\"text-xs text-content-tertiary shrink-0\">({{ path.instance || 'unknown' }})</span>\n <span class=\"inline-flex shrink-0 gap-0.5 items-center\">\n <button\n type=\"button\"\n @click.stop=\"sortDocs(1, path.path)\"\n class=\"p-0.5 rounded text-content-tertiary hover:text-content hover:bg-muted\"\n :class=\"{ 'text-primary font-semibold': sortBy[path.path] === 1 }\"\n :title=\"sortBy[path.path] === 1 ? 'Clear sort' : 'Sort ascending'\"\n >\n ↑\n </button>\n <button\n type=\"button\"\n @click.stop=\"sortDocs(-1, path.path)\"\n class=\"p-0.5 rounded text-content-tertiary hover:text-content hover:bg-muted\"\n :class=\"{ 'text-primary font-semibold': sortBy[path.path] === -1 }\"\n :title=\"sortBy[path.path] === -1 ? 'Clear sort' : 'Sort descending'\"\n >\n ↓\n </button>\n <button\n v-if=\"filteredPaths.length > 0\"\n type=\"button\"\n @click.stop=\"removeField(path)\"\n class=\"p-1.5 rounded-md border border-transparent text-content-tertiary hover:text-valencia-600 hover:bg-valencia-50 hover:border-valencia-200 focus:outline-none focus:ring-2 focus:ring-valencia-500/30\"\n title=\"Remove column\"\n aria-label=\"Remove column\"\n >\n <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\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18 18 6M6 6l12 12\" />\n </svg>\n </button>\n </span>\n </div>\n </th>\n <th v-if=\"filteredPaths.length > 0\" class=\"px-2 py-2.5 border-r border-edge last:border-r-0 align-middle w-0 bg-slate-50 dark:bg-shark-800/80\">\n <div class=\"relative\" ref=\"addFieldContainer\">\n <button\n type=\"button\"\n @click=\"toggleAddFieldDropdown()\"\n class=\"flex items-center justify-center w-8 h-8 rounded border border-dashed border-edge text-content-tertiary hover:border-primary hover:text-primary hover:bg-primary-subtle/30\"\n title=\"Add column\"\n aria-label=\"Add column\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" class=\"w-5 h-5\"> <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 4.5v15m7.5-7.5h-15\" /> </svg>\n </button>\n <div\n v-if=\"showAddFieldDropdown\"\n class=\"absolute right-0 top-full mt-1 z-[100] min-w-[180px] max-w-[280px] rounded-md border border-edge bg-surface shadow-lg py-1 max-h-48 overflow-y-auto\"\n >\n <input\n v-if=\"availablePathsToAdd.length > 5\"\n ref=\"addFieldFilterInput\"\n v-model=\"addFieldFilterText\"\n type=\"text\"\n placeholder=\"Filter fields...\"\n class=\"mx-2 mb-1 w-[calc(100%-1rem)] rounded border border-edge px-2 py-1 text-sm\"\n @click.stop\n />\n <button\n v-for=\"p in filteredPathsToAdd\"\n :key=\"p.path\"\n type=\"button\"\n class=\"w-full px-3 py-1.5 text-left text-sm hover:bg-muted\"\n @click.stop=\"addField(p)\"\n >\n {{ p.path }}\n </button>\n <p v-if=\"filteredPathsToAdd.length === 0\" class=\"px-3 py-2 text-sm text-content-tertiary\">\n {{ addFieldFilterText.trim() ? 'No matching fields' : 'All fields added' }}\n </p>\n </div>\n </div>\n </th>\n </tr>\n </thead>\n <tbody class=\"bg-surface\">\n <tr\n v-for=\"(document, docIndex) in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentClick(document, $event)\"\n class=\"border-b border-edge cursor-pointer transition-colors hover:bg-muted/60\"\n :class=\"{ 'bg-primary-subtle/50 hover:bg-primary-subtle/70': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }\"\n >\n <td v-if=\"showRowNumbers\" class=\"px-3 py-2 border-r border-edge align-top text-content-tertiary whitespace-nowrap\">\n {{ docIndex + 1 }}\n </td>\n <td\n v-for=\"schemaPath in tableDisplayPaths\"\n :key=\"schemaPath.path\"\n class=\"px-3 py-2 border-r border-edge last:border-r-0 align-top max-w-[280px]\"\n >\n <div class=\"table-cell-content flex items-center gap-1.5 min-w-0 group\">\n <span class=\"min-w-0 overflow-hidden text-ellipsis flex-1\">\n <component\n :is=\"getComponentForPath(schemaPath)\"\n :value=\"getValueForPath(document, schemaPath.path)\"\n :allude=\"getReferenceModel(schemaPath)\"\n />\n </span>\n <button\n type=\"button\"\n class=\"table-cell-copy shrink-0 p-1 rounded text-content-tertiary hover:text-content hover:bg-muted focus:outline-none focus:ring-1 focus:ring-edge\"\n aria-label=\"Copy cell value\"\n @click.stop=\"copyCellValue(getValueForPath(document, schemaPath.path))\"\n >\n <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\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8 5H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1M8 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M8 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m0 0h2a2 2 0 0 1 2 2v3m2 4H10m0 0l3-3m-3 3l3 3\" />\n </svg>\n </button>\n </div>\n </td>\n <td v-if=\"filteredPaths.length > 0\" class=\"w-0 px-0 py-0 border-r border-edge last:border-r-0 bg-slate-50/50 dark:bg-shark-800/30\"></td>\n </tr>\n </tbody>\n </table>\n </div>\n <div v-if=\"outputType === 'table' && (loadingMore || (status === 'loading' && documents.length > 0))\" class=\"flex items-center justify-center gap-2 py-3 text-sm text-content-tertiary border-t border-edge bg-surface\">\n <img src=\"images/loader.gif\" alt=\"\" class=\"h-5 w-5\">\n <span>Loading documents…</span>\n </div>\n <p v-if=\"outputType === 'table' && documents.length === 0 && status === 'loaded'\" class=\"mt-2 text-sm text-content-tertiary px-1\">\n No documents to show. Use Projection in the menu to choose columns.\n </p>\n </div>\n <div v-else-if=\"outputType === 'json'\" class=\"flex flex-col space-y-2 p-1 mt-1\">\n <div\n v-for=\"document in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentContainerClick(document, $event)\"\n :class=\"[\n 'group relative transition-colors rounded-md border border-slate-100',\n selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:shadow-sm hover:border-slate-300 bg-surface'\n ]\"\n >\n <router-link\n 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\"\n :to=\"{ path: '/model/' + currentModel + '/document/' + document._id, query: $route.query }\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n @click.stop\n >\n Open\n </router-link>\n <list-json :value=\"filterDocument(document)\" :references=\"referenceMap\">\n </list-json>\n </div>\n <div v-if=\"outputType === 'json' && (loadingMore || (status === 'loading' && documents.length > 0))\" class=\"flex items-center justify-center gap-2 py-3 text-sm text-content-tertiary\">\n <img src=\"images/loader.gif\" alt=\"\" class=\"h-5 w-5\">\n <span>Loading documents…</span>\n </div>\n </div>\n <div v-else-if=\"outputType === 'map'\" class=\"flex flex-col h-full\">\n <div class=\"p-2 bg-surface border-b flex items-center gap-2\">\n <label class=\"text-sm font-medium text-content-secondary\">GeoJSON Field:</label>\n <select\n :value=\"selectedGeoField\"\n @change=\"setSelectedGeoField($event.target.value)\"\n class=\"rounded-md border border-edge-strong py-1 px-2 text-sm focus:border-primary focus:ring-primary\"\n >\n <option v-for=\"field in geoJsonFields\" :key=\"field.path\" :value=\"field.path\">\n {{ field.label }}\n </option>\n </select>\n <async-button\n @click=\"loadMoreDocuments\"\n :disabled=\"loadedAllDocs\"\n type=\"button\"\n 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\"\n :class=\"loadedAllDocs ? 'bg-gray-400 cursor-not-allowed' : 'bg-primary hover:bg-primary-hover'\"\n >\n Load more\n </async-button>\n </div>\n <div class=\"flex-1 min-h-[400px]\" ref=\"modelsMap\"></div>\n </div>\n <div v-if=\"status === 'loading' && !loadingMore && documents.length === 0\" class=\"loader loader-overlay\" aria-busy=\"true\">\n <img src=\"images/loader.gif\" alt=\"Loading\">\n </div>\n </div>\n </div>\n <modal v-if=\"shouldShowExportModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowExportModal = false\">×</div>\n <export-query-results\n :schemaPaths=\"schemaPaths\"\n :search-text=\"searchText\"\n :currentModel=\"currentModel\"\n @done=\"shouldShowExportModal = false\">\n </export-query-results>\n </template>\n </modal>\n <modal v-if=\"shouldShowIndexModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowIndexModal = false\">×</div>\n <div class=\"text-xl font-bold mb-2\">Indexes</div>\n <div v-for=\"index in mongoDBIndexes\" class=\"w-full flex items-center\">\n <div class=\"grow shrink text-left flex justify-between items-center\" v-if=\"index.name != '_id_'\">\n <div>\n <div class=\"font-bold flex items-center gap-2\">\n <div>{{ index.name }}</div>\n <div v-if=\"isTTLIndex(index)\" class=\"rounded-full bg-primary-subtle px-2 py-0.5 text-xs font-semibold text-primary\">\n TTL: {{ formatTTL(index.expireAfterSeconds) }}\n </div>\n </div>\n <div class=\"text-sm font-mono\">{{ JSON.stringify(index.key) }}</div>\n </div>\n <div>\n <async-button\n type=\"button\"\n @click=\"dropIndex(index.name)\"\n 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 disabled:bg-gray-400 disabled:cursor-not-allowed\">\n Drop\n </async-button>\n </div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCollectionInfoModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCollectionInfoModal = false\">×</div>\n <div class=\"text-xl font-bold mb-2\">Collection Info</div>\n <div v-if=\"!collectionInfo\" class=\"text-gray-600\">Loading collection details...</div>\n <div v-else class=\"space-y-3\">\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Documents</div>\n <div class=\"text-content\">{{ formatNumber(collectionInfo.documentCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Indexes</div>\n <div class=\"text-content\">{{ formatNumber(collectionInfo.indexCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Total Index Size</div>\n <div class=\"text-content\">{{ formatCollectionSize(collectionInfo.totalIndexSize) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Total Storage Size</div>\n <div class=\"text-content\">{{ formatCollectionSize(collectionInfo.size) }}</div>\n </div>\n <div class=\"flex flex-col gap-1\">\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Collation</div>\n <div class=\"text-content\">{{ collectionInfo.hasCollation ? 'Yes' : 'No' }}</div>\n </div>\n <div v-if=\"collectionInfo.hasCollation\" class=\"rounded bg-muted p-3 text-sm text-gray-800 overflow-x-auto\">\n <pre class=\"whitespace-pre-wrap\">{{ JSON.stringify(collectionInfo.collation, null, 2) }}</pre>\n </div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Capped</div>\n <div class=\"text-content\">{{ collectionInfo.capped ? 'Yes' : 'No' }}</div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCreateModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCreateModal = false;\">×</div>\n <create-document :currentModel=\"currentModel\" :paths=\"schemaPaths\" @close=\"closeCreationModal\"></create-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowUpdateMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowUpdateMultipleModal = false;\">×</div>\n <update-document :currentModel=\"currentModel\" :document=\"selectedDocuments\" :multiple=\"true\" @update=\"updateDocuments\" @close=\"shouldShowUpdateMultipleModal=false;\"></update-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteMultipleModal = false;\">×</div>\n <h2>Are you sure you want to delete {{selectedDocuments.length}} documents?</h2>\n <div>\n <list-json :value=\"selectedDocuments\"></list-json>\n </div>\n <div class=\"flex gap-4\">\n <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\">\n Confirm\n </async-button>\n <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\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n <model-switcher\n :show=\"showModelSwitcher\"\n :models=\"models\"\n :recently-viewed-models=\"recentlyViewedModels\"\n :model-document-counts=\"modelDocumentCounts\"\n @close=\"showModelSwitcher = false\"\n @select=\"selectSwitcherModel\"\n ></model-switcher>\n</div>\n";
|
|
52187
|
+
|
|
52188
|
+
/***/ },
|
|
52189
|
+
|
|
52190
|
+
/***/ "./frontend/src/models/projection-search/projection-search.html"
|
|
52191
|
+
/*!**********************************************************************!*\
|
|
52192
|
+
!*** ./frontend/src/models/projection-search/projection-search.html ***!
|
|
52193
|
+
\**********************************************************************/
|
|
52194
|
+
(module) {
|
|
52195
|
+
|
|
52196
|
+
"use strict";
|
|
52197
|
+
module.exports = "<div class=\"relative flex-grow m-0\">\n <input\n ref=\"projectionInput\"\n class=\"w-full min-w-0 rounded border border-edge bg-surface px-2 py-1.5 text-sm font-mono placeholder:text-content-tertiary focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\n type=\"text\"\n placeholder=\"Projection: name email, or -password\"\n v-model=\"projectionText\"\n @focus=\"initProjection\"\n @input=\"handleInput\"\n @keydown=\"handleKeyDown\"\n @blur=\"hideAutocomplete\"\n />\n <ul\n v-if=\"autocompleteSuggestions.length\"\n class=\"absolute left-0 right-0 top-full z-[9999] mt-1 max-h-40 overflow-y-auto rounded border border-edge bg-surface shadow\"\n >\n <li\n v-for=\"(suggestion, index) in autocompleteSuggestions\"\n :key=\"suggestion\"\n class=\"cursor-pointer px-2 py-1 text-sm font-mono\"\n :class=\"{ 'bg-primary-subtle': index === autocompleteIndex }\"\n @mousedown.prevent=\"applySuggestion(index)\"\n >\n {{ suggestion }}\n </li>\n </ul>\n</div>\n";
|
|
51478
52198
|
|
|
51479
52199
|
/***/ },
|
|
51480
52200
|
|
|
@@ -57018,6 +57738,89 @@ var lexer = _Lexer.lex;
|
|
|
57018
57738
|
//# sourceMappingURL=marked.cjs.map
|
|
57019
57739
|
|
|
57020
57740
|
|
|
57741
|
+
/***/ },
|
|
57742
|
+
|
|
57743
|
+
/***/ "./node_modules/mongoose/lib/helpers/symbols.js"
|
|
57744
|
+
/*!******************************************************!*\
|
|
57745
|
+
!*** ./node_modules/mongoose/lib/helpers/symbols.js ***!
|
|
57746
|
+
\******************************************************/
|
|
57747
|
+
(__unused_webpack_module, exports) {
|
|
57748
|
+
|
|
57749
|
+
"use strict";
|
|
57750
|
+
|
|
57751
|
+
|
|
57752
|
+
exports.arrayAtomicsBackupSymbol = Symbol('mongoose#Array#atomicsBackup');
|
|
57753
|
+
exports.arrayAtomicsSymbol = Symbol('mongoose#Array#_atomics');
|
|
57754
|
+
exports.arrayParentSymbol = Symbol('mongoose#Array#_parent');
|
|
57755
|
+
exports.arrayPathSymbol = Symbol('mongoose#Array#_path');
|
|
57756
|
+
exports.arraySchemaSymbol = Symbol('mongoose#Array#_schema');
|
|
57757
|
+
exports.documentArrayParent = Symbol('mongoose#documentArrayParent');
|
|
57758
|
+
exports.documentIsSelected = Symbol('mongoose#Document#isSelected');
|
|
57759
|
+
exports.documentIsModified = Symbol('mongoose#Document#isModified');
|
|
57760
|
+
exports.documentModifiedPaths = Symbol('mongoose#Document#modifiedPaths');
|
|
57761
|
+
exports.documentSchemaSymbol = Symbol('mongoose#Document#schema');
|
|
57762
|
+
exports.getSymbol = Symbol('mongoose#Document#get');
|
|
57763
|
+
exports.modelSymbol = Symbol('mongoose#Model');
|
|
57764
|
+
exports.objectIdSymbol = Symbol('mongoose#ObjectId');
|
|
57765
|
+
exports.populateModelSymbol = Symbol('mongoose#PopulateOptions#Model');
|
|
57766
|
+
exports.schemaTypeSymbol = Symbol('mongoose#schemaType');
|
|
57767
|
+
exports.sessionNewDocuments = Symbol('mongoose#ClientSession#newDocuments');
|
|
57768
|
+
exports.scopeSymbol = Symbol('mongoose#Document#scope');
|
|
57769
|
+
exports.validatorErrorSymbol = Symbol('mongoose#validatorError');
|
|
57770
|
+
|
|
57771
|
+
|
|
57772
|
+
/***/ },
|
|
57773
|
+
|
|
57774
|
+
/***/ "./node_modules/mongoose/lib/types/objectid.js"
|
|
57775
|
+
/*!*****************************************************!*\
|
|
57776
|
+
!*** ./node_modules/mongoose/lib/types/objectid.js ***!
|
|
57777
|
+
\*****************************************************/
|
|
57778
|
+
(module, __unused_webpack_exports, __webpack_require__) {
|
|
57779
|
+
|
|
57780
|
+
"use strict";
|
|
57781
|
+
/**
|
|
57782
|
+
* ObjectId type constructor
|
|
57783
|
+
*
|
|
57784
|
+
* #### Example:
|
|
57785
|
+
*
|
|
57786
|
+
* const id = new mongoose.Types.ObjectId;
|
|
57787
|
+
*
|
|
57788
|
+
* @constructor ObjectId
|
|
57789
|
+
*/
|
|
57790
|
+
|
|
57791
|
+
|
|
57792
|
+
|
|
57793
|
+
const ObjectId = (__webpack_require__(/*! mongodb/lib/bson */ "./node_modules/mongodb/lib/bson.js").ObjectId);
|
|
57794
|
+
const objectIdSymbol = (__webpack_require__(/*! ../helpers/symbols */ "./node_modules/mongoose/lib/helpers/symbols.js").objectIdSymbol);
|
|
57795
|
+
|
|
57796
|
+
/**
|
|
57797
|
+
* Getter for convenience with populate, see gh-6115
|
|
57798
|
+
* @api private
|
|
57799
|
+
*/
|
|
57800
|
+
|
|
57801
|
+
Object.defineProperty(ObjectId.prototype, '_id', {
|
|
57802
|
+
enumerable: false,
|
|
57803
|
+
configurable: true,
|
|
57804
|
+
get: function() {
|
|
57805
|
+
return this;
|
|
57806
|
+
}
|
|
57807
|
+
});
|
|
57808
|
+
|
|
57809
|
+
/*!
|
|
57810
|
+
* Convenience `valueOf()` to allow comparing ObjectIds using double equals re: gh-7299
|
|
57811
|
+
*/
|
|
57812
|
+
|
|
57813
|
+
if (!Object.hasOwn(ObjectId.prototype, 'valueOf')) {
|
|
57814
|
+
ObjectId.prototype.valueOf = function objectIdValueOf() {
|
|
57815
|
+
return this.toString();
|
|
57816
|
+
};
|
|
57817
|
+
}
|
|
57818
|
+
|
|
57819
|
+
ObjectId.prototype[objectIdSymbol] = true;
|
|
57820
|
+
|
|
57821
|
+
module.exports = ObjectId;
|
|
57822
|
+
|
|
57823
|
+
|
|
57021
57824
|
/***/ },
|
|
57022
57825
|
|
|
57023
57826
|
/***/ "./node_modules/bson/lib/bson.mjs"
|
|
@@ -62887,7 +63690,7 @@ var src_default = VueToastificationPlugin;
|
|
|
62887
63690
|
(module) {
|
|
62888
63691
|
|
|
62889
63692
|
"use strict";
|
|
62890
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.
|
|
63693
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.4.1","description":"A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.","homepage":"https://mongoosestudio.app/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"@ai-sdk/anthropic":"2.x","@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","ace-builds":"^1.43.6","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","node-inspect-extracted":"3.x","regexp.escape":"^2.0.1","tailwindcss":"3.4.0","time-commando":"1.0.1","typescript":"^6.0.2","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x","xss":"^1.0.15"},"peerDependencies":{"mongoose":"7.x || 8.x || ^9.0.0"},"optionalPeerDependencies":{"@mongoosejs/task":"0.5.x || 0.6.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongodb-memory-server":"^11.0.1","mongoose":"9.x","sinon":"^21.0.1"},"scripts":{"lint":"eslint .","seed":"node seed/index.js","start":"node ./local.js","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js","test:frontend":"mocha test/frontend/*.test.js"}}');
|
|
62891
63694
|
|
|
62892
63695
|
/***/ }
|
|
62893
63696
|
|