humanmap-vas 1.0.22 → 1.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/humanmap-vas-standalone.js +350 -14
- package/package.json +1 -1
@@ -6,12 +6,18 @@
|
|
6
6
|
.hm-toolbar { display:grid; grid-template-columns: auto 1fr auto; align-items:center; gap:8px; padding:8px 10px; border-bottom:1px solid #eef2f7; background:#fafafa; }
|
7
7
|
.hm-center { text-align:center; font-weight:600; color:#1f2937; }
|
8
8
|
.hm-toolbar select, .hm-toolbar button { appearance:none; border:1px solid #d1d5db; border-radius:10px; padding:6px 10px; background:#fff; cursor:pointer; font-weight:500; }
|
9
|
-
.hm-canvas-wrap { position:relative; width:100%; height:
|
9
|
+
.hm-canvas-wrap { position:relative; width:100%; height:600px; margin:auto; aspect-ratio: 2/3; background:#fff; }
|
10
10
|
svg.hm-svg { position:absolute; inset:0; width:100%; height:100%; }
|
11
11
|
.zone { fill: rgba(31,41,55,0); transition: fill 120ms ease; cursor: pointer; }
|
12
12
|
.zone:hover { fill: rgba(31,41,55,0.22); }
|
13
|
+
.zone.readonly { cursor: default; }
|
14
|
+
.zone.readonly:not(.selected):hover { fill: rgba(31,41,55,0); }
|
13
15
|
.zone.selected { fill: rgba(31,41,55,0.36); }
|
16
|
+
.hm-all-label { fill: #111827; font-weight: 700; font-size: 36px; text-anchor: middle; dominant-baseline: middle; }
|
14
17
|
.label { fill:#0a0a0a; font-size:36px; pointer-events: none; user-select: none; text-anchor: middle; dominant-baseline: middle; font-weight:800; }
|
18
|
+
.hm-print-btn { position: absolute; top: 10px; right: 10px; background: rgba(17,24,39,0.85); color: #f9fafb; border: none; border-radius: 8px; cursor: pointer; font-size: 18px; padding: 6px 10px; box-shadow: 0 2px 4px rgba(0,0,0,0.3); transition: opacity 0.25s ease, background 0.2s ease; opacity: 0; pointer-events: none; }
|
19
|
+
.hm-canvas-wrap:hover .hm-print-btn { opacity: 1; pointer-events: auto; }
|
20
|
+
.hm-print-btn:hover { background: rgba(37,99,235,0.9); }
|
15
21
|
`;
|
16
22
|
|
17
23
|
// ───────────────────────────────────────────────────────────────────────────
|
@@ -117,6 +123,7 @@
|
|
117
123
|
this._view=this.getAttribute('view') || 'head_right';
|
118
124
|
this._zones=ZONES;
|
119
125
|
this._selected=new Set();
|
126
|
+
this._readOnly = this.hasAttribute('read-only') && this.getAttribute('read-only') !== 'false';
|
120
127
|
|
121
128
|
this._upgradeProperty('selectedIds');
|
122
129
|
this._upgradeProperty('selectedZones');
|
@@ -161,9 +168,16 @@
|
|
161
168
|
}
|
162
169
|
}
|
163
170
|
|
164
|
-
connectedCallback(){
|
171
|
+
connectedCallback(){
|
172
|
+
this._renderShell();
|
173
|
+
this._renderCanvas();
|
174
|
+
this.dispatchEvent(new CustomEvent('human-map-vas:ready'));
|
175
|
+
this.dispatchEvent(new CustomEvent('human-map-vas:readonly-change', {
|
176
|
+
detail: { readOnly: this._readOnly }
|
177
|
+
}));
|
178
|
+
}
|
165
179
|
|
166
|
-
static get observedAttributes() { return ['view', 'img-root']; }
|
180
|
+
static get observedAttributes() { return ['view', 'img-root', 'read-only']; }
|
167
181
|
|
168
182
|
attributeChangedCallback (name, oldValue, newValue) {
|
169
183
|
if (oldValue === newValue) return;
|
@@ -187,6 +201,34 @@
|
|
187
201
|
if (this._root) this._renderCanvas();
|
188
202
|
return;
|
189
203
|
}
|
204
|
+
|
205
|
+
if (name === 'read-only') {
|
206
|
+
this._readOnly = newValue === 'true' || newValue === true;
|
207
|
+
|
208
|
+
if (this._root) {
|
209
|
+
// Refrescar la vista completamente según el modo
|
210
|
+
if (this._view === 'all') {
|
211
|
+
this._renderAllViews();
|
212
|
+
} else {
|
213
|
+
this._renderCanvas();
|
214
|
+
}
|
215
|
+
|
216
|
+
// Actualizar la visibilidad de toolbar y botón de impresión
|
217
|
+
const toolbar = this._root.querySelector('.hm-toolbar');
|
218
|
+
const printBtn = this.shadowRoot.getElementById('printBtn');
|
219
|
+
|
220
|
+
if (this._view === 'all' && this._readOnly) {
|
221
|
+
if (toolbar) toolbar.style.display = 'none';
|
222
|
+
if (printBtn) printBtn.style.display = 'block';
|
223
|
+
} else {
|
224
|
+
if (toolbar) toolbar.style.display = '';
|
225
|
+
if (printBtn) printBtn.style.display = 'none';
|
226
|
+
}
|
227
|
+
}
|
228
|
+
|
229
|
+
return;
|
230
|
+
}
|
231
|
+
|
190
232
|
}
|
191
233
|
|
192
234
|
// Devuelve solo IDs seleccionados
|
@@ -198,7 +240,13 @@
|
|
198
240
|
set selectedIds(ids) {
|
199
241
|
if (!Array.isArray(ids)) return;
|
200
242
|
this._selected = new Set(ids);
|
201
|
-
|
243
|
+
|
244
|
+
if (this._root) {
|
245
|
+
// Si está en modo global, renderizamos todas las vistas
|
246
|
+
if (this._view === 'all') this._renderAllViews();
|
247
|
+
else this._renderZones();
|
248
|
+
this._emit();
|
249
|
+
}
|
202
250
|
}
|
203
251
|
|
204
252
|
// Devuelve objetos completos (id, code, label, view)
|
@@ -242,7 +290,11 @@
|
|
242
290
|
this._root=document.createElement('div');
|
243
291
|
this._root.className='hm';
|
244
292
|
|
245
|
-
const opts=
|
293
|
+
const opts = [
|
294
|
+
`<option value="all">Todas las vistas</option>`,
|
295
|
+
...VIEWS.map(v => `<option value="${v.id}">${v.label}</option>`)
|
296
|
+
].join('');
|
297
|
+
|
246
298
|
this._root.innerHTML=`
|
247
299
|
<div class="hm-toolbar">
|
248
300
|
<button id="prev">◀</button>
|
@@ -259,6 +311,7 @@
|
|
259
311
|
<g id="bg"></g>
|
260
312
|
<g id="zones"></g>
|
261
313
|
</svg>
|
314
|
+
<button id="printBtn" title="Imprimir vista" class="hm-print-btn">🖨️</button>
|
262
315
|
</div>`;
|
263
316
|
this.shadowRoot.append(style,this._root);
|
264
317
|
|
@@ -277,6 +330,15 @@
|
|
277
330
|
this._els.prev.addEventListener('click',()=>this._cycle(-1));
|
278
331
|
this._els.next.addEventListener('click',()=>this._cycle(1));
|
279
332
|
this._els.reset.addEventListener('click',()=>this.clear());
|
333
|
+
this._els.printBtn = this.shadowRoot.getElementById('printBtn');
|
334
|
+
this._els.printBtn.addEventListener('click', () => {
|
335
|
+
this._els.printBtn.style.transform = 'scale(0.94)';
|
336
|
+
setTimeout(() => {
|
337
|
+
this._els.printBtn.style.transform = '';
|
338
|
+
this._printCanvasOnly(); // ⬅️ imprime solo el área actual
|
339
|
+
}, 120);
|
340
|
+
});
|
341
|
+
|
280
342
|
}
|
281
343
|
|
282
344
|
_cycle(dir){
|
@@ -286,8 +348,55 @@
|
|
286
348
|
}
|
287
349
|
|
288
350
|
_renderCanvas(){
|
289
|
-
|
290
|
-
|
351
|
+
// Ocultar toolbar si está en modo global y solo lectura
|
352
|
+
if (this._els && this._root) {
|
353
|
+
const toolbar = this._root.querySelector('.hm-toolbar');
|
354
|
+
if (this._view === 'all' && this._readOnly) {
|
355
|
+
toolbar.style.display = 'none';
|
356
|
+
} else {
|
357
|
+
toolbar.style.display = '';
|
358
|
+
}
|
359
|
+
}
|
360
|
+
|
361
|
+
// Mostrar botón de impresión solo en modo global + solo lectura
|
362
|
+
const printBtn = this.shadowRoot.getElementById('printBtn');
|
363
|
+
if (printBtn) {
|
364
|
+
if (this._view === 'all' && this._readOnly) {
|
365
|
+
printBtn.style.display = 'block';
|
366
|
+
} else {
|
367
|
+
printBtn.style.display = 'none';
|
368
|
+
}
|
369
|
+
}
|
370
|
+
|
371
|
+
// Limpia cualquier render anterior (modo all o vista única)
|
372
|
+
if (this._els && this._els.svg) {
|
373
|
+
this._els.svg.innerHTML = `
|
374
|
+
<defs id="defs"></defs>
|
375
|
+
<g id="bg"></g>
|
376
|
+
<g id="zones"></g>
|
377
|
+
`;
|
378
|
+
this._els.bg = this._els.svg.querySelector('#bg');
|
379
|
+
this._els.zones = this._els.svg.querySelector('#zones');
|
380
|
+
}
|
381
|
+
|
382
|
+
// 🆕 Detección de modo global
|
383
|
+
if (this._view === 'all') {
|
384
|
+
this._renderAllViews();
|
385
|
+
this._els.cur.textContent = 'Todas las vistas';
|
386
|
+
this._els.picker.value = 'all';
|
387
|
+
this._els.prev.disabled = true;
|
388
|
+
this._els.next.disabled = true;
|
389
|
+
this._els.reset.disabled = this._readOnly;
|
390
|
+
return;
|
391
|
+
} else {
|
392
|
+
this._els.prev.disabled = false;
|
393
|
+
this._els.next.disabled = false;
|
394
|
+
this._els.reset.disabled = false;
|
395
|
+
}
|
396
|
+
|
397
|
+
// --- Render normal ---
|
398
|
+
const layout = VIEW_LAYOUTS[this._view];
|
399
|
+
const v = VIEWS.find(v => v.id === this._view);
|
291
400
|
if(!layout||!v)return;
|
292
401
|
this._els.cur.textContent=v.label;
|
293
402
|
this._els.picker.value=v.id;
|
@@ -298,6 +407,112 @@
|
|
298
407
|
this._renderZones();
|
299
408
|
}
|
300
409
|
|
410
|
+
_renderAllViews() {
|
411
|
+
const svg = this._els.svg;
|
412
|
+
svg.innerHTML = ''; // limpiar
|
413
|
+
|
414
|
+
// Creamos un grupo por cada vista
|
415
|
+
const cols = 2, gap = 40;
|
416
|
+
const cellW = 480, cellH = 720;
|
417
|
+
const gridW = cols * (cellW + gap);
|
418
|
+
const gridH = Math.ceil(VIEWS.length / cols) * (cellH + gap);
|
419
|
+
|
420
|
+
svg.setAttribute('viewBox', `0 0 ${gridW} ${gridH}`);
|
421
|
+
const gRoot = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
422
|
+
svg.appendChild(gRoot);
|
423
|
+
|
424
|
+
VIEWS.forEach((v, i) => {
|
425
|
+
const layout = VIEW_LAYOUTS[v.id] || { vb: [0, 0, 1024, 1536], y: 0, h: 1536, rotate: 0 };
|
426
|
+
const row = Math.floor(i / cols);
|
427
|
+
const col = i % cols;
|
428
|
+
const gx = col * (cellW + gap);
|
429
|
+
const gy = row * (cellH + gap);
|
430
|
+
|
431
|
+
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
432
|
+
g.setAttribute('transform', `translate(${gx}, ${gy}) scale(0.45)`);
|
433
|
+
|
434
|
+
// Fondo (imagen)
|
435
|
+
const img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
|
436
|
+
img.setAttribute('href', this._bg[v.id]);
|
437
|
+
img.setAttribute('x', '0');
|
438
|
+
img.setAttribute('y', layout?.y || 0);
|
439
|
+
img.setAttribute('width', layout?.vb[2] || 1024);
|
440
|
+
img.setAttribute('height', layout?.h || 1536);
|
441
|
+
img.setAttribute('preserveAspectRatio', 'xMidYMid meet');
|
442
|
+
g.appendChild(img);
|
443
|
+
|
444
|
+
// Grupo para las zonas (permite rotar sin afectar la imagen)
|
445
|
+
const gZones = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
446
|
+
|
447
|
+
// Rotar solo las zonas del cuello
|
448
|
+
let rotation = 0;
|
449
|
+
if (v.id === 'neck_right') rotation = 12;
|
450
|
+
if (v.id === 'neck_left') rotation = -12;
|
451
|
+
if (rotation !== 0) {
|
452
|
+
gZones.setAttribute(
|
453
|
+
'transform',
|
454
|
+
`rotate(${rotation}, ${layout.vb[2]*0.55}, ${layout.vb[3]*0.45})`
|
455
|
+
);
|
456
|
+
}
|
457
|
+
|
458
|
+
// --- Dibujar zonas ---
|
459
|
+
const zones = this._zones.filter(z => z.view === v.id);
|
460
|
+
zones.forEach(z => {
|
461
|
+
const { x, y, w, h } = z.shape;
|
462
|
+
|
463
|
+
// Zona rectangular
|
464
|
+
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
465
|
+
rect.setAttribute('x', x * layout.vb[2]);
|
466
|
+
rect.setAttribute('y', y * layout.vb[3]);
|
467
|
+
rect.setAttribute('width', w * layout.vb[2]);
|
468
|
+
rect.setAttribute('height', h * layout.vb[3]);
|
469
|
+
rect.setAttribute('rx', Math.min(w, h) * layout.vb[2] * 0.1);
|
470
|
+
rect.classList.add('zone');
|
471
|
+
if (this._selected.has(z.id)) rect.classList.add('selected');
|
472
|
+
if (this._readOnly) {
|
473
|
+
rect.classList.add('readonly');
|
474
|
+
} else {
|
475
|
+
rect.addEventListener('click', e => {
|
476
|
+
e.stopPropagation();
|
477
|
+
if (this._selected.has(z.id)) this._selected.delete(z.id);
|
478
|
+
else this._selected.add(z.id);
|
479
|
+
this._renderAllViews(); // refrescar vista global
|
480
|
+
this._emit();
|
481
|
+
});
|
482
|
+
}
|
483
|
+
gZones.appendChild(rect);
|
484
|
+
|
485
|
+
// Label centrado
|
486
|
+
const t = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
487
|
+
t.textContent = z.code;
|
488
|
+
t.setAttribute('x', (x + w / 2) * layout.vb[2]);
|
489
|
+
t.setAttribute('y', (y + h / 2) * layout.vb[3]);
|
490
|
+
t.setAttribute('fill', '#0a0a0a');
|
491
|
+
t.setAttribute('font-size', '32');
|
492
|
+
t.setAttribute('font-weight', '700');
|
493
|
+
t.setAttribute('text-anchor', 'middle');
|
494
|
+
t.setAttribute('dominant-baseline', 'middle');
|
495
|
+
t.setAttribute('pointer-events', 'none');
|
496
|
+
gZones.appendChild(t);
|
497
|
+
});
|
498
|
+
|
499
|
+
// Añadir grupo de zonas dentro del grupo principal
|
500
|
+
g.appendChild(gZones);
|
501
|
+
|
502
|
+
|
503
|
+
// Label de la vista
|
504
|
+
const t = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
505
|
+
t.textContent = v.label;
|
506
|
+
t.setAttribute('x', layout.vb[2] / 2);
|
507
|
+
t.setAttribute('y', 80);
|
508
|
+
t.setAttribute('class', 'hm-all-label');
|
509
|
+
g.appendChild(t);
|
510
|
+
|
511
|
+
|
512
|
+
gRoot.appendChild(g);
|
513
|
+
});
|
514
|
+
}
|
515
|
+
|
301
516
|
_renderZones(){
|
302
517
|
const g=this._els.zones;g.innerHTML='';
|
303
518
|
const layout=VIEW_LAYOUTS[this._view];
|
@@ -309,19 +524,34 @@
|
|
309
524
|
Z.forEach(z=>{
|
310
525
|
const{x,y,w,h}=z.shape;
|
311
526
|
const rect=document.createElementNS('http://www.w3.org/2000/svg','rect');
|
527
|
+
|
312
528
|
rect.setAttribute('x',x*vw);rect.setAttribute('y',y*vh);
|
313
529
|
rect.setAttribute('width',w*vw);rect.setAttribute('height',h*vh);
|
314
530
|
rect.setAttribute('rx',Math.min(w*vw,h*vh)*0.1);
|
531
|
+
|
315
532
|
rect.classList.add('zone');
|
316
|
-
|
317
|
-
rect.
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
}
|
533
|
+
|
534
|
+
if (this._selected.has(z.id)) rect.classList.add('selected');
|
535
|
+
|
536
|
+
// Si está en modo lectura, añadir clase visual
|
537
|
+
if (this._readOnly) {
|
538
|
+
rect.classList.add('readonly');
|
539
|
+
} else {
|
540
|
+
rect.classList.remove('readonly');
|
541
|
+
rect.addEventListener('click', e => {
|
542
|
+
e.stopPropagation();
|
543
|
+
if (this._selected.has(z.id)) this._selected.delete(z.id);
|
544
|
+
else this._selected.add(z.id);
|
545
|
+
this._renderZones();
|
546
|
+
this._emit();
|
547
|
+
});
|
548
|
+
}
|
549
|
+
|
550
|
+
|
323
551
|
g.appendChild(rect);
|
552
|
+
|
324
553
|
const t=document.createElementNS('http://www.w3.org/2000/svg','text');
|
554
|
+
|
325
555
|
t.setAttribute('x',(x+w/2)*vw);
|
326
556
|
t.setAttribute('y',(y+h/2)*vh);
|
327
557
|
t.textContent=z.code;
|
@@ -330,6 +560,112 @@
|
|
330
560
|
});
|
331
561
|
}
|
332
562
|
|
563
|
+
// Imprime solo el área visible (hm-canvas-wrap) en una nueva ventana
|
564
|
+
_printCanvasOnly() {
|
565
|
+
if (!this._els || !this._els.svg) return;
|
566
|
+
|
567
|
+
// Clonar el SVG actual completo (vistas individuales o "all")
|
568
|
+
const clone = this._els.svg.cloneNode(true);
|
569
|
+
|
570
|
+
// Asegurar rutas absolutas en <image> (importante para que se muestren)
|
571
|
+
clone.querySelectorAll('image').forEach(img => {
|
572
|
+
const href = img.getAttribute('href') || img.getAttributeNS('http://www.w3.org/1999/xlink', 'href');
|
573
|
+
if (href) {
|
574
|
+
const a = document.createElement('a');
|
575
|
+
a.href = href;
|
576
|
+
const abs = a.href;
|
577
|
+
img.setAttribute('href', abs);
|
578
|
+
img.setAttributeNS('http://www.w3.org/1999/xlink', 'href', abs);
|
579
|
+
}
|
580
|
+
});
|
581
|
+
|
582
|
+
// Inyectar los estilos básicos directamente en el SVG (para conservar colores y transparencias)
|
583
|
+
const style = document.createElementNS('http://www.w3.org/2000/svg', 'style');
|
584
|
+
style.textContent = `
|
585
|
+
.zone { fill: rgba(31,41,55,0); cursor: pointer; transition: fill 120ms ease; }
|
586
|
+
.zone:hover { fill: rgba(31,41,55,0.22); }
|
587
|
+
.zone.selected { fill: rgba(31,41,55,0.36); }
|
588
|
+
.label { fill: #0a0a0a; font-size: 36px; font-weight: 800;
|
589
|
+
text-anchor: middle; dominant-baseline: middle; pointer-events: none; user-select: none; }
|
590
|
+
`;
|
591
|
+
clone.insertBefore(style, clone.firstChild);
|
592
|
+
|
593
|
+
const serializer = new XMLSerializer();
|
594
|
+
const svgMarkup = serializer.serializeToString(clone);
|
595
|
+
|
596
|
+
const isGlobal = this._view === 'all';
|
597
|
+
|
598
|
+
// HTML limpio con estilos mínimos
|
599
|
+
const html = `
|
600
|
+
<!DOCTYPE html>
|
601
|
+
<html lang="es">
|
602
|
+
<head>
|
603
|
+
<meta charset="utf-8">
|
604
|
+
<title>Impresión del mapa anatómico</title>
|
605
|
+
<style>
|
606
|
+
html, body {
|
607
|
+
margin: 0;
|
608
|
+
padding: 0mm;
|
609
|
+
background: #fff;
|
610
|
+
text-align: center;
|
611
|
+
}
|
612
|
+
svg {
|
613
|
+
width: 100%;
|
614
|
+
height: auto;
|
615
|
+
display: block;
|
616
|
+
}
|
617
|
+
.hm-all-label {
|
618
|
+
font-family: system-ui, sans-serif;
|
619
|
+
font-size: 48px;
|
620
|
+
font-weight: 800;
|
621
|
+
fill: #111827;
|
622
|
+
text-anchor: middle;
|
623
|
+
dominant-baseline: middle;
|
624
|
+
}
|
625
|
+
@page {
|
626
|
+
size: ${isGlobal ? 'A4 landscape' : 'A4 portrait'};
|
627
|
+
margin: 5mm;
|
628
|
+
}
|
629
|
+
</style>
|
630
|
+
</head>
|
631
|
+
<body>
|
632
|
+
${svgMarkup}
|
633
|
+
</body>
|
634
|
+
</html>
|
635
|
+
`;
|
636
|
+
|
637
|
+
// Abrir ventana y escribir el HTML
|
638
|
+
const printWin = window.open('', '_blank', 'width=1024,height=768');
|
639
|
+
if (!printWin) {
|
640
|
+
alert('El navegador bloqueó la ventana de impresión. Permite popups para continuar.');
|
641
|
+
return;
|
642
|
+
}
|
643
|
+
|
644
|
+
printWin.document.open();
|
645
|
+
printWin.document.write(html);
|
646
|
+
printWin.document.close();
|
647
|
+
|
648
|
+
// Esperar hasta que las imágenes del SVG estén listas
|
649
|
+
const waitForImages = () => {
|
650
|
+
const imgs = printWin.document.querySelectorAll('image');
|
651
|
+
const promises = Array.from(imgs).map(img => {
|
652
|
+
return new Promise(resolve => {
|
653
|
+
const test = new Image();
|
654
|
+
test.onload = test.onerror = resolve;
|
655
|
+
test.src = img.getAttribute('href') || img.getAttributeNS('http://www.w3.org/1999/xlink', 'href');
|
656
|
+
});
|
657
|
+
});
|
658
|
+
return Promise.all(promises);
|
659
|
+
};
|
660
|
+
|
661
|
+
waitForImages().then(() => {
|
662
|
+
printWin.focus();
|
663
|
+
printWin.print();
|
664
|
+
setTimeout(() => printWin.close(), 1000);
|
665
|
+
});
|
666
|
+
}
|
667
|
+
|
668
|
+
|
333
669
|
_emit(){this.dispatchEvent(new CustomEvent('human-map-vas:select',{detail:{selected:this.getSelected()}}));}
|
334
670
|
}
|
335
671
|
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "humanmap-vas",
|
3
|
-
"version": "1.0.
|
3
|
+
"version": "1.0.23",
|
4
4
|
"description": "**HumanMap VAS** es una librería web que permite graficar el cuerpo humano con vistas anatómicas interactivas para identificar zonas según el sistema VAS. Desarrollada como *Web Component standalone*, puede integrarse fácilmente en proyectos **HTML**, **Django**, o **Vue.js**.",
|
5
5
|
"main": "humanmap-vas-standalone.js",
|
6
6
|
"files": [
|