Dynamically generate navigation menu based on user info from server

This commit is contained in:
Debanjum Singh Solanky
2024-04-07 20:58:43 +05:30
parent 94c69eb8e3
commit 8ff3890ba8
7 changed files with 199 additions and 33 deletions

View File

@@ -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;
}
}

View File

@@ -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 @@
}
</script>
<body>
<div id="khoj-empty-container" class="khoj-empty-container">
</div>
<div id="khoj-empty-container" class="khoj-empty-container"></div>
<!--Add Header Logo and Nav Pane-->
<div class="khoj-header">
<a class="khoj-logo" href="/">
<img class="khoj-logo" src="./assets/icons/khoj-logo-sideways-500.png" alt="Khoj"></img>
</a>
<nav class="khoj-nav">
<a class="khoj-nav khoj-nav-selected" href="./chat.html">💬 Chat</a>
<a class="khoj-nav" href="./search.html">🔎 Search</a>
<a class="khoj-nav" href="./config.html">⚙️ Settings</a>
</nav>
</div>
<div id="khoj-header" class="khoj-header"></div>
<div id="chat-section-wrapper">
<div id="side-panel-wrapper">

View File

@@ -9,19 +9,20 @@
<link rel="stylesheet" href="./assets/khoj.css">
</head>
<script src="./utils.js"></script>
<script>
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("settings-nav")?.classList.add("khoj-nav-selected");
})
</script>
<body>
<!--Add Header Logo and Nav Pane-->
<div class="khoj-header">
<a class="khoj-logo" href="./chat.html">
<img class="khoj-logo" src="./assets/icons/khoj-logo-sideways-500.png" alt="Khoj"></img>
</a>
<nav class="khoj-nav">
<a class="khoj-nav" href="./chat.html">💬 Chat</a>
<a class="khoj-nav" href="./search.html">🔎 Search</a>
<a class="khoj-nav khoj-nav-selected" href="./config.html">⚙️ Settings</a>
</nav>
</div>
<div id="khoj-header" class="khoj-header"></div>
<div class="section-cards">
<div class="card-description-row">
<div class="card configuration">

View File

@@ -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);

View File

@@ -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')

View File

@@ -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 @@
<body>
<!--Add Header Logo and Nav Pane-->
<div class="khoj-header">
<a class="khoj-logo" href="./index.html">
<img class="khoj-logo" src="./assets/icons/khoj-logo-sideways-500.png" alt="Khoj"></img>
</a>
<nav class="khoj-nav">
<a class="khoj-nav" href="./chat.html">💬 Chat</a>
<a class="khoj-nav khoj-nav-selected" href="./search.html">🔎 Search</a>
<a class="khoj-nav" href="./config.html">⚙️ Settings</a>
</nav>
</div>
<div id="khoj-header" class="khoj-header"></div>
<!--Add Text Box To Enter Query, Trigger Incremental Search OnChange -->
<input type="text" id="query" class="option" onkeyup=incrementalSearch(event) autofocus="autofocus" placeholder="Search your knowledge base using natural language">

View File

@@ -24,3 +24,56 @@ window.appInfoAPI.getInfo((_, info) => {
khojTitleElement.innerHTML = '<b>Khoj for ' + (info.platform === 'win32' ? 'Windows' : info.platform === 'darwin' ? 'macOS' : 'Linux') + '</b>';
}
});
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 `
<a class="khoj-logo" href="/">
<img class="khoj-logo" src="./assets/icons/khoj-logo-sideways-500.png" alt="Khoj"></img>
</a>
<nav class="khoj-nav">
<a id="chat-nav" class="khoj-nav" href="./chat.html">💬 <span class="khoj-nav-item-text">Chat</span></a>
${has_documents ? '<a id="search-nav" class="khoj-nav" href="./search.html">🔎 <span class="khoj-nav-item-text">Search</span></a>' : ''}
${username ? `
<div id="khoj-nav-menu-container" class="khoj-nav dropdown">
${user_photo && user_photo != "None" ? `
<img id="profile-picture" class="${is_active ? 'circle subscribed' : 'circle'}" src="${user_photo}" alt="${username[0].toUpperCase()}" referrerpolicy="no-referrer">
` : `
<div id="profile-picture" class="${is_active ? 'circle user-initial subscribed' : 'circle user-initial'}" alt="${username[0].toUpperCase()}">${username[0].toUpperCase()}</div>
`}
<div id="khoj-nav-menu" class="khoj-nav-dropdown-content">
<div class="khoj-nav-username"> ${username} </div>
<a id="settings-nav" class="khoj-nav" href="./config.html">⚙️ Settings</a>
</div>
</div>
` : ''}
</nav>
`;
}