diff --git a/src/interface/desktop/assets/khoj.css b/src/interface/desktop/assets/khoj.css index c2a7d367..f754ca0a 100644 --- a/src/interface/desktop/assets/khoj.css +++ b/src/interface/desktop/assets/khoj.css @@ -71,14 +71,19 @@ div.khoj-header { gap: 20px; padding: 24px 16px 0px 0px; margin: 0 0 16px 0; + align-items: center; + user-select: none; -webkit-user-select: none; -webkit-app-region: drag; } +/* Keeps the navigation menu clickable */ a.khoj-nav { -webkit-app-region: no-drag; } - +div.khoj-nav { + -webkit-app-region: no-drag; +} nav.khoj-nav { display: grid; grid-auto-flow: column; @@ -112,12 +117,96 @@ a.khoj-logo { a.khoj-nav-selected { background-color: var(--primary); } +span.khoj-nav-item-text { + padding-left: 8px; +} img.khoj-logo { width: min(60vw, 90px); max-width: 100%; justify-self: center; } +/* Dropdown in navigation menu*/ +#khoj-nav-menu-container { + display: flex; + align-items: center; +} +.khoj-nav-dropdown-content { + display: block; + grid-auto-flow: row; + position: absolute; + background-color: var(--background-color); + min-width: 160px; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + right: 5vw; + top: 64px; + z-index: 1; + opacity: 0; + transition: opacity 0.1s ease-in-out; + pointer-events: none; + text-align: left; +} +.khoj-nav-dropdown-content.show { + opacity: 1; + pointer-events: auto; +} +.khoj-nav-dropdown-content a { + color: black; + padding: 12px 16px; + text-decoration: none; + display: block; +} +.khoj-nav-dropdown-content a:hover { + background-color: var(--primary-hover); +} +.khoj-nav-username { + padding: 12px 16px; + text-decoration: none; + display: block; + font-weight: bold; +} +.circle { + border-radius: 50%; + border: 3px dotted var(--main-text-color); + width: 32px; + height: 32px; + padding: 3px; + cursor: pointer; +} +.circle:hover { + background-color: var(--primary-hover); +} +.user-initial { + background-color: var(--background-color); + color: black; + display: grid; + justify-content: center; + align-items: center; + font-size: 20px; + box-sizing: unset; + width: 40px; + height: 40px; +} +.subscribed { + border: 3px solid var(--primary-hover); +} + +@media screen and (max-width: 600px) { + .khoj-nav-dropdown-content { + display: block; + grid-auto-flow: row; + position: absolute; + background-color: var(--background-color); + min-width: 160px; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + right: 10px; + z-index: 1; + opacity: 0; + transition: opacity 0.1s ease-in-out; + pointer-events: none; + } +} + @media only screen and (max-width: 600px) { div.khoj-header { display: grid; @@ -131,4 +220,10 @@ img.khoj-logo { grid-gap: 0px; justify-content: space-between; } + a.khoj-nav { + padding: 0 16px; + } + span.khoj-nav-item-text { + display: none; + } } diff --git a/src/interface/desktop/chat.html b/src/interface/desktop/chat.html index 732e8803..14dd9e6b 100644 --- a/src/interface/desktop/chat.html +++ b/src/interface/desktop/chat.html @@ -629,11 +629,21 @@ document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight; } + window.addEventListener("DOMContentLoaded", async() => { + // Setup the header pane + document.getElementById("khoj-header").innerHTML = await populateHeaderPane(); + // Setup the nav menu + document.getElementById("profile-picture").addEventListener("click", toggleNavMenu); + // Set the active nav pane + document.getElementById("chat-nav")?.classList.add("khoj-nav-selected"); + }) + window.addEventListener('load', async() => { await loadChat(); }); async function loadChat() { + // Load chat history const hostURL = await window.hostURLAPI.getURL(); const khojToken = await window.tokenAPI.getToken(); const headers = { 'Authorization': `Bearer ${khojToken}` }; @@ -1065,20 +1075,10 @@ } -
-
+
-
- - -
+
diff --git a/src/interface/desktop/config.html b/src/interface/desktop/config.html index 4fbea24b..d6a3e702 100644 --- a/src/interface/desktop/config.html +++ b/src/interface/desktop/config.html @@ -9,19 +9,20 @@ + - +
diff --git a/src/interface/desktop/main.js b/src/interface/desktop/main.js index ec631384..1610a5e7 100644 --- a/src/interface/desktop/main.js +++ b/src/interface/desktop/main.js @@ -351,6 +351,17 @@ async function deleteAllFiles () { } } +// Fetch user info from Khoj server +async function getUserInfo() { + const getUserInfoURL = `${store.get('hostURL') || KHOJ_URL}/api/v1/user?client=desktop`; + const headers = { 'Authorization': `Bearer ${store.get('khojToken')}` }; + try { + let response = await axios.get(getUserInfoURL, { headers }); + return response.data; + } catch (err) { + console.error(err); + } +} let firstRun = true; let win = null; @@ -466,6 +477,7 @@ app.whenReady().then(() => { ipcMain.handle('setToken', setToken); ipcMain.handle('getToken', getToken); + ipcMain.handle('getUserInfo', getUserInfo); ipcMain.handle('syncData', (event, regenerate) => { syncData(regenerate); diff --git a/src/interface/desktop/preload.js b/src/interface/desktop/preload.js index 8d5152b7..24dc9919 100644 --- a/src/interface/desktop/preload.js +++ b/src/interface/desktop/preload.js @@ -53,6 +53,10 @@ contextBridge.exposeInMainWorld('syncDataAPI', { deleteAllFiles: () => ipcRenderer.invoke('deleteAllFiles') }) +contextBridge.exposeInMainWorld('userInfoAPI', { + getUserInfo: () => ipcRenderer.invoke('getUserInfo') +}) + contextBridge.exposeInMainWorld('tokenAPI', { setToken: (token) => ipcRenderer.invoke('setToken', token), getToken: () => ipcRenderer.invoke('getToken') diff --git a/src/interface/desktop/search.html b/src/interface/desktop/search.html index ffc1bf64..9a3e0e0b 100644 --- a/src/interface/desktop/search.html +++ b/src/interface/desktop/search.html @@ -246,6 +246,16 @@ window.history.pushState({}, "", url.href); } + window.addEventListener("DOMContentLoaded", async() => { + // Setup the header pane + document.getElementById("khoj-header").innerHTML = await populateHeaderPane(); + // Setup the nav menu + document.getElementById("profile-picture").addEventListener("click", toggleNavMenu); + // Set the active nav pane + document.getElementById("search-nav")?.classList.add("khoj-nav-selected"); + }) + + window.addEventListener("load", async function() { // Dynamically populate type dropdown based on enabled content types and type passed as URL query parameter await populate_type_dropdown(); @@ -259,16 +269,7 @@ - +
diff --git a/src/interface/desktop/utils.js b/src/interface/desktop/utils.js index 8f9c0aeb..5b6c2fb3 100644 --- a/src/interface/desktop/utils.js +++ b/src/interface/desktop/utils.js @@ -24,3 +24,56 @@ window.appInfoAPI.getInfo((_, info) => { khojTitleElement.innerHTML = 'Khoj for ' + (info.platform === 'win32' ? 'Windows' : info.platform === 'darwin' ? 'macOS' : 'Linux') + ''; } }); + +function toggleNavMenu() { + let menu = document.getElementById("khoj-nav-menu"); + menu.classList.toggle("show"); +} + +// Close the dropdown menu if the user clicks outside of it +document.addEventListener('click', function(event) { + let menu = document.getElementById("khoj-nav-menu"); + let menuContainer = document.getElementById("khoj-nav-menu-container"); + let isClickOnMenu = menuContainer.contains(event.target) || menuContainer === event.target; + if (isClickOnMenu === false && menu.classList.contains("show")) { + menu.classList.remove("show"); + } +}); + +async function populateHeaderPane() { + let userInfo = null; + try { + userInfo = await window.userInfoAPI.getUserInfo(); + } catch (error) { + console.log("User not logged in"); + } + + let username = userInfo?.username ?? "?"; + let user_photo = userInfo?.photo; + let is_active = userInfo?.is_active; + let has_documents = userInfo?.has_documents; + + // Populate the header element with the navigation pane + return ` + + + `; +}