Actualización del sistema de gestión de ecuaciones en el panel LaTeX, implementando un nuevo manejador de MathJax para mejorar la carga de ecuaciones. Se añade un menú para seleccionar el tamaño de fuente y se optimiza la visibilidad del panel LaTeX con un nuevo diseño de splitter. Se mejora la gestión de la configuración de tamaño de fuente y se implementa la funcionalidad para guardar el HTML del panel LaTeX para diagnóstico.
This commit is contained in:
parent
01651fc454
commit
8c84eb4195
|
@ -1,11 +1,16 @@
|
||||||
|
|
||||||
salario=horas*tarifa
|
salario=horas*tarifa
|
||||||
salario=800
|
#salario=8000
|
||||||
tarifa=36
|
#tarifa=36
|
||||||
|
|
||||||
horas=?
|
horas=?
|
||||||
|
|
||||||
|
|
||||||
|
plot(sin(x),(x,0,1))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,8 @@ __pycache__/
|
||||||
|
|
||||||
*.json
|
*.json
|
||||||
|
|
||||||
|
.mathjax/
|
||||||
|
|
||||||
# Distribution / packaging
|
# Distribution / packaging
|
||||||
.Python
|
.Python
|
||||||
build/
|
build/
|
||||||
|
|
|
@ -332,3 +332,10 @@ class EvaluationManager:
|
||||||
"""Programa una evaluación con debounce"""
|
"""Programa una evaluación con debounce"""
|
||||||
self._debounce_timer.stop()
|
self._debounce_timer.stop()
|
||||||
self._debounce_timer.start(300) # 300ms de debounce
|
self._debounce_timer.start(300) # 300ms de debounce
|
||||||
|
|
||||||
|
def handle_output_link_click(self, link_id: str, result_object):
|
||||||
|
"""Maneja clicks en links del output delegando al interactive manager"""
|
||||||
|
if self.main_window.interactive_manager:
|
||||||
|
self.main_window.interactive_manager.handle_interactive_click(result_object)
|
||||||
|
else:
|
||||||
|
self.logger.warning("Interactive manager no disponible para manejar click")
|
306
app/gui_latex.py
306
app/gui_latex.py
|
@ -10,6 +10,7 @@ from PySide6.QtWebEngineWidgets import QWebEngineView
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Dict, Optional
|
from typing import List, Dict, Optional
|
||||||
from .evaluation import EvaluationResult
|
from .evaluation import EvaluationResult
|
||||||
|
from .mathjax_manager import MathJaxManager
|
||||||
import sympy
|
import sympy
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,6 +25,12 @@ class LatexPanel(QWidget):
|
||||||
self._pending_equations = []
|
self._pending_equations = []
|
||||||
self._parent_calculator = parent
|
self._parent_calculator = parent
|
||||||
self._webview_initialized = False
|
self._webview_initialized = False
|
||||||
|
self._mathjax_timeout_count = 0
|
||||||
|
self._max_mathjax_timeout = 20 # 10 segundos (20 * 500ms)
|
||||||
|
|
||||||
|
# Manager de MathJax local
|
||||||
|
self.mathjax_manager = MathJaxManager()
|
||||||
|
|
||||||
self._setup_ui()
|
self._setup_ui()
|
||||||
|
|
||||||
# Timer para verificar si MathJax está listo (inicializado bajo demanda)
|
# Timer para verificar si MathJax está listo (inicializado bajo demanda)
|
||||||
|
@ -107,93 +114,131 @@ class LatexPanel(QWidget):
|
||||||
""")
|
""")
|
||||||
|
|
||||||
def _generate_mathjax_html(self):
|
def _generate_mathjax_html(self):
|
||||||
"""Genera HTML base con MathJax configurado para SVG"""
|
"""Genera HTML base con MathJax configurado para SVG (local o CDN)"""
|
||||||
return """
|
# Obtener URL de MathJax (local si está disponible, CDN como fallback)
|
||||||
<!DOCTYPE html>
|
mathjax_url = self.mathjax_manager.get_mathjax_url()
|
||||||
|
|
||||||
|
return f"""<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
|
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
|
||||||
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"></script>
|
|
||||||
<script>
|
<script>
|
||||||
window.MathJax = {
|
// Intentar cargar MathJax local primero, luego CDN como fallback
|
||||||
tex: {
|
window.mathjaxLoadAttempts = 0;
|
||||||
inlineMath: [['$', '$'], ['\\(', '\\)']],
|
window.mathJaxReady = false;
|
||||||
displayMath: [['$$', '$$'], ['\\[', '\\]']],
|
|
||||||
|
function loadMathJax(url, isFallback = false) {{
|
||||||
|
return new Promise((resolve, reject) => {{
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.id = 'MathJax-script';
|
||||||
|
script.async = true;
|
||||||
|
script.src = url;
|
||||||
|
|
||||||
|
script.onload = () => {{
|
||||||
|
console.log('MathJax cargado desde:', url);
|
||||||
|
resolve();
|
||||||
|
}};
|
||||||
|
|
||||||
|
script.onerror = () => {{
|
||||||
|
console.error('Error cargando MathJax desde:', url);
|
||||||
|
reject(new Error('MathJax load failed'));
|
||||||
|
}};
|
||||||
|
|
||||||
|
document.head.appendChild(script);
|
||||||
|
}});
|
||||||
|
}}
|
||||||
|
|
||||||
|
// Configuración de MathJax
|
||||||
|
window.MathJax = {{
|
||||||
|
tex: {{
|
||||||
|
inlineMath: [['$', '$'], ['\\\\(', '\\\\)']],
|
||||||
|
displayMath: [['$$', '$$'], ['\\\\[', '\\\\]']],
|
||||||
processEscapes: true
|
processEscapes: true
|
||||||
},
|
}},
|
||||||
svg: {
|
svg: {{
|
||||||
scale: 0.9,
|
scale: 0.9,
|
||||||
minScale: 0.5,
|
minScale: 0.5,
|
||||||
fontCache: 'global'
|
fontCache: 'global'
|
||||||
},
|
}},
|
||||||
startup: {
|
startup: {{
|
||||||
ready: function () {
|
ready: function () {{
|
||||||
MathJax.startup.defaultReady();
|
MathJax.startup.defaultReady();
|
||||||
// Notificar que MathJax está listo
|
|
||||||
window.mathJaxReady = true;
|
window.mathJaxReady = true;
|
||||||
console.log('MathJax SVG completamente cargado');
|
console.log('MathJax SVG completamente listo');
|
||||||
}
|
}}
|
||||||
}
|
}}
|
||||||
};
|
}};
|
||||||
|
|
||||||
|
// Intentar cargar MathJax local, luego CDN
|
||||||
|
loadMathJax('{mathjax_url}').catch(() => {{
|
||||||
|
console.log('Fallback a CDN de MathJax...');
|
||||||
|
return loadMathJax('https://cdn.jsdelivr.net/npm/mathjax@3.2.2/es5/tex-svg.js', true);
|
||||||
|
}}).catch(() => {{
|
||||||
|
console.error('No se pudo cargar MathJax desde ninguna fuente');
|
||||||
|
// Marcar como listo para mostrar contenido sin renderizado
|
||||||
|
setTimeout(() => {{
|
||||||
|
window.mathJaxReady = true;
|
||||||
|
console.log('Modo fallback activado - sin renderizado LaTeX');
|
||||||
|
}}, 2000);
|
||||||
|
}});
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {{
|
||||||
background-color: #1a1a1a;
|
background-color: #1a1a1a;
|
||||||
color: #d4d4d4;
|
color: #d4d4d4;
|
||||||
font-family: 'Segoe UI', Arial, sans-serif;
|
font-family: 'Segoe UI', Arial, sans-serif;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}}
|
||||||
.equation-block {
|
.equation-block {{
|
||||||
background: rgba(45, 45, 48, 0.8);
|
background: rgba(45, 45, 48, 0.8);
|
||||||
border-left: 3px solid;
|
border-left: 3px solid;
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: all 0.1s ease;
|
transition: all 0.1s ease;
|
||||||
}
|
}}
|
||||||
.equation-block:hover {
|
.equation-block:hover {{
|
||||||
background: rgba(45, 45, 48, 0.9);
|
background: rgba(45, 45, 48, 0.9);
|
||||||
}
|
}}
|
||||||
.comment { border-left-color: #6a9955; }
|
.comment {{ border-left-color: #6a9955; }}
|
||||||
.assignment { border-left-color: #dcdcaa; }
|
.assignment {{ border-left-color: #dcdcaa; }}
|
||||||
.equation { border-left-color: #c586c0; }
|
.equation {{ border-left-color: #c586c0; }}
|
||||||
.symbolic { border-left-color: #9cdcfe; }
|
.symbolic {{ border-left-color: #9cdcfe; }}
|
||||||
|
|
||||||
.math-content {
|
.math-content {{
|
||||||
margin: 2px 0;
|
margin: 2px 0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}}
|
||||||
|
|
||||||
.comment-text {
|
.comment-text {{
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
color: #6a9955;
|
color: #6a9955;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}}
|
||||||
|
|
||||||
.info-message {
|
.info-message {{
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
color: #666;
|
color: #666;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}}
|
||||||
|
|
||||||
/* Optimizaciones para SVG rendering */
|
/* Optimizaciones para SVG rendering */
|
||||||
mjx-container[jax="SVG"] {
|
mjx-container[jax="SVG"] {{
|
||||||
margin: 2px 0 !important;
|
margin: 2px 0 !important;
|
||||||
}
|
}}
|
||||||
|
|
||||||
mjx-container[jax="SVG"] svg {
|
mjx-container[jax="SVG"] svg {{
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}}
|
||||||
|
|
||||||
/* Mejorar contraste del SVG */
|
mjx-container[jax="SVG"] svg text {{
|
||||||
mjx-container[jax="SVG"] svg text {
|
|
||||||
fill: #d4d4d4 !important;
|
fill: #d4d4d4 !important;
|
||||||
}
|
}}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -204,53 +249,90 @@ class LatexPanel(QWidget):
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
window.mathJaxReady = false;
|
function addEquation(type, content) {{
|
||||||
|
try {{
|
||||||
|
var container = document.getElementById('equations-container');
|
||||||
|
if (!container) {{
|
||||||
|
console.error('Container no encontrado');
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
|
||||||
function addEquation(type, content) {
|
// Limpiar mensaje inicial si existe
|
||||||
var container = document.getElementById('equations-container');
|
var infoMsg = container.querySelector('.info-message');
|
||||||
|
if (infoMsg) infoMsg.remove();
|
||||||
|
|
||||||
// Limpiar mensaje inicial si existe
|
var equation = document.createElement('div');
|
||||||
var infoMsg = container.querySelector('.info-message');
|
equation.className = 'equation-block ' + type;
|
||||||
if (infoMsg) infoMsg.remove();
|
|
||||||
|
|
||||||
var equation = document.createElement('div');
|
var mathContent = document.createElement('div');
|
||||||
equation.className = 'equation-block ' + type;
|
mathContent.className = 'math-content';
|
||||||
|
|
||||||
var mathContent = document.createElement('div');
|
if (type === 'comment') {{
|
||||||
mathContent.className = 'math-content';
|
mathContent.className = 'comment-text';
|
||||||
|
mathContent.textContent = content;
|
||||||
|
console.log('Comentario agregado:', content);
|
||||||
|
}} else {{
|
||||||
|
mathContent.innerHTML = '$$' + content + '$$';
|
||||||
|
console.log('Ecuación agregada:', content);
|
||||||
|
}}
|
||||||
|
|
||||||
if (type === 'comment') {
|
equation.appendChild(mathContent);
|
||||||
mathContent.className = 'comment-text';
|
container.appendChild(equation);
|
||||||
mathContent.textContent = content;
|
|
||||||
} else {
|
|
||||||
mathContent.innerHTML = '$$' + content + '$$';
|
|
||||||
}
|
|
||||||
|
|
||||||
equation.appendChild(mathContent);
|
// Re-renderizar MathJax solo si está listo
|
||||||
container.appendChild(equation);
|
if (window.MathJax && window.mathJaxReady && type !== 'comment') {{
|
||||||
|
console.log('Renderizando con MathJax...');
|
||||||
|
MathJax.typesetPromise([equation]).then(() => {{
|
||||||
|
console.log('Ecuación renderizada exitosamente');
|
||||||
|
}}).catch(function(err) {{
|
||||||
|
console.error('Error renderizando con MathJax:', err);
|
||||||
|
// Mostrar como texto plano si falla
|
||||||
|
mathContent.innerHTML = content;
|
||||||
|
}});
|
||||||
|
}} else if (type !== 'comment') {{
|
||||||
|
console.log('MathJax no listo, mostrando como texto:', window.mathJaxReady);
|
||||||
|
// Mostrar como texto plano si MathJax no está listo
|
||||||
|
mathContent.innerHTML = content;
|
||||||
|
}}
|
||||||
|
|
||||||
// Re-renderizar MathJax solo si está listo
|
}} catch (e) {{
|
||||||
if (window.MathJax && window.mathJaxReady && type !== 'comment') {
|
console.error('Error en addEquation:', e);
|
||||||
MathJax.typesetPromise([equation]).catch(function(err) {
|
}}
|
||||||
console.error('MathJax SVG error:', err);
|
}}
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearEquations() {
|
function clearEquations() {{
|
||||||
var container = document.getElementById('equations-container');
|
try {{
|
||||||
container.innerHTML = '<div class="info-message">Panel de Ecuaciones LaTeX (SVG)</div>';
|
var container = document.getElementById('equations-container');
|
||||||
}
|
if (container) {{
|
||||||
|
container.innerHTML = '<div class="info-message">Panel de Ecuaciones LaTeX (SVG)</div>';
|
||||||
|
console.log('Ecuaciones limpiadas');
|
||||||
|
}}
|
||||||
|
}} catch (e) {{
|
||||||
|
console.error('Error en clearEquations:', e);
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
|
||||||
// Función para notificar que está listo para renderizar
|
// Función para notificar que está listo para renderizar
|
||||||
function triggerInitialRender() {
|
function triggerInitialRender() {{
|
||||||
if (window.mathJaxReady) {
|
try {{
|
||||||
// Notificar a Python que MathJax está listo
|
console.log('Verificando estado MathJax:', window.mathJaxReady);
|
||||||
console.log('MathJax SVG listo para renderizado inicial');
|
if (window.mathJaxReady) {{
|
||||||
return true;
|
console.log('MathJax SVG listo para renderizado inicial');
|
||||||
}
|
return true;
|
||||||
return false;
|
}}
|
||||||
}
|
return false;
|
||||||
|
}} catch (e) {{
|
||||||
|
console.error('Error en triggerInitialRender:', e);
|
||||||
|
return false;
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
|
||||||
|
// Monitor de estado para debug
|
||||||
|
setInterval(() => {{
|
||||||
|
if (window.mathJaxReady) {{
|
||||||
|
console.log('🎯 MathJax listo detectado');
|
||||||
|
}}
|
||||||
|
}}, 3000);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>"""
|
</html>"""
|
||||||
|
@ -281,27 +363,67 @@ class LatexPanel(QWidget):
|
||||||
if not self._webview_available:
|
if not self._webview_available:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Verificar si MathJax está listo
|
# Verificar múltiples condiciones de MathJax
|
||||||
self.webview.page().runJavaScript(
|
js_check = """
|
||||||
"window.mathJaxReady || false;",
|
(function() {
|
||||||
self._on_mathjax_ready_check
|
try {
|
||||||
)
|
// Verificar si window.mathJaxReady está definido
|
||||||
|
if (window.mathJaxReady === true) return true;
|
||||||
|
|
||||||
|
// Verificar si MathJax está disponible y listo
|
||||||
|
if (window.MathJax && window.MathJax.startup && window.MathJax.startup.document && window.MathJax.startup.document.state() >= 8) {
|
||||||
|
window.mathJaxReady = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar si existen las funciones necesarias
|
||||||
|
if (typeof addEquation === 'function' && typeof clearEquations === 'function') {
|
||||||
|
window.mathJaxReady = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} catch(e) {
|
||||||
|
console.error('Error verificando MathJax:', e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.webview.page().runJavaScript(js_check, self._on_mathjax_ready_check)
|
||||||
|
|
||||||
def _on_mathjax_ready_check(self, ready):
|
def _on_mathjax_ready_check(self, ready):
|
||||||
"""Callback cuando se verifica el estado de MathJax"""
|
"""Callback cuando se verifica el estado de MathJax"""
|
||||||
|
logging.debug(f"🔍 MathJax ready check: {ready}")
|
||||||
|
|
||||||
if ready and not self._mathjax_ready:
|
if ready and not self._mathjax_ready:
|
||||||
self._mathjax_ready = True
|
self._mathjax_ready = True
|
||||||
self._mathjax_check_timer.stop()
|
self._mathjax_check_timer.stop()
|
||||||
logging.debug("✅ MathJax listo, procesando ecuaciones pendientes")
|
logging.debug(f"✅ MathJax listo, procesando {len(self._pending_equations)} ecuaciones pendientes")
|
||||||
|
|
||||||
# Renderizar ecuaciones pendientes
|
# Renderizar ecuaciones pendientes
|
||||||
for eq in self._pending_equations:
|
for eq in self._pending_equations:
|
||||||
self._add_equation_to_webview(eq['type'], eq['content'])
|
self._add_equation_to_webview(eq['type'], eq['content'])
|
||||||
|
logging.debug(f"📊 Ecuación agregada al WebView: {eq['type']}")
|
||||||
self._pending_equations.clear()
|
self._pending_equations.clear()
|
||||||
|
|
||||||
# Trigger initial render si hay un calculador padre
|
# Trigger initial render si hay un calculador padre
|
||||||
if self._parent_calculator and hasattr(self._parent_calculator, '_trigger_initial_latex_render'):
|
if self._parent_calculator and hasattr(self._parent_calculator, '_trigger_initial_latex_render'):
|
||||||
self._parent_calculator._trigger_initial_latex_render()
|
self._parent_calculator._trigger_initial_latex_render()
|
||||||
|
elif not ready:
|
||||||
|
self._mathjax_timeout_count += 1
|
||||||
|
if self._mathjax_timeout_count >= self._max_mathjax_timeout:
|
||||||
|
# Timeout alcanzado, forzar como listo para mostrar contenido en texto plano
|
||||||
|
logging.warning("⚠️ MathJax timeout - cambiando a modo fallback")
|
||||||
|
self._mathjax_ready = True
|
||||||
|
self._mathjax_check_timer.stop()
|
||||||
|
|
||||||
|
# Procesar ecuaciones pendientes como texto plano
|
||||||
|
for eq in self._pending_equations:
|
||||||
|
self._add_equation_as_text(eq['type'], eq['content'])
|
||||||
|
self._pending_equations.clear()
|
||||||
|
else:
|
||||||
|
logging.debug(f"⏳ MathJax aún no está listo ({self._mathjax_timeout_count}/{self._max_mathjax_timeout}), continuando verificación...")
|
||||||
|
|
||||||
def _add_equation_to_webview(self, eq_type: str, content: str):
|
def _add_equation_to_webview(self, eq_type: str, content: str):
|
||||||
"""Añade una ecuación directamente al webview"""
|
"""Añade una ecuación directamente al webview"""
|
||||||
|
@ -310,6 +432,22 @@ class LatexPanel(QWidget):
|
||||||
js_code = f"addEquation('{eq_type}', '{escaped_content}');"
|
js_code = f"addEquation('{eq_type}', '{escaped_content}');"
|
||||||
self.webview.page().runJavaScript(js_code)
|
self.webview.page().runJavaScript(js_code)
|
||||||
|
|
||||||
|
def _add_equation_as_text(self, eq_type: str, content: str):
|
||||||
|
"""Añade una ecuación como texto plano cuando MathJax falla"""
|
||||||
|
if self._webview_available:
|
||||||
|
escaped_content = content.replace('\\', '\\\\').replace("'", "\\'").replace('"', '\\"')
|
||||||
|
# Agregar ecuación como texto plano sin MathJax
|
||||||
|
js_code = f"""
|
||||||
|
var container = document.getElementById('equations-container');
|
||||||
|
if (container) {{
|
||||||
|
var equation = document.createElement('div');
|
||||||
|
equation.className = 'equation-block {eq_type}';
|
||||||
|
equation.innerHTML = '<div class="math-content" style="font-family: monospace;">{escaped_content}</div>';
|
||||||
|
container.appendChild(equation);
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
self.webview.page().runJavaScript(js_code)
|
||||||
|
|
||||||
def add_equation(self, eq_type: str, content: str):
|
def add_equation(self, eq_type: str, content: str):
|
||||||
"""Añade una ecuación al panel"""
|
"""Añade una ecuación al panel"""
|
||||||
self.equations.append({'type': eq_type, 'content': content})
|
self.equations.append({'type': eq_type, 'content': content})
|
||||||
|
|
|
@ -124,26 +124,40 @@ class HybridCalculatorPySide6(QMainWindow):
|
||||||
# Sincronizar scroll
|
# Sincronizar scroll
|
||||||
self._setup_scroll_sync()
|
self._setup_scroll_sync()
|
||||||
|
|
||||||
# Añadir splitter al layout
|
# Crear splitter secundario para LaTeX
|
||||||
main_layout.addWidget(self.main_splitter)
|
self.secondary_splitter = QSplitter(Qt.Horizontal)
|
||||||
|
self.secondary_splitter.addWidget(self.main_splitter)
|
||||||
|
|
||||||
# Botón expandible para LaTeX
|
# Botón expandible para LaTeX
|
||||||
self.latex_button = ExpandableLatexButton()
|
self.latex_button = ExpandableLatexButton()
|
||||||
self.latex_button.clicked.connect(self._toggle_latex_panel)
|
self.latex_button.clicked.connect(self._toggle_latex_panel)
|
||||||
main_layout.addWidget(self.latex_button)
|
|
||||||
|
|
||||||
# Panel LaTeX (inicialmente oculto)
|
# Panel LaTeX con splitter
|
||||||
self.latex_panel = LatexPanel(self)
|
self.latex_panel = LatexPanel(self)
|
||||||
self.latex_panel.setMinimumWidth(300)
|
self.latex_panel.setMinimumWidth(200)
|
||||||
self.latex_panel.setMaximumWidth(500)
|
|
||||||
|
|
||||||
if self.latex_panel_visible:
|
if self.latex_panel_visible:
|
||||||
main_layout.addWidget(self.latex_panel)
|
self.secondary_splitter.addWidget(self.latex_panel)
|
||||||
self.latex_button.setChecked(True)
|
self.latex_button.setChecked(True)
|
||||||
|
# Configurar tamaños: 70% para main_splitter, 30% para latex_panel
|
||||||
|
latex_width = self.settings.get("latex_panel_width", 300)
|
||||||
|
total_width = 1000 # Ancho por defecto de la ventana
|
||||||
|
main_width = total_width - latex_width
|
||||||
|
self.secondary_splitter.setSizes([main_width, latex_width])
|
||||||
else:
|
else:
|
||||||
# Asegurar que el panel está oculto por defecto
|
# Asegurar que el panel está oculto por defecto
|
||||||
self.latex_panel.hide()
|
self.latex_panel.hide()
|
||||||
self.latex_button.setChecked(False)
|
self.latex_button.setChecked(False)
|
||||||
|
self.secondary_splitter.setSizes([1000])
|
||||||
|
|
||||||
|
# Layout principal con splitter secundario y botón
|
||||||
|
splitter_layout = QHBoxLayout()
|
||||||
|
splitter_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
splitter_layout.setSpacing(0)
|
||||||
|
splitter_layout.addWidget(self.secondary_splitter)
|
||||||
|
splitter_layout.addWidget(self.latex_button)
|
||||||
|
|
||||||
|
main_layout.addLayout(splitter_layout)
|
||||||
|
|
||||||
# Los formatos de salida se configuran automáticamente en EvaluationManager
|
# Los formatos de salida se configuran automáticamente en EvaluationManager
|
||||||
|
|
||||||
|
@ -155,6 +169,9 @@ class HybridCalculatorPySide6(QMainWindow):
|
||||||
# Aplicar tema oscuro
|
# Aplicar tema oscuro
|
||||||
self._apply_dark_theme()
|
self._apply_dark_theme()
|
||||||
|
|
||||||
|
# Aplicar tamaño de fuente guardado
|
||||||
|
self._apply_saved_font_size()
|
||||||
|
|
||||||
def _setup_scroll_sync(self):
|
def _setup_scroll_sync(self):
|
||||||
"""Sincroniza el scroll entre entrada y salida"""
|
"""Sincroniza el scroll entre entrada y salida"""
|
||||||
def sync_input_to_output():
|
def sync_input_to_output():
|
||||||
|
@ -242,6 +259,22 @@ class HybridCalculatorPySide6(QMainWindow):
|
||||||
"""
|
"""
|
||||||
self.setStyleSheet(dark_style)
|
self.setStyleSheet(dark_style)
|
||||||
|
|
||||||
|
def _apply_saved_font_size(self):
|
||||||
|
"""Aplica el tamaño de fuente guardado"""
|
||||||
|
is_large = self.settings_manager.get_setting("font_size_large", False)
|
||||||
|
font_size = 14 if is_large else 11
|
||||||
|
|
||||||
|
# Aplicar al estilo actual
|
||||||
|
current_style = self.styleSheet()
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Reemplazar font-size en QPlainTextEdit y QTextEdit
|
||||||
|
pattern = r'(QPlainTextEdit, QTextEdit\s*\{[^}]*?)font-size:\s*\d+px;([^}]*\})'
|
||||||
|
replacement = rf'\1font-size: {font_size}px;\2'
|
||||||
|
|
||||||
|
new_style = re.sub(pattern, replacement, current_style)
|
||||||
|
self.setStyleSheet(new_style)
|
||||||
|
|
||||||
def _on_input_changed(self):
|
def _on_input_changed(self):
|
||||||
"""Maneja cambios en el texto de entrada"""
|
"""Maneja cambios en el texto de entrada"""
|
||||||
# Delegar al autocomplete manager
|
# Delegar al autocomplete manager
|
||||||
|
@ -265,21 +298,28 @@ class HybridCalculatorPySide6(QMainWindow):
|
||||||
|
|
||||||
def _toggle_latex_panel(self):
|
def _toggle_latex_panel(self):
|
||||||
"""Togglea la visibilidad del panel LaTeX"""
|
"""Togglea la visibilidad del panel LaTeX"""
|
||||||
main_layout = self.centralWidget().layout()
|
|
||||||
|
|
||||||
if self.latex_panel_visible:
|
if self.latex_panel_visible:
|
||||||
# Ocultar panel
|
# Ocultar panel
|
||||||
main_layout.removeWidget(self.latex_panel)
|
self.secondary_splitter.widget(1).hide() if self.secondary_splitter.count() > 1 else None
|
||||||
self.latex_panel.hide()
|
self.latex_panel.hide()
|
||||||
self.latex_button.setChecked(False)
|
self.latex_button.setChecked(False)
|
||||||
self.latex_panel_visible = False
|
self.latex_panel_visible = False
|
||||||
|
self.secondary_splitter.setSizes([1000])
|
||||||
else:
|
else:
|
||||||
# Mostrar panel
|
# Mostrar panel
|
||||||
main_layout.addWidget(self.latex_panel)
|
if self.secondary_splitter.count() == 1:
|
||||||
|
self.secondary_splitter.addWidget(self.latex_panel)
|
||||||
|
|
||||||
self.latex_panel.show()
|
self.latex_panel.show()
|
||||||
self.latex_button.setChecked(True)
|
self.latex_button.setChecked(True)
|
||||||
self.latex_panel_visible = True
|
self.latex_panel_visible = True
|
||||||
|
|
||||||
|
# Configurar tamaños
|
||||||
|
latex_width = self.settings.get("latex_panel_width", 300)
|
||||||
|
total_width = self.width()
|
||||||
|
main_width = total_width - latex_width - 50 # 50 para el botón
|
||||||
|
self.secondary_splitter.setSizes([main_width, latex_width])
|
||||||
|
|
||||||
# Actualizar panel con ecuaciones pendientes
|
# Actualizar panel con ecuaciones pendientes
|
||||||
self._sync_latex_equations_on_show()
|
self._sync_latex_equations_on_show()
|
||||||
|
|
||||||
|
@ -305,6 +345,13 @@ class HybridCalculatorPySide6(QMainWindow):
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
"""Maneja el cierre de la aplicación"""
|
"""Maneja el cierre de la aplicación"""
|
||||||
|
# Guardar ancho del panel LaTeX si está visible
|
||||||
|
if self.latex_panel_visible and self.secondary_splitter.count() > 1:
|
||||||
|
sizes = self.secondary_splitter.sizes()
|
||||||
|
if len(sizes) > 1:
|
||||||
|
latex_width = sizes[1]
|
||||||
|
self.settings_manager.set_setting("latex_panel_width", latex_width)
|
||||||
|
|
||||||
# Delegar al settings manager
|
# Delegar al settings manager
|
||||||
self.settings_manager.save_all()
|
self.settings_manager.save_all()
|
||||||
event.accept()
|
event.accept()
|
||||||
|
|
134
app/gui_menus.py
134
app/gui_menus.py
|
@ -76,6 +76,31 @@ class MenuManager:
|
||||||
|
|
||||||
view_menu.addSeparator()
|
view_menu.addSeparator()
|
||||||
|
|
||||||
|
# Submenu de fuentes
|
||||||
|
font_menu = view_menu.addMenu("Tamaño de fuente")
|
||||||
|
|
||||||
|
font_small_action = QAction("Pequeña (11px)", self.main_window)
|
||||||
|
font_small_action.setCheckable(True)
|
||||||
|
font_small_action.triggered.connect(lambda: self.set_font_size(False))
|
||||||
|
font_menu.addAction(font_small_action)
|
||||||
|
|
||||||
|
font_large_action = QAction("Grande (14px)", self.main_window)
|
||||||
|
font_large_action.setCheckable(True)
|
||||||
|
font_large_action.triggered.connect(lambda: self.set_font_size(True))
|
||||||
|
font_menu.addAction(font_large_action)
|
||||||
|
|
||||||
|
# Marcar el tamaño actual
|
||||||
|
is_large = self.main_window.settings_manager.get_setting("font_size_large", False)
|
||||||
|
if is_large:
|
||||||
|
font_large_action.setChecked(True)
|
||||||
|
else:
|
||||||
|
font_small_action.setChecked(True)
|
||||||
|
|
||||||
|
self.font_small_action = font_small_action
|
||||||
|
self.font_large_action = font_large_action
|
||||||
|
|
||||||
|
view_menu.addSeparator()
|
||||||
|
|
||||||
system_info_action = QAction("Información del sistema", self.main_window)
|
system_info_action = QAction("Información del sistema", self.main_window)
|
||||||
system_info_action.triggered.connect(self.show_types_info)
|
system_info_action.triggered.connect(self.show_types_info)
|
||||||
view_menu.addAction(system_info_action)
|
view_menu.addAction(system_info_action)
|
||||||
|
@ -100,6 +125,10 @@ class MenuManager:
|
||||||
latex_status_action.triggered.connect(self._show_latex_panel_status)
|
latex_status_action.triggered.connect(self._show_latex_panel_status)
|
||||||
diag_menu.addAction(latex_status_action)
|
diag_menu.addAction(latex_status_action)
|
||||||
|
|
||||||
|
save_html_action = QAction("💾 Guardar HTML del Panel LaTeX", self.main_window)
|
||||||
|
save_html_action.triggered.connect(self._save_latex_html)
|
||||||
|
diag_menu.addAction(save_html_action)
|
||||||
|
|
||||||
diag_menu.addSeparator()
|
diag_menu.addSeparator()
|
||||||
|
|
||||||
copy_debug_action = QAction("📋 Copiar Debug al Portapapeles", self.main_window)
|
copy_debug_action = QAction("📋 Copiar Debug al Portapapeles", self.main_window)
|
||||||
|
@ -212,6 +241,47 @@ class MenuManager:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
QMessageBox.critical(self.main_window, "Error", f"No se pudo limpiar el historial:\n{e}")
|
QMessageBox.critical(self.main_window, "Error", f"No se pudo limpiar el historial:\n{e}")
|
||||||
|
|
||||||
|
def set_font_size(self, is_large: bool):
|
||||||
|
"""Cambia el tamaño de fuente de los paneles"""
|
||||||
|
# Guardar configuración
|
||||||
|
self.main_window.settings_manager.set_setting("font_size_large", is_large)
|
||||||
|
|
||||||
|
# Actualizar checkboxes
|
||||||
|
self.font_small_action.setChecked(not is_large)
|
||||||
|
self.font_large_action.setChecked(is_large)
|
||||||
|
|
||||||
|
# Aplicar nuevo tamaño
|
||||||
|
font_size = 14 if is_large else 11
|
||||||
|
self._apply_font_size_to_panels(font_size)
|
||||||
|
|
||||||
|
status_text = f"🔤 Fuente cambiada a {'grande' if is_large else 'pequeña'} ({font_size}px)"
|
||||||
|
self.main_window._update_status(status_text)
|
||||||
|
|
||||||
|
def _apply_font_size_to_panels(self, font_size: int):
|
||||||
|
"""Aplica el tamaño de fuente a los paneles de texto"""
|
||||||
|
style_update = f"""
|
||||||
|
QPlainTextEdit, QTextEdit {{
|
||||||
|
font-size: {font_size}px;
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Actualizar estilo de los paneles principales
|
||||||
|
current_style = self.main_window.styleSheet()
|
||||||
|
# Reemplazar solo la parte de font-size en QPlainTextEdit y QTextEdit
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Patrón para encontrar y reemplazar font-size en QPlainTextEdit y QTextEdit
|
||||||
|
pattern = r'(QPlainTextEdit, QTextEdit\s*\{[^}]*?)font-size:\s*\d+px;([^}]*\})'
|
||||||
|
replacement = rf'\1font-size: {font_size}px;\2'
|
||||||
|
|
||||||
|
new_style = re.sub(pattern, replacement, current_style)
|
||||||
|
|
||||||
|
# Si no se encontró el patrón, agregar el estilo
|
||||||
|
if new_style == current_style:
|
||||||
|
new_style += style_update
|
||||||
|
|
||||||
|
self.main_window.setStyleSheet(new_style)
|
||||||
|
|
||||||
# ========== FUNCIONES DE MENÚ HERRAMIENTAS ==========
|
# ========== FUNCIONES DE MENÚ HERRAMIENTAS ==========
|
||||||
|
|
||||||
def reload_types(self):
|
def reload_types(self):
|
||||||
|
@ -314,6 +384,70 @@ PARA SOLUCIONAR:
|
||||||
|
|
||||||
self._show_info_dialog("Estado Panel LaTeX", status_message)
|
self._show_info_dialog("Estado Panel LaTeX", status_message)
|
||||||
|
|
||||||
|
def _save_latex_html(self):
|
||||||
|
"""Guarda el HTML del panel LaTeX para diagnóstico"""
|
||||||
|
try:
|
||||||
|
# Obtener HTML del panel LaTeX
|
||||||
|
latex_panel = self.main_window.latex_panel
|
||||||
|
|
||||||
|
if hasattr(latex_panel, '_webview_available') and latex_panel._webview_available:
|
||||||
|
# Panel con WebEngine
|
||||||
|
if hasattr(latex_panel, 'webview') and latex_panel._webview_initialized:
|
||||||
|
# Obtener HTML actual del WebView
|
||||||
|
def on_html_received(html_content):
|
||||||
|
self._save_html_to_file(html_content, "webview_current")
|
||||||
|
|
||||||
|
latex_panel.webview.page().toHtml(on_html_received)
|
||||||
|
|
||||||
|
# También guardar el HTML base generado
|
||||||
|
base_html = latex_panel._generate_mathjax_html()
|
||||||
|
self._save_html_to_file(base_html, "webview_base")
|
||||||
|
else:
|
||||||
|
# WebEngine no inicializado, solo HTML base
|
||||||
|
base_html = latex_panel._generate_mathjax_html()
|
||||||
|
self._save_html_to_file(base_html, "webview_not_initialized")
|
||||||
|
else:
|
||||||
|
# Panel con fallback text browser
|
||||||
|
if hasattr(latex_panel, 'text_browser'):
|
||||||
|
fallback_html = latex_panel.text_browser.toHtml()
|
||||||
|
self._save_html_to_file(fallback_html, "fallback_textbrowser")
|
||||||
|
else:
|
||||||
|
self._save_html_to_file("No hay contenido HTML disponible", "no_content")
|
||||||
|
|
||||||
|
QMessageBox.information(
|
||||||
|
self.main_window,
|
||||||
|
"HTML Guardado",
|
||||||
|
"El HTML del panel LaTeX ha sido guardado en ./debug_html/ para diagnóstico."
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error guardando HTML del panel LaTeX: {e}")
|
||||||
|
QMessageBox.critical(
|
||||||
|
self.main_window,
|
||||||
|
"Error",
|
||||||
|
f"Error guardando HTML: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _save_html_to_file(self, html_content, file_suffix):
|
||||||
|
"""Guarda contenido HTML a un archivo"""
|
||||||
|
# Crear directorio de debug si no existe
|
||||||
|
debug_dir = Path("./debug_html")
|
||||||
|
debug_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# Generar nombre de archivo con timestamp
|
||||||
|
timestamp = time.strftime("%Y%m%d_%H%M%S")
|
||||||
|
filename = f"latex_panel_{file_suffix}_{timestamp}.html"
|
||||||
|
filepath = debug_dir / filename
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(filepath, "w", encoding="utf-8") as f:
|
||||||
|
f.write(html_content)
|
||||||
|
|
||||||
|
self.logger.info(f"HTML guardado en: {filepath}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error escribiendo archivo HTML: {e}")
|
||||||
|
|
||||||
def _copy_debug_to_clipboard(self):
|
def _copy_debug_to_clipboard(self):
|
||||||
"""Copia información de debug completa al portapapeles"""
|
"""Copia información de debug completa al portapapeles"""
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -31,7 +31,9 @@ class SettingsManager:
|
||||||
"window_geometry": None,
|
"window_geometry": None,
|
||||||
"splitter_sizes": None,
|
"splitter_sizes": None,
|
||||||
"debug_mode": False,
|
"debug_mode": False,
|
||||||
"latex_panel_visible": False
|
"latex_panel_visible": False,
|
||||||
|
"latex_panel_width": 300,
|
||||||
|
"font_size_large": False # False = pequeña (11px), True = grande (14px)
|
||||||
}
|
}
|
||||||
|
|
||||||
def _save_settings(self):
|
def _save_settings(self):
|
||||||
|
|
|
@ -48,9 +48,13 @@ class OutputTextEdit(QTextEdit):
|
||||||
cursor = self.cursorForPosition(event.pos())
|
cursor = self.cursorForPosition(event.pos())
|
||||||
pos = cursor.position()
|
pos = cursor.position()
|
||||||
|
|
||||||
|
logging.debug(f"🖱️ Click en posición {pos}, links disponibles: {len(self.clickable_links)}")
|
||||||
|
|
||||||
# Buscar si el click fue en un link
|
# Buscar si el click fue en un link
|
||||||
for (start, end), (link_id, obj) in self.clickable_links.items():
|
for (start, end), (link_id, obj) in self.clickable_links.items():
|
||||||
|
logging.debug(f"🔍 Verificando link {start}-{end}: {link_id}")
|
||||||
if start <= pos <= end:
|
if start <= pos <= end:
|
||||||
|
logging.debug(f"✅ Click en link detectado: {link_id}")
|
||||||
self.link_clicked.emit(link_id, obj)
|
self.link_clicked.emit(link_id, obj)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
"""
|
||||||
|
Sistema de gestión de MathJax local con descarga automática
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import zipfile
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from urllib.request import urlretrieve
|
||||||
|
from urllib.error import URLError
|
||||||
|
|
||||||
|
|
||||||
|
class MathJaxManager:
|
||||||
|
"""Gestor de MathJax local con descarga automática"""
|
||||||
|
|
||||||
|
MATHJAX_VERSION = "3.2.2"
|
||||||
|
MATHJAX_URL = f"https://github.com/mathjax/MathJax/archive/{MATHJAX_VERSION}.zip"
|
||||||
|
LOCAL_DIR = Path(".mathjax")
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
self.mathjax_dir = self.LOCAL_DIR / f"MathJax-{self.MATHJAX_VERSION}"
|
||||||
|
self.es5_dir = self.mathjax_dir / "es5"
|
||||||
|
|
||||||
|
def get_mathjax_url(self) -> str:
|
||||||
|
"""
|
||||||
|
Obtiene la URL de MathJax (local si está disponible, CDN si no)
|
||||||
|
"""
|
||||||
|
if self.is_local_available():
|
||||||
|
# Construir URL local relativa para file://
|
||||||
|
local_url = f"file:///{self.es5_dir.absolute().as_posix()}/tex-svg.js"
|
||||||
|
self.logger.debug(f"Usando MathJax local: {local_url}")
|
||||||
|
return local_url
|
||||||
|
else:
|
||||||
|
# Descargar automáticamente si no existe
|
||||||
|
if self.download_mathjax():
|
||||||
|
local_url = f"file:///{self.es5_dir.absolute().as_posix()}/tex-svg.js"
|
||||||
|
self.logger.info(f"MathJax descargado y configurado localmente: {local_url}")
|
||||||
|
return local_url
|
||||||
|
else:
|
||||||
|
# Fallback a CDN
|
||||||
|
cdn_url = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"
|
||||||
|
self.logger.warning(f"Fallback a CDN: {cdn_url}")
|
||||||
|
return cdn_url
|
||||||
|
|
||||||
|
def is_local_available(self) -> bool:
|
||||||
|
"""Verifica si MathJax está disponible localmente"""
|
||||||
|
tex_svg_file = self.es5_dir / "tex-svg.js"
|
||||||
|
return tex_svg_file.exists() and tex_svg_file.is_file()
|
||||||
|
|
||||||
|
def download_mathjax(self) -> bool:
|
||||||
|
"""
|
||||||
|
Descarga MathJax automáticamente si no está disponible
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True si la descarga fue exitosa, False si falló
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.logger.info(f"Descargando MathJax {self.MATHJAX_VERSION}...")
|
||||||
|
|
||||||
|
# Crear directorio si no existe
|
||||||
|
self.LOCAL_DIR.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# Archivo temporal para la descarga
|
||||||
|
zip_path = self.LOCAL_DIR / f"mathjax-{self.MATHJAX_VERSION}.zip"
|
||||||
|
|
||||||
|
# Descargar archivo ZIP
|
||||||
|
self.logger.debug(f"Descargando desde: {self.MATHJAX_URL}")
|
||||||
|
urlretrieve(self.MATHJAX_URL, zip_path)
|
||||||
|
|
||||||
|
# Extraer archivo
|
||||||
|
self.logger.debug(f"Extrayendo en: {self.LOCAL_DIR}")
|
||||||
|
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||||
|
zip_ref.extractall(self.LOCAL_DIR)
|
||||||
|
|
||||||
|
# Limpiar archivo ZIP
|
||||||
|
zip_path.unlink()
|
||||||
|
|
||||||
|
# Verificar que se extrajo correctamente
|
||||||
|
if self.is_local_available():
|
||||||
|
self.logger.info(f"✅ MathJax {self.MATHJAX_VERSION} descargado correctamente")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.logger.error("❌ Error: MathJax no se extrajo correctamente")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except URLError as e:
|
||||||
|
self.logger.error(f"❌ Error de red descargando MathJax: {e}")
|
||||||
|
return False
|
||||||
|
except zipfile.BadZipFile as e:
|
||||||
|
self.logger.error(f"❌ Error: archivo ZIP corrupto: {e}")
|
||||||
|
# Limpiar archivo corrupto
|
||||||
|
if zip_path.exists():
|
||||||
|
zip_path.unlink()
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"❌ Error inesperado descargando MathJax: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def clean_local_mathjax(self):
|
||||||
|
"""
|
||||||
|
Limpia la instalación local de MathJax
|
||||||
|
(Para forzar redownload en la siguiente inicialización)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if self.LOCAL_DIR.exists():
|
||||||
|
import shutil
|
||||||
|
shutil.rmtree(self.LOCAL_DIR)
|
||||||
|
self.logger.info("🗑️ MathJax local limpiado")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error limpiando MathJax local: {e}")
|
||||||
|
|
||||||
|
def get_status_info(self) -> dict:
|
||||||
|
"""Obtiene información del estado de MathJax"""
|
||||||
|
return {
|
||||||
|
"local_available": self.is_local_available(),
|
||||||
|
"local_dir": str(self.LOCAL_DIR.absolute()),
|
||||||
|
"mathjax_dir": str(self.mathjax_dir.absolute()) if self.mathjax_dir.exists() else None,
|
||||||
|
"version": self.MATHJAX_VERSION,
|
||||||
|
"tex_svg_file": str(self.es5_dir / "tex-svg.js") if self.is_local_available() else None
|
||||||
|
}
|
|
@ -0,0 +1,218 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
|
||||||
|
<script>
|
||||||
|
// Intentar cargar MathJax local primero, luego CDN como fallback
|
||||||
|
window.mathjaxLoadAttempts = 0;
|
||||||
|
window.mathJaxReady = false;
|
||||||
|
|
||||||
|
function loadMathJax(url, isFallback = false) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.id = 'MathJax-script';
|
||||||
|
script.async = true;
|
||||||
|
script.src = url;
|
||||||
|
|
||||||
|
script.onload = () => {
|
||||||
|
console.log('MathJax cargado desde:', url);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
script.onerror = () => {
|
||||||
|
console.error('Error cargando MathJax desde:', url);
|
||||||
|
reject(new Error('MathJax load failed'));
|
||||||
|
};
|
||||||
|
|
||||||
|
document.head.appendChild(script);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuración de MathJax
|
||||||
|
window.MathJax = {
|
||||||
|
tex: {
|
||||||
|
inlineMath: [['$', '$'], ['\\(', '\\)']],
|
||||||
|
displayMath: [['$$', '$$'], ['\\[', '\\]']],
|
||||||
|
processEscapes: true
|
||||||
|
},
|
||||||
|
svg: {
|
||||||
|
scale: 0.9,
|
||||||
|
minScale: 0.5,
|
||||||
|
fontCache: 'global'
|
||||||
|
},
|
||||||
|
startup: {
|
||||||
|
ready: function () {
|
||||||
|
MathJax.startup.defaultReady();
|
||||||
|
window.mathJaxReady = true;
|
||||||
|
console.log('MathJax SVG completamente listo');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Intentar cargar MathJax local, luego CDN
|
||||||
|
loadMathJax('file:///D:/Proyectos/Scripts/Calcv2/.mathjax/MathJax-3.2.2/es5/tex-svg.js').catch(() => {
|
||||||
|
console.log('Fallback a CDN de MathJax...');
|
||||||
|
return loadMathJax('https://cdn.jsdelivr.net/npm/mathjax@3.2.2/es5/tex-svg.js', true);
|
||||||
|
}).catch(() => {
|
||||||
|
console.error('No se pudo cargar MathJax desde ninguna fuente');
|
||||||
|
// Marcar como listo para mostrar contenido sin renderizado
|
||||||
|
setTimeout(() => {
|
||||||
|
window.mathJaxReady = true;
|
||||||
|
console.log('Modo fallback activado - sin renderizado LaTeX');
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
color: #d4d4d4;
|
||||||
|
font-family: 'Segoe UI', Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 8px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
.equation-block {
|
||||||
|
background: rgba(45, 45, 48, 0.8);
|
||||||
|
border-left: 3px solid;
|
||||||
|
margin: 4px 0;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.1s ease;
|
||||||
|
}
|
||||||
|
.equation-block:hover {
|
||||||
|
background: rgba(45, 45, 48, 0.9);
|
||||||
|
}
|
||||||
|
.comment { border-left-color: #6a9955; }
|
||||||
|
.assignment { border-left-color: #dcdcaa; }
|
||||||
|
.equation { border-left-color: #c586c0; }
|
||||||
|
.symbolic { border-left-color: #9cdcfe; }
|
||||||
|
|
||||||
|
.math-content {
|
||||||
|
margin: 2px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-text {
|
||||||
|
font-style: italic;
|
||||||
|
color: #6a9955;
|
||||||
|
font-size: 12px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-message {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optimizaciones para SVG rendering */
|
||||||
|
mjx-container[jax="SVG"] {
|
||||||
|
margin: 2px 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
mjx-container[jax="SVG"] svg {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
mjx-container[jax="SVG"] svg text {
|
||||||
|
fill: #d4d4d4 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="equations-container">
|
||||||
|
<div class="info-message">
|
||||||
|
Panel de Ecuaciones LaTeX (SVG)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function addEquation(type, content) {
|
||||||
|
try {
|
||||||
|
var container = document.getElementById('equations-container');
|
||||||
|
if (!container) {
|
||||||
|
console.error('Container no encontrado');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limpiar mensaje inicial si existe
|
||||||
|
var infoMsg = container.querySelector('.info-message');
|
||||||
|
if (infoMsg) infoMsg.remove();
|
||||||
|
|
||||||
|
var equation = document.createElement('div');
|
||||||
|
equation.className = 'equation-block ' + type;
|
||||||
|
|
||||||
|
var mathContent = document.createElement('div');
|
||||||
|
mathContent.className = 'math-content';
|
||||||
|
|
||||||
|
if (type === 'comment') {
|
||||||
|
mathContent.className = 'comment-text';
|
||||||
|
mathContent.textContent = content;
|
||||||
|
console.log('Comentario agregado:', content);
|
||||||
|
} else {
|
||||||
|
mathContent.innerHTML = '$$' + content + '$$';
|
||||||
|
console.log('Ecuación agregada:', content);
|
||||||
|
}
|
||||||
|
|
||||||
|
equation.appendChild(mathContent);
|
||||||
|
container.appendChild(equation);
|
||||||
|
|
||||||
|
// Re-renderizar MathJax solo si está listo
|
||||||
|
if (window.MathJax && window.mathJaxReady && type !== 'comment') {
|
||||||
|
console.log('Renderizando con MathJax...');
|
||||||
|
MathJax.typesetPromise([equation]).then(() => {
|
||||||
|
console.log('Ecuación renderizada exitosamente');
|
||||||
|
}).catch(function(err) {
|
||||||
|
console.error('Error renderizando con MathJax:', err);
|
||||||
|
// Mostrar como texto plano si falla
|
||||||
|
mathContent.innerHTML = content;
|
||||||
|
});
|
||||||
|
} else if (type !== 'comment') {
|
||||||
|
console.log('MathJax no listo, mostrando como texto:', window.mathJaxReady);
|
||||||
|
// Mostrar como texto plano si MathJax no está listo
|
||||||
|
mathContent.innerHTML = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error en addEquation:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearEquations() {
|
||||||
|
try {
|
||||||
|
var container = document.getElementById('equations-container');
|
||||||
|
if (container) {
|
||||||
|
container.innerHTML = '<div class="info-message">Panel de Ecuaciones LaTeX (SVG)</div>';
|
||||||
|
console.log('Ecuaciones limpiadas');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error en clearEquations:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Función para notificar que está listo para renderizar
|
||||||
|
function triggerInitialRender() {
|
||||||
|
try {
|
||||||
|
console.log('Verificando estado MathJax:', window.mathJaxReady);
|
||||||
|
if (window.mathJaxReady) {
|
||||||
|
console.log('MathJax SVG listo para renderizado inicial');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error en triggerInitialRender:', e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monitor de estado para debug
|
||||||
|
setInterval(() => {
|
||||||
|
if (window.mathJaxReady) {
|
||||||
|
console.log('🎯 MathJax listo detectado');
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue