PageSourceSearch

https://altobonito-1.vercel.app/js/render/f-svg-sync.js?v=16

js altobonito-1.vercel.app collected 2026-10-03 06:15:26 UTC 8,807 bytes, 238 lines download raw bytes

1/**
2 * f-svg-sync.js — Delta sync entre allDrawnLines y DOMCache
3 *
4 * REGLA: NUNCA usar innerHTML para limpiar el SVG overlay.
5 *        Solo .remove() elemento por elemento.
6 * REGLA: Solo crear lo que es nuevo, solo borrar lo que ya no existe.
7 * REGLA: #kpk-draw-overlay siempre debe ser el lastElementChild del SVG.
8 *
9 * Tipos SVG soportados:
10 *   lote-libre, lote-organico → #layer-lotes
11 *   calle                     → #layer-calles-asfalto
12 *   calle-curva-arq2          → #layer-calles-arq2
13 */
14
15'use strict';
16
17(function() {
18
19  const SVG_NS = 'http://www.w3.org/2000/svg';
20
21  // Mapeo tipo → id de capa SVG destino
22  const LAYER_MAP = {
23    'lote-libre':          'layer-lotes',
24    'lote-organico':       'layer-lotes',
25    'calle':               'layer-calles-asfalto',
26    'calle-curva-arq2':    'layer-calles-arq2',
27    'calle-retorno':       'layer-calles-asfalto',
28    'division-curva':      'layer-divisiones',
29    'franja-grupo':        'layer-lotes',
30    'franja-curva-grupo':  'layer-lotes',
31    'costura':             'layer-calles-bordes',
32    'kprano-capsule':      'layer-lotes',
33    'fila-variable-lote':  'layer-lotes',
34  };
35
36  /**
37   * Crea un elemento SVG con namespace correcto (Lección cross-browser).
38   */
39  function _createSVGEl(tag) {
40    return document.createElementNS(SVG_NS, tag);
41  }
42
43  /**
44   * Reenvía un evento al canvas de Pannellum para no bloquear el arrastre de la cámara
45   * cuando el usuario hace clic sobre un polígono con pointer-events: all.
46   */
47  function _forwardToPannellum(e) {
48    const canvas = document.querySelector('.pnlm-canvas');
49    if (canvas) {
50      // Clonamos el evento para el canvas
51      let clonedEvent;
52      if (e.constructor.name === 'TouchEvent') {
53        clonedEvent = new TouchEvent(e.type, e);
54      } else {
55        clonedEvent = new MouseEvent(e.type, e);
56      }
57      canvas.dispatchEvent(clonedEvent);
58    }
59  }
60
61  /**
62   * Crea el grupo SVG (<g>) para una línea.
63   * @param {Object} line — entrada de allDrawnLines
64   * @returns {SVGGElement}
65   */
66  function _createLineGroup(line) {
67    const g = _createSVGEl('g');
68    g.setAttribute('data-id', line.id);
69    g.setAttribute('data-tipo', line.tipo);
70    g.classList.add('ferrari-line-group');
71
72
73    const tipo = line.tipo;
74    const estado = line.estado || 'disponible';
75
76    const isLoteInteractivo = (tipo === 'lote-libre' || tipo === 'lote-organico' || tipo === 'franja-grupo' || tipo === 'kprano-capsule');
77    if (isLoteInteractivo) {
78      g.classList.add('lote-interactivo');
79      g.style.pointerEvents = 'all';
80      g.style.cursor = 'pointer';
81      g.style.userSelect = 'none';
82      g.style.webkitUserDrag = 'none';
83
84      // Forward mousedown/touchstart para permitir drag de la cámara
85      // Se quita passive para poder hacer preventDefault si es necesario en otras capas
86      g.addEventListener('mousedown', (e) => {
87          e.preventDefault(); // Evita que el navegador intente arrastrar el SVG (efecto fantasma)
88          _forwardToPannellum(e);
89      }, { passive: false });
90      
91      g.addEventListener('touchstart', (e) => {
92          _forwardToPannellum(e);
93      }, { passive: true });
94
95      // Click Nativo para la herramienta Smart Pin
96      g.addEventListener('click', (e) => {
97        if (window.FerrariAddPin && window.FerrariAddPin.isActive()) {
98          e.stopPropagation();
99          let coords = null;
100          try {
101            const viewer = window.Ferrari && window.Ferrari.viewer;
102            if (viewer) coords = viewer.mouseEventToCoords(e);
103          } catch (err) { /* usar centro automático como fallback */ }
104          window.FerrariAddPin.injectPin(line.id, coords);
105        }
106      });
107
108      // Glow futurista en vértices al pasar el cursor / pointer sobre el lote
109      g.addEventListener('pointerenter', () => {
110        g.classList.add('is-pointer');
111        if (window.FerrariSVGPaths && window.FerrariSVGPaths.setHoveredLote) {
112          window.FerrariSVGPaths.setHoveredLote(line.id);
113        }
114      });
115      g.addEventListener('pointerleave', () => {
116        g.classList.remove('is-pointer');
117        if (window.FerrariSVGPaths && window.FerrariSVGPaths.setHoveredLote) {
118          window.FerrariSVGPaths.setHoveredLote(null);
119        }
120      });
121    }
122
123    // ─── 2. Clases base ────────────────────────────────────────────────
124    g.classList.add(`path-${tipo}`);
125    
126    // Solo aplicar clase de estado premium si tiene el Smart Pin activado
127    if (line.hasSmartPin) {
128      g.classList.add(`status-${estado}`);
129    }
130
131    // ─── 3. Paths principales ─────────────────────────────────────────
132    if (tipo === 'calle' || tipo === 'calle-curva-arq2') {
133      const p1 = _createSVGEl('path');
134      p1.classList.add('path-calle-edge');
135      g.appendChild(p1);
136
137      const p2 = _createSVGEl('path');
138      p2.classList.add('path-calle-centro');
139      g.appendChild(p2);
140    } 
141    else if (tipo === 'calle-retorno') {
142      const p1 = _createSVGEl('path');
143      p1.classList.add('path-calle-edge');
144      g.appendChild(p1);
145    }
146    else if (tipo === 'division-curva') {
147      const p1 = _createSVGEl('path');
148      p1.classList.add('path-division-curva-line');
149      g.appendChild(p1);
150    }
151    else if (tipo === 'lote-libre' || tipo === 'lote-organico') {
152      const p1 = _createSVGEl('path');
153      p1.classList.add('path-lote');
154      g.appendChild(p1);
155    }
156    else {
157      const p1 = _createSVGEl('path');
158      p1.classList.add('path-default');
159      g.appendChild(p1);
160    }
161
162    // Smart Pins ahora son elementos HTML gestionados por f-smart-pins.js.
163    // No se crean <g> SVG para pins — evita repintado de capa SVG en cada frame.
164
165    return g;
166  }
167
168  /**
169   * syncSVGElements — Delta sync entre allDrawnLines y DOMCache.paths
170   *
171   * Algoritmo:
172   * 1. Construir Set de ids actuales en allDrawnLines
173   * 2. Para cada id en allDrawnLines que NO está en DOMCache → crear y añadir
174   * 3. Para cada id en DOMCache que NO está en allDrawnLines → .remove() y borrar del cache
175   * 4. Mover #kpk-draw-overlay al lastElementChild
176   * 5. Marcar lastSyncedVersion = DOMCache.version
177   */
178  function syncSVGElements() {
179    const lines    = window.allDrawnLines;
180    const cache    = window.DOMCache.paths;
181
182    // ── Paso 1: Set de ids actuales ──────────────────────────────────
183    const currentIds = new Set(lines.map(l => l.id));
184
185    // ── Paso 2: Crear elementos nuevos ───────────────────────────────
186    for (const line of lines) {
187      if (!cache.has(line.id)) {
188        const layerId = LAYER_MAP[line.tipo] || 'layer-lotes';
189        const layer   = document.getElementById(layerId);
190        if (!layer) {
191          console.warn('[Ferrari/SVGSync] Capa no encontrada:', layerId);
192          continue;
193        }
194
195        const gNode = _createLineGroup(line);
196        layer.appendChild(gNode);
197
198        cache.set(line.id, {
199          gNode,
200          pathEls: Array.from(gNode.querySelectorAll('path')),
201          pinGroup: null, // Smart Pins migrados a HTML overlay (f-smart-pins.js)
202          tipo: line.tipo
203        });
204        // Notificar al HTML overlay de Smart Pins que debe reconstruirse
205        if (window.FerrariSmartPins) window.FerrariSmartPins.markDirty();
206      }
207    }
208
209    // ── Paso 3: Eliminar elementos que ya no existen ─────────────────
210    for (const [id, entry] of cache.entries()) {
211      if (!currentIds.has(id)) {
212        // NUNCA innerHTML — usar .remove() directo (Lección 4)
213        if (entry.gNode && entry.gNode.parentNode) {
214          entry.gNode.remove();
215        }
216        cache.delete(id);
217      }
218    }
219
220    // ── Paso 4: Garantizar draw overlay como lastElementChild ─────────
221    const svg     = document.getElementById('loteo-svg');
222    const overlay = document.getElementById('kpk-draw-overlay');
223    if (svg && overlay && overlay !== svg.lastElementChild) {
224      svg.appendChild(overlay);
225    }
226
227    // ── Paso 5: Marcar como sincronizado ─────────────────────────────
228    window.DOMCache.lastSyncedVersion = window.DOMCache.version;
229  }
230
231  // ─── API PÚBLICA ────────────────────────────────────────────────────
232  window.FerrariSVGSync = { 
233    syncSVGElements
234  };
235
236  console.log('[Ferrari/SVGSync] ✓ Módulo inicializado');
237
238})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.