Content is user-generated and unverified.
// ============================================================================= // map-dashboard.component.ts — Composant parent : pilote N iframes de cartes // ============================================================================= // Démontre : // - Affichage de plusieurs iframes côte-à-côte // - Sélection de l'iframe active // - Dispatch d'actions (immédiates ou différées) // - Lecture réactive du store (signals) // ============================================================================= import { Component, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MapSignalStore, MapObject } from './map-store'; import { IframeManagerService } from './iframe-manager.service'; import { IframeMapHostDirective } from './iframe-map-host.directive'; @Component({ selector: 'app-map-dashboard', standalone: true, imports: [CommonModule, IframeMapHostDirective], template: ` <!-- ============================================================ --> <!-- Toolbar --> <!-- ============================================================ --> <header class="toolbar"> <h1>Multi-Map Dashboard</h1> <div class="toolbar-actions"> <button (click)="addSampleObjects()"> + Add sample objects </button> <button (click)="store.clearSelection()"> Clear selection ({{ store.selectedCount() }}) </button> <button (click)="broadcastFitBounds()"> Fit bounds (all maps) </button> </div> </header> <!-- ============================================================ --> <!-- Iframe tabs --> <!-- ============================================================ --> <nav class="iframe-tabs"> @for (inst of manager.instanceList(); track inst.id) { <button class="tab" [class.active]="inst.id === manager.activeId()" [class.ready]="inst.ready" (click)="manager.setActive(inst.id)" > <span class="status-dot" [class.ready]="inst.ready"></span> {{ inst.label }} @if (inst.pendingActions.length > 0) { <span class="badge">{{ inst.pendingActions.length }}</span> } </button> } </nav> <!-- ============================================================ --> <!-- Iframes grid --> <!-- ============================================================ --> <section class="iframe-grid"> <!-- Iframe 1 : carte OpenStreetMap --> <div class="iframe-wrapper" [class.active]="manager.activeId() === 'map-osm'"> <iframe [mapIframeHost]="'map-osm'" iframeLabel="OpenStreetMap" [src]="'/assets/map-osm.html'" sandbox="allow-scripts allow-same-origin" ></iframe> </div> <!-- Iframe 2 : carte Satellite --> <div class="iframe-wrapper" [class.active]="manager.activeId() === 'map-satellite'"> <iframe [mapIframeHost]="'map-satellite'" iframeLabel="Satellite" [src]="'/assets/map-satellite.html'" sandbox="allow-scripts allow-same-origin" ></iframe> </div> <!-- Iframe 3 : carte 3D --> <div class="iframe-wrapper" [class.active]="manager.activeId() === 'map-3d'"> <iframe [mapIframeHost]="'map-3d'" iframeLabel="3D View" [src]="'/assets/map-3d.html'" sandbox="allow-scripts allow-same-origin" ></iframe> </div> </section> <!-- ============================================================ --> <!-- State inspector panel --> <!-- ============================================================ --> <aside class="state-panel"> <h2>Store State</h2> <div class="stat"> <span class="label">Objects</span> <span class="value">{{ store.objectList().length }}</span> </div> <div class="stat"> <span class="label">Visible</span> <span class="value">{{ store.visibleObjects().length }}</span> </div> <div class="stat"> <span class="label">Selected</span> <span class="value">{{ store.selectedCount() }}</span> </div> <div class="stat"> <span class="label">Pending actions</span> <span class="value">{{ manager.pendingActionCount() }}</span> </div> <div class="stat"> <span class="label">Ready iframes</span> <span class="value"> {{ manager.readyInstances().length }} / {{ manager.instanceList().length }} </span> </div> <h3>Objects</h3> <ul class="object-list"> @for (obj of store.objectList(); track obj.id) { <li [class.selected]="store.selection().selectedIds.has(obj.id)" (click)="store.toggleSelection(obj.id)" > <span class="obj-type">{{ obj.type }}</span> {{ obj.id }} @if (!obj.visible) { <span class="hidden-badge">hidden</span> } </li> } </ul> </aside> `, styles: [` :host { display: grid; grid-template-columns: 1fr 280px; grid-template-rows: auto auto 1fr; height: 100vh; gap: 0; font-family: 'IBM Plex Sans', system-ui, sans-serif; background: #0f1117; color: #e2e4e9; } /* Toolbar */ .toolbar { grid-column: 1 / -1; display: flex; align-items: center; justify-content: space-between; padding: 12px 20px; background: #161921; border-bottom: 1px solid #2a2d37; } .toolbar h1 { font-size: 16px; font-weight: 600; letter-spacing: -0.02em; } .toolbar-actions { display: flex; gap: 8px; } .toolbar-actions button { padding: 6px 14px; border-radius: 6px; border: 1px solid #3a3d47; background: #1e2029; color: #c8cad0; font-size: 13px; cursor: pointer; transition: all 0.15s; } .toolbar-actions button:hover { background: #2a2d37; border-color: #5a5d67; } /* Tabs */ .iframe-tabs { grid-column: 1 / 2; display: flex; gap: 2px; padding: 8px 20px; background: #131520; } .tab { display: flex; align-items: center; gap: 6px; padding: 6px 14px; border: none; background: transparent; color: #7a7d87; font-size: 13px; cursor: pointer; border-radius: 6px; transition: all 0.15s; } .tab:hover { background: #1e2029; color: #c8cad0; } .tab.active { background: #1e2029; color: #fff; } .status-dot { width: 8px; height: 8px; border-radius: 50%; background: #555; transition: background 0.3s; } .status-dot.ready { background: #34d399; } .badge { background: #f59e0b; color: #000; font-size: 10px; font-weight: 700; padding: 1px 6px; border-radius: 10px; } /* Iframe grid */ .iframe-grid { grid-column: 1 / 2; display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 4px; padding: 4px; background: #0f1117; overflow: hidden; } .iframe-wrapper { position: relative; border-radius: 8px; overflow: hidden; border: 2px solid transparent; transition: border-color 0.2s; } .iframe-wrapper.active { border-color: #6366f1; } .iframe-wrapper iframe { width: 100%; height: 100%; min-height: 400px; border: none; background: #1a1c24; } /* State panel */ .state-panel { grid-column: 2 / 3; grid-row: 2 / 4; background: #161921; border-left: 1px solid #2a2d37; padding: 16px; overflow-y: auto; } .state-panel h2 { font-size: 13px; text-transform: uppercase; letter-spacing: 0.08em; color: #6366f1; margin-bottom: 16px; } .state-panel h3 { font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; color: #7a7d87; margin: 20px 0 8px; } .stat { display: flex; justify-content: space-between; padding: 6px 0; font-size: 13px; border-bottom: 1px solid #1e2029; } .stat .label { color: #7a7d87; } .stat .value { font-weight: 600; font-variant-numeric: tabular-nums; } .object-list { list-style: none; padding: 0; margin: 0; } .object-list li { display: flex; align-items: center; gap: 8px; padding: 6px 8px; font-size: 12px; border-radius: 4px; cursor: pointer; transition: background 0.1s; } .object-list li:hover { background: #1e2029; } .object-list li.selected { background: #2d2566; } .obj-type { font-size: 10px; text-transform: uppercase; padding: 2px 6px; border-radius: 3px; background: #2a2d37; color: #9ca3af; } .hidden-badge { font-size: 10px; color: #f59e0b; margin-left: auto; } `], }) export class MapDashboardComponent { readonly store = inject(MapSignalStore); readonly manager = inject(IframeManagerService); private objectCounter = 0; /** Ajoute des objets de démo dans le store */ addSampleObjects(): void { const samples: MapObject[] = [ { id: `marker-${++this.objectCounter}`, type: 'marker', coordinates: [2.3522 + Math.random() * 0.05, 48.8566 + Math.random() * 0.05], properties: { name: `Point ${this.objectCounter}` }, visible: true, }, { id: `polygon-${++this.objectCounter}`, type: 'polygon', coordinates: [ [2.34, 48.85], [2.36, 48.85], [2.36, 48.87], [2.34, 48.87], ], properties: { name: `Zone ${this.objectCounter}` }, visible: true, }, ]; this.store.upsertMany(samples); } /** Broadcast FIT_BOUNDS à toutes les iframes */ broadcastFitBounds(): void { this.manager.broadcast({ type: 'FIT_BOUNDS', payload: { bounds: this.store.visibleObjects().flatMap(o => Array.isArray(o.coordinates[0]) ? o.coordinates : [o.coordinates], ), }, deduplicate: true, }); } }
Content is user-generated and unverified.
    Angular Multi-Map Dashboard Component Guide | Claude