2025-02-28 16:44:23 +01:00

521 lines
20 KiB
TypeScript
Executable File

import { addSubscription } from '../../utils/subscription.utils';
import Services from '../../services/service';
import { getCorrectDOM } from '~/utils/html.utils';
import { Process } from 'pkg/sdk_client';
import chatStyle from '../../../public/style/chat.css?inline';
import { Database } from '../../services/database.service';
// Initialize function, create initial tokens with itens that are already selected by the user
export async function init() {
const container = getCorrectDOM('process-list-4nk-component') as HTMLElement;
const element = container.querySelector('select') as HTMLSelectElement;
// Create div that wroaps all the elements inside (select, elements selected, search div) to put select inside
const wrapper = document.createElement('div');
if (wrapper) addSubscription(wrapper, 'click', clickOnWrapper);
wrapper.classList.add('multi-select-component');
wrapper.classList.add('input-field');
// Create elements of search
const search_div = document.createElement('div');
search_div.classList.add('search-container');
const input = document.createElement('input');
input.classList.add('selected-input');
input.setAttribute('autocomplete', 'off');
input.setAttribute('tabindex', '0');
if (input) {
addSubscription(input, 'keyup', inputChange);
addSubscription(input, 'keydown', deletePressed);
addSubscription(input, 'click', openOptions);
}
const dropdown_icon = document.createElement('a');
dropdown_icon.classList.add('dropdown-icon');
if (dropdown_icon) addSubscription(dropdown_icon, 'click', clickDropdown);
const autocomplete_list = document.createElement('ul');
autocomplete_list.classList.add('autocomplete-list');
search_div.appendChild(input);
search_div.appendChild(autocomplete_list);
search_div.appendChild(dropdown_icon);
// set the wrapper as child (instead of the element)
element.parentNode?.replaceChild(wrapper, element);
// set element as child of wrapper
wrapper.appendChild(element);
wrapper.appendChild(search_div);
addPlaceholder(wrapper);
}
function removePlaceholder(wrapper: HTMLElement) {
const input_search = wrapper.querySelector('.selected-input');
input_search?.removeAttribute('placeholder');
}
function addPlaceholder(wrapper: HTMLElement) {
const input_search = wrapper.querySelector('.selected-input');
const tokens = wrapper.querySelectorAll('.selected-wrapper');
if (!tokens.length && !(document.activeElement === input_search)) input_search?.setAttribute('placeholder', '---------');
}
// Listener of user search
function inputChange(e: Event) {
const target = e.target as HTMLInputElement;
const wrapper = target?.parentNode?.parentNode;
const select = wrapper?.querySelector('select') as HTMLSelectElement;
const dropdown = wrapper?.querySelector('.dropdown-icon');
const input_val = target?.value;
if (input_val) {
dropdown?.classList.add('active');
populateAutocompleteList(select, input_val.trim());
} else {
dropdown?.classList.remove('active');
const event = new Event('click');
dropdown?.dispatchEvent(event);
}
}
// Listen for clicks on the wrapper, if click happens focus on the input
function clickOnWrapper(e: Event) {
const wrapper = e.target as HTMLElement;
if (wrapper.tagName == 'DIV') {
const input_search = wrapper.querySelector('.selected-input');
const dropdown = wrapper.querySelector('.dropdown-icon');
if (!dropdown?.classList.contains('active')) {
const event = new Event('click');
dropdown?.dispatchEvent(event);
}
(input_search as HTMLInputElement)?.focus();
removePlaceholder(wrapper);
}
}
function openOptions(e: Event) {
const input_search = e.target as HTMLElement;
const wrapper = input_search?.parentElement?.parentElement;
const dropdown = wrapper?.querySelector('.dropdown-icon');
if (!dropdown?.classList.contains('active')) {
const event = new Event('click');
dropdown?.dispatchEvent(event);
}
e.stopPropagation();
}
// Function that create a token inside of a wrapper with the given value
function createToken(wrapper: HTMLElement, value: any) {
const container = getCorrectDOM('process-list-4nk-component') as HTMLElement;
const search = wrapper.querySelector('.search-container');
const inputInderline = container.querySelector('.selected-processes');
// Create token wrapper
const token = document.createElement('div');
token.classList.add('selected-wrapper');
const token_span = document.createElement('span');
token_span.classList.add('selected-label');
token_span.innerText = value;
const close = document.createElement('a');
close.classList.add('selected-close');
close.setAttribute('tabindex', '-1');
close.setAttribute('data-option', value);
close.setAttribute('data-hits', '0');
close.innerText = 'x';
if (close) addSubscription(close, 'click', removeToken);
token.appendChild(token_span);
token.appendChild(close);
inputInderline?.appendChild(token);
}
// Listen for clicks in the dropdown option
function clickDropdown(e: Event) {
const dropdown = e.target as HTMLElement;
const wrapper = dropdown?.parentNode?.parentNode;
const input_search = wrapper?.querySelector('.selected-input') as HTMLInputElement;
const select = wrapper?.querySelector('select') as HTMLSelectElement;
dropdown.classList.toggle('active');
if (dropdown.classList.contains('active')) {
removePlaceholder(wrapper as HTMLElement);
input_search?.focus();
if (!input_search?.value) {
populateAutocompleteList(select, '', true);
} else {
populateAutocompleteList(select, input_search.value);
}
} else {
clearAutocompleteList(select);
addPlaceholder(wrapper as HTMLElement);
}
}
// Clears the results of the autocomplete list
function clearAutocompleteList(select: HTMLSelectElement) {
const wrapper = select.parentNode;
const autocomplete_list = wrapper?.querySelector('.autocomplete-list');
if (autocomplete_list) autocomplete_list.innerHTML = '';
}
async function populateAutocompleteList(select: HTMLSelectElement, query: string, dropdown = false) {
const { autocomplete_options } = getOptions(select);
let options_to_show = [];
const service = await Services.getInstance();
const mineArray: string[] = await service.getMyProcesses();
const allProcesses = await service.getProcesses();
const allArray: string[] = Object.keys(allProcesses).filter(x => !mineArray.includes(x));
const wrapper = select.parentNode;
const input_search = wrapper?.querySelector('.search-container');
const autocomplete_list = wrapper?.querySelector('.autocomplete-list');
if (autocomplete_list) autocomplete_list.innerHTML = '';
const addProcessToList = (processId:string, isMine: boolean) => {
const li = document.createElement('li');
li.innerText = processId;
li.setAttribute("data-value", processId);
if (isMine) {
li.classList.add("my-process");
li.style.cssText = `color: var(--accent-color)`;
}
if (li) addSubscription(li, 'click', selectOption);
autocomplete_list?.appendChild(li);
};
mineArray.forEach(processId => addProcessToList(processId, true));
allArray.forEach(processId => addProcessToList(processId, false));
if (mineArray.length === 0 && allArray.length === 0) {
const li = document.createElement('li');
li.classList.add('not-cursor');
li.innerText = 'No options found';
autocomplete_list?.appendChild(li);
}
}
// Listener to autocomplete results when clicked set the selected property in the select option
function selectOption(e: any) {
console.log('🎯 Click event:', e);
console.log('🎯 Target value:', e.target.dataset.value);
const wrapper = e.target.parentNode.parentNode.parentNode;
const select = wrapper.querySelector('select');
const input_search = wrapper.querySelector('.selected-input');
const option = wrapper.querySelector(`select option[value="${e.target.dataset.value}"]`);
console.log('🎯 Selected option:', option);
console.log('🎯 Process ID:', option?.getAttribute('data-process-id'));
if (e.target.dataset.value.includes('messaging')) {
const messagingNumber = parseInt(e.target.dataset.value.split(' ')[1]);
const processId = select.getAttribute(`data-messaging-id-${messagingNumber}`);
console.log('🚀 Dispatching newMessagingProcess event:', {
processId,
processName: `Messaging Process ${processId}`
});
// Dispatch l'événement avant la navigation
document.dispatchEvent(new CustomEvent('newMessagingProcess', {
detail: {
processId: processId,
processName: `Messaging Process ${processId}`
}
}));
// Navigation vers le chat
const navigateEvent = new CustomEvent('navigate', {
detail: {
page: 'chat',
processId: processId || ''
}
});
document.dispatchEvent(navigateEvent);
return;
}
option.setAttribute('selected', '');
createToken(wrapper, e.target.dataset.value);
if (input_search.value) {
input_search.value = '';
}
showSelectedProcess(e.target.dataset.value);
input_search.focus();
e.target.remove();
const autocomplete_list = wrapper.querySelector('.autocomplete-list');
if (!autocomplete_list.children.length) {
const li = document.createElement('li');
li.classList.add('not-cursor');
li.innerText = 'No options found';
autocomplete_list.appendChild(li);
}
const event = new Event('keyup');
input_search.dispatchEvent(event);
e.stopPropagation();
}
// function that returns a list with the autcomplete list of matches
function autocomplete(query: string, options: any) {
// No query passed, just return entire list
if (!query) {
return options;
}
let options_return = [];
for (let i = 0; i < options.length; i++) {
if (query.toLowerCase() === options[i].slice(0, query.length).toLowerCase()) {
options_return.push(options[i]);
}
}
return options_return;
}
// Returns the options that are selected by the user and the ones that are not
function getOptions(select: HTMLSelectElement) {
// Select all the options available
const all_options = Array.from(select.querySelectorAll('option')).map((el) => el.value);
// Get the options that are selected from the user
const options_selected = Array.from(select.querySelectorAll('option:checked')).map((el: any) => el.value);
// Create an autocomplete options array with the options that are not selected by the user
const autocomplete_options: any[] = [];
all_options.forEach((option) => {
if (!options_selected.includes(option)) {
autocomplete_options.push(option);
}
});
autocomplete_options.sort();
return {
options_selected,
autocomplete_options,
};
}
// Listener for when the user wants to remove a given token.
function removeToken(e: Event) {
// Get the value to remove
const target = e.target as HTMLSelectElement;
const value_to_remove = target.dataset.option;
const wrapper = target.parentNode?.parentNode?.parentNode;
const input_search = wrapper?.querySelector('.selected-input');
const dropdown = wrapper?.querySelector('.dropdown-icon');
// Get the options in the select to be unselected
const option_to_unselect = wrapper?.querySelector(`select option[value="${value_to_remove}"]`);
option_to_unselect?.removeAttribute('selected');
// Remove token attribute
(target.parentNode as any)?.remove();
dropdown?.classList.remove('active');
const container = getCorrectDOM('process-list-4nk-component') as HTMLElement;
const process = container.querySelector('#' + target.dataset.option);
process?.remove();
}
// Listen for 2 sequence of hits on the delete key, if this happens delete the last token if exist
function deletePressed(e: Event) {
const input_search = e.target as HTMLInputElement;
const wrapper = input_search?.parentNode?.parentNode;
const key = (e as KeyboardEvent).keyCode || (e as KeyboardEvent).charCode;
const tokens = wrapper?.querySelectorAll('.selected-wrapper');
if (tokens?.length) {
const last_token_x = tokens[tokens.length - 1].querySelector('a');
let hits = +(last_token_x?.dataset?.hits || 0);
if (key == 8 || key == 46) {
if (!input_search.value) {
if (hits > 1) {
// Trigger delete event
const event = new Event('click');
last_token_x?.dispatchEvent(event);
} else {
if (last_token_x?.dataset.hits) last_token_x.dataset.hits = '2';
}
}
} else {
if (last_token_x?.dataset.hits) last_token_x.dataset.hits = '0';
}
}
return true;
}
// Dismiss on outside click
addSubscription(document, 'click', () => {
// get select that has the options available
const select = document.querySelectorAll('[data-multi-select-plugin]');
for (let i = 0; i < select.length; i++) {
if (event) {
var isClickInside = select[i].parentElement?.parentElement?.contains(event.target as Node);
if (!isClickInside) {
const wrapper = select[i].parentElement?.parentElement;
const dropdown = wrapper?.querySelector('.dropdown-icon');
const autocomplete_list = wrapper?.querySelector('.autocomplete-list');
//the click was outside the specifiedElement, do something
dropdown?.classList.remove('active');
if (autocomplete_list) autocomplete_list.innerHTML = '';
addPlaceholder(wrapper as HTMLElement);
}
}
}
});
async function showSelectedProcess(elem: MouseEvent) {
const container = getCorrectDOM('process-list-4nk-component') as HTMLElement;
if (elem) {
const cardContent = container.querySelector('.process-card-content');
const processes = await getProcesses();
const process = processes.find((process: any) => process[1].title === elem);
if (process) {
const processDiv = document.createElement('div');
processDiv.className = 'process';
processDiv.id = process[0];
const titleDiv = document.createElement('div');
titleDiv.className = 'process-title';
titleDiv.innerHTML = `${process[1].title} : ${process[1].description}`;
processDiv.appendChild(titleDiv);
for (const zone of process.zones) {
const zoneElement = document.createElement('div');
zoneElement.className = 'process-element';
const zoneId = process[1].title + '-' + zone.id;
zoneElement.setAttribute('zone-id', zoneId);
zoneElement.setAttribute('process-title', process[1].title);
zoneElement.setAttribute('process-id', `${process[0]}_${zone.id}`);
zoneElement.innerHTML = `${zone.title}: ${zone.description}`;
addSubscription(zoneElement, 'click', select);
processDiv.appendChild(zoneElement);
}
if (cardContent) cardContent.appendChild(processDiv);
}
}
}
function select(event: Event) {
const container = getCorrectDOM('process-list-4nk-component') as HTMLElement;
const target = event.target as HTMLElement;
const oldSelectedProcess = container.querySelector('.selected-process-zone');
oldSelectedProcess?.classList.remove('selected-process-zone');
if (target) {
target.classList.add('selected-process-zone');
}
const name = target.getAttribute('zone-id');
console.log('🚀 ~ select ~ name:', name);
}
function goToProcessPage() {
const container = getCorrectDOM('process-list-4nk-component') as HTMLElement;
const target = container.querySelector('.selected-process-zone');
console.log('🚀 ~ goToProcessPage ~ event:', target);
if (target) {
const process = target?.getAttribute('process-id');
console.log('=======================> going to process page', process);
// navigate('process-element/' + process);
document.querySelector('process-list-4nk-component')?.dispatchEvent(
new CustomEvent('processSelected', {
detail: {
process: process,
},
}),
);
}
}
(window as any).goToProcessPage = goToProcessPage;
async function createMessagingProcess(): Promise<void> {
console.log('Creating messaging process');
const service = await Services.getInstance();
const otherMembers = [
{
sp_addresses: [
"tsp1qqd7snxfh44am8f7a3x36znkh4v0dcagcgakfux488ghsg0tny7degq4gd9q4n4us0cyp82643f2p4jgcmtwknadqwl3waf9zrynl6n7lug5tg73a",
"tsp1qqvd8pak9fyz55rxqj90wxazqzwupf2egderc96cn84h3l84z8an9vql85scudrmwvsnltfuy9ungg7pxnhys2ft5wnf2gyr3n4ukvezygswesjuc"
]
},
{
sp_addresses: [
"tsp1qqgl5vawdey6wnnn2sfydcejsr06uzwsjlfa6p6yr8u4mkqwezsnvyqlazuqmxhxd8crk5eq3wfvdwv4k3tn68mkj2nj72jj39d2ngauu4unfx0q7",
"tsp1qqthmj56gj8vvkjzwhcmswftlrf6ye7ukpks2wra92jkehqzrvx7m2q570q5vv6zj6dnxvussx2h8arvrcfwz9sp5hpdzrfugmmzz90pmnganxk28"
]
},
{
sp_addresses: [
"tsp1qqwjtxr9jye7d40qxrsmd6h02egdwel6mfnujxzskgxvxphfya4e6qqjq4tsdmfdmtnmccz08ut24q8y58qqh4lwl3w8pvh86shlmavrt0u3smhv2",
"tsp1qqwn7tf8q2jhmfh8757xze53vg2zc6x5u6f26h3wyty9mklvcy0wnvqhhr4zppm5uyyte4y86kljvh8r0tsmkmszqqwa3ecf2lxcs7q07d56p8sz5"
]
}
];
await service.checkConnections(otherMembers);
const relayAddress = service.getAllRelays().pop();
if (!relayAddress) {
throw new Error('Empty relay address list');
}
const feeRate = 1;
setTimeout(async () => {
const createProcessReturn = await service.createMessagingProcess(otherMembers, relayAddress.spAddress, feeRate);
const updatedProcess = createProcessReturn.updated_process.current_process;
if (!updatedProcess) {
console.error('Failed to retrieved new messaging process');
return;
}
const processId = updatedProcess.states[0].commited_in;
const stateId = updatedProcess.states[0].state_id;
await service.handleApiReturn(createProcessReturn);
const createPrdReturn = await service.createPrdUpdate(processId, stateId);
await service.handleApiReturn(createPrdReturn);
const approveChangeReturn = service.approveChange(processId, stateId);
await service.handleApiReturn(approveChangeReturn);
}, 500)
}
async function getDescription(processId: string, process: Process): Promise<string | null> {
const service = await Services.getInstance();
// Get the `commited_in` value of the last state and remove it from the array
const currentCommitedIn = process.states.pop()?.commited_in;
if (currentCommitedIn === undefined) {
return null; // No states available
}
// Find the last state where `commited_in` is different
let lastDifferentState = process.states.findLast(
state => state.commited_in !== currentCommitedIn
);
if (!lastDifferentState) {
// It means that we only have one state that is not commited yet, that can happen with process we just created
// let's assume that the right description is in the last concurrent state and not handle the (arguably rare) case where we have multiple concurrent states on a creation
lastDifferentState = process.states.pop();
}
// Take the description out of the state, if any
const description = lastDifferentState!.pcd_commitment['description'];
if (description) {
const userDiff = await service.getDiffByValue(description);
if (userDiff) {
console.log("Successfully retrieved userDiff:", userDiff);
return userDiff.new_value;
} else {
console.log("Failed to retrieve a non-null userDiff.");
}
}
return null;
}