Vistas
{#if views.length === 0}
Sin vistas
{:else} {#each views as view, idx (view.id)} {@const isViewSel = selected !== 'general' && (selected as any).viewId === view.id} {@const isViewActive = isViewSel && (selected as any).kind === 'view'}
{#each (view.hotspots ?? []) as hs (hs.id)} {@const isHsSel = selected !== 'general' && (selected as any).kind === 'hotspot' && (selected as any).viewId === view.id && (selected as any).hotspotId === hs.id} {@const hsLabel = getLabelStr(hs.label)}
{/each} {/each} {/if}
{#if selected === 'general'}

Configuración general

patchVisual({ basePath: e.currentTarget.value || undefined })} placeholder={catDefaults.basePath || 'Heredado del catálogo'} class="field-input text-xs font-mono" />
patchVisual({ fallback: e.currentTarget.value || undefined })} placeholder={catDefaults.fallback || 'Heredado del catálogo'} class="field-input text-xs font-mono" />
{#if views.length > 0}
{:else}

Añade vistas desde el panel lateral.

{/if}
{:else if selectedView && (selected as any).kind === 'view'} {@const view = selectedView} {@const isNew = newViewId === view.id}
{isNew ? 'Nueva vista' : (getLabelStr(view.label) || view.code)}
{#if isNew} {:else} {/if}
{#if isNew}
vw_ viewIdError = ''} onkeydown={e => { if (e.key === 'Enter') saveViewNew() }} placeholder="frontal, detalle-rueda…" class={['field-input font-mono text-xs flex-1', viewIdError ? 'border-red-600' : ''].join(' ')} />
{#if viewIdError}{viewIdError}{/if} {:else} {view.id} {/if}
{ updateView(view.id, { code: e.currentTarget.value }) validateViewField('code', e.currentTarget.value) }} placeholder="front, detail-wheel…" class={['field-input font-mono text-xs', codeError ? 'border-red-600' : ''].join(' ')} /> {#if codeError}{codeError}{/if}
{ const v = parseInt(e.currentTarget.value) || 0 updateView(view.id, { width: v }) validateViewField('width', v) }} placeholder="800" class={['field-input text-xs', widthError ? 'border-red-600' : ''].join(' ')} /> {#if widthError}{widthError}{/if}
{ const v = parseInt(e.currentTarget.value) || 0 updateView(view.id, { height: v }) validateViewField('height', v) }} placeholder="600" class={['field-input text-xs', heightError ? 'border-red-600' : ''].join(' ')} /> {#if heightError}{heightError}{/if}
updateView(view.id, { label: v as any })} />
updateView(view.id, { icon: e.currentTarget.value || undefined })} placeholder="car-front, image, eye…" class="field-input text-xs font-mono" />
{#if view.config.strategy === 'static'}
updateView(view.id, { config: { ...view.config, url: e.currentTarget.value } as any })} placeholder="images/front.jpg" class="field-input font-mono text-xs" />
{:else if view.config.strategy === 'dynamic'}
updateView(view.id, { config: { ...view.config, template: e.currentTarget.value } as any })} placeholder={"car_{at_color}.jpg"} class="field-input font-mono text-xs" />
{:else if view.config.strategy === 'api'}
updateView(view.id, { config: { ...view.config, endpoint: e.currentTarget.value } as any })} placeholder="https://render.example.com" class="field-input font-mono text-xs" />
{:else if view.config.strategy === 'd3'}
updateView(view.id, { config: { ...view.config, modelUrl: e.currentTarget.value } as any })} placeholder="models/object.glb" class="field-input font-mono text-xs" />
{:else}
Editable en modo avanzado
{/if}
updateView(view.id, { metadata: Object.keys(m).filter(k => k !== 'order').length === 0 ? undefined : m })} />
{:else if selectedView && selectedHotspot && (selected as any).kind === 'hotspot'} {@const view = selectedView} {@const hs = selectedHotspot} {@const isNew = newHotspotId === hs.id}
{isNew ? 'Nuevo hotspot' : (getLabelStr(hs.label) || hs.id)}
{#if isNew} {:else} {/if}
{#if isNew}
hs_ hotspotIdError = ''} onkeydown={e => { if (e.key === 'Enter') saveHotspotNew() }} placeholder="rueda, interior…" class={['field-input font-mono text-xs flex-1', hotspotIdError ? 'border-red-600' : ''].join(' ')} />
{#if hotspotIdError}{hotspotIdError}{/if} {:else} {hs.id} {/if}
updateHotspot(view.id, hs.id, { label: v as any })} />
updateHotspot(view.id, hs.id, { targetAtt: (e.currentTarget.value || undefined) as any })} placeholder="at_color…" class="field-input font-mono text-xs" />
{ const v = clampPos(parseFloat(e.currentTarget.value) || 0) updateHotspot(view.id, hs.id, { posX: v }) validateHotspotField('posX', v) }} placeholder="50" class={['field-input font-mono text-xs', posXError ? 'border-red-600' : ''].join(' ')} /> {#if posXError}{posXError}{/if}
{ const v = clampPos(parseFloat(e.currentTarget.value) || 0) updateHotspot(view.id, hs.id, { posY: v }) validateHotspotField('posY', v) }} placeholder="50" class={['field-input font-mono text-xs', posYError ? 'border-red-600' : ''].join(' ')} /> {#if posYError}{posYError}{/if}
{ updateHotspot(view.id, hs.id, { color: e.currentTarget.value }) validateHotspotField('color', e.currentTarget.value) }} class="h-8 w-10 rounded border border-gray-800 bg-transparent cursor-pointer p-0.5" /> { updateHotspot(view.id, hs.id, { color: e.currentTarget.value || undefined }) validateHotspotField('color', e.currentTarget.value) }} placeholder="#4f46e5" class={['field-input font-mono text-xs flex-1', colorError ? 'border-red-600' : ''].join(' ')} />
{#if colorError}{colorError}{/if}
updateHotspot(view.id, hs.id, { radius: parseInt(e.currentTarget.value) || undefined })} placeholder="16" class="field-input text-xs" />
updateHotspot(view.id, hs.id, { zIndex: parseInt(e.currentTarget.value) || undefined })} placeholder="10" class="field-input text-xs" />
updateHotspot(view.id, hs.id, { tooltip: (Object.keys(v).length ? v : undefined) as any })} />
updateHotspot(view.id, hs.id, { metadata: Object.keys(m).filter(k => k !== 'order').length === 0 ? undefined : m })} />
{/if}
{#if showPreview && previewView}
Preview
{#if previewUrl} {getLabelStr(previewView.label) {:else}
{pickingPos ? 'Haz click para posicionar' : 'Sin imagen'}
{/if} {#each (previewView.hotspots ?? []) as marker (marker.id)} {@const isActive = selectedHotspot?.id === marker.id} {@const mX = typeof marker.posX === 'number' ? marker.posX : parseFloat(String(marker.posX)) || 0} {@const mY = typeof marker.posY === 'number' ? marker.posY : parseFloat(String(marker.posY)) || 0} {@const size = (marker.radius ?? 8) * 2}
{/each}
{previewView.width || '?'} x {previewView.height || '?'} px {#if pickingPos} Click en imagen para posicionar {/if}
{/if}