Make chat a page and add sidebar with buttons to cycle through pages on desktop.

This commit is contained in:
天クマ 2026-07-17 13:46:24 -03:00
commit dcd40790e0
5 changed files with 101 additions and 106 deletions

View file

@ -22,6 +22,11 @@
}); });
</script> </script>
<div class="app-container no-active-chat"> <div class="app-container no-active-chat">
<aside id="desktop-aside">
<button id="chats-sidebar-btn" class="icon-btn mif-qa mif-2x" data-page="chat-page"></button>
<button id="settings-sidebar-btn" class="icon-btn mif-cog mif-2x" data-page="settings-page"></button>
</aside>
<!-- Sidebar --> <!-- Sidebar -->
<aside class="sidebar"> <aside class="sidebar">
<header class="sidebar-header"> <header class="sidebar-header">
@ -32,11 +37,6 @@
<h3 id="pandora-username">Pandora User</h3> <h3 id="pandora-username">Pandora User</h3>
</div> </div>
</div> </div>
<div class="header-actions">
<!-- <button class="text-btn" title="New Chat">new chat</button> -->
<button class="text-btn" id="refresh-chats-btn" title="Refresh Chats">refresh</button>
<button class="text-btn" title="Settings">settings</button>
</div>
</header> </header>
<div class="search-container"> <div class="search-container">
@ -68,59 +68,61 @@
</div> </div>
</aside> </aside>
<!-- Main Chat Area --> <div class="extra-page" id="chat-page">
<main class="chat-area"> <!-- Main Chat Area -->
<!-- No Chat Selected State --> <main class="chat-area">
<div class="no-chat-state" id="no-chat-state"> <!-- No Chat Selected State -->
<div class="empty-state-content"> <div class="no-chat-state" id="no-chat-state">
<div class="empty-state-icon mif-qa mif-3x"> <div class="empty-state-content">
</div> <div class="empty-state-icon mif-qa mif-3x">
<p>Select a contact from the sidebar to view the conversation or start a new chat.</p>
</div>
</div>
<!-- Active Chat State -->
<div class="active-chat-container hidden" id="active-chat-container">
<header class="chat-header">
<div class="active-contact-info">
<!-- <button class="icon-btn back-btn mif-arrow-left mif-2x" id="back-to-sidebar" title="Back to chats"></button> -->
<div class="avatar active-avatar" id="active-chat-avatar">C</div>
<div>
<h3 id="active-chat-name">Contact Name</h3>
<span class="contact-status" id="active-chat-status"></span>
</div> </div>
<p>Select a contact to view the conversation or start a new chat.</p>
</div> </div>
<div class="chat-actions">
<!-- <button class="icon-btn" title="Search in conversation"><i data-lucide="search"></i></button>
<button class="icon-btn" title="Call"><i data-lucide="phone"></i></button>
<button class="icon-btn" title="Video Call"><i data-lucide="video"></i></button>
<button class="icon-btn" title="More Options"><i data-lucide="more-vertical"></i></button> -->
</div>
</header>
<!-- Messages Area -->
<div class="messages-container" id="messages-container">
<!-- Messages will be dynamically injected here -->
</div> </div>
<!-- Input Panel --> <!-- Active Chat State -->
<footer id="chat-input-panel" class="chat-input-panel"> <div class="active-chat-container hidden" id="active-chat-container">
<div class="input-actions-left"> <header class="chat-header">
<button id="chat-bottom-bar-btn" class="icon-btn mif-expand-less mif-3x" title="Expand"></button> <div class="active-contact-info">
<!-- <button class="icon-btn back-btn mif-arrow-left mif-2x" id="back-to-sidebar" title="Back to chats"></button> -->
<div class="avatar active-avatar" id="active-chat-avatar">C</div>
<div>
<h3 id="active-chat-name">Contact Name</h3>
<span class="contact-status" id="active-chat-status"></span>
</div>
</div>
<div class="chat-actions">
<!-- <button class="icon-btn" title="Search in conversation"><i data-lucide="search"></i></button>
<button class="icon-btn" title="Call"><i data-lucide="phone"></i></button>
<button class="icon-btn" title="Video Call"><i data-lucide="video"></i></button>
<button class="icon-btn" title="More Options"><i data-lucide="more-vertical"></i></button> -->
</div>
</header>
<!-- Messages Area -->
<div class="messages-container" id="messages-container">
<!-- Messages will be dynamically injected here -->
</div> </div>
<form class="input-form" id="message-form">
<input type="text" id="message-input" placeholder="Type a message..." autocomplete="off"> <!-- Input Panel -->
<button type="submit" class="send-btn mif-paper-plane mif-3x" id="send-button"> <footer id="chat-input-panel" class="chat-input-panel">
</button> <div class="input-actions-left">
</form> <button id="chat-bottom-bar-btn" class="icon-btn mif-expand-less mif-3x" title="Expand"></button>
</footer> </div>
<!-- Bottom Bar --> <form class="input-form" id="message-form">
<footer id="chat-bottom-bar" class="chat-expanded-panel alternate-panel collapsed"> <input type="text" id="message-input" placeholder="Type a message..." autocomplete="off">
<input id="attachment-input" style="display: none;" type="file"> <button type="submit" class="send-btn mif-paper-plane mif-3x" id="send-button">
<button id="attachment-btn" class="icon-btn send-btn mif-attachment mif-2x" title="Attach file"></button> </button>
</footer> </form>
</div> </footer>
</main> <!-- Bottom Bar -->
<footer id="chat-bottom-bar" class="chat-expanded-panel alternate-panel collapsed">
<input id="attachment-input" style="display: none;" type="file">
<button id="attachment-btn" class="icon-btn send-btn mif-attachment mif-2x" title="Attach file"></button>
</footer>
</div>
</main>
</div>
<section class="extra-page" id="settings-page"> <section class="extra-page" id="settings-page">
<div class="modal-content"> <div class="modal-content">
@ -143,7 +145,6 @@
<p class="settings-warning">Note: Requests are sent directly from your browser to the WAHA server. Make sure CORS is enabled on your WAHA instance.</p> <p class="settings-warning">Note: Requests are sent directly from your browser to the WAHA server. Make sure CORS is enabled on your WAHA instance.</p>
</div> </div>
<footer class="modal-footer"> <footer class="modal-footer">
<button class="btn secondary-btn" id="cancel-settings">Cancel</button>
<button class="btn primary-btn" id="save-settings">Save Config</button> <button class="btn primary-btn" id="save-settings">Save Config</button>
</footer> </footer>
</div> </div>

