working chat

This commit is contained in:
NicolasCantu 2025-02-20 09:59:55 +01:00
parent 3be5efd60c
commit 512a9025b1
3 changed files with 198 additions and 36 deletions

View File

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

View File

@ -49,7 +49,8 @@ class ChatElement extends HTMLElement {
private messageState: number = 0;
private selectedRole: string | null = null;
private userProcessSet: Set<string> = new Set();
private addressMap: Record<string, string> = {}; // map each address to its pairing process id
private dmMembersSet: Set<string> = new Set();
private addressMap: Record<string, string> = {};
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<string | null> {
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<string>();
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);
}
});
}
}

View File

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