diff --git a/dist.zip b/dist.zip new file mode 100644 index 0000000..7ec85d2 Binary files /dev/null and b/dist.zip differ diff --git a/docs/INDEX.md b/docs/INDEX.md index ebaf909..d681964 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -19,10 +19,8 @@ Description fonctionnelle complète (parcours, règles de gestion, contraintes). Guide complet pour utiliser l'interface utilisateur ihm_client au quotidien. - **Démarrage du serveur de développement** - **Utilisation de l'interface utilisateur** -- **Gestion des profils utilisateurs** +- **Navigation Accueil et Compte** - **Opérations de pairing et wallet** -- **Gestion des processus et documents** -- **Système de chat et notifications** - **Tests et validation** ### ⚙️ [Guide de Configuration](CONFIGURATION.md) diff --git a/docs/USAGE.md b/docs/USAGE.md index e35a066..1c26a4c 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -24,12 +24,8 @@ npm run dev ### Navigation Principale -- **🏠 Accueil** - Vue d'ensemble et statistiques +- **🏠 Accueil** - Vue d'ensemble et navigation - **👤 Compte** - Gestion du profil utilisateur -- **📄 Processus** - Création et gestion des processus -- **✍️ Signature** - Signatures de documents -- **💬 Chat** - Communication entre membres -- **🔗 Pairing** - Connexion avec d'autres utilisateurs ### Tableau de Bord diff --git a/src/main.ts b/src/main.ts index b0f47a4..80e6840 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,16 +1,13 @@ -import { SignatureComponent } from './pages/signature/signature-component'; -import { SignatureElement } from './pages/signature/signature'; /*import { ChatComponent } from './pages/chat/chat-component'; import { ChatElement } from './pages/chat/chat';*/ import { AccountComponent } from './pages/account/account-component'; import { AccountElement } from './pages/account/account'; -export { SignatureComponent, SignatureElement, AccountComponent, AccountElement }; +export { AccountComponent, AccountElement }; declare global { interface HTMLElementTagNameMap { - 'signature-component': SignatureComponent; - 'signature-element': SignatureElement; + // Signatures supprimées /*'chat-component': ChatComponent; 'chat-element': ChatElement;*/ 'account-component': AccountComponent; @@ -21,8 +18,7 @@ declare global { // Configuration pour le mode indépendant if ((import.meta as any).env.VITE_IS_INDEPENDANT_LIB) { // Initialiser les composants si nécessaire - customElements.define('signature-component', SignatureComponent); - customElements.define('signature-element', SignatureElement); + // Signatures supprimées /*customElements.define('chat-component', ChatComponent); customElements.define('chat-element', ChatElement);*/ customElements.define('account-component', AccountComponent); diff --git a/src/pages/chat/chat-component.ts b/src/pages/chat/chat-component.ts deleted file mode 100644 index 2ded0b8..0000000 --- a/src/pages/chat/chat-component.ts +++ /dev/null @@ -1,49 +0,0 @@ -/*import { ChatElement } from './chat'; -import chatCss from '../../../public/style/chat.css?raw'; -import Services from '../../services/service.js'; - -class ChatComponent extends HTMLElement { - _callback: any; - chatElement: ChatElement | null = null; - - constructor() { - super(); - console.log('INIT'); - this.attachShadow({ mode: 'open' }); - - this.chatElement = this.shadowRoot?.querySelector('chat-element') || null; - } - - connectedCallback() { - console.log('CALLBACKs'); - this.render(); - - if (!customElements.get('chat-element')) { - customElements.define('chat-element', ChatElement); - } - } - - set callback(fn) { - if (typeof fn === 'function') { - this._callback = fn; - } else { - console.error('Callback is not a function'); - } - } - - get callback() { - return this._callback; - } - - render() { - if (this.shadowRoot) { - // Créer l'élément chat-element - const chatElement = document.createElement('chat-element'); - this.shadowRoot.innerHTML = ``; - this.shadowRoot.appendChild(chatElement); - } - } -} - -export { ChatComponent }; -customElements.define('chat-component', ChatComponent);*/ diff --git a/src/pages/chat/chat.html b/src/pages/chat/chat.html deleted file mode 100755 index 5265c88..0000000 --- a/src/pages/chat/chat.html +++ /dev/null @@ -1,14 +0,0 @@ - -
- -
- - -
-
- -
-
- -
- - -
- - - - -
-
- - -
-
-

Signature

- -
-
-
-

Description

-
-
-
-

Documents

