You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

681 lines
27 KiB

// tabs.js
let tabCount = 0;
let activeTabId = null;
let tabNodes = {}; // Almacenar los nodos por pestaña
let tabConnections = {}; // Almacenar las conexiones por pestaña
document.addEventListener('DOMContentLoaded', () => {
loadExistingTabs();
document.getElementById("createTabButton").addEventListener("click", () => {
const tabName = prompt('Ingrese el nombre para la nueva pestaña:', `Pestaña ${tabCount + 1}`);
if (tabName) {
createNewTab(tabName);
}
});
});
// Función para obtener el CSRF token
function getCSRFToken() {
const name = 'csrftoken';
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
// Función para crear el menú contextual
function createContextMenu(x, y, options) {
// Eliminar cualquier menú existente
removeContextMenu();
const menu = document.createElement('div');
menu.classList.add('context-menu');
menu.style.top = `${y}px`;
menu.style.left = `${x}px`;
options.forEach(option => {
const item = document.createElement('div');
item.classList.add('context-menu-item');
item.textContent = option.label;
item.addEventListener('click', option.action);
menu.appendChild(item);
});
document.body.appendChild(menu);
menu.classList.add('show');
// Cerrar el menú al hacer clic en cualquier parte
document.addEventListener('click', removeContextMenu);
}
// Función para eliminar el menú contextual
function removeContextMenu() {
const existingMenu = document.querySelector('.context-menu');
if (existingMenu) {
existingMenu.remove();
document.removeEventListener('click', removeContextMenu);
}
}
async function loadExistingTabs() {
try {
const response = await fetch('/list-tabs/');
if (response.ok) {
const data = await response.json();
let maxTabId = 0;
for (const tab of data.tabs) {
await createNewTab(tab.name, tab.id);
maxTabId = Math.max(maxTabId, parseInt(tab.id));
}
tabCount = maxTabId; // Establecer tabCount al ID máximo existente
} else {
console.error('Error al cargar las pestañas existentes');
}
} catch (error) {
console.error('Error de red al cargar las pestañas existentes:', error);
}
}
async function createNewTab(tabName, existingTabId = null) {
let tabId;
if (existingTabId) {
// Usar el ID existente sin incrementar tabCount
tabId = existingTabId;
} else {
// Incrementar tabCount y asignarlo como nuevo ID
tabCount++;
tabId = tabCount;
}
const tabList = document.getElementById("tabsList");
const tabContent = document.getElementById("tabContent");
const newTab = document.createElement("li");
newTab.textContent = tabName;
newTab.id = `tab${tabId}`;
newTab.classList.add("tab");
newTab.setAttribute('data-tab-name', tabName); // Guardar el nombre de la pestaña
newTab.addEventListener("click", () => activateTab(newTab));
newTab.addEventListener("dblclick", () => renameTab(newTab)); // Permitir renombrar
// Agregar evento de clic derecho para mostrar el menú contextual
newTab.addEventListener('contextmenu', (e) => {
e.preventDefault();
createContextMenu(e.pageX, e.pageY, [
{
label: 'Eliminar Pestaña',
action: () => deleteTab(newTab)
}
]);
});
const newContent = document.createElement("div");
newContent.id = `content${tabId}`;
newContent.classList.add("tabContent");
newContent.style.display = "none";
// Crear el sidebar y el canvas dentro de la pestaña
newContent.innerHTML = `
<div class="tabNodes" id="tabNodes${tabId}">
<div class="container_canvas">
<canvas id="canvas${tabId}" width="800" height="600"></canvas>
<div class="content" id="nodeContainer${tabId}">
<!-- Aquí se agregarán los nodos -->
</div>
</div>
<div class="right_sidebar">
<div class="node" data-node-type="node1">DID o Extensión</div>
<div class="node" data-node-type="node2">Audio</div>
<div class="node" data-node-type="node3">Prompt y Condiciones</div>
<div class="node" data-node-type="node4">Transferencia llamada</div>
<button class="btnGuardar" onclick="saveTabData(${tabId})">Guardar</button>
</div>
</div>`;
tabList.appendChild(newTab);
tabContent.appendChild(newContent);
tabNodes[tabId] = [];
tabConnections[tabId] = [];
const rightSidebar = newContent.querySelector('.right_sidebar');
const nodeElements = rightSidebar.querySelectorAll('.node');
nodeElements.forEach(nodeEl => {
nodeEl.addEventListener("click", (e) => {
createNode(e, tabId);
});
});
// Activar la pestaña recién creada
activateTab(newTab);
// Esperar a que el DOM se actualice
await new Promise(resolve => requestAnimationFrame(resolve));
initializeCanvas(tabId);
// Solo llamar loadTabData si es una pestaña existente (ya guardada en el servidor)
if (existingTabId) {
await loadTabData(tabId);
}
}
function activateTab(selectedTab) {
const tabs = document.querySelectorAll("#tabsList li.tab");
const contents = document.querySelectorAll(".tabContent");
tabs.forEach(tab => {
tab.classList.remove("active");
});
contents.forEach(content => {
content.style.display = "none";
});
selectedTab.classList.add("active");
document.getElementById(`content${selectedTab.id.slice(3)}`).style.display = "block";
activeTabId = selectedTab.id.slice(3);
// Esperamos a que el DOM se actualice antes de inicializar el canvas y actualizar conexiones
requestAnimationFrame(() => {
initializeCanvas(activeTabId);
updateConnectionPoints(activeTabId);
updateConnections(activeTabId);
});
}
async function renameTab(tabElement) {
const oldName = tabElement.getAttribute('data-tab-name');
const newName = prompt('Ingrese el nuevo nombre para la pestaña:', oldName);
if (newName && newName !== oldName) {
const tabId = tabElement.id.slice(3); // Asumiendo que el ID es 'tab<id>'
try {
const response = await fetch('/rename_tab/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCSRFToken(),
},
body: JSON.stringify({
tabId: tabId,
newTabName: newName
})
});
const result = await response.json();
if (result.status === 'success') {
tabElement.textContent = newName;
tabElement.setAttribute('data-tab-name', newName);
alert("Pestaña renombrada exitosamente.");
} else {
alert(`Error al renombrar la pestaña: ${result.message}`);
}
} catch (error) {
console.error('Error al renombrar la pestaña:', error);
alert('Ocurrió un error al renombrar la pestaña.');
}
}
}
// Función para eliminar una pestaña
async function deleteTab(tabElement) {
const confirmDelete = confirm("¿Estás seguro de que deseas eliminar esta pestaña y todos sus contenidos?");
if (!confirmDelete) return;
const tabId = tabElement.id.slice(3); // Asumiendo que el ID es 'tab<id>'
const tabName = tabElement.getAttribute('data-tab-name');
try {
const response = await fetch('/delete_tab/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCSRFToken(),
},
body: JSON.stringify({
tabId: tabId,
tabName: tabName
})
});
const result = await response.json();
if (result.status === 'success') {
// Eliminar la pestaña y su contenido del DOM
const contentElement = document.getElementById(`content${tabId}`);
tabElement.remove();
contentElement.remove();
// Eliminar datos de conexiones y nodos almacenados en JS
delete window.tabConnections[tabId];
delete tabNodes[tabId];
alert("Pestaña eliminada exitosamente.");
} else {
alert(`Error: ${result.message}`);
}
} catch (error) {
console.error('Error al eliminar la pestaña:', error);
alert('Ocurrió un error al eliminar la pestaña.');
}
}
async function saveTabData(tabId) {
const tabElement = document.getElementById(`tab${tabId}`);
const tabName = tabElement.getAttribute('data-tab-name') || `Pestaña ${tabId}`;
// Recopilar datos de los nodos
const nodes = Array.from(document.querySelectorAll(`#nodeContainer${tabId} .slice_node`)).map(node => {
const nodeType = node.getAttribute('data-node-type');
if (nodeType === 'node1') {
return {
id: node.id,
title: node.querySelector('.node-title').textContent,
extensionNumber: node.extensionNumber || "",
position: {
top: node.style.top,
left: node.style.left
},
nodeType: nodeType,
};
} else if (nodeType === 'node2') {
return {
id: node.id,
title: node.querySelector('.node-title').textContent,
audioFile: node.audioFile || "",
position: {
top: node.style.top,
left: node.style.left
},
nodeType: nodeType,
};
} else if (nodeType === 'node3') {
return {
id: node.id,
title: node.querySelector('.node-title').textContent,
content: node.content || "",
position: {
top: node.style.top,
left: node.style.left
},
nodeType: nodeType,
textFile: node.textFile || "",
conditions: node.conditions || [] // Incluir condiciones dinámicas
};
} else if (nodeType === 'node4') {
return {
id: node.id,
title: node.querySelector('.node-title').textContent,
position: {
top: node.style.top,
left: node.style.left
},
nodeType: nodeType,
extensionNumber: node.extensionNumber || "" // Incluido extensionNumber
};
} else {
return {
id: node.id,
title: node.querySelector('.node-title').textContent,
extensionNumber: node.extensionNumber || "",
content: node.content || "",
position: {
top: node.style.top,
left: node.style.left
},
nodeType: nodeType,
audioFiles: node.audioFile ? [node.audioFile] : [],
textFile: node.textFile ? extractFileName(node.textFile) : ""
};
}
});
// Recolectar conexiones
const connections = window.tabConnections[tabId].map(conn => {
return {
startNodeId: conn.startPoint.from.id,
startPortClass: conn.startPoint.portClass,
endNodeId: conn.endPoint.to.id,
endPortClass: conn.endPoint.portClass,
isActive: conn.isActive || false // Incluir isActive en las conexiones
};
});
const data = {
tabId,
tabName,
nodes,
connections
};
try {
// Primero, enviar cada nodo3 a save_tts para generar los archivos de texto
for (let node of nodes) {
// Solo enviar nodos3 que tengan contenido y no hayan sido guardados previamente
if (node.nodeType === 'node3' && node.content && node.content.trim() !== "" && (!node.textFile)) {
const response = await fetch('/save_tts/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCSRFToken(),
},
body: JSON.stringify({
tabId: tabId,
nodeId: node.id, // Incluir nodeId
nodeType: node.nodeType,
title: node.title,
content: node.content
})
});
const result = await response.json();
if (result.status === 'success') {
// Actualizar el nodo con las URLs de los archivos
node.textFile = result.textFile;
} else if (result.status !== 'skipped') {
console.error(`Error al generar archivos para el nodo ${node.id}: ${result.message}`);
alert(`Error al generar archivos para el nodo "${node.title}": ${result.message}`);
// Continuar con otros nodos en lugar de detenerse
}
}
}
// Luego, enviar los datos completos de la pestaña a save_tab_data
const response = await fetch('/save-tab-data/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCSRFToken(),
},
body: JSON.stringify(data)
});
if (response.ok) {
const result = await response.json();
alert(result.message);
} else {
const errorData = await response.json();
console.error('Error al guardar los datos:', errorData.error);
alert(`Error al guardar los datos: ${errorData.error}`);
}
} catch (error) {
console.error('Error de red o servidor:', error);
alert('Error de red o servidor. Por favor, intenta nuevamente.');
}
}
async function loadTabData(tabId, tabName = null) {
try {
const response = await fetch(`/load-tab-data/${tabId}/`);
if (response.ok) {
const data = await response.json();
const nodesData = Array.isArray(data.nodes) ? data.nodes : [];
const connectionsData = Array.isArray(data.connections) ? data.connections : [];
// Utilizar el tabName proporcionado si data.tabName no está disponible
const effectiveTabName = data.tabName || tabName || `Pestaña ${tabId}`;
// Actualizar el nombre de la pestaña
const tabElement = document.getElementById(`tab${tabId}`);
if (tabElement) {
tabElement.textContent = effectiveTabName;
tabElement.setAttribute('data-tab-name', effectiveTabName);
}
const contentElement = document.getElementById(`nodeContainer${tabId}`);
if (contentElement) {
contentElement.innerHTML = '';
}
const nodeMap = {};
nodesData.forEach(nodeData => {
const node = document.createElement('div');
node.id = nodeData.id || `node${Date.now()}`;
node.className = 'slice_node';
node.style.position = 'absolute';
node.style.top = nodeData.position.top;
node.style.left = nodeData.position.left;
node.setAttribute('data-node-type', nodeData.nodeType);
// Asignar las propiedades según el tipo de nodo
if (nodeData.nodeType === 'node1') {
node.extensionNumber = nodeData.extensionNumber || "";
} else if (nodeData.nodeType === 'node3') {
node.textFile = nodeData.textFile || "";
node.content = nodeData.content || "";
node.conditions = nodeData.conditions || []; // Cargar condiciones
} else if (nodeData.nodeType === 'node4') {
node.extensionNumber = nodeData.extensionNumber || "";
} else {
node.audioFile = nodeData.audioFile || "";
node.textFile = nodeData.textFile || "";
node.content = nodeData.content || "";
}
// Crear elemento para el título
const titleElement = document.createElement('div');
titleElement.className = 'node-title';
titleElement.textContent = nodeData.title;
node.appendChild(titleElement);
// Crear elemento para el nombre del archivo .txt (solo para node3)
if (nodeData.nodeType === 'node3') {
const txtFileElement = document.createElement('div');
txtFileElement.className = 'txt-file';
txtFileElement.style = 'display: none;'
txtFileElement.textContent = nodeData.textFile ? extractFileName(nodeData.textFile) : "No guardado";
node.appendChild(txtFileElement);
}
// Crear elemento para el ícono de reproducción (Node2 y Node3)
if (nodeData.nodeType === "node2") { // Solo para node2
const playIcon = document.createElement('span');
playIcon.className = 'play-icon';
playIcon.innerHTML = '&#9654;'; // Unicode para el triángulo de reproducción
playIcon.title = "Reproducir Audio";
playIcon.style.cursor = 'pointer';
playIcon.style.marginLeft = '10px';
playIcon.style.color = 'white';
playIcon.style.fontSize = '16px';
playIcon.style.verticalAlign = 'middle';
playIcon.onclick = function() {
if (node.audioFile) {
let audio = new Audio(node.audioFile);
audio.play();
} else {
alert("No hay audio asociado a este nodo.");
}
};
node.appendChild(playIcon);
}
// Crear elemento para node4
if (nodeData.nodeType === 'node4') {
const displayElement = document.createElement('div');
displayElement.className = 'display-extension';
displayElement.textContent = nodeData.extensionNumber || "No recibido"; // Mostrar el número de extensión
displayElement.style = "display: none;";
node.appendChild(displayElement);
}
// Crear y agregar puertos según el tipo de nodo
if (nodeData.nodeType === "node1") {
const portOut = document.createElement('div');
portOut.className = "port port-out";
node.appendChild(portOut);
node.style.background = "#92a5b9";
} else if (nodeData.nodeType === "node2") {
const portIn = document.createElement('div');
portIn.className = "port port-in";
node.appendChild(portIn);
const portOut = document.createElement('div');
portOut.className = "port port-out";
node.appendChild(portOut);
node.style.background = "#00BFA5";
} else if (nodeData.nodeType === "node3") {
const portIn1 = document.createElement("div");
portIn1.className = "port port-in";
node.appendChild(portIn1);
const portIn2 = document.createElement("div");
portIn2.className = "port port-in";
node.appendChild(portIn2);
// Añadir puertos de salida para node3
const portOut1 = document.createElement("div");
portOut1.className = "port port-out";
node.appendChild(portOut1);
const portOut2 = document.createElement("div");
portOut2.className = "port port-out";
node.appendChild(portOut2);
node.style.background = "#DDDDDD";
} else if (nodeData.nodeType === "node4") {
const portIn1 = document.createElement("div");
portIn1.className = "port port-in";
node.appendChild(portIn1);
const portIn2 = document.createElement("div");
portIn2.className = "port port-in";
node.appendChild(portIn2);
node.style.background = "#1DE9B6"; // Color actualizado
}
node.addEventListener('mousedown', (event) => {
if (!event.target.classList.contains('port') && !event.target.classList.contains('play-icon')) {
mouseDown(event, node, tabId);
}
});
// Agregar evento de doble clic para abrir el modal
node.addEventListener('dblclick', (e) => openModal(e, node));
// Agregar eventos a los puertos
const ports = node.querySelectorAll('.port');
ports.forEach(port => {
port.addEventListener('mousedown', (e) => {
startConnection(e, tabId);
});
});
// Añadir evento de clic derecho para mostrar el menú contextual
node.addEventListener('contextmenu', (e) => {
e.preventDefault();
createContextMenu(e.pageX, e.pageY, [
{
label: 'Eliminar Nodo',
action: () => deleteNode(node, tabId)
}
]);
});
if (contentElement) {
contentElement.appendChild(node);
}
nodeMap[node.id] = node;
});
window.tabConnections[tabId] = [];
// Esperamos a que el DOM se actualice antes de calcular las posiciones
await new Promise(resolve => requestAnimationFrame(resolve));
connectionsData.forEach(connData => {
const startNode = nodeMap[connData.startNodeId];
const endNode = nodeMap[connData.endNodeId];
if (!startNode || !endNode) {
console.warn('No se encontró el nodo para la conexión:', connData);
return;
}
const startPort = startNode.querySelector(connData.startPortClass === 'out' ? '.port-out' : '.port-in');
const endPort = endNode.querySelector(connData.endPortClass === 'in' ? '.port-in' : '.port-out');
if (!startPort || !endPort) {
console.warn('No se encontró el puerto para la conexión:', connData);
return;
}
const canvasRect = document.getElementById(`canvas${tabId}`).getBoundingClientRect();
const startPortRect = startPort.getBoundingClientRect();
const endPortRect = endPort.getBoundingClientRect();
const startPoint = {
x: startPortRect.left + startPort.clientWidth / 2 - canvasRect.left,
y: startPortRect.top + startPort.clientHeight / 2 - canvasRect.top,
from: startNode,
portClass: connData.startPortClass,
};
const endPoint = {
x: endPortRect.left + endPort.clientWidth / 2 - canvasRect.left,
y: endPortRect.top + endPort.clientHeight / 2 - canvasRect.top,
to: endNode,
portClass: connData.endPortClass,
};
window.tabConnections[tabId].push({ startPoint, endPoint, isActive: connData.isActive });
});
updateConnectionPoints(tabId);
updateConnections(tabId);
// Actualizar los números de extensión para node4 basado en las conexiones activas
nodesData.forEach(nodeData => {
if (nodeData.nodeType === 'node4') {
window.tabConnections[tabId].forEach(conn => {
if (conn.endPoint.to.id === nodeData.id && nodeData.nodeType === 'node4' && conn.isActive) {
const startNode = conn.startPoint.from;
const startNodeType = startNode.getAttribute('data-node-type');
if (startNodeType === 'node3') {
const node3 = startNode;
nodeData.extensionNumber = node3.content || "";
const displayElement = nodeMap[nodeData.id]?.querySelector('.display-extension');
if (displayElement) {
displayElement.textContent = nodeData.extensionNumber;
}
}
}
});
}
});
} else {
console.error('Error al cargar los datos de la pestaña:', response);
}
} catch (error) {
console.error('Error al cargar los datos de la pestaña:', error);
}
}
// Atajo para guardar con Ctrl+S
document.addEventListener('keydown', function(event) {
if (event.ctrlKey && event.key === "s") {
event.preventDefault();
if (activeTabId !== null) {
saveTabData(activeTabId);
} else {
alert("No hay pestaña activa");
}
}
});
// Exportar funciones necesarias para conexiones.js
window.initializeCanvas = initializeCanvas;
window.updateConnectionPoints = updateConnectionPoints;
window.updateConnections = updateConnections;
window.startConnection = startConnection;
window.deleteTab = deleteTab; // Exportar deleteTab para que pueda ser llamado desde el menú contextual