View file

@ -120,14 +120,6 @@ function setupEventListeners() {
} }
}); });
elements.refreshChatsBtn.addEventListener('click', () => {
elements.refreshChatsBtn.classList.add('spinning');
loadChats()
setTimeout(() => {
elements.refreshChatsBtn.classList.remove('spinning');
}, 600);
});
elements.chatSearch.addEventListener('input', (e) => { elements.chatSearch.addEventListener('input', (e) => {
const query = e.target.value.toLowerCase(); const query = e.target.value.toLowerCase();
const filtered = getChats().filter(chat => const filtered = getChats().filter(chat =>
@ -164,9 +156,13 @@ function setupEventListeners() {
elements.backToSidebarBtn.addEventListener('click', () => { elements.backToSidebarBtn.addEventListener('click', () => {
closeActiveChat(false); closeActiveChat(false);
}); });
elements.settingsIconBtn.addEventListener('click', openSettings); elements.desktopSidebarButtons.forEach(sidebarBtn => {
elements.cancelSettingsBtn.addEventListener('click', () => ui.toggleModal(false)); sidebarBtn.addEventListener('click', () => {
ui.showExtraPage(sidebarBtn.dataset.page);
})
})
elements.saveSettingsBtn.addEventListener('click', saveSettings); elements.saveSettingsBtn.addEventListener('click', saveSettings);
} }
@ -271,20 +267,15 @@ async function closeActiveChat(isPopState = false) {
if (window.innerWidth <= 768) { if (window.innerWidth <= 768) {
scrollToList(); scrollToList();
setTimeout(() => {
if (!activeChatState) {
elements.appContainer.classList.add('no-active-chat');
// ui.toggleChatState(false);
}
}, 350);
} else { } else {
ui.toggleChatState(false); ui.toggleChatState(false);
} }
if (!isPopState) { if (!isPopState) {
// if (window.location.hash.startsWith('#chat-')) { if (window.location.hash.startsWith('#chat-')) {
// history.back(); history.back();
// } }
} }
} }
@ -383,18 +374,13 @@ async function sendFileMessage(file) {
} }
} }
function openSettings() {
ui.toggleModal(true);
}
function saveSettings() { function saveSettings() {
config.save( config.save(
elements.inputWahaUrl.value, elements.inputWahaUrl.value,
elements.inputSession.value, elements.inputSession.value,
elements.inputApiKey.value elements.inputApiKey.value
); );
ui.toggleModal(false); location.reload();
loadChats(); loadChats();
checkWahaStatus(); checkWahaStatus();
initWebSocket(); initWebSocket();

View file

@ -1,13 +1,10 @@
import { formatTime, normalizeId } from "./utils.js"; import { formatTime, normalizeId } from "./utils.js";
import { waha } from "./waha.js";
import { config } from "./config.js";
import { getChatPicture, getMessage, getMedia, getMoreChatMessages } from "./storage.js"; import { getChatPicture, getMessage, getMedia, getMoreChatMessages } from "./storage.js";
export const elements = { export const elements = {
chatList: document.getElementById('chat-list'), chatList: document.getElementById('chat-list'),
chatsLoader: document.getElementById('chats-loader'), chatsLoader: document.getElementById('chats-loader'),
chatSearch: document.getElementById('chat-search'), chatSearch: document.getElementById('chat-search'),
refreshChatsBtn: document.getElementById('refresh-chats-btn'),
backendStatusText: document.getElementById('backend-status-text'), backendStatusText: document.getElementById('backend-status-text'),
apiStatusIndicator: document.querySelector('.pulse-dot'), apiStatusIndicator: document.querySelector('.pulse-dot'),
noChatState: document.getElementById('no-chat-state'), noChatState: document.getElementById('no-chat-state'),
@ -21,8 +18,7 @@ export const elements = {
sidebar: document.querySelector('.sidebar'), sidebar: document.querySelector('.sidebar'),
appContainer: document.querySelector('.app-container'), appContainer: document.querySelector('.app-container'),
settingsModal: document.getElementById('settings-page'), settingsModal: document.getElementById('settings-page'),
settingsIconBtn: document.querySelector('.header-actions button[title="Settings"]'), settingsIconBtn: document.getElementById('settings-sidebar-btn'),
cancelSettingsBtn: document.getElementById('cancel-settings'),
saveSettingsBtn: document.getElementById('save-settings'), saveSettingsBtn: document.getElementById('save-settings'),
inputWahaUrl: document.getElementById('settings-waha-url'), inputWahaUrl: document.getElementById('settings-waha-url'),
inputSession: document.getElementById('settings-session'), inputSession: document.getElementById('settings-session'),
@ -33,20 +29,21 @@ export const elements = {
chatInputPanel: document.getElementById('chat-input-panel'), chatInputPanel: document.getElementById('chat-input-panel'),
attachmentInput: document.getElementById('attachment-input'), attachmentInput: document.getElementById('attachment-input'),
attachmentBtn: document.getElementById('attachment-btn'), attachmentBtn: document.getElementById('attachment-btn'),
extraPages: document.querySelectorAll('.extra-page'),
desktopSidebarButtons: document.querySelectorAll("#desktop-aside button")
}; };
export const ui = { export const ui = {
/** showExtraPage(pageId) {
* Show or hide Settings connection modal elements.extraPages.forEach(page => {
*/ if (page.id != pageId) {
toggleModal(show) { page.style.display = "none";
if (show) { } else {
elements.settingsModal.scrollTo(); page.style.display = "flex";
} else { }
elements.sidebar.scrollTo(); })
}
}, },
/** /**
* Switch view state when a contact chat is opened or closed * Switch view state when a contact chat is opened or closed
*/ */

View file

@ -1,7 +1,3 @@
body {
background-color: #4e8ba6;
}
.dots { .dots {
position: relative; position: relative;
width: 100%; width: 100%;

View file

@ -364,6 +364,16 @@ input:focus {
to { transform: rotate(360deg); } to { transform: rotate(360deg); }
} }
/* Desktop aside */
#desktop-aside {
height: 100%;
background-color: black;
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
padding: .2em;
}
/* Sidebar Footer */ /* Sidebar Footer */
.sidebar-footer { .sidebar-footer {
padding: 14px 20px; padding: 14px 20px;
@ -383,14 +393,14 @@ input:focus {
height: 8px; height: 8px;
background-color: var(--online-color); background-color: var(--online-color);
border-radius: 50%; border-radius: 50%;
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7); box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7);
animation: pulse 1.8s infinite; animation: pulse 1.8s infinite;
} }
@keyframes pulse { @keyframes pulse {
0% { 0% {
transform: scale(0.95); transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7); box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7);
} }
70% { 70% {
transform: scale(1); transform: scale(1);
@ -740,6 +750,7 @@ input:focus {
/* Extra Pages */ /* Extra Pages */
.extra-page { .extra-page {
display: none; display: none;
width: 100%;
} }
.extra-page-content { .extra-page-content {
@ -747,6 +758,10 @@ input:focus {
color: var(--text-secondary); color: var(--text-secondary);
} }
#chat-page {
display: flex;
}
@media (max-width: 768px) { @media (max-width: 768px) {
.app-container { .app-container {
width: 100%; width: 100%;
@ -806,11 +821,6 @@ input:focus {
overflow-y: auto; overflow-y: auto;
} }
/* Disable scrolling to active chat if none is selected */
/* .app-container.no-active-chat .chat-area {
display: none !important;
} */
.back-btn { .back-btn {
display: flex; display: flex;
margin-right: 12px; margin-right: 12px;
@ -823,6 +833,10 @@ input:focus {
.message-image-attachement { .message-image-attachement {
max-height: 20em; max-height: 20em;
} }
#desktop-aside {
display: none;
}
} }
/* Modal Styling */ /* Modal Styling */
@ -847,6 +861,7 @@ input:focus {
backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%;
} }
.modal-header { .modal-header {