- -
-
-
    -
    -
    -
    -
    - - `; - window.loadMemberChat = async (memberId: string | number) => { - if (typeof memberId === 'string') { - return await this.loadMemberChat(memberId); - } else { - console.error('Invalid memberId type. Expected string, got number.'); - } - }; - - document.addEventListener('newDataReceived', async (event: CustomEvent) => { - const { detail } = event; - console.log('New data event received:', JSON.stringify(detail)); - - if (detail.processId && detail.processId === this.selectedChatProcessId) { - console.log('Detected update to chat'); - if (this.selectedMember) { - await this.loadMemberChat(this.selectedMember); - } else { - console.error('No selected member?'); - } - } else { - console.log('Received an update for another process'); - } - }); - - - document.addEventListener('DOMContentLoaded', () => { - this.notificationBadge = document.querySelector('.notification-badge'); - this.notificationBoard = document.getElementById('notification-board'); - this.notificationBell = document.getElementById('notification-bell'); - - if (!this.notificationBadge || !this.notificationBoard || !this.notificationBell) { - console.error('Notification elements not found'); - } - }); - - // Initialiser les événements de notification - document.addEventListener('click', (event: Event): void => { - if (this.notificationBoard && this.notificationBoard.style.display === 'block' && - !this.notificationBoard.contains(event.target as Node) && - this.notificationBell && !this.notificationBell.contains(event.target as Node)) { - this.notificationBoard.style.display = 'none'; - } - }); - this.initMessageEvents(); - - } - - private initMessageEvents() { - const sendButton = this.shadowRoot?.querySelector('#send-button'); - if (sendButton) { - sendButton.addEventListener('click', async () => { - await this.sendMessage(); - setTimeout(async () => await this.reloadMemberChat(this.selectedMember), 600); - messageInput.value = ''; - }); - } - - const messageInput = this.shadowRoot?.querySelector('#message-input'); - if (messageInput) { - messageInput.addEventListener('keypress', async (event: Event) => { - const keyEvent = event as KeyboardEvent; - if (keyEvent.key === 'Enter' && !keyEvent.shiftKey) { - event.preventDefault(); - await this.sendMessage(); - setTimeout(async () => await this.reloadMemberChat(this.selectedMember), 600); - messageInput.value = ''; - } - }); - } - - const fileInput = this.shadowRoot?.querySelector('#file-input') as HTMLInputElement; - if (fileInput) { - fileInput.addEventListener('change', (event: Event) => { - const target = event.target as HTMLInputElement; - if (target.files && target.files.length > 0) { - this.sendFile(target.files[0]); - } - }); - } - } - - ///////////////////// Notification module ///////////////////// - // Delete a notification - private removeNotification(index: number) { - this.notifications?.splice(index, 1); // Ajout de ?. - this.renderNotifications(); - this.updateNotificationBadge(); - } - // Show notifications - private renderNotifications() { - if (!this.notificationBoard) return; - - // Reset the interface - this.notificationBoard.innerHTML = ''; - - // Displays "No notifications available" if there are no notifications - if (this.notifications.length === 0) { - this.notificationBoard.innerHTML = '
    No notifications available
    '; - return; - } - - // Add each notification to the list - this.notifications.forEach((notif, index) => { - const notifElement = document.createElement('div'); - notifElement.className = 'notification-item'; - notifElement.textContent = `${notif.text} at ${notif.time}`; - notifElement.onclick = async () => { - await this.loadMemberChat(notif.memberId); - await this.removeNotification(index); - }; - this.notificationBoard?.appendChild(notifElement); - }); - } - private updateNotificationBadge() { - if (!this.notificationBadge) return; - const count = this.notifications.length; - this.notificationBadge.textContent = count > 99 ? '+99' : count.toString(); - (this.notificationBadge as HTMLElement).style.display = count > 0 ? 'block' : 'none'; - } - - - // Add notification - private async addNotification(memberId: string, message: any) { - try { - // Obtenir l'emoji à partir du Pairing Process - const pairingProcess = await this.getPairingProcess(memberId); - const memberEmoji = await addressToEmoji(pairingProcess); - - // Obtenir le processus et le rôle - const processId = this.getAttribute('process-id'); - const processEmoji = processId ? await addressToEmoji(processId) : '📝'; - - // Trouver le rôle du membre - const member = this.allMembers.find(m => String(m.id) === memberId); - const role = message.metadata?.roleName || 'Member'; - - // Déterminer le texte de la notification - let notificationText = ''; - if (message.type === 'file') { - notificationText = `${memberEmoji} (${role}) in ${processEmoji}: New file - ${message.fileName}`; - } else { - notificationText = `${memberEmoji} (${role}) in ${processEmoji}: ${message.metadata.text}`; - } - - // Créer la notification - const notification = { - memberId, - text: notificationText, - time: new Date(message.metadata.timestamp).toLocaleString('fr-FR') - }; - - // Ajouter la notification et mettre à jour l'interface - this.notifications.push(notification); - this.renderNotifications(); - this.updateNotificationBadge(); - - } catch (error) { - console.error('Error creating notification:', error); - } - } - - private async sendMessage() { - const messageInput = this.shadowRoot?.querySelector('#message-input') as HTMLInputElement; - if (!messageInput || !this.selectedMember) { - console.error('❌ Missing message input or selected member'); - return; - } - - if (!this.selectedChatProcessId) { - console.error('no process id set'); - return; - } - - const messageText = messageInput.value.trim(); - if (messageText === '') { - console.error('❌ Empty message'); - return; - } - - try { - const service = await Services.getInstance(); - const myProcessId = await this.getMyProcessId(); - - if (!myProcessId) { - console.error('No paired member found'); - return; - } - - const timestamp = Date.now(); - const message = { - state: this.messageState, - type: 'text', - content: messageText, - metadata: { - createdAt: timestamp, - lastModified: timestamp, - sender: myProcessId, - recipient: this.selectedMember, - } - }; - - console.log("----this.selectedChatProcessId",this.selectedChatProcessId ); - const process = await service.getProcess(this.selectedChatProcessId); - - if (!process) { - console.error('Failed to retrieve process from DB'); - return; - } - - // For a dm process, there are only 2 attributes, description will stay the same, message is the new message - // We don't need to get previous values for now, so let's just skip it - let newState = { - message: message, - description: 'dm' - }; - - // Now we create a new state for the dm process - let apiReturn; - try { - console.log(process); - apiReturn = await service.updateProcess(process, newState, null); - } catch (e) { - console.error('Failed to update process:', e); - return; - } - const updatedProcess = apiReturn.updated_process.current_process; - const newStateId = updatedProcess.states[updatedProcess.states.length - 2 ].state_id; // We take the last concurrent state, just before the tip - console.log(`newStateId: ${newStateId}`); - await service.handleApiReturn(apiReturn); - - const createPrdReturn = service.createPrdUpdate(this.selectedChatProcessId, newStateId); - await service.handleApiReturn(createPrdReturn); - - // Now we validate the new state - const approveChangeReturn = await service.approveChange(this.selectedChatProcessId, newStateId); - await service.handleApiReturn(approveChangeReturn); - - await this.lookForMyDms(); - - const groupList = this.shadowRoot?.querySelector('#group-list'); - const tabs = this.shadowRoot?.querySelectorAll('.tab'); - const memberList = groupList?.querySelector('.member-list'); - - if (memberList) { - memberList.innerHTML = ''; - await this.loadAllMembers(); - if (tabs) { - await this.switchTab('members', tabs); - } - } - } catch (error) { - console.error('❌ Error in sendMessage:', error); - } - } - - private scrollToBottom(container: Element) { - (container as HTMLElement).scrollTop = (container as HTMLElement).scrollHeight; - } - - // Get the diff by state id - async getDiffByStateId(stateId: string) { - try { - const database = await Database.getInstance(); - const diff = await database.requestStoreByIndex('diffs', 'byStateId', stateId); - return diff; - } catch (error) { - console.error('Error getting diff by state id:', error); - } - } - - // TODO rewrite that - // private async lookForChildren(): Promise { - // // Filter processes for the children of current process - // const service = await Services.getInstance(); - // if (!this.selectedChatProcessId) { - // console.error('No process id'); - // return null; - // } - // const children: string[] = await service.getChildrenOfProcess(this.selectedChatProcessId); - - // const processRoles = this.processRoles; - // const selectedMember = this.selectedMember; - // for (const child of children) { - // const roles = service.getRoles(JSON.parse(child)); - // // Check that we and the other members are in the role - // if (!service.isChildRole(processRoles, roles)) { - // console.error('Child process roles are not a subset of parent') - // continue; - // } - // if (!service.rolesContainsMember(roles, selectedMember)) { - // console.error('Member is not part of the process'); - // continue; - // } - // if (!service.rolesContainsUs(roles)) { - // console.error('We\'re not part of child process'); - // continue; - // } - // return child; - // } - - // return null; - // } - - private async loadAllMembers() { - const groupList = this.shadowRoot?.querySelector('#group-list'); - if (!groupList) return; - - const service = await Services.getInstance(); - const members = await service.getAllMembers(); - const processes = await service.getProcesses(); - - const memberList = document.createElement('ul'); - memberList.className = 'member-list active'; - - // Partition members into prioritized and remaining arrays. - const prioritizedMembers: [string, Member][] = []; - const remainingMembers: [string, Member][] = []; - 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); - - // Process each member. - for (const [processId, member] of sortedMembers) { - const memberItem = document.createElement('li'); - memberItem.className = 'member-item'; - - // Apply special styling if the member is prioritized. - if (this.dmMembersSet.has(processId)) { - memberItem.style.cssText = ` - background-color: var(--accent-color); - transition: background-color 0.3s ease; - cursor: pointer; - `; - memberItem.addEventListener('mouseover', () => { - memberItem.style.backgroundColor = 'var(--accent-color-hover)'; - }); - memberItem.addEventListener('mouseout', () => { - memberItem.style.backgroundColor = 'var(--accent-color)'; - }); - } - - // Create a container for the member content. - const memberContainer = document.createElement('div'); - memberContainer.className = 'member-container'; - - // Create the emoji span and load its label. - const emojiSpan = document.createElement('span'); - emojiSpan.className = 'member-emoji'; - const emojis = await addressToEmoji(processId); - emojiSpan.dataset.emojis = emojis; - - // Get the member name, if any, and add it to the display - const process = processes[processId]; - let memberPublicName; - if (process) { - const publicMemberData = service.getPublicData(process); - if (publicMemberData) { - const extractedName = publicMemberData['memberPublicName']; - if (extractedName !== undefined && extractedName !== null) { - memberPublicName = extractedName; - } - } - } - if (!memberPublicName) { - memberPublicName = 'Unnamed Member'; - } - - emojiSpan.textContent = `${memberPublicName} (${emojis})` - - memberContainer.appendChild(emojiSpan); - memberItem.appendChild(memberContainer); - - // Add click handler to load member chat. - memberItem.addEventListener('click', async () => { - await this.loadMemberChat(processId); - }); - - // Create and configure the edit label button. - 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(); - event.preventDefault(); - - const newLabel = prompt("Set a new name for the member:"); - if (!newLabel) return; - - const db = await Database.getInstance(); - this.updateLabelForEmoji(emojis, newLabel, db, emojiSpan, processId); - }); - memberContainer.appendChild(editLabelButton); - - memberList.appendChild(memberItem); - } - - groupList.appendChild(memberList); - } - - // Helper function to update a label in IndexedDB. - private updateLabelForEmoji( - emojis: string, - newLabel: string, - db: IDBDatabase, - emojiSpan: HTMLElement, - processId: string - ) { - const transaction = db.transaction("labels", "readwrite"); - const store = transaction.objectStore("labels"); - const labelObject = { emoji: emojis, label: newLabel }; - const request = store.put(labelObject); - - request.onsuccess = () => { - emojiSpan.textContent = `${newLabel} : ${emojis}`; - this.reloadMemberChat(processId); - }; - } - - private async lookForDmProcess(): Promise { - const service = await Services.getInstance(); - const processes = await service.getMyProcesses(); - console.log(processes); - const recipientAddresses = await service.getAddressesForMemberId(this.selectedMember).sp_addresses; - console.log(recipientAddresses); - - for (const processId of processes) { - try { - const process = await service.getProcess(processId); - console.log(process); - const state = process.states[0]; // We assume that description never change and that we are part of the process from the beginning - const description = await service.decryptAttribute(processId, state, 'description'); - console.log(description); - if (!description || description !== "dm") { - continue; - } - const roles = service.getRoles(process); - if (!service.rolesContainsMember(roles, recipientAddresses)) { - console.error('Member is not part of the process'); - continue; - } - return processId; - } catch (e) { - console.error(e); - } - } - - 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(processId, state, 'description'); - if (!description || description !== "dm") { - continue; - } - const roles = service.getRoles(process); - const members = roles.dm.members; - for (const member of members) {; - if (!service.compareMembers(member.sp_addresses, myAddresses)) { - for (const [id, mem] of Object.entries(allMembers)) { - if (service.compareMembers(mem.sp_addresses, member.sp_addresses)) { - this.dmMembersSet.add(id); - break; - } - } - } - } - } - } catch (e) { - console.error(e); - } - console.log("dmMembersSet:", this.dmMembersSet); - return null; - } - - private async loadMemberChat(pairingProcess: string) { - if (this.isLoading) { - console.log('Already loading messages, skipping...'); - return; - } - - try { - this.isLoading = true; - 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; - } - - // Set the selected member - this.selectedMember = pairingProcess; - console.log("SELECTED MEMBER: ", this.selectedMember); - - const chatHeader = this.shadowRoot?.querySelector('#chat-header'); - const messagesContainer = this.shadowRoot?.querySelector('#messages'); - - if (!chatHeader || !messagesContainer) return; - - messagesContainer.innerHTML = ''; - - 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})`; - }; - - let dmProcessId = await this.lookForDmProcess(); - - if (dmProcessId === null) { - console.log('Create a new dm process'); - // We need to create a new process - try { - const memberAddresses = await service.getAddressesForMemberId(this.selectedMember); - console.log("MEMBER ADDRESSES: ", memberAddresses); - // await service.checkConnections(otherMembers); - const res = await service.createDmProcess(memberAddresses.sp_addresses); - // We catch the new process here - const updatedProcess = res.updated_process?.current_process; - const processId = updatedProcess?.states[0]?.commited_in; - const stateId = updatedProcess?.states[0]?.state_id; - await service.handleApiReturn(res); - setTimeout(async () => { - // Now create a first commitment - console.log('Created a dm process', processId); - this.selectedChatProcessId = processId; - const createPrdReturn = await service.createPrdUpdate(processId, stateId); - await service.handleApiReturn(createPrdReturn); - const approveChangeReturn = await service.approveChange(processId, stateId); - await service.handleApiReturn(approveChangeReturn); - }, 500); - } catch (e) { - console.error(e); - return; - } - - while (dmProcessId === null) { - dmProcessId = await this.lookForDmProcess(); - await new Promise(r => setTimeout(r, 1000)); - } - } else { - console.log('Found DM process', dmProcessId); - this.selectedChatProcessId = dmProcessId; - } - - // Récupérer les messages depuis les états du processus - const allMessages: any[] = []; - - const dmProcess = await service.getProcess(this.selectedChatProcessId); - - console.log(dmProcess); - - if (dmProcess?.states) { - for (const state of dmProcess.states) { - if (state.state_id === '') { continue; } - const message = await service.decryptAttribute(this.selectedChatProcessId, state, 'message'); - if (message === "" || message === undefined || message === null) { - continue; - } - console.log('message', message); - allMessages.push(message); - } - } - - if (allMessages.length > 0) { - console.log('Messages found:', allMessages); - allMessages.sort((a, b) => a.metadata.createdAt - b.metadata.createdAt); - for (const message of allMessages) { - const messageElement = document.createElement('div'); - messageElement.className = 'message-container'; - - const myProcessId = await this.getMyProcessId(); - - const isCurrentUser = message.metadata.sender === myProcessId; - messageElement.style.justifyContent = isCurrentUser ? 'flex-end' : 'flex-start'; - - const messageContent = document.createElement('div'); - messageContent.className = isCurrentUser ? 'message user' : 'message'; - - const myEmoji = await addressToEmoji(myProcessId); - const otherEmoji = await addressToEmoji(this.selectedMember); - - const senderEmoji = isCurrentUser ? myEmoji : otherEmoji; - - if (message.type === 'file') { - let fileContent = ''; - if (message.content.type.startsWith('image/')) { - fileContent = ` -
    - Image -
    - `; - } else { - const blob = this.base64ToBlob(message.content.data, message.content.type); - const url = URL.createObjectURL(blob); - fileContent = ` - - `; - } - - messageContent.innerHTML = ` -
    - ${senderEmoji}: ${fileContent} -
    -
    - ${new Date(message.metadata.createdAt).toLocaleString('fr-FR')} -
    - `; - } else { - messageContent.innerHTML = ` -
    - ${senderEmoji}: ${message.content} -
    -
    - ${new Date(message.metadata.createdAt).toLocaleString('fr-FR')} -
    - `; - } - - messageElement.appendChild(messageContent); - messagesContainer.appendChild(messageElement); - } - - this.scrollToBottom(messagesContainer); - } else { - console.log('No messages found'); - } - this.scrollToBottom(messagesContainer); - } catch (error) { - console.error('❌ Error in loadMemberChat:', error); - } finally { - this.isLoading = false; - } - } - - private async reloadMemberChat(pairingProcess: string) { - try { - const service = await Services.getInstance(); - const database = await Database.getInstance(); - const db = database.db; - - const chatHeader = this.shadowRoot?.querySelector('#chat-header'); - const messagesContainer = this.shadowRoot?.querySelector('#messages'); - - if (!chatHeader || !messagesContainer) return; - - messagesContainer.innerHTML = ''; - - 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; - if (this.selectedMember === pairingProcess) { - chatHeader.textContent = label ? `Chat with ${label.label} (${emojis})` : `Chat with member (${emojis})`; - } - - }; - - request.onerror = () => { - chatHeader.textContent = `Chat with member (${emojis})`; - }; - - let dmProcessId = await this.selectedChatProcessId; - - // Récupérer les messages depuis les états du processus - const allMessages: any[] = []; - - const dmProcess = await service.getProcess(dmProcessId); - - console.log(dmProcess); - - if (dmProcess?.states) { - for (const state of dmProcess.states) { - if (!state.state_id) { continue; } - const message = await service.decryptAttribute(dmProcessId, 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) { - console.log('Messages found:', allMessages); - for (const message of allMessages) { - const messageElement = document.createElement('div'); - messageElement.className = 'message-container'; - - const myProcessId = await this.getMyProcessId(); - - const isCurrentUser = message.metadata.sender === myProcessId; - messageElement.style.justifyContent = isCurrentUser ? 'flex-end' : 'flex-start'; - - const messageContent = document.createElement('div'); - messageContent.className = isCurrentUser ? 'message user' : 'message'; - - - - const myEmoji = await addressToEmoji(myProcessId); - const otherEmoji = await addressToEmoji(this.selectedMember); - - const senderEmoji = isCurrentUser ? myEmoji : otherEmoji; - - if (message.type === 'file') { - let fileContent = ''; - if (message.content.type.startsWith('image/')) { - fileContent = ` -
    - Image -
    - `; - } else { - const blob = this.base64ToBlob(message.content.data, message.content.type); - const url = URL.createObjectURL(blob); - fileContent = ` - - `; - } - - messageContent.innerHTML = ` -
    - ${senderEmoji}: ${fileContent} -
    -
    - ${new Date(message.metadata.createdAt).toLocaleString('fr-FR')} -
    - `; - } else { - messageContent.innerHTML = ` -
    - ${senderEmoji}: ${message.content} -
    -
    - ${new Date(message.metadata.createdAt).toLocaleString('fr-FR')} -
    - `; - } - - messageElement.appendChild(messageContent); - messagesContainer.appendChild(messageElement); - } - - this.scrollToBottom(messagesContainer); - } else { - console.log('No messages found'); - } - this.scrollToBottom(messagesContainer); - } catch (error) { - console.error('❌ Error in reloadMemberChat:', error); - } - } - - private base64ToBlob(base64: string, type: string): Blob { - const byteCharacters = atob(base64.split(',')[1]); - const byteArrays = []; - - for (let offset = 0; offset < byteCharacters.length; offset += 512) { - const slice = byteCharacters.slice(offset, offset + 512); - const byteNumbers = new Array(slice.length); - - for (let i = 0; i < slice.length; i++) { - byteNumbers[i] = slice.charCodeAt(i); - } - - const byteArray = new Uint8Array(byteNumbers); - byteArrays.push(byteArray); - } - - return new Blob(byteArrays, { type: type }); - } - - //To get a map with key: sp address, value: pairing process - async getAddressMap() { - const service = await Services.getInstance(); - const allMembers = await service.getAllMembers(); - - const addressMap: Record = {}; - - for (const [key, values] of Object.entries(allMembers)) { - - if (values.sp_addresses) { - for (let value of values.sp_addresses) { - this.addressMap[value] = key; - } - } else { - console.log(`No sp_addresses array found for key "${key}"`); - } - } - return this.addressMap; - } - - async findProcessIdFromAddresses(addresses: string[]): Promise { - console.log('Addresses to find:', addresses); - const service = await Services.getInstance(); - const allMembers = await service.getAllMembers(); - console.log('Available members:', allMembers); - - const sortedAddresses = [...addresses].sort(); - - for (const [key, value] of Object.entries(allMembers)) { - if (value.sp_addresses.length === sortedAddresses.length) { - const sortedValue = [...value.sp_addresses].sort(); - if (sortedValue.every((val, index) => val === sortedAddresses [index])) { - return key; // Found a match - } - } - } - - return null; // No match found - } - - private async toggleMembers(roleData: any, roleElement: HTMLElement) { - console.log('Toggle members called with roleData:', roleData); - let memberList = roleElement.querySelector('.member-list'); - const roleName = roleElement.querySelector('.role-name')?.textContent || ''; - - if (memberList) { - console.log('Existing memberList found, toggling display'); - (memberList as HTMLElement).style.display = - (memberList as HTMLElement).style.display === 'none' ? 'block' : 'none'; - return; - } - - console.log('Creating new memberList'); - memberList = document.createElement('ul'); - memberList.className = 'member-list'; - - if (roleData.members) { - console.log('Members found:', roleData.members); - for (const member of roleData.members) { - console.log('Processing member:', member); - const memberItem = document.createElement('li'); - memberItem.className = 'member-item'; - - const memberContainer = document.createElement('div'); - memberContainer.className = 'member-container'; - - const emojiSpan = document.createElement('span'); - emojiSpan.className = 'member-emoji'; - - const pairingProcess = await this.findProcessIdFromAddresses(member.sp_addresses); - console.log('PairingProcess:', pairingProcess); - if (pairingProcess) { - //TO DO : faire apparaitre les membres avec lesquelels je suis pairé ? - const emojis = await addressToEmoji(pairingProcess); - console.log('Adresse pairée:', emojis); - emojiSpan.textContent = emojis; - } else { - const emojis = await addressToEmoji(member.sp_addresses[0]); - emojiSpan.textContent = emojis; - } - - memberContainer.appendChild(emojiSpan); - memberItem.appendChild(memberContainer); - - memberItem.onclick = async (event) => { - event.stopPropagation(); - try { - if (pairingProcess) { - await this.loadMemberChat(pairingProcess); - } - } catch (error) { - console.error('❌ Error handling member click:', error); - } - }; - - memberList.appendChild(memberItem); - } - } else { - console.log('No members found in roleData'); - } - - roleElement.appendChild(memberList); - } - - - private async switchTab(tabType: string, tabs: NodeListOf) { - const service = await Services.getInstance(); - - // Mettre à jour les classes des onglets - tabs.forEach(tab => { - tab.classList.toggle('active', tab.getAttribute('data-tab') === tabType); - }); - - // Supprimer le contenu existant sauf les onglets - const groupList = this.shadowRoot?.querySelector('#group-list'); - if (!groupList) return; - - const children = Array.from(groupList.children); - children.forEach(child => { - if (!child.classList.contains('tabs')) { - groupList.removeChild(child); - } - }); - - // Charger le contenu approprié - switch (tabType) { - case 'processes': - const processSet = await service.getMyProcesses(); - await this.loadAllProcesses(processSet); - break; - case 'members': - await this.lookForMyDms(); - await this.loadAllMembers(); - break; - default: - console.error('Unknown tab type:', tabType); - } - } - - //load all processes from the service - private async loadAllProcesses() { - console.log('🎯 Loading all processes'); - this.closeSignature(); - - const service = await Services.getInstance(); - const allProcesses: Record = await service.getProcesses(); - console.log('All processes:', allProcesses); - const myProcesses: string[] = await service.getMyProcesses(); - console.log('My processes:', myProcesses); - - const groupList = this.shadowRoot?.querySelector('#group-list'); - if (!groupList) { - console.warn('⚠️ Group list element not found'); - return; - } - - groupList.innerHTML = ''; - - const tabContent = document.createElement('div'); - tabContent.className = 'tabs'; - tabContent.innerHTML = ` - - - `; - groupList.appendChild(tabContent); - - // Ajouter les event listeners - const tabs = tabContent.querySelectorAll('.tab'); - tabs.forEach(tab => { - tab.addEventListener('click', () => { - const tabType = tab.getAttribute('data-tab'); - if (tabType) { - this.switchTab(tabType, tabs); - } - }); - }); - - // Trier les processus : ceux de l'utilisateur en premier - const sortedEntries = Object.entries(allProcesses).sort( - ([keyA], [keyB]) => { - const inSetA = myProcesses.includes(keyA); - const inSetB = myProcesses.includes(keyB); - return inSetB ? 1 : inSetA ? -1 : 0; - } - ); - - for (const [processId, process] of sortedEntries) { - // Create and configure the main list item. - const li = document.createElement('li'); - li.className = 'group-list-item'; - li.setAttribute('data-process-id', processId); - - // Retrieve roles for the current process. - const roles = service.getRoles(process); - if (!roles) { - console.error('Failed to get roles for process:', process); - continue; - } - - // If process is a pairing process, we don't want it in the list - if (service.isPairingProcess(roles)) { - continue; - } - - const publicData = service.getPublicData(process); - const processName = publicData['processName']; - const emoji = await addressToEmoji(processId); - - let displayName; - if (processName) { - displayName = `${processName} (${emoji})`; - } else { - displayName = `${defaultProcessName} (${emoji})`; - } - - // If the process is part of myProcesses, apply special styling. - if (myProcesses && myProcesses.includes(processId)) { - li.style.cssText = ` - background-color: var(--accent-color); - transition: background-color 0.3s ease; - cursor: pointer; - `; - li.addEventListener('mouseover', () => { - li.style.backgroundColor = 'var(--accent-color-hover)'; - }); - li.addEventListener('mouseout', () => { - li.style.backgroundColor = 'var(--accent-color)'; - }); - console.log("✅ Processus trouvé dans le set:", processId); - } - - // Attach a click handler for the process. - li.addEventListener('click', async (event) => { - event.stopPropagation(); - console.log("CLICKED ON PROCESS:", processId); - - // Update the signature header with the corresponding emoji. - const signatureHeader = this.shadowRoot?.querySelector('.signature-header h1'); - if (signatureHeader) { - if (processName) { - signatureHeader.textContent = `Signature of ${displayName}`; - } else { - signatureHeader.textContent = `Signature of ${displayName}`; - } - } - - this.openSignature(); - console.log('🎯 Roles de signature:', roles); - await this.loadAllRolesAndMembersInSignature(roles); - await this.newRequest(processId); - }); - - // Create the container for the process name and emoji. - const container = document.createElement('div'); - container.className = 'group-item-container'; - - // Create and set the process name element. - const nameSpan = document.createElement('span'); - nameSpan.className = 'process-name'; - nameSpan.textContent = displayName; - container.appendChild(nameSpan); - - li.appendChild(container); - - // Create a hidden list for roles. - const roleList = document.createElement('ul'); - roleList.className = 'role-list'; - roleList.style.display = 'none'; - - // Process each role and create role items. - Object.entries(roles).forEach(([roleName, roleData]) => { - const roleItem = document.createElement('li'); - roleItem.className = 'role-item'; - - const roleContainer = document.createElement('div'); - roleContainer.className = 'role-item-container'; - - const roleNameSpan = document.createElement('span'); - roleNameSpan.className = 'role-name'; - roleNameSpan.textContent = roleName; - - // Filter duplicate members by using the first sp_address as a key. - const uniqueMembers = new Map(); - roleData.members?.forEach(member => { - const spAddress = member.sp_addresses?.[0]; - if (spAddress && !uniqueMembers.has(spAddress)) { - uniqueMembers.set(spAddress, member); - } - }); - - // Create a new roleData object with unique members. - const filteredRoleData = { - ...roleData, - members: Array.from(uniqueMembers.values()), - }; - - // Attach a click handler for the role. - roleContainer.addEventListener('click', async (event) => { - event.stopPropagation(); - console.log("CLICKED ON ROLE:", roleName); - await this.toggleMembers(filteredRoleData, roleItem); - }); - - roleContainer.appendChild(roleNameSpan); - roleItem.appendChild(roleContainer); - roleList.appendChild(roleItem); - }); - - li.appendChild(roleList); - - // Toggle role list display when the container is clicked. - container.addEventListener('click', (event) => { - event.stopPropagation(); - container.classList.toggle('expanded'); - roleList.style.display = container.classList.contains('expanded') ? 'block' : 'none'; - }); - - // Append the completed process list item once. - groupList.appendChild(li); - } - - } - - private async newRequest(processId: string) { - const emoji = await addressToEmoji(processId); - const members = await this.getMembersFromProcess(processId); - const newRequestButton = this.shadowRoot?.querySelector('#request-document-button'); - if (newRequestButton) { - newRequestButton.replaceWith(newRequestButton.cloneNode(true)); - const freshButton = this.shadowRoot?.querySelector('#request-document-button'); - freshButton?.addEventListener('click', async () => { - const membersList = await this.generateMembersList(members); - - const modal = document.createElement('div'); - modal.className = 'request-modal'; - const today = new Date().toISOString().split('T')[0]; - - modal.innerHTML = ` - - `; - - this.shadowRoot?.appendChild(modal); - this.handleFileUpload(modal); - this.handleRequestButton(modal); - const closeButton = modal.querySelector('.close-modal'); - closeButton?.addEventListener('click', () => { - modal.remove(); - }); - }); - } - } - - //request button in the modal - private handleRequestButton(modal: HTMLElement) { - const requestButton = modal.querySelector('#send-request-button'); - requestButton?.addEventListener('click', () => { - console.log("REQUEST SENT"); - if (modal) { - //vérifier qu'au moins un membre est coché - const membersList = modal.querySelector('.members-list-modal'); - if (membersList) { - const members = membersList.querySelectorAll('.member-checkbox:checked'); - if (members.length === 0) { - alert('Please select at least one member'); - return; - } - } - //vérifier que la date est valide - const dateInput = modal.querySelector('#date-input') as HTMLInputElement; - if (dateInput) { - const date = new Date(dateInput.value); - if (isNaN(date.getTime())) { - alert('Please select a valid date'); - return; - } - } - - //verifier qu'un fichier a été load - const fileList = modal.querySelector('#file-list'); - if (fileList && fileList.children.length === 0) { - alert('Please upload at least one file'); - return; - } - - //récupérer le message - const messageInput = modal.querySelector('#message-input') as HTMLTextAreaElement; - if (messageInput) { - const message = messageInput.value; - } - //modal.remove(); - } - }); - } - - private handleFileUpload(modal: HTMLElement) { - const fileInput = modal.querySelector('#file-input') as HTMLInputElement; - const fileList = modal.querySelector('#file-list'); - const selectedFiles = new Set(); - - fileInput?.addEventListener('change', () => { - if (fileList && fileInput.files) { - Array.from(fileInput.files).forEach(file => { - if (!Array.from(selectedFiles).some(f => f.name === file.name)) { - selectedFiles.add(file); - const fileItem = document.createElement('div'); - fileItem.className = 'file-item'; - fileItem.innerHTML = ` - ${file.name} - - `; - fileList.appendChild(fileItem); - - fileItem.querySelector('.remove-file')?.addEventListener('click', () => { - selectedFiles.delete(file); - fileItem.remove(); - }); - } - }); - fileInput.value = ''; - } - }); - - return selectedFiles; - } - - private async generateMembersList(members: string[]) { - let html = ''; - for (const member of members) { - const emoji = await addressToEmoji(member); - html += `
  • ${emoji}
  • `; - } - return html; - } - - - //Send a set of members from a process - private async getMembersFromProcess(processId: string) { - const service = await Services.getInstance(); - const process = await service.getProcess(processId); - console.log("Process récupéré:", process); - - // Récupérer les rôles directement depuis le dernier état - const roles = service.getRoles(process); - console.log("Roles trouvés:", roles); - - if (!roles) return []; - type RoleData = { - members?: { sp_addresses?: string[] }[]; - }; - const uniqueMembers = new Set(); - Object.values(roles as unknown as Record).forEach((roleData: RoleData) => { - roleData.members?.forEach((member) => { - if (member.sp_addresses && member.sp_addresses[0]) { - uniqueMembers.add(member.sp_addresses[0]); - } - }); - }); - return Array.from(uniqueMembers); - } - - private async loadAllRolesAndMembersInSignature(roles: any) { - console.log('🎯 Roles:', roles); - const signatureDescription = this.shadowRoot?.querySelector('.signature-description'); - if (signatureDescription) { - signatureDescription.innerHTML = ''; - Object.entries(roles).forEach(([roleName, roleData]: [string, any]) => { - const roleItem = document.createElement('li'); - roleItem.className = 'role-signature'; - - const roleContainer = document.createElement('div'); - roleContainer.className = 'role-signature-container'; - - const roleNameSpan = document.createElement('span'); - roleNameSpan.className = 'role-signature-name'; - roleNameSpan.textContent = roleName; - - const uniqueMembers = new Map(); - roleData.members?.forEach((member: any) => { - const spAddress = member.sp_addresses?.[0]; - if (spAddress && !uniqueMembers.has(spAddress)) { - uniqueMembers.set(spAddress, member); - } - }); - - const filteredRoleData = { - ...roleData, - members: Array.from(uniqueMembers.values()) - }; - - roleContainer.addEventListener('click', async (event) => { - console.log("CLICKED ON ROLE:", roleName); - event.stopPropagation(); - await this.toggleMembers(filteredRoleData, roleItem); - }); - - roleContainer.appendChild(roleNameSpan); - roleItem.appendChild(roleContainer); - signatureDescription.appendChild(roleItem); - }); - } - } - - //fonction qui ferme la signature - private closeSignature() { - const closeSignature = this.shadowRoot?.querySelector('#close-signature'); - const signatureArea = this.shadowRoot?.querySelector('.signature-area'); - if (closeSignature && signatureArea) { - closeSignature.addEventListener('click', () => { - signatureArea.classList.add('hidden'); - }); - } - } - - //fonction qui ouvre la signature - private openSignature() { - const signatureArea = this.shadowRoot?.querySelector('.signature-area'); - if (signatureArea) { - signatureArea.classList.remove('hidden'); - } - } - - private async getMyProcessId() { - const service = await Services.getInstance(); - return service.getPairingProcessId(); - } - - //fonction qui renvoie les processus où le sp_adress est impliqué - private async getProcessesWhereTheCurrentMemberIs() { - const service = await Services.getInstance(); - try { - const currentMember = await service.getMemberFromDevice(); - if (!currentMember) { - console.error('❌ Pas de membre trouvé'); - return this.userProcessSet; - } - - const pairingProcess = await this.getMyProcessId(); - const memberEmoji = await addressToEmoji(pairingProcess); - console.log("Mon adresse:", currentMember[0], memberEmoji); - - const processes = await service.getProcesses(); - - for (const [processId, process] of Object.entries(processes)) { - try { - const roles = process.states[0]?.roles; - - if (!roles) { - console.log(`Pas de rôles trouvés pour le processus ${processId}`); - continue; - } - - for (const roleName in roles) { - const role = roles[roleName]; - - if (role.members && Array.isArray(role.members)) { - for (const member of role.members) { - if (member.sp_addresses && Array.isArray(member.sp_addresses)) { - if (member.sp_addresses.includes(currentMember[0])) { - this.userProcessSet.add(processId); - console.log(`Ajout du process ${processId} au Set (trouvé dans le rôle ${roleName})`); - break; - } - } - } - } - } - } catch (e) { - console.log(`Erreur lors du traitement du processus ${processId}:`, e); - continue; - } - } - - return this.userProcessSet; - } catch (e) { - console.error('❌ Erreur:', e); - return this.userProcessSet; - } - } - - // Send a file - private async sendFile(file: File) { - const MAX_FILE_SIZE = 1 * 1024 * 1024; - if (file.size > MAX_FILE_SIZE) { - alert('Le fichier est trop volumineux. Taille maximum : 1MB'); - return; - } - - try { - const service = await Services.getInstance(); - const myAddresses = await service.getMemberFromDevice(); - if (!myAddresses) throw new Error('No paired member found'); - - let fileData: string; - if (file.type.startsWith('image/')) { - fileData = await this.compressImage(file); - } else { - fileData = await this.readFileAsBase64(file); - } - - const timestamp = Date.now(); - const processId = this.getAttribute('process-id'); - const uniqueKey = `${processId}${timestamp}`; - - const dbRequest = indexedDB.open('4nk'); - - dbRequest.onerror = (event) => { - console.error("Database error:", dbRequest.error); - }; - - dbRequest.onsuccess = async (event) => { - const db = dbRequest.result; - const transaction = db.transaction(['diffs'], 'readwrite'); - const store = transaction.objectStore('diffs'); - - try { - // Message du fichier - const fileTemplate = { - value_commitment: uniqueKey, - messaging_id: processId, - description: 'message_content', - metadata: { - text: `Fichier envoyé: ${file.name}`, - timestamp: timestamp, - sender: myAddresses[0], - recipient: this.selectedMember, - messageState: this.messageState, - roleName: this.selectedRole, - type: 'file', - fileName: file.name, - fileType: file.type, - fileData: fileData - } - }; - - await new Promise((resolve, reject) => { - const request = store.add(fileTemplate); - request.onsuccess = () => { - console.log('✅ File message saved'); - resolve(); - }; - request.onerror = () => reject(request.error); - }); - - // Réponse automatique - const autoReplyTemplate = { - value_commitment: `${processId}${timestamp + 1000}`, - messaging_id: processId, - description: 'message_content', - metadata: { - text: "J'ai bien reçu votre fichier 📎", - timestamp: timestamp + 1000, - sender: this.selectedMember, - recipient: myAddresses[0], - messageState: this.messageState, - roleName: this.selectedRole - } - }; - - await new Promise((resolve, reject) => { - const request = store.add(autoReplyTemplate); - request.onsuccess = () => { - console.log('✅ Auto reply saved'); - if (myAddresses[0]) { - this.addNotification(myAddresses[0], autoReplyTemplate); - } - resolve(); - }; - request.onerror = () => reject(request.error); - }); - - // Attendre la fin de la transaction - await new Promise((resolve, reject) => { - transaction.oncomplete = () => { - console.log('✅ Transaction completed'); - resolve(); - }; - transaction.onerror = () => reject(transaction.error); - }); - - // Réinitialiser l'input file - const fileInput = this.shadowRoot?.querySelector('#file-input') as HTMLInputElement; - if (fileInput) fileInput.value = ''; - - // Recharger les messages - if (this.selectedMember) { - await this.loadMemberChat(this.selectedMember); - } - - } catch (error) { - console.error('❌ Transaction error:', error); - } - }; - - } catch (error) { - console.error('❌ Error in sendFile:', error); - } - } - - private async readFileAsBase64(file: File): Promise { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => resolve(reader.result as string); - reader.onerror = reject; - reader.readAsDataURL(file); - }); - } - - private async compressImage(file: File): Promise { - return new Promise((resolve, reject) => { - const img = new Image(); - const canvas = document.createElement('canvas'); - const ctx = canvas.getContext('2d'); - - img.onload = () => { - let width = img.width; - let height = img.height; - const MAX_WIDTH = 800; - const MAX_HEIGHT = 600; - - if (width > height) { - if (width > MAX_WIDTH) { - height *= MAX_WIDTH / width; - width = MAX_WIDTH; - } - } else { - if (height > MAX_HEIGHT) { - width *= MAX_HEIGHT / height; - height = MAX_HEIGHT; - } - } - - canvas.width = width; - canvas.height = height; - ctx?.drawImage(img, 0, 0, width, height); - - resolve(canvas.toDataURL('image/jpeg', 0.7)); - }; - - img.onerror = reject; - img.src = URL.createObjectURL(file); - }); - } - - private async getProcesses(): Promise { - const service = await Services.getInstance(); - const processes = await service.getProcesses(); - - const res = Object.entries(processes).map(([key, value]) => ({ - key, - value, - })); - - return res; - } - - async connectedCallback() { - const service = await Services.getInstance(); - - const loadPage = async () => { - console.log("🔍 Chargement des processus par défaut"); - await this.loadAllProcesses(); - - if (this.selectedMember) { - console.log('🔍 Loading chat for selected member:', this.selectedMember); - await this.loadMemberChat(this.selectedMember); - } else { - console.warn('⚠️ No member selected yet. Waiting for selection...'); - } - } - - let timeout: NodeJS.Timeout; - window.addEventListener('process-updated', async (e: CustomEvent) => { - const processId = e.detail.processId; - console.log('Notified of an update for process', processId); - await loadPage(); - }); - - await loadPage(); - } -} - -customElements.define('chat-element', ChatElement); -export { ChatElement };*/ - diff --git a/src/pages/process-element/process-component.ts b/src/pages/process-element/process-component.ts deleted file mode 100644 index 31ba6e6..0000000 --- a/src/pages/process-element/process-component.ts +++ /dev/null @@ -1,51 +0,0 @@ -import processHtml from './process-element.html?raw'; -import processScript from './process-element.ts?raw'; -import processCss from '../../4nk.css?raw'; -import { initProcessElement } from './process-element'; - -export class ProcessListComponent extends HTMLElement { - _callback: any; - id: string = ''; - zone: string = ''; - - constructor() { - super(); - this.attachShadow({ mode: 'open' }); - } - - connectedCallback() { - console.log('CALLBACK PROCESS LIST PAGE'); - this.render(); - setTimeout(() => { - initProcessElement(this.id, this.zone); - }, 500); - } - - set callback(fn) { - if (typeof fn === 'function') { - this._callback = fn; - } else { - console.error('Callback is not a function'); - } - } - - get callback() { - return this._callback; - } - - render() { - if (this.shadowRoot) - this.shadowRoot.innerHTML = ` - ${processHtml} - - - - diff --git a/src/pages/signature/signature.ts b/src/pages/signature/signature.ts deleted file mode 100755 index f5b60fa..0000000 --- a/src/pages/signature/signature.ts +++ /dev/null @@ -1,1758 +0,0 @@ -import signatureStyle from '../../../public/style/signature.css?inline'; - -declare global { - interface Window { - toggleUserList: () => void; - switchUser: (userId: string | number) => void; - closeProcessDetails: (groupId: number) => void; - loadMemberChat: (memberId: string | number) => void; - closeRoleDocuments: (roleName: string) => void; - newRequest: (params: RequestParams) => void; - submitRequest: () => void; - closeNewRequest: () => void; - closeModal: (button: HTMLElement) => void; - submitDocumentRequest: (documentId: number) => void; - submitNewDocument: (event: Event) => void; - submitCommonDocument: (event: Event) => void; - signDocument: (documentId: number, processId: number, isCommonDocument: boolean) => void; - confirmSignature: (documentId: number, processId: number, isCommonDocument: boolean) => void; - } -} - -import { groupsMock } from '../../mocks/mock-signature/groupsMock'; -import { messagesMock as initialMessagesMock, messagesMock } from '../../mocks/mock-signature/messagesMock'; -import { membersMock } from '../../mocks/mock-signature/membersMocks'; -import { - Message, - DocumentSignature, - RequestParams} from '../../models/signature.models'; -import { messageStore } from '../../utils/messageMock'; -import { Member } from '../../interface/memberInterface'; -import { Group } from '../../interface/groupInterface'; -import { getCorrectDOM } from '../../utils/document.utils'; - - -let currentUser: Member = membersMock[0]; - -interface LocalNotification { - memberId: string; - text: string; - time: string; -} - - -class SignatureElement extends HTMLElement { - private selectedMemberId: string | null = null; - private messagesMock: any[] = []; - private dom: Node; - private notifications: LocalNotification[] = []; - private notificationBadge = document.querySelector('.notification-badge'); - private notificationBoard = document.getElementById('notification-board'); - private notificationBell = document.getElementById('notification-bell'); - private selectedSignatories: DocumentSignature[] = []; - private allMembers = membersMock.map(member => ({ - id: member.id, - name: member.name, - roleName: 'Default Role' - })); - - private showAlert(message: string): void { - // Créer la popup si elle n'existe pas - let alertPopup = this.shadowRoot?.querySelector('.alert-popup'); - if (!alertPopup) { - alertPopup = document.createElement('div'); - alertPopup.className = 'alert-popup'; - this.shadowRoot?.appendChild(alertPopup); - } - - // Définir le message et afficher la popup - alertPopup.textContent = message; - (alertPopup as HTMLElement).style.display = 'block'; - - // Cacher la popup après 3 secondes - setTimeout(() => { - (alertPopup as HTMLElement).style.display = 'none'; - }, 3000); - } - - private signDocument(documentId: number, processId: number, isCommonDocument: boolean = false): void { - try { - if (typeof window === 'undefined' || typeof document === 'undefined') { - console.error('Cette fonction ne peut être exécutée que dans un navigateur'); - return; - } - - const groups = JSON.parse(localStorage.getItem('groups') || JSON.stringify(groupsMock)); - const group = groups.find((g: Group) => g.id === processId); - - if (!group) { - throw new Error('Process not found'); - } - - let targetDoc; - if (isCommonDocument) { - targetDoc = group.commonDocuments.find((d: any) => d.id === documentId); - } else { - for (const role of group.roles) { - if (role.documents) { - targetDoc = role.documents.find((d: any) => d.id === documentId); - if (targetDoc) break; - } - } - } - - if (!targetDoc) { - throw new Error('Document not found'); - } - - const canSign = isCommonDocument ? - targetDoc.signatures?.some((sig: DocumentSignature) => - sig.member?.name === currentUser?.name && !sig.signed - ) : - this.canUserSignDocument(targetDoc, currentUser?.name, currentUser); - - if (!canSign) { - this.showAlert("You do not have the necessary rights to sign this document."); - return; - } - - // Create and insert the modal directly into the body - const modalHtml = ` - `; - - - const modalElement = document.createElement('div'); - modalElement.className = 'modal-overlay'; - modalElement.innerHTML = modalHtml; - this.shadowRoot?.appendChild(modalElement); - - - const slider = modalElement.querySelector('#signatureSlider'); - const sliderTrack = slider?.parentElement; - let isDragging = false; - let startX: number; - let sliderLeft: number; - - if (slider && sliderTrack) { - slider.addEventListener('mousedown', (e: Event) => initDrag(e as MouseEvent)); - slider.addEventListener('touchstart', (e: Event) => initDrag(e as TouchEvent)); - - modalElement.addEventListener('mousemove', (e: Event) => drag.call(this, e as MouseEvent)); - modalElement.addEventListener('touchmove', (e: Event) => drag.call(this, e as TouchEvent)); - modalElement.addEventListener('mouseup', (e: Event) => stopDrag(e as MouseEvent)); - modalElement.addEventListener('touchend', (e: Event) => stopDrag(e as TouchEvent)); - } - - function initDrag(e: MouseEvent | TouchEvent) { - isDragging = true; - startX = 'touches' in e ? e.touches[0].clientX : e.clientX; - sliderLeft = (slider as HTMLElement)?.offsetLeft || 0; - } - - function drag(this: SignatureElement, e: MouseEvent | TouchEvent) { - if (!isDragging || !slider || !sliderTrack) return; - - e.preventDefault(); - const x = 'touches' in e ? e.touches[0].clientX : e.clientX; - const deltaX = x - startX; - - // Calculate the position relative to the track - let newLeft = sliderLeft + deltaX; - - // Limit the movement - const maxLeft = (sliderTrack as HTMLElement).offsetWidth - (slider as HTMLElement).offsetWidth; - newLeft = Math.max(0, Math.min(newLeft, maxLeft)); - - // Update the position - (slider as HTMLElement).style.left = `${newLeft}px`; - - // If the slider reaches 90% of the path, trigger the signature - if (newLeft > maxLeft * 0.9) { - stopDrag(e); - this.confirmSignature(documentId, processId, isCommonDocument); - } - } - - function stopDrag(e: MouseEvent | TouchEvent) { - if (!isDragging || !slider) return; - isDragging = false; - - // Reset the position if not enough dragged - if ((slider as HTMLElement).offsetLeft < ((sliderTrack as HTMLElement)?.offsetWidth || 0) * 0.9) { - (slider as HTMLElement).style.left = '0px'; - } - } - - } catch (error) { - console.error('Error displaying modal:', error); - this.showAlert(error instanceof Error ? error.message : 'Error displaying modal'); - } - } - - constructor() { - super(); - this.attachShadow({ mode: 'open' }); - this.dom = getCorrectDOM('signature-element'); - - this.shadowRoot!.innerHTML = ` - -
    - -
    -
      -
    -
    - - -
    -
    - -
    -
    - -
    - - -
    - - - - -
    -
    -
    - `; - - window.toggleUserList = this.toggleUserList.bind(this); - window.switchUser = this.switchUser.bind(this); - window.closeProcessDetails = this.closeProcessDetails.bind(this); - window.loadMemberChat = this.loadMemberChat.bind(this); - window.closeRoleDocuments = this.closeRoleDocuments.bind(this); - window.newRequest = this.newRequest.bind(this); - window.submitRequest = this.submitRequest.bind(this); - window.closeNewRequest = this.closeNewRequest.bind(this); - window.closeModal = this.closeModal.bind(this); - window.submitNewDocument = this.submitNewDocument.bind(this); - window.submitCommonDocument = this.submitCommonDocument.bind(this); - window.signDocument = this.signDocument.bind(this); - window.confirmSignature = this.confirmSignature.bind(this); - window.submitDocumentRequest = this.submitDocumentRequest.bind(this); - - // Initialiser les événements de notification - document.addEventListener('click', (event: Event): void => { - if (this.notificationBoard && this.notificationBoard.style.display === 'block' && - !this.notificationBoard.contains(event.target as Node) && - this.notificationBell && !this.notificationBell.contains(event.target as Node)) { - this.notificationBoard.style.display = 'none'; - } - }); - this.initMessageEvents(); - this.initFileUpload(); - } - - private initMessageEvents() { - // Pour le bouton Send - const sendButton = this.shadowRoot?.getElementById('send-button'); - if (sendButton) { - sendButton.addEventListener('click', () => this.sendMessage()); - } - - // Pour la touche Entrée - const messageInput = this.shadowRoot?.getElementById('message-input'); - if (messageInput) { - messageInput.addEventListener('keypress', (event: KeyboardEvent) => { - if (event.key === 'Enter' && !event.shiftKey) { - event.preventDefault(); - this.sendMessage(); - } - }); - } - } - - private initFileUpload() { - const fileInput = this.shadowRoot?.getElementById('file-input') as HTMLInputElement; - if (fileInput) { - fileInput.addEventListener('change', (event: Event) => { - const target = event.target as HTMLInputElement; - if (target.files && target.files.length > 0) { - this.sendFile(target.files[0]); - } - }); - } - } - - - private calculateDuration(startDate: string | null | undefined, endDate: string | null | undefined): number { - const start = new Date(startDate || ''); - const end = new Date(endDate || ''); - const duration = end.getTime() - start.getTime(); - return Math.floor(duration / (1000 * 60 * 60 * 24)); - } - - // Add this helper function - private canUserAccessDocument(document: any, roleId: string, currentUserRole: string): boolean { - // Modify the access logic - if (document.visibility === 'public') { - return true; // Can see but not necessarily sign - } - return roleId === currentUserRole; - } - - private canUserSignDocument(document: any, role: string, user: Member): boolean { - console.log('Checking signing rights for:', { - document, - role, - user, - userRoles: user.processRoles - }); - - // Vérifier si l'utilisateur est dans la liste des signatures - const isSignatory = document.signatures?.some((sig: DocumentSignature) => - sig.member && 'id' in sig.member && sig.member.id === user.id && !sig.signed - ); - - if (!isSignatory) { - console.log('User is not in signatures list or has already signed'); - return false; - } - - // Si l'utilisateur est dans la liste des signatures, il peut signer - return true; - } - - private closeProcessDetails(groupId: number) { - const detailsArea = this.shadowRoot?.getElementById(`process-details-${groupId}`); - const chatArea = this.shadowRoot?.getElementById('chat-area'); - - if (detailsArea) { - detailsArea.style.display = 'none'; - } - - if (chatArea) { - chatArea.style.display = 'block'; - } - } - - ///////////////////// Notification module ///////////////////// - // Delete a notification - private removeNotification(index: number) { - this.notifications?.splice(index, 1); // Ajout de ?. - this.renderNotifications(); - this.updateNotificationBadge(); - } - // Show notifications - private renderNotifications() { - if (!this.notificationBoard) return; - - // Reset the interface - this.notificationBoard.innerHTML = ''; - - // Displays "No notifications available" if there are no notifications - if (this.notifications.length === 0) { - this.notificationBoard.innerHTML = '
    No notifications available
    '; - return; - } - - // Add each notification to the list - this.notifications.forEach((notif, index) => { - const notifElement = document.createElement('div'); - notifElement.className = 'notification-item'; - notifElement.textContent = `${notif.text} at ${notif.time}`; - notifElement.onclick = () => { - this.loadMemberChat(notif.memberId); - this.removeNotification(index); - }; - this.notificationBoard?.appendChild(notifElement); - }); - } - private updateNotificationBadge() { - if (!this.notificationBadge) return; - const count = this.notifications.length; - this.notificationBadge.textContent = count > 99 ? '+99' : count.toString(); - (this.notificationBadge as HTMLElement).style.display = count > 0 ? 'block' : 'none'; - } - - - // Add notification - private addNotification(memberId: string, message: Message) { - // Creating a new notification - const notification = { - memberId, - text: `New message from Member ${memberId}: ${message.text}`, - time: message.time - }; - - // Added notification to list and interface - this.notifications.push(notification); - this.renderNotifications(); - this.updateNotificationBadge(); - } -// Send a messsage - private sendMessage() { - const messageInput = this.shadowRoot?.getElementById('message-input') as HTMLInputElement; - if (!messageInput) return; - const messageText = messageInput.value.trim(); - - if (messageText === '' || this.selectedMemberId === null) { - return; - } - - const newMessage: Message = { - id: Date.now(), - sender: "4NK", - text: messageText, - time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), - type: 'text' as const - }; - // Add and display the message immediately - messageStore.addMessage(this.selectedMemberId, newMessage); - this.messagesMock = messageStore.getMessages(); - this.loadMemberChat(this.selectedMemberId); - - // Reset the input - messageInput.value = ''; - - // Automatic response after 2 seconds - setTimeout(() => { - if (this.selectedMemberId) { - const autoReply = this.generateAutoReply(`Member ${this.selectedMemberId}`); - messageStore.addMessage(this.selectedMemberId, autoReply); - this.messagesMock = messageStore.getMessages(); - this.loadMemberChat(this.selectedMemberId); - this.addNotification(this.selectedMemberId, autoReply); - } - }, 2000); -} - - - private showProcessDetails(group: Group, groupId: number) { - console.log('Showing details for group:', groupId); - - // Close all existing process views - const allDetailsAreas = this.shadowRoot?.querySelectorAll('.process-details'); - if (allDetailsAreas) { - allDetailsAreas.forEach(area => { - (area as HTMLElement).style.display = 'none'; - }); - } - - const container = this.shadowRoot?.querySelector('.container'); - if (!container) { - console.error('Container not found'); - return; - } - - // Load the data from localStorage - const storedGroups = JSON.parse(localStorage.getItem('groups') || '[]'); - const storedGroup = storedGroups.find((g: Group) => g.id === groupId); - - // Use the data from localStorage if available, otherwise use the group passed as a parameter - const displayGroup = storedGroup || group; - - let detailsArea = this.shadowRoot?.getElementById(`process-details-${groupId}`); - if (!detailsArea) { - detailsArea = document.createElement('div'); - detailsArea.id = `process-details-${groupId}`; - detailsArea.className = 'process-details'; - container.appendChild(detailsArea); - } - - if (detailsArea) { - detailsArea.style.display = 'block'; - detailsArea.innerHTML = ` -
    -

    ${displayGroup.name}

    -
    -
    -
    -
    -
    -

    Description

    -

    ${displayGroup.description || 'No description available'}

    -
    -
    -

    Documents Communs

    -
    - ${displayGroup.commonDocuments.map((document: any) => { - const totalSignatures = document.signatures?.length || 0; - const signedCount = document.signatures?.filter((sig: DocumentSignature) => sig.signed).length || 0; - const percentage = totalSignatures > 0 ? (signedCount / totalSignatures) * 100 : 0; - const isVierge = !document.createdAt || !document.deadline || !document.signatures?.length; - const canSign = document.signatures?.some((sig: DocumentSignature) => - sig.member && 'id' in sig.member && sig.member.id === currentUser.id && !sig.signed - ); - - const signButton = !isVierge ? ` - ${totalSignatures > 0 && signedCount < totalSignatures && canSign ? ` - - ` : ''} - ` : ''; - - return ` -
    -
    -

    ${isVierge ? `⚠️ ${document.name}` : document.name}

    - ${document.visibility} -
    -
    - ${!isVierge ? ` -

    Created on: ${document.createdAt ? new Date(document.createdAt).toLocaleDateString() : 'N/A'}

    -

    Deadline: ${document.deadline ? new Date(document.deadline).toLocaleDateString() : 'N/A'}

    -

    Duration: ${this.calculateDuration(document.createdAt || '', document.deadline || '')} days

    -
    -
    Signatures:
    -
    - ${document.signatures?.map((sig: DocumentSignature) => ` -
    - ${sig.member.name} - - ${sig.signed ? - `✓ Signed on ${sig.signedAt ? new Date(sig.signedAt).toLocaleDateString() : 'unknown date'}` : - '⌛ Pending'} - -
    - `).join('')} -
    -
    -
    -
    -

    ${signedCount} out of ${totalSignatures} signed (${percentage.toFixed(0)}%)

    -
    - ` : ` -

    Document vierge - Waiting for creation

    - - `} - ${signButton} -
    -
    - `; - }).join('')} -
    -
    -
    -

    Roles and Documents

    - ${displayGroup.roles.map((role: { name: string; documents?: any[] }) => { - // Filter the documents according to the access rights - const accessibleDocuments = (role.documents || []).filter(doc => - this.canUserAccessDocument(doc, role.name, currentUser.processRoles?.[0]?.role || '') - ); - - return ` -
    -

    ${role.name}

    -
    - ${accessibleDocuments.map(document => { - const isVierge = !document.createdAt || - !document.deadline || - document.signatures.length === 0; - - const canSign = this.canUserSignDocument(document, role.name, currentUser); - - const signButton = !isVierge ? ` - ${document.signatures.length > 0 && - document.signatures.filter((sig: DocumentSignature) => sig.signed).length < document.signatures.length && - canSign ? ` - - ` : ''} - ` : ''; - - return ` -
    -
    -

    ${isVierge ? `⚠️ ${document.name}` : document.name}

    - ${document.visibility} -
    -
    - ${!isVierge ? ` -

    Created on: ${document.createdAt ? new Date(document.createdAt).toLocaleDateString() : 'N/A'}

    -

    Deadline: ${document.deadline ? new Date(document.deadline).toLocaleDateString() : 'N/A'}

    -

    Duration: ${this.calculateDuration(document.createdAt || '', document.deadline || '')} days

    - ` : '

    Document vierge - Waiting for creation

    '} -
    - ${!isVierge ? ` -
    -
    Signatures:
    -
    - ${document.signatures.map((sig: DocumentSignature) => ` -
    - ${sig.member.name} - - ${sig.signed ? - `✓ Signé le ${sig.signedAt ? new Date(sig.signedAt).toLocaleDateString() : 'date inconnue'}` : - '⌛ En attente'} - -
    - `).join('')} -
    -
    -
    -
    -

    ${document.signatures.filter((sig: DocumentSignature) => sig.signed).length} out of ${document.signatures.length} signed (${(document.signatures.filter((sig: DocumentSignature) => sig.signed).length / document.signatures.length * 100).toFixed(0)}%)

    -
    - ` : ''} - ${signButton} -
    - `; - }).join('')} -
    -
    - `; - }).join('')} -
    -
    -

    Members by Role

    -
    - ${displayGroup.roles.map((role: { name: string; members: Array<{ id: string | number; name: string }> }) => ` -
    -

    ${role.name}

    -
      - ${role.members.map(member => ` -
    • ${member.name}
    • - `).join('')} -
    -
    - `).join('')} -
    -
    - `; - - - const newCloseProcessButton = document.createElement('button'); - newCloseProcessButton.className = 'close-btn'; - newCloseProcessButton.textContent = 'x'; - newCloseProcessButton.addEventListener('click', () => this.closeProcessDetails(groupId)); - - const headerButtons = detailsArea.querySelector('.header-buttons'); - if (headerButtons) { - headerButtons.appendChild(newCloseProcessButton); - } - } - } - - // Scroll down the conversation after loading messages - private scrollToBottom(container: HTMLElement) { - container.scrollTop = container.scrollHeight; - } - - - // Load the list of members - private loadMemberChat(memberId: string | number) { - this.selectedMemberId = String(memberId); - const memberMessages = this.messagesMock.find(m => String(m.memberId) === String(memberId)); - - // Find the process and the role of the member - let memberInfo = { processName: '', roleName: '', memberName: '' }; - groupsMock.forEach(process => { - process.roles.forEach(role => { - const member = role.members.find(m => String(m.id) === String(memberId)); - if (member) { - memberInfo = { - processName: process.name, - roleName: role.name, - memberName: member.name - }; - } - }); - }); - - const chatHeader = this.shadowRoot?.getElementById('chat-header'); - const messagesContainer = this.shadowRoot?.getElementById('messages'); - - if (!chatHeader || !messagesContainer) return; - - chatHeader.textContent = `Chat with ${memberInfo.roleName} ${memberInfo.memberName} from ${memberInfo.processName}`; - messagesContainer.innerHTML = ''; - - if (memberMessages) { - memberMessages.messages.forEach((message: Message) => { - const messageElement = document.createElement('div'); - messageElement.className = 'message-container'; - - const messageContent = document.createElement('div'); - messageContent.className = 'message'; - if (message.type === 'file') { - messageContent.innerHTML = `${message.fileName}`; - messageContent.classList.add('user'); - } else { - messageContent.innerHTML = `${message.sender}: ${message.text} ${message.time}`; - if (message.sender === "4NK") { - messageContent.classList.add('user'); - } - } - - messageElement.appendChild(messageContent); - messagesContainer.appendChild(messageElement); - }); - } - - - this.scrollToBottom(messagesContainer); - } - - private toggleMembers(role: { members: { id: string | number; name: string; }[] }, roleElement: HTMLElement) { - let memberList = roleElement.querySelector('.member-list'); - if (memberList) { - (memberList as HTMLElement).style.display = (memberList as HTMLElement).style.display === 'none' ? 'block' : 'none'; - return; - } - - memberList = document.createElement('ul'); - memberList.className = 'member-list'; - - role.members.forEach(member => { - const memberItem = document.createElement('li'); - memberItem.textContent = member.name; - - memberItem.onclick = (event) => { - event.stopPropagation(); - this.loadMemberChat(member.id.toString()); - }; - - memberList.appendChild(memberItem); - }); - - roleElement.appendChild(memberList); - } - - - // Toggle the list of Roles - private toggleRoles(group: Group, groupElement: HTMLElement) { - console.log('=== toggleRoles START ==='); - console.log('Group:', group.name); - console.log('Group roles:', group.roles); // Afficher tous les rôles disponibles - - let roleList = groupElement.querySelector('.role-list'); - console.log('Existing roleList:', roleList); - - if (roleList) { - const roleItems = roleList.querySelectorAll('.role-item'); - roleItems.forEach(roleItem => { - console.log('Processing roleItem:', roleItem.innerHTML); // Voir le contenu HTML complet - - let container = roleItem.querySelector('.role-item-container'); - if (!container) { - container = document.createElement('div'); - container.className = 'role-item-container'; - - // Créer un span pour le nom du rôle - const nameSpan = document.createElement('span'); - nameSpan.className = 'role-name'; - nameSpan.textContent = roleItem.textContent?.trim() || ''; - - container.appendChild(nameSpan); - roleItem.textContent = ''; - roleItem.appendChild(container); - } - - // Récupérer le nom du rôle - const roleName = roleItem.textContent?.trim(); - console.log('Role name from textContent:', roleName); - - // Alternative pour obtenir le nom du rôle - const roleNameAlt = container.querySelector('.role-name')?.textContent; - console.log('Role name from span:', roleNameAlt); - - if (!container.querySelector('.folder-icon')) { - const folderButton = document.createElement('span'); - folderButton.innerHTML = '📁'; - folderButton.className = 'folder-icon'; - - folderButton.addEventListener('click', (event) => { - event.stopPropagation(); - console.log('Clicked role name:', roleName); - console.log('Available roles:', group.roles.map(r => r.name)); - - const role = group.roles.find(r => r.name === roleName); - if (role) { - console.log('Found role:', role); - this.showRoleDocuments(role, group); - } else { - console.error('Role not found. Name:', roleName); - console.error('Available roles:', group.roles); - } - }); - - container.appendChild(folderButton); - } - }); - - (roleList as HTMLElement).style.display = - (roleList as HTMLElement).style.display === 'none' ? 'block' : 'none'; - } - } - - - private loadGroupList(): void { - const groupList = this.shadowRoot?.getElementById('group-list'); - if (!groupList) return; - - groupsMock.forEach(group => { - const li = document.createElement('li'); - li.className = 'group-list-item'; - - // Create a flex container for the name and the icon - const container = document.createElement('div'); - container.className = 'group-item-container'; - - // Span for the process name - const nameSpan = document.createElement('span'); - nameSpan.textContent = group.name; - nameSpan.className = 'process-name'; - - // Add click event to show roles - nameSpan.addEventListener('click', (event) => { - event.stopPropagation(); - this.toggleRoles(group, li); - }); - - // Add the ⚙️ icon - const settingsIcon = document.createElement('span'); - settingsIcon.textContent = '⚙️'; - settingsIcon.className = 'settings-icon'; - settingsIcon.id = `settings-${group.id}`; - - settingsIcon.onclick = (event) => { - event.stopPropagation(); - this.showProcessDetails(group, group.id); - }; - - // Assemble the elements - container.appendChild(nameSpan); - container.appendChild(settingsIcon); - li.appendChild(container); - - // Create and append the role list container - const roleList = document.createElement('ul'); - roleList.className = 'role-list'; - roleList.style.display = 'none'; - - // Add roles for this process - group.roles.forEach(role => { - const roleItem = document.createElement('li'); - roleItem.className = 'role-item'; - roleItem.textContent = role.name; - roleItem.onclick = (event) => { - event.stopPropagation(); - this.toggleMembers(role, roleItem); - }; - roleList.appendChild(roleItem); - }); - - li.appendChild(roleList); - groupList.appendChild(li); - }); - } - - - // Function to manage the list of users - private toggleUserList() { - const userList = getCorrectDOM('userList'); - if (!userList) return; - - if (!(userList as HTMLElement).classList.contains('show')) { - (userList as HTMLElement).innerHTML = membersMock.map(member => ` -
    - ${member.avatar} -
    - ${member.name} - ${member.email} -
    -
    - `).join(''); - } - (userList as HTMLElement).classList.toggle('show'); - } - - private switchUser(userId: string | number) { - const user = membersMock.find(member => member.id === userId); - if (!user) return; - currentUser = user; - this.updateCurrentUserDisplay(); - const userList = getCorrectDOM('userList') as HTMLElement; - userList?.classList.remove('show'); - } - - // Function to update the display of the current user - private updateCurrentUserDisplay() { - const userDisplay = getCorrectDOM('current-user') as HTMLElement; - if (userDisplay) { - userDisplay.innerHTML = ` - - `; - } - } - // Generate an automatic response - private generateAutoReply(senderName: string): Message { - return { - id: Date.now(), - sender: senderName, - text: "OK...", - time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), - type: 'text' as const - }; - } - - // Send a file - private sendFile(file: File) { - console.log('SendFile called with file:', file); - const reader = new FileReader(); - reader.onloadend = () => { - const fileData = reader.result; - const fileName = file.name; - console.log('File loaded:', fileName); - - if (this.selectedMemberId) { - messageStore.addMessage(this.selectedMemberId, { - id: Date.now(), - sender: "4NK", - fileName: fileName, - fileData: fileData, - time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), - type: 'file' - }); - console.log('Message added to store'); - - this.messagesMock = messageStore.getMessages(); - this.loadMemberChat(this.selectedMemberId); - } - }; - reader.readAsDataURL(file); - } - - // Managing the sent file - private fileList: HTMLDivElement = this.shadowRoot?.getElementById('fileList') as HTMLDivElement; - private getFileList() { - const files = Array.from(this.fileList?.querySelectorAll('.file-item') || []).map((fileItem: Element) => { - const fileName = fileItem.querySelector('.file-name')?.textContent || ''; - return { - name: fileName, - url: (fileItem as HTMLElement).dataset.content || '#', - }; - }); - return files; - } - - // New function to display the documents of a role - private showRoleDocuments(role: { - name: string; - documents?: Array<{ - name: string; - visibility: string; - createdAt: string | null | undefined; - deadline: string | null | undefined; - signatures: DocumentSignature[]; - id: number; - description?: string; - status?: string; - files?: Array<{ name: string; url: string }>; - }>; - id?: number; - }, group: Group) { - // Load the data from localStorage - const storedGroups = JSON.parse(localStorage.getItem('groups') || '[]'); - const storedGroup = storedGroups.find((g: Group) => g.id === group.id); - const storedRole = storedGroup?.roles.find((r: any) => r.name === role.name); - - // Use the data from localStorage if available, otherwise use the data passed as a parameter - const displayRole = storedRole || role; - - console.log('Showing documents for role:', displayRole.name, 'in group:', group.name); - // Close all existing document views first - const allDetailsAreas = this.shadowRoot?.querySelectorAll('.process-details'); - allDetailsAreas?.forEach(area => { - area.remove(); - }); - - const container = this.shadowRoot?.querySelector('.container'); - if (!container) { - console.error('Container not found'); - return; - } - - // Create a new details area - const detailsArea = document.createElement('div'); - detailsArea.id = `role-documents-${displayRole.name}`; - detailsArea.className = 'process-details'; - // Filter the accessible documents - const accessibleDocuments = (displayRole.documents || []).filter((doc: { - name: string; - visibility: string; - createdAt: string | null | undefined; - deadline: string | null | undefined; - signatures: DocumentSignature[]; - id: number; - description?: string; - status?: string; - }) => - this.canUserAccessDocument(doc, displayRole.name, currentUser.processRoles?.[0]?.role || '') - ); - - detailsArea.innerHTML = ` - - `; - - container.appendChild(detailsArea); - } - - // Function to close the documents view of a role - private closeRoleDocuments(roleName: string) { - const detailsArea = this.shadowRoot?.getElementById(`role-documents-${roleName}`); - if (detailsArea) { - - detailsArea.remove(); - } - } - - private handleFiles(files: FileList, fileList: HTMLDivElement) { - Array.from(files).forEach(file => { - const reader = new FileReader(); - reader.onload = (e) => { - const fileContent = e.target?.result; - const existingFiles = fileList.querySelectorAll('.file-name'); - const isDuplicate = Array.from(existingFiles).some( - existingFile => existingFile.textContent === file.name - ); - - if (!isDuplicate) { - const fileItem = document.createElement('div'); - fileItem.className = 'file-item'; - fileItem.innerHTML = ` -
    - ${file.name} - (${(file.size / 1024).toFixed(1)} KB) -
    - - `; - fileItem.dataset.content = fileContent as string; - - const removeBtn = fileItem.querySelector('.remove-file'); - if (removeBtn) { - removeBtn.addEventListener('click', () => fileItem.remove()); - } - - fileList.appendChild(fileItem); - } - }; - reader.readAsDataURL(file); - }); - } - - // Function to manage the new request - private newRequest(params: RequestParams) { - // Add parameter validation - if (!params || !params.processId) { - console.error('Paramètres invalides:', params); - this.showAlert('Invalid parameters for new request'); - return; - } - - const modal = document.createElement('div'); - modal.className = 'modal-overlay'; - - // Retrieve the process with a verification - const process = groupsMock.find(g => g.id === params.processId); - if (!process) { - console.error('Processus non trouvé:', params.processId); - this.showAlert('Process not found'); - return; - } - - // Determine the members with an additional verification - let membersToDisplay = []; - try { - if (params.roleName === 'common') { - membersToDisplay = process.roles.reduce((members: any[], role) => { - return members.concat(role.members.map(member => ({ - ...member, - roleName: role.name - }))); - }, []); - } else { - const role = process.roles.find(r => r.name === params.roleName); - if (!role) { - throw new Error(`Role ${params.roleName} not found`); - } - membersToDisplay = role.members.map(member => ({ - ...member, - roleName: params.roleName - })); - } - } catch (error) { - console.error('Error retrieving members:', error); - this.showAlert('Error retrieving members'); - return; - } - - - - modal.innerHTML = ` - - `; - - this.shadowRoot?.appendChild(modal); - - const dropZone = modal.querySelector('#dropZone') as HTMLDivElement; - const fileInput = modal.querySelector('#fileInput') as HTMLInputElement; - const fileList = modal.querySelector('#fileList') as HTMLDivElement; - - // Make the area clickable - dropZone.addEventListener('click', () => { - fileInput.click(); - }); - - // Manage the file selection - fileInput.addEventListener('change', (e: Event) => { - const target = e.target as HTMLInputElement; - if (target.files && target.files.length > 0) { - this.handleFiles(target.files, fileList); - } - }); - - // Manage the drag & drop - dropZone.addEventListener('dragover', (e: DragEvent) => { - e.preventDefault(); - dropZone.classList.add('dragover'); - }); - - dropZone.addEventListener('dragleave', () => { - dropZone.classList.remove('dragover'); - }); - - dropZone.addEventListener('drop', (e: DragEvent) => { - e.preventDefault(); - dropZone.classList.remove('dragover'); - if (e.dataTransfer?.files) { - this.handleFiles(e.dataTransfer.files, fileList); - } - }); - } - - private closeModal(button: HTMLElement) { - const modalOverlay = button.closest('.modal-overlay'); - if (modalOverlay) { - modalOverlay.remove(); - } - } - - private submitNewDocument(event: Event) { - event.preventDefault(); - - const form = this.shadowRoot?.getElementById('newDocumentForm') as HTMLFormElement; - if (!form) { - this.showAlert('Form not found'); - return; - } - - // Retrieve the files - const fileList = this.shadowRoot?.getElementById('fileList') as HTMLDivElement; - const files = Array.from(fileList?.querySelectorAll('.file-item') || []).map(fileItem => { - const fileName = fileItem.querySelector('.file-name')?.textContent || ''; - return { - name: fileName, - url: (fileItem as HTMLElement).dataset.content || '#', - }; - }); - - // Retrieve the values from the form - const processId = Number((form.querySelector('#processId') as HTMLInputElement)?.value); - const documentId = Number((form.querySelector('#documentId') as HTMLInputElement)?.value); - const documentName = (form.querySelector('#documentName') as HTMLInputElement)?.value?.trim(); - const description = (form.querySelector('#description') as HTMLTextAreaElement)?.value?.trim(); - const deadline = (form.querySelector('#deadline') as HTMLInputElement)?.value; - const visibility = (form.querySelector('#visibility') as HTMLSelectElement)?.value; - - // Validation - if (!documentName || !description || !deadline) { - this.showAlert('Please fill in all required fields'); - return; - } - - try { - // Retrieve the current data - const groups = JSON.parse(localStorage.getItem('groups') || JSON.stringify(groupsMock)); - const group = groups.find((g: Group) => g.id === processId); - - if (!group) { - this.showAlert('Process not found'); - return; - } - - const role = group.roles.find((r: any) => - r.documents?.some((d: any) => d.id === documentId) - ); - - if (!role) { - this.showAlert('Role not found'); - return; - } - - // Create the new document with the signatures of the role members - const updatedDocument = { - id: documentId, - name: documentName, - description: description, - createdAt: new Date().toISOString(), - deadline: deadline, - visibility: visibility, - status: "pending", - signatures: role.members.map((member: { id: string | number; name: string }) => ({ - member: member, - signed: false, - signedAt: null - })), - files: files // Ajout des fichiers au document - }; - - // Update the document in the role - const documentIndex = role.documents.findIndex((d: any) => d.id === documentId); - if (documentIndex !== -1) { - role.documents[documentIndex] = updatedDocument; - } - - // Save in localStorage - localStorage.setItem('groups', JSON.stringify(groups)); - - // Also update groupsMock for consistency - const mockGroup = groupsMock.find(g => g.id === processId); - if (mockGroup) { - const mockRole = mockGroup?.roles.find(r => r.name === role.name); - if (mockRole?.documents) { - const mockDocIndex = mockRole.documents.findIndex(d => d.id === documentId); - if (mockDocIndex !== -1) { - mockRole.documents[mockDocIndex] = { - ...updatedDocument, - status: undefined - }; - } - } - } - - // Close the modal - if (event.target instanceof HTMLElement) { - this.closeModal(event.target); - } - - // Reload the documents view with the updated data - this.showRoleDocuments(role, group); - this.showAlert('Document updated successfully!'); - - } catch (error) { - console.error('Error saving:', error); - this.showAlert('An error occurred while saving'); - } - } - - private submitCommonDocument(event: Event) { - event.preventDefault(); - - const form = this.shadowRoot?.getElementById('newDocumentForm') as HTMLFormElement; - if (!form) { - this.showAlert('Form not found'); - return; - } - - const processId = Number((form.querySelector('#processId') as HTMLInputElement)?.value); - const documentId = Number((form.querySelector('#documentId') as HTMLInputElement)?.value); - const documentName = (form.querySelector('#documentName') as HTMLInputElement)?.value?.trim(); - const description = (form.querySelector('#description') as HTMLTextAreaElement)?.value?.trim(); - const deadline = (form.querySelector('#deadline') as HTMLInputElement)?.value; - const visibility = (form.querySelector('#visibility') as HTMLSelectElement)?.value; - - if (!documentName || !description || !deadline) { - this.showAlert('Please fill in all required fields'); - return; - } - - try { - const groups = JSON.parse(localStorage.getItem('groups') || JSON.stringify(groupsMock)); - const group = groups.find((g: Group) => g.id === processId); - - if (!group) { - this.showAlert('Process not found'); - return; - } - - // Retrieve all members of all roles in the group - const allMembers = group.roles.reduce((acc: any[], role: any) => { - return acc.concat(role.members); - }, []); - - const fileList = this.shadowRoot?.getElementById('fileList') as HTMLDivElement; - const files = Array.from(fileList?.querySelectorAll('.file-item') || []).map(fileItem => { - const fileName = fileItem.querySelector('.file-name')?.textContent || ''; - return { - name: fileName, - url: (fileItem as HTMLElement).dataset.content || '#', - }; - }); - - const updatedDocument = { - id: documentId, - name: documentName, - description: description, - createdAt: new Date().toISOString(), - deadline: deadline, - visibility: visibility, - status: "pending", - signatures: allMembers.map((member: { id: string | number; name: string }) => ({ - member: member, - signed: false, - signedAt: null - })), - files: files - }; - - // Update the common document - const documentIndex = group.commonDocuments.findIndex((d: { id: number }) => d.id === documentId); - if (documentIndex !== -1) { - group.commonDocuments[documentIndex] = updatedDocument; - } - - localStorage.setItem('groups', JSON.stringify(groups)); - - if (event.target instanceof HTMLElement) { - this.closeModal(event.target); - } - - this.showProcessDetails(group, group.id); - this.showAlert('Document common updated successfully!'); - - } catch (error) { - console.error('Error saving:', error); - this.showAlert('An error occurred while saving'); - } - } - - - private submitRequest() { - - this.showAlert("Request submitted!"); - } - - private closeNewRequest() { - const newRequestView = document.getElementById('new-request-view'); - if (newRequestView) { - newRequestView.style.display = 'none'; - newRequestView.remove(); - } - } - - private submitDocumentRequest(documentId: number) { - const createdAt = (this.shadowRoot?.getElementById('createdAt') as HTMLInputElement)?.value || ''; - const deadline = (this.shadowRoot?.getElementById('deadline') as HTMLInputElement)?.value || ''; - const visibility = (this.shadowRoot?.getElementById('visibility') as HTMLSelectElement)?.value || ''; - const description = (this.shadowRoot?.getElementById('description') as HTMLTextAreaElement)?.value || ''; - - const selectedMembers = Array.from( - this.shadowRoot?.querySelectorAll('input[name="selected-members"]:checked') || [] - ).map(checkbox => (checkbox as HTMLInputElement).value); - - if (!createdAt || !deadline || selectedMembers.length === 0) { - this.showAlert('Please fill in all required fields and select at least one member.'); - return; - } - - console.log('Document submission:', { - documentId, - createdAt, - deadline, - visibility, - description, - selectedMembers - }); - - this.showAlert('Document request submitted successfully!'); - this.closeNewRequest(); - } - - // FUNCTIONS FOR SIGNATURE - - // New function to confirm the signature - private confirmSignature(documentId: number, processId: number, isCommonDocument: boolean) { - try { - // Add console.log to see the current user - console.log('Current user:', currentUser); - - const groups = JSON.parse(localStorage.getItem('groups') || JSON.stringify(groupsMock)); - const group = groups.find((g: Group) => g.id === processId); - - if (!group) { - throw new Error('Process not found'); - } - - let targetDoc; - if (isCommonDocument) { - targetDoc = group.commonDocuments.find((d: any) => d.id === documentId); - } else { - for (const role of group.roles) { - if (role.documents) { - targetDoc = role.documents.find((d: any) => d.id === documentId); - if (targetDoc) break; - } - } - } - - if (!targetDoc) { - throw new Error('Document not found'); - } - - const userSignature = targetDoc.signatures.find((sig: DocumentSignature) => - sig.member.name === currentUser.name - ); - - if (!userSignature) { - throw new Error(`The user ${currentUser.name} is not authorized to sign this document. Please log in with an authorized user.`); - } - - // Mettre à jour la signature - userSignature.signed = true; - userSignature.signedAt = new Date().toISOString(); - localStorage.setItem('groups', JSON.stringify(groups)); - - // Supprimer la modal de signature - const modalOverlay = this.shadowRoot?.querySelector('.modal-overlay'); - if (modalOverlay) { - modalOverlay.remove(); - } - - // Rafraîchir l'affichage - if (isCommonDocument) { - this.showProcessDetails(group, processId); - } else { - const role = group.roles.find((r: any) => r.documents?.includes(targetDoc)); - if (role) { - this.showRoleDocuments(role, group); - } - } - - this.showAlert('Document signed successfully!'); - - } catch (error) { - console.error('Error signing document:', error); - this.showAlert(error instanceof Error ? error.message : 'Error signing document'); - } - } - - - private initializeEventListeners() { - document.addEventListener('DOMContentLoaded', (): void => { - const newRequestBtn = this.shadowRoot?.getElementById('newRequestBtn'); - if (newRequestBtn) { - newRequestBtn.addEventListener('click', (): void => { - this.newRequest({ - processId: 0, - processName: '', - roleId: 0, - roleName: '', - documentId: 0, - documentName: '' - }); - }); - } - }); - - // Gestionnaire d'événements pour le chat - const sendBtn = this.shadowRoot?.querySelector('#send-button'); - if (sendBtn) { - sendBtn.addEventListener('click', this.sendMessage.bind(this)); - } - - const messageInput = this.shadowRoot?.querySelector('#message-input'); - if (messageInput) { - messageInput.addEventListener('keypress', (event: Event) => { - if ((event as KeyboardEvent).key === 'Enter') { - event.preventDefault(); - this.sendMessage(); - } - }); - } - - // Gestionnaire pour l'envoi de fichiers - const fileInput = this.shadowRoot?.querySelector('#file-input'); - if (fileInput) { - fileInput.addEventListener('change', (event: Event) => { - const file = (event.target as HTMLInputElement).files?.[0]; - if (file) { - this.sendFile(file); - } - }); - } - } - - connectedCallback() { - this.messagesMock = messageStore.getMessages(); - if (this.messagesMock.length === 0) { - messageStore.setMessages(initialMessagesMock); - this.messagesMock = messageStore.getMessages(); - } - this.updateCurrentUserDisplay(); - this.initializeEventListeners(); - this.loadGroupList(); - } -} - -customElements.define('signature-element', SignatureElement); -export { SignatureElement }; - diff --git a/src/router.ts b/src/router.ts index dba8f57..5383e71 100755 --- a/src/router.ts +++ b/src/router.ts @@ -1,6 +1,5 @@ import '../public/style/4nk.css'; import { initHeader } from './components/header/header'; -/*import { initChat } from '../src/pages/chat/chat';*/ import Database from './services/database.service'; import Services from './services/service'; import TokenService from './services/token'; @@ -14,11 +13,7 @@ import type { MerkleProofResult } from 'pkg/sdk_client'; const routes: { [key: string]: string } = { home: '/src/pages/home/home.html', - process: '/src/pages/process/process.html', - 'process-element': '/src/pages/process-element/process-element.html', account: '/src/pages/account/account.html', - chat: '/src/pages/chat/chat.html', - signature: '/src/pages/signature/signature.html', }; export let currentRoute = ''; @@ -53,7 +48,7 @@ async function handleLocation(path: string) { const accountComponent = document.createElement('login-4nk-component'); accountComponent.setAttribute('style', 'width: 100vw; height: 100vh; position: relative; grid-row: 2;'); if (container) container.appendChild(accountComponent); - } else if (path !== 'process') { + } else { const html = await fetch(routeHtml).then((data) => data.text()); content.innerHTML = html; } @@ -64,28 +59,6 @@ async function handleLocation(path: string) { // const modalService = await ModalService.getInstance() // modalService.injectValidationModal() switch (path) { - case 'process': - // const { init } = await import('./pages/process/process'); - //const { ProcessListComponent } = await import('./pages/process/process-list-component'); - - const container2 = document.querySelector('#containerId'); - const accountComponent = document.createElement('process-list-4nk-component'); - - //if (!customElements.get('process-list-4nk-component')) { - //customElements.define('process-list-4nk-component', ProcessListComponent); - //} - accountComponent.setAttribute('style', 'height: 100vh; position: relative; grid-row: 2; grid-column: 4;'); - if (container2) container2.appendChild(accountComponent); - break; - - case 'process-element': - if (parsedPath && parsedPath.length) { - const { initProcessElement } = await import('./pages/process-element/process-element'); - const parseProcess = parsedPath[1].split('_'); - initProcessElement(parseProcess[0], parseProcess[1]); - } - break; - case 'account': const { AccountComponent } = await import('./pages/account/account-component'); const accountContainer = document.querySelector('.parameter-list'); @@ -97,30 +70,6 @@ async function handleLocation(path: string) { accountContainer.appendChild(accountComponent); } break; - - /*case 'chat': - const { ChatComponent } = await import('./pages/chat/chat-component'); - const chatContainer = document.querySelector('.group-list'); - if (chatContainer) { - if (!customElements.get('chat-component')) { - customElements.define('chat-component', ChatComponent); - } - const chatComponent = document.createElement('chat-component'); - chatContainer.appendChild(chatComponent); - } - break;*/ - - case 'signature': - const { SignatureComponent } = await import('./pages/signature/signature-component'); - const container = document.querySelector('.group-list'); - if (container) { - if (!customElements.get('signature-component')) { - customElements.define('signature-component', SignatureComponent); - } - const signatureComponent = document.createElement('signature-component'); - container.appendChild(signatureComponent); - } - break; } } } @@ -130,7 +79,7 @@ window.onpopstate = async () => { if (!services.isPaired()) { handleLocation('home'); } else { - handleLocation('process'); + handleLocation('account'); } }; @@ -162,9 +111,9 @@ export async function init(): Promise { await services.restoreSecretsFromDB(); if (!isE2E) { - // We connect to all relays now - await services.connectAllRelays(); - await services.updateDeviceBlockHeight(); + // We connect to all relays now + await services.connectAllRelays(); + await services.updateDeviceBlockHeight(); } // We register all the event listeners if we run in an iframe @@ -172,11 +121,8 @@ export async function init(): Promise { await registerAllListeners(); } - if (services.isPaired()) { - await navigate('process'); - } else { - await navigate('home'); - } + // Si appairé, rester sur home/account uniquement + await navigate('home'); } catch (error) { console.error(error); await navigate('home'); @@ -214,23 +160,23 @@ export async function registerAllListeners() { window.parent.postMessage(acceptedMsg, event.origin); return; } else { - const modalService = await ModalService.getInstance(); - const result = await modalService.showConfirmationModal({ - title: 'Confirmation de liaison', - content: ` - - `, - confirmText: 'Ajouter un service', - cancelText: 'Annuler' - }, true); - if (!result) { - const errorMsg = 'Failed to pair device: User refused to link'; - errorResponse(errorMsg, event.origin, event.data.messageId); + const modalService = await ModalService.getInstance(); + const result = await modalService.showConfirmationModal({ + title: 'Confirmation de liaison', + content: ` + + `, + confirmText: 'Ajouter un service', + cancelText: 'Annuler' + }, true); + if (!result) { + const errorMsg = 'Failed to pair device: User refused to link'; + errorResponse(errorMsg, event.origin, event.data.messageId); } } @@ -445,11 +391,11 @@ export async function registerAllListeners() { const isValid = await tokenService.validateToken(accessToken, event.origin); window.parent.postMessage({ - type: MessageType.VALIDATE_TOKEN, + type: MessageType.VALIDATE_TOKEN, accessToken, refreshToken, isValid, - messageId: event.data.messageId + messageId: event.data.messageId }, event.origin); }; @@ -481,10 +427,10 @@ export async function registerAllListeners() { } window.parent.postMessage({ - type: MessageType.RENEW_TOKEN, - accessToken: newAccessToken, + type: MessageType.RENEW_TOKEN, + accessToken: newAccessToken, refreshToken, - messageId: event.data.messageId + messageId: event.data.messageId }, event.origin); } catch (error) { const errorMsg = `Failed to renew token: ${error}`; @@ -562,9 +508,9 @@ export async function registerAllListeners() { const res = { processId, process, processData }; window.parent.postMessage({ - type: MessageType.PROCESS_CREATED, - processCreated: res, - messageId: event.data.messageId + type: MessageType.PROCESS_CREATED, + processCreated: res, + messageId: event.data.messageId }, event.origin); } catch (e) { const errorMsg = `Failed to create process: ${e}`; @@ -980,3 +926,4 @@ document.addEventListener('navigate', ((e: Event) => { } } })); + diff --git a/src/utils/notification.store.ts b/src/utils/notification.store.ts index 88c5caf..64e3ce0 100755 --- a/src/utils/notification.store.ts +++ b/src/utils/notification.store.ts @@ -79,9 +79,6 @@ class NotificationStore { ${notif.time ? `
    ${notif.time}
    ` : ''} `; notifElement.onclick = () => { - if (notif.memberId) { - window.loadMemberChat(notif.memberId); - } this.removeNotification(index); }; board.appendChild(notifElement); diff --git a/test-results/.last-run.json b/test-results/.last-run.json new file mode 100644 index 0000000..d7404fa --- /dev/null +++ b/test-results/.last-run.json @@ -0,0 +1,8 @@ +{ + "status": "failed", + "failedTests": [ + "8441ffd23af3346276e7-56f9d5b0e8b6ed86465b", + "8441ffd23af3346276e7-f630137873bddb821e00", + "37e4c99d8313ab3db5d7-b8719acdf6b3b69064f7" + ] +} \ No newline at end of file diff --git a/test-results/channel-channel-REQUEST-LINK---LINK-ACCEPTED-chromium/error-context.md b/test-results/channel-channel-REQUEST-LINK---LINK-ACCEPTED-chromium/error-context.md new file mode 100644 index 0000000..5043d0a --- /dev/null +++ b/test-results/channel-channel-REQUEST-LINK---LINK-ACCEPTED-chromium/error-context.md @@ -0,0 +1,6 @@ +# Page snapshot + +```yaml +- iframe [ref=e3]: + +``` \ No newline at end of file diff --git a/test-results/channel-channel-VALIDATE-TOKEN-puis-RENEW-TOKEN-chromium/error-context.md b/test-results/channel-channel-VALIDATE-TOKEN-puis-RENEW-TOKEN-chromium/error-context.md new file mode 100644 index 0000000..5043d0a --- /dev/null +++ b/test-results/channel-channel-VALIDATE-TOKEN-puis-RENEW-TOKEN-chromium/error-context.md @@ -0,0 +1,6 @@ +# Page snapshot + +```yaml +- iframe [ref=e3]: + +``` \ No newline at end of file diff --git a/test-results/process-channel-CREATE-PRO-5e777-OCESS-CREATED-flux-minimal--chromium/error-context.md b/test-results/process-channel-CREATE-PRO-5e777-OCESS-CREATED-flux-minimal--chromium/error-context.md new file mode 100644 index 0000000..5043d0a --- /dev/null +++ b/test-results/process-channel-CREATE-PRO-5e777-OCESS-CREATED-flux-minimal--chromium/error-context.md @@ -0,0 +1,6 @@ +# Page snapshot + +```yaml +- iframe [ref=e3]: + +``` \ No newline at end of file