diff --git a/public/style/chat.css b/public/style/chat.css index 7f019b1..ad2f97f 100755 --- a/public/style/chat.css +++ b/public/style/chat.css @@ -186,6 +186,28 @@ body { } +.group-list .member-container { + position: relative; +} + +.group-list .member-container button { + margin-left: 40px; + padding: 5px; + cursor: pointer; + background: var(--primary-color); + color: white; + border: 0px solid var(--primary-color); + border-radius: 50px; + position: absolute; + top: -25px; + right: -25px; +} + +.group-list .member-container button:hover { + background: var(--accent-color) +} + + /* Zone de chat */ .chat-area { display: flex; @@ -552,4 +574,24 @@ body { .chat-area { margin: 0; } +} + + +::-webkit-scrollbar { + width: 5px; + height: 5px; +} + +::-webkit-scrollbar-track { + background: var(--primary-color); + border-radius: 5px; +} + +::-webkit-scrollbar-thumb { + background: var(--secondary-color); + border-radius: 5px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--accent-color); } \ No newline at end of file diff --git a/src/pages/chat/chat.ts b/src/pages/chat/chat.ts index 8c4c343..d24401f 100755 --- a/src/pages/chat/chat.ts +++ b/src/pages/chat/chat.ts @@ -49,7 +49,8 @@ class ChatElement extends HTMLElement { private messageState: number = 0; private selectedRole: string | null = null; private userProcessSet: Set = new Set(); - private addressMap: Record = {}; // map each address to its pairing process id + private dmMembersSet: Set = new Set(); + private addressMap: Record = {}; constructor() { super(); @@ -156,7 +157,7 @@ class ChatElement extends HTMLElement { if (sendButton) { sendButton.addEventListener('click', () => { this.sendMessage(); - setTimeout(() => this.reloadMemberChat(this.selectedMember), 500); + setTimeout(async () => await this.reloadMemberChat(this.selectedMember), 600); messageInput.value = ''; }); } @@ -168,7 +169,7 @@ class ChatElement extends HTMLElement { if (keyEvent.key === 'Enter' && !keyEvent.shiftKey) { event.preventDefault(); this.sendMessage(); - setTimeout(() => this.reloadMemberChat(this.selectedMember), 500); + setTimeout(async () => await this.reloadMemberChat(this.selectedMember), 600); messageInput.value = ''; } }); @@ -285,8 +286,9 @@ class ChatElement extends HTMLElement { try { const service = await Services.getInstance(); - const myAddresses = await service.getMemberFromDevice(); - if (!myAddresses) { + const myProcessId = await this.getMyProcessId(); + + if (!myProcessId) { console.error('No paired member found'); return; } @@ -299,7 +301,7 @@ class ChatElement extends HTMLElement { metadata: { createdAt: timestamp, lastModified: timestamp, - sender: myAddresses, + sender: myProcessId, recipient: this.selectedMember, } }; @@ -405,9 +407,36 @@ class ChatElement extends HTMLElement { const memberList = document.createElement('ul'); memberList.className = 'member-list active'; + const prioritizedMembers: [string, any][] = []; + const remainingMembers: [string, any][] = []; + + for (const [processId, member] of Object.entries(members)) { + if (this.dmMembersSet.has(processId)) { + prioritizedMembers.push([processId, member]); + } else { + remainingMembers.push([processId, member]); + } + } + + const sortedMembers = prioritizedMembers.concat(remainingMembers); + for (const [processId, member] of Object.entries(members)) { const memberItem = document.createElement('li'); memberItem.className = 'member-item'; + + if (this.dmMembersSet.has(processId)) { + memberItem.style.cssText = ` + background-color: var(--accent-color); + transition: background-color 0.3s ease; + cursor: pointer; + `; + memberItem.onmouseover = () => { + memberItem.style.backgroundColor = 'var(--accent-color-hover)'; + }; + memberItem.onmouseout = () => { + memberItem.style.backgroundColor = 'var(--accent-color)'; + }; + } const memberContainer = document.createElement('div'); memberContainer.className = 'member-container'; @@ -438,9 +467,13 @@ class ChatElement extends HTMLElement { await this.loadMemberChat(processId); }); - const editLabelButton = document.createElement('span'); + const editLabelButton = document.createElement('button'); editLabelButton.className = 'edit-label-button'; editLabelButton.textContent = "✏️"; + + editLabelButton.addEventListener("click", (event) => { + event.stopPropagation(); + }); editLabelButton.addEventListener("dblclick", async (event) => { event.stopPropagation(); @@ -457,11 +490,12 @@ class ChatElement extends HTMLElement { putRequest.onsuccess = () => { emojiSpan.textContent = `${newLabel} : ${emojis}`; + this.reloadMemberChat(processId); }; }); memberList.appendChild(memberItem); - memberList.appendChild(editLabelButton); + memberContainer.appendChild(editLabelButton); } groupList.appendChild(memberList); @@ -497,11 +531,58 @@ class ChatElement extends HTMLElement { return null; } + + private async lookForMyDms(): Promise { + const service = await Services.getInstance(); + const processes = await service.getMyProcesses(); + const myAddresses = await service.getMemberFromDevice(); + const allMembers = await service.getAllMembers(); + + this.dmMembersSet.clear(); + + try { + for (const processId of processes) { + const process = await service.getProcess(processId); + const state = process.states[0]; + const description = await service.decryptAttribute(state, 'description'); + if (!description || description !== "dm") { + continue; + } + const roles = await service.getRoles(process); + if (!service.rolesContainsMember(roles, myAddresses)) { + continue; + } + const members = roles.dm.members; + for (const member of members) {; + if (JSON.stringify(member.sp_addresses) !== JSON.stringify(myAddresses)) { + this.dmMembersSet.add(member.sp_addresses); + } + } + } + + const updatedDmMembersSet = new Set(); + for (const dmMember of this.dmMembersSet) { + for (const [processId, member] of Object.entries(allMembers)) { + if (JSON.stringify(member.sp_addresses) === JSON.stringify(dmMember)) { + updatedDmMembersSet.add(processId); + } + } + } + this.dmMembersSet = updatedDmMembersSet; + } catch (e) { + console.error(e); + } + console.log("SET DE MEMBRES AVEC QUI JE DM:", this.dmMembersSet); + return null; + } private async loadMemberChat(pairingProcess: string) { try { const service = await Services.getInstance(); const myAddresses = await service.getMemberFromDevice(); + const database = await Database.getInstance(); + const db = database.db; + if (!myAddresses) { console.error('No paired member found'); return; @@ -518,7 +599,19 @@ class ChatElement extends HTMLElement { const emojis = await addressToEmoji(pairingProcess); - chatHeader.textContent = `Chat with ${emojis}`; + const transaction = db.transaction("labels", "readonly"); + const store = transaction.objectStore("labels"); + const request = store.get(emojis); + + request.onsuccess = () => { + const label = request.result; + chatHeader.textContent = label ? `Chat with ${label.label} (${emojis})` : `Chat with member (${emojis})`; + }; + + request.onerror = () => { + chatHeader.textContent = `Chat with member (${emojis})`; + }; + messagesContainer.innerHTML = ''; let dmProcessId = await this.lookForDmProcess(); @@ -598,15 +691,19 @@ class ChatElement extends HTMLElement { for (const message of allMessages) { const messageElement = document.createElement('div'); messageElement.className = 'message-container'; + + const myProcessId = await this.getMyProcessId(); - const isCurrentUser = message.metadata.sender === myAddresses[0]; + const isCurrentUser = message.metadata.sender === myProcessId; messageElement.style.justifyContent = isCurrentUser ? 'flex-end' : 'flex-start'; - + const messageContent = document.createElement('div'); - messageContent.className = 'message user'; + messageContent.className = isCurrentUser ? 'message user' : 'message'; - const pairingProcess = await this.getMyProcessId(); - const senderEmoji = await addressToEmoji(pairingProcess); + const myEmoji = await addressToEmoji(myProcessId); + const otherEmoji = await addressToEmoji(this.selectedMember); + + const senderEmoji = isCurrentUser ? myEmoji : otherEmoji; if (message.type === 'file') { let fileContent = ''; @@ -664,18 +761,28 @@ class ChatElement extends HTMLElement { private async reloadMemberChat(pairingProcess: string) { try { const service = await Services.getInstance(); - const myAddresses = await service.getMemberFromDevice(); - if (!myAddresses) { - console.error('No paired member found'); - return; - } - + const database = await Database.getInstance(); + const db = database.db; + const chatHeader = this.shadowRoot?.querySelector('#chat-header'); const messagesContainer = this.shadowRoot?.querySelector('#messages'); - if (!messagesContainer) return; + if (!chatHeader || !messagesContainer) return; const emojis = await addressToEmoji(pairingProcess); + + const transaction = db.transaction("labels", "readonly"); + const store = transaction.objectStore("labels"); + const request = store.get(emojis); + + request.onsuccess = () => { + const label = request.result; + chatHeader.textContent = label ? `Chat with ${label.label} (${emojis})` : `Chat with member (${emojis})`; + }; + + request.onerror = () => { + chatHeader.textContent = `Chat with member (${emojis})`; + }; messagesContainer.innerHTML = ''; @@ -691,20 +798,14 @@ class ChatElement extends HTMLElement { if (dmProcess?.states) { for (const state of dmProcess.states) { const pcd_commitment = state.pcd_commitment; - if (pcd_commitment) { - const message_hash = pcd_commitment.message; - if (message_hash) { - const diff = await service.getDiffByValue(message_hash); - const message = diff?.new_value; - if (message === "" || message === undefined) { + const message = await service.decryptAttribute(state, 'message'); + if (message === "" || message === undefined || message === null) { continue; } console.log('message', message); allMessages.push(message); } } - } - } allMessages.sort((a, b) => a.metadata.createdAt - b.metadata.createdAt); if (allMessages.length > 0) { @@ -712,16 +813,21 @@ class ChatElement extends HTMLElement { for (const message of allMessages) { const messageElement = document.createElement('div'); messageElement.className = 'message-container'; + + const myProcessId = await this.getMyProcessId(); - const isCurrentUser = message.metadata.sender === myAddresses[0]; + const isCurrentUser = message.metadata.sender === myProcessId; messageElement.style.justifyContent = isCurrentUser ? 'flex-end' : 'flex-start'; - + const messageContent = document.createElement('div'); - messageContent.className = 'message user'; + messageContent.className = isCurrentUser ? 'message user' : 'message'; + - console.log("SENDER: ", message.metadata.sender); - const pairingProcess = await this.getMyProcessId(); - const senderEmoji = await addressToEmoji(pairingProcess); + + const myEmoji = await addressToEmoji(myProcessId); + const otherEmoji = await addressToEmoji(this.selectedMember); + + const senderEmoji = isCurrentUser ? myEmoji : otherEmoji; if (message.type === 'file') { let fileContent = ''; @@ -924,7 +1030,8 @@ class ChatElement extends HTMLElement { await this.loadAllProcesses(processSet); break; case 'members': - this.loadAllMembers(); + await this.lookForMyDms(): + await this.loadAllMembers(); break; default: console.error('Unknown tab type:', tabType); @@ -1692,6 +1799,13 @@ class ChatElement extends HTMLElement { } else { console.warn('⚠️ No member selected yet. Waiting for selection...'); } + + window.addEventListener('process-updated', async (e: CustomEvent) => { + const processId = e.detail.processId; + if (processId === this.processId) { + setTimeout(async () => await this.reloadMemberChat(this.selectedMember), 3000); + } + }); } } diff --git a/src/services/service.ts b/src/services/service.ts index 1ef9532..d734d47 100755 --- a/src/services/service.ts +++ b/src/services/service.ts @@ -1136,6 +1136,12 @@ export default class Services { const existing = await this.getProcess(processId); if (existing) { console.log(`${processId} already in db`); + + const event = new CustomEvent('process-updated', { + detail: { processId } + }); + window.dispatchEvent(event); + // We may learn an update for this process // TODO maybe actually check if what the relay is sending us contains more information than what we have // relay should always have more info than us, but we never know