diff --git a/src/app/floor-plan-modal/floor-plan-modal.component.html b/src/app/floor-plan-modal/floor-plan-modal.component.html index abcfb881..7419bd09 100644 --- a/src/app/floor-plan-modal/floor-plan-modal.component.html +++ b/src/app/floor-plan-modal/floor-plan-modal.component.html @@ -18,9 +18,19 @@ [draggableImage]="false" [limitZoom]="'original image size'" backgroundColor="#ffffff"> - +
+ +
+ + +
+
diff --git a/src/app/floor-plan-modal/floor-plan-modal.component.scss b/src/app/floor-plan-modal/floor-plan-modal.component.scss index e0a56449..d317e75a 100644 --- a/src/app/floor-plan-modal/floor-plan-modal.component.scss +++ b/src/app/floor-plan-modal/floor-plan-modal.component.scss @@ -8,10 +8,60 @@ background: #ffffff; } -.floor-plan-zoom img { +.floor-plan-canvas { + position: relative; + width: 100%; + display: block; +} + +.floor-plan-canvas img { width: 100%; height: auto; display: block; -webkit-user-drag: none; user-select: none; } + +.floor-plan-pin { + position: absolute; + width: 0; + height: 0; + pointer-events: none; + z-index: 2; +} + +.floor-plan-pin-dot, +.floor-plan-pin-pulse { + position: absolute; + left: 50%; + top: 50%; + border-radius: 50%; + transform: translate(-50%, -50%); +} + +.floor-plan-pin-dot { + width: 10px; + height: 10px; + background: #DD04D2; + border: 1.5px solid #ffffff; + box-shadow: 0 0 0 1.5px rgba(0, 0, 0, 0.18), 0 1px 3px rgba(0, 0, 0, 0.45); +} + +.floor-plan-pin-pulse { + width: 10px; + height: 10px; + background: #DD04D2; + opacity: 0.5; + animation: floor-plan-pin-pulse 1.8s ease-out infinite; +} + +@keyframes floor-plan-pin-pulse { + 0% { + transform: translate(-50%, -50%) scale(1); + opacity: 0.45; + } + 100% { + transform: translate(-50%, -50%) scale(3); + opacity: 0; + } +} diff --git a/src/app/floor-plan-modal/floor-plan-modal.component.ts b/src/app/floor-plan-modal/floor-plan-modal.component.ts index 5533e2ff..b6d95c11 100644 --- a/src/app/floor-plan-modal/floor-plan-modal.component.ts +++ b/src/app/floor-plan-modal/floor-plan-modal.component.ts @@ -10,10 +10,17 @@ export class FloorPlanModalComponent { @Input() title!: string; @Input() imageSrc!: string; @Input() altText?: string; + @Input() pinXPct?: number; + @Input() pinYPct?: number; + @Input() pinLabel?: string; constructor(private modalCtrl: ModalController) {} close() { this.modalCtrl.dismiss(); } + + get hasPin(): boolean { + return this.pinXPct != null && this.pinYPct != null; + } } diff --git a/src/app/floor-plan-modal/floor-plan-modal.module.ts b/src/app/floor-plan-modal/floor-plan-modal.module.ts new file mode 100644 index 00000000..6346aa8f --- /dev/null +++ b/src/app/floor-plan-modal/floor-plan-modal.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from '@ionic/angular'; +import { PinchZoomModule } from '@ciag/ngx-pinch-zoom'; + +import { FloorPlanModalComponent } from './floor-plan-modal.component'; + +@NgModule({ + imports: [CommonModule, IonicModule, PinchZoomModule], + declarations: [FloorPlanModalComponent], + exports: [FloorPlanModalComponent], +}) +export class FloorPlanModalModule {} diff --git a/src/app/location-map/location-map.component.html b/src/app/location-map/location-map.component.html new file mode 100644 index 00000000..e74a7ccb --- /dev/null +++ b/src/app/location-map/location-map.component.html @@ -0,0 +1,24 @@ + diff --git a/src/app/location-map/location-map.component.scss b/src/app/location-map/location-map.component.scss new file mode 100644 index 00000000..8ca2b28a --- /dev/null +++ b/src/app/location-map/location-map.component.scss @@ -0,0 +1,123 @@ +.location-map { + display: flex; + align-items: center; + width: 100%; + background: var(--ion-card-background, #ffffff); + border: 1px solid var(--ion-color-step-150, rgba(0, 0, 0, 0.08)); + border-radius: 14px; + padding: 0; + margin: 0; + text-align: left; + cursor: pointer; + overflow: hidden; + transition: transform 0.15s ease, box-shadow 0.15s ease; +} + +.location-map:active { + transform: scale(0.985); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} + +.location-map-thumb { + position: relative; + flex: 0 0 144px; + width: 144px; + background: #ffffff; + border-right: 1px solid var(--ion-color-step-150, rgba(0, 0, 0, 0.08)); + overflow: hidden; + font-size: 0; +} + +.location-map-thumb img { + display: block; + width: 100%; + height: auto; +} + +.location-map-pin { + position: absolute; + width: 0; + height: 0; + pointer-events: none; +} + +.location-map-pin-dot, +.location-map-pin-pulse { + position: absolute; + left: 50%; + top: 50%; + border-radius: 50%; + transform: translate(-50%, -50%); +} + +.location-map-pin-dot { + width: 8px; + height: 8px; + background: #DD04D2; + border: 1.5px solid #ffffff; + box-shadow: 0 0 0 1.5px rgba(0, 0, 0, 0.18), 0 1px 2px rgba(0, 0, 0, 0.4); +} + +.location-map-pin-pulse { + width: 8px; + height: 8px; + background: #DD04D2; + opacity: 0.55; + animation: location-map-pin-pulse 1.8s ease-out infinite; +} + +@keyframes location-map-pin-pulse { + 0% { + transform: translate(-50%, -50%) scale(1); + opacity: 0.5; + } + 100% { + transform: translate(-50%, -50%) scale(2.6); + opacity: 0; + } +} + +.location-map-zoom-hint { + position: absolute; + bottom: 6px; + right: 6px; + width: 24px; + height: 24px; + border-radius: 50%; + background: rgba(0, 0, 0, 0.55); + color: #ffffff; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; +} + +.location-map-meta { + flex: 1 1 auto; + padding: 12px 14px; + display: flex; + flex-direction: column; + justify-content: center; + min-width: 0; +} + +.location-map-label { + font-size: 16px; + font-weight: 600; + color: var(--ion-text-color, #1a1a1a); + line-height: 1.25; +} + +.location-map-sublabel { + font-size: 13px; + color: var(--ion-color-step-650, #555); + margin-top: 2px; +} + +.location-map-floor { + font-size: 12px; + color: var(--ion-color-medium, #92949c); + margin-top: 4px; + text-transform: uppercase; + letter-spacing: 0.04em; +} diff --git a/src/app/location-map/location-map.component.ts b/src/app/location-map/location-map.component.ts new file mode 100644 index 00000000..e8c44e41 --- /dev/null +++ b/src/app/location-map/location-map.component.ts @@ -0,0 +1,36 @@ +import { Component, Input } from '@angular/core'; +import { ModalController } from '@ionic/angular'; + +import { FloorPlanModalComponent } from '../floor-plan-modal/floor-plan-modal.component'; + +@Component({ + selector: 'app-location-map', + templateUrl: './location-map.component.html', + styleUrls: ['./location-map.component.scss'], +}) +export class LocationMapComponent { + @Input() label!: string; + @Input() sublabel?: string; + @Input() floorTitle!: string; + @Input() thumbSrc!: string; + @Input() fullSrc!: string; + @Input() pinXPct!: number; + @Input() pinYPct!: number; + + constructor(private modalCtrl: ModalController) {} + + async openFull() { + const modal = await this.modalCtrl.create({ + component: FloorPlanModalComponent, + componentProps: { + title: this.floorTitle, + imageSrc: this.fullSrc, + altText: `${this.floorTitle} floor plan with ${this.label} highlighted`, + pinXPct: this.pinXPct, + pinYPct: this.pinYPct, + pinLabel: this.label, + }, + }); + await modal.present(); + } +} diff --git a/src/app/location-map/location-map.module.ts b/src/app/location-map/location-map.module.ts new file mode 100644 index 00000000..5252d9eb --- /dev/null +++ b/src/app/location-map/location-map.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from '@ionic/angular'; + +import { FloorPlanModalModule } from '../floor-plan-modal/floor-plan-modal.module'; + +import { LocationMapComponent } from './location-map.component'; + +@NgModule({ + imports: [CommonModule, IonicModule, FloorPlanModalModule], + declarations: [LocationMapComponent], + exports: [LocationMapComponent], +}) +export class LocationMapModule {} diff --git a/src/app/location-map/venue-locations.ts b/src/app/location-map/venue-locations.ts new file mode 100644 index 00000000..522b19fb --- /dev/null +++ b/src/app/location-map/venue-locations.ts @@ -0,0 +1,85 @@ +export interface VenueLocation { + key: string; + label: string; + sublabel?: string; + floorTitle: string; + thumbSrc: string; + fullSrc: string; + pinXPct: number; + pinYPct: number; +} + +const CONCOURSE_THUMB = 'assets/img/floor-plans/floor-plan-1-thumb.jpg'; +const CONCOURSE_FULL = 'assets/img/floor-plans/floor-plan-1.jpg'; +const CONCOURSE_TITLE = 'Concourse Level'; + +const UPPER_THUMB = 'assets/img/floor-plans/floor-plan-2-thumb.jpg'; +const UPPER_FULL = 'assets/img/floor-plans/floor-plan-2.jpg'; +const UPPER_TITLE = 'Upper Level'; + +const ARENA_THUMB = 'assets/img/floor-plans/floor-plan-3-thumb.jpg'; +const ARENA_FULL = 'assets/img/floor-plans/floor-plan-3.jpg'; +const ARENA_TITLE = 'Arena & Exhibit Halls'; + +export const VENUE_LOCATIONS: Record = { + infoDesk: { + key: 'infoDesk', + label: 'Information Desk', + sublabel: 'Promenade Lobby (Main Entrance)', + floorTitle: CONCOURSE_TITLE, + thumbSrc: CONCOURSE_THUMB, + fullSrc: CONCOURSE_FULL, + pinXPct: 55, + pinYPct: 53, + }, + registration: { + key: 'registration', + label: 'Registration', + sublabel: 'Promenade Lobby (Main Entrance)', + floorTitle: CONCOURSE_TITLE, + thumbSrc: CONCOURSE_THUMB, + fullSrc: CONCOURSE_FULL, + pinXPct: 67, + pinYPct: 58, + }, + quietRoom: { + key: 'quietRoom', + label: 'Attendee Quiet Room', + sublabel: 'VIP Room A & B', + floorTitle: CONCOURSE_TITLE, + thumbSrc: CONCOURSE_THUMB, + fullSrc: CONCOURSE_FULL, + pinXPct: 58, + pinYPct: 36, + }, + swagPickup: { + key: 'swagPickup', + label: 'Swag Pickup', + sublabel: 'Expo Hall A & B', + floorTitle: ARENA_TITLE, + thumbSrc: ARENA_THUMB, + fullSrc: ARENA_FULL, + pinXPct: 42, + pinYPct: 68, + }, + lostAndFound: { + key: 'lostAndFound', + label: 'Lost & Found', + sublabel: 'Info Desk during open hours; Staff Office overnight', + floorTitle: UPPER_TITLE, + thumbSrc: UPPER_THUMB, + fullSrc: UPPER_FULL, + pinXPct: 40, + pinYPct: 58, + }, + staffOffice: { + key: 'staffOffice', + label: 'Staff Office', + sublabel: 'Room 203 A & B', + floorTitle: UPPER_TITLE, + thumbSrc: UPPER_THUMB, + fullSrc: UPPER_FULL, + pinXPct: 40, + pinYPct: 58, + }, +}; diff --git a/src/app/pages/conference-map/conference-map.module.ts b/src/app/pages/conference-map/conference-map.module.ts index a4582ad5..b8f7d07d 100644 --- a/src/app/pages/conference-map/conference-map.module.ts +++ b/src/app/pages/conference-map/conference-map.module.ts @@ -7,7 +7,7 @@ import { IonicModule } from '@ionic/angular'; import { PinchZoomModule } from '@ciag/ngx-pinch-zoom'; import { ExpoHallMapModule } from '../../expo-hall-map/expo-hall-map.module'; -import { FloorPlanModalComponent } from '../../floor-plan-modal/floor-plan-modal.component'; +import { FloorPlanModalModule } from '../../floor-plan-modal/floor-plan-modal.module'; import { ConferenceMapPageRoutingModule } from './conference-map-routing.module'; @@ -20,8 +20,9 @@ import { ConferenceMapPage } from './conference-map.page'; IonicModule, PinchZoomModule, ExpoHallMapModule, + FloorPlanModalModule, ConferenceMapPageRoutingModule ], - declarations: [ConferenceMapPage, FloorPlanModalComponent] + declarations: [ConferenceMapPage] }) export class ConferenceMapPageModule {} diff --git a/src/app/pages/conference-map/conference-map.page.html b/src/app/pages/conference-map/conference-map.page.html index 94b64a57..3547e596 100644 --- a/src/app/pages/conference-map/conference-map.page.html +++ b/src/app/pages/conference-map/conference-map.page.html @@ -3,7 +3,7 @@ - Conference Center + {{ pageTitle }} diff --git a/src/app/pages/conference-map/conference-map.page.ts b/src/app/pages/conference-map/conference-map.page.ts index 5907b112..000e8966 100644 --- a/src/app/pages/conference-map/conference-map.page.ts +++ b/src/app/pages/conference-map/conference-map.page.ts @@ -1,4 +1,5 @@ -import { Component, ViewChild } from '@angular/core'; +import { Component, OnInit, ViewChild } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; import { ModalController } from '@ionic/angular'; import { ExpoHallMapComponent } from '../../expo-hall-map/expo-hall-map.component'; @@ -20,12 +21,20 @@ interface FloorPlan { templateUrl: './conference-map.page.html', styleUrls: ['./conference-map.page.scss'], }) -export class ConferenceMapPage { +export class ConferenceMapPage implements OnInit { @ViewChild('expoMap') expoMap?: ExpoHallMapComponent; mapView: MapView = 'floor-plans'; + pageTitle = 'Conference Center'; floorPlans: FloorPlan[] = [ + { + id: 'campus', + title: 'Campus Overview', + subtitle: 'Convention center, hotels, halls, and entrances', + thumb: 'assets/img/floor-plans/campus-map-thumb.jpg', + full: 'assets/img/floor-plans/campus-map.jpg', + }, { id: 'concourse', title: 'Concourse Level', @@ -59,8 +68,23 @@ export class ConferenceMapPage { constructor( public liveUpdateService: LiveUpdateService, private modalCtrl: ModalController, + private route: ActivatedRoute, ) { } + ngOnInit() { + let r: ActivatedRoute | null = this.route; + while (r) { + const data = r.snapshot.data || {}; + if (data['initialView']) { + this.mapView = data['initialView'] as MapView; + } + if (data['pageTitle']) { + this.pageTitle = data['pageTitle']; + } + r = r.parent; + } + } + toggleExpoSearch() { this.expoMap?.toggleSearch(); } diff --git a/src/app/pages/expo-hall/expo-hall-routing.module.ts b/src/app/pages/expo-hall/expo-hall-routing.module.ts deleted file mode 100644 index 855acc9d..00000000 --- a/src/app/pages/expo-hall/expo-hall-routing.module.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { NgModule } from '@angular/core'; -import { Routes, RouterModule } from '@angular/router'; - -import { ExpoHallPage } from './expo-hall.page'; - -const routes: Routes = [ - { - path: '', - component: ExpoHallPage - } -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule], -}) -export class ExpoHallPageRoutingModule {} diff --git a/src/app/pages/expo-hall/expo-hall.module.ts b/src/app/pages/expo-hall/expo-hall.module.ts deleted file mode 100644 index d20d170c..00000000 --- a/src/app/pages/expo-hall/expo-hall.module.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; - -import { IonicModule } from '@ionic/angular'; - -import { ExpoHallMapModule } from '../../expo-hall-map/expo-hall-map.module'; - -import { ExpoHallPageRoutingModule } from './expo-hall-routing.module'; - -import { ExpoHallPage } from './expo-hall.page'; - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - IonicModule, - ExpoHallMapModule, - ExpoHallPageRoutingModule, - ], - declarations: [ExpoHallPage] -}) -export class ExpoHallPageModule {} diff --git a/src/app/pages/expo-hall/expo-hall.page.html b/src/app/pages/expo-hall/expo-hall.page.html deleted file mode 100644 index 0ad9022c..00000000 --- a/src/app/pages/expo-hall/expo-hall.page.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - Expo Hall - - - - - - - - - - - diff --git a/src/app/pages/expo-hall/expo-hall.page.scss b/src/app/pages/expo-hall/expo-hall.page.scss deleted file mode 100644 index 40132bde..00000000 --- a/src/app/pages/expo-hall/expo-hall.page.scss +++ /dev/null @@ -1,2 +0,0 @@ -// Page-level styles intentionally empty — all layout lives in the -// shared component. diff --git a/src/app/pages/expo-hall/expo-hall.page.ts b/src/app/pages/expo-hall/expo-hall.page.ts deleted file mode 100644 index 9d33c285..00000000 --- a/src/app/pages/expo-hall/expo-hall.page.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Component, ViewChild } from '@angular/core'; - -import { ExpoHallMapComponent } from '../../expo-hall-map/expo-hall-map.component'; -import { LiveUpdateService } from '../../providers/live-update.service'; - -@Component({ - selector: 'app-expo-hall', - templateUrl: './expo-hall.page.html', - styleUrls: ['./expo-hall.page.scss'], -}) -export class ExpoHallPage { - @ViewChild('expoMap') expoMap!: ExpoHallMapComponent; - - constructor(public liveUpdateService: LiveUpdateService) {} - - toggleSearch() { - this.expoMap?.toggleSearch(); - } -} diff --git a/src/app/pages/help/help.module.ts b/src/app/pages/help/help.module.ts index a8a7f3b5..c982ee9e 100644 --- a/src/app/pages/help/help.module.ts +++ b/src/app/pages/help/help.module.ts @@ -2,6 +2,8 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { IonicModule } from '@ionic/angular'; +import { LocationMapModule } from '../../location-map/location-map.module'; + import { HelpPageRoutingModule } from './help-routing.module'; import { HelpPage } from './help.page'; @@ -9,6 +11,7 @@ import { HelpPage } from './help.page'; imports: [ CommonModule, IonicModule, + LocationMapModule, HelpPageRoutingModule ], declarations: [HelpPage] diff --git a/src/app/pages/help/help.page.html b/src/app/pages/help/help.page.html index e160dd1f..80a2b03f 100644 --- a/src/app/pages/help/help.page.html +++ b/src/app/pages/help/help.page.html @@ -19,11 +19,23 @@

Help & Safety

At the Conference +
+ + +
+ -

Info Desk & Conference Map

-

Find the registration desk, help desk, and venue layout

+

Full Conference Map

+

Browse all floor plans and the expo hall

diff --git a/src/app/pages/help/help.page.scss b/src/app/pages/help/help.page.scss index c5ac4160..fe6b82fc 100644 --- a/src/app/pages/help/help.page.scss +++ b/src/app/pages/help/help.page.scss @@ -22,6 +22,10 @@ ion-title { } } +.help-info-desk-map { + padding: 8px 16px 12px; +} + .page-hero { display: flex; flex-direction: column; diff --git a/src/app/pages/help/help.page.ts b/src/app/pages/help/help.page.ts index 2c408e05..4449449b 100644 --- a/src/app/pages/help/help.page.ts +++ b/src/app/pages/help/help.page.ts @@ -1,5 +1,8 @@ import { Component, ViewChild } from '@angular/core'; -import { IonContent } from '@ionic/angular'; +import { IonContent, ModalController } from '@ionic/angular'; + +import { FloorPlanModalComponent } from '../../floor-plan-modal/floor-plan-modal.component'; +import { VENUE_LOCATIONS, VenueLocation } from '../../location-map/venue-locations'; @Component({ selector: 'app-help', @@ -10,6 +13,10 @@ export class HelpPage { @ViewChild(IonContent) content: IonContent; showTitle = false; + infoDesk: VenueLocation = VENUE_LOCATIONS['infoDesk']; + + constructor(private modalCtrl: ModalController) {} + onScroll(event: any) { this.showTitle = event.detail.scrollTop > 100; } @@ -17,4 +24,19 @@ export class HelpPage { openUrl(url: string) { window.open(url, '_system', 'location=yes'); } + + async openInfoDesk() { + const modal = await this.modalCtrl.create({ + component: FloorPlanModalComponent, + componentProps: { + title: this.infoDesk.floorTitle, + imageSrc: this.infoDesk.fullSrc, + altText: `${this.infoDesk.floorTitle} floor plan with Information Desk highlighted`, + pinXPct: this.infoDesk.pinXPct, + pinYPct: this.infoDesk.pinYPct, + pinLabel: this.infoDesk.label, + }, + }); + await modal.present(); + } } diff --git a/src/app/pages/tabs-page/tabs-page-routing.module.ts b/src/app/pages/tabs-page/tabs-page-routing.module.ts index 3a63beef..9656360b 100644 --- a/src/app/pages/tabs-page/tabs-page-routing.module.ts +++ b/src/app/pages/tabs-page/tabs-page-routing.module.ts @@ -64,7 +64,8 @@ const routes: Routes = [ }, { path: 'expo-hall', - loadChildren: () => import('../expo-hall/expo-hall.module').then( m => m.ExpoHallPageModule) + loadChildren: () => import('../conference-map/conference-map.module').then( m => m.ConferenceMapPageModule), + data: { initialView: 'expo-hall', pageTitle: 'Expo Hall' } }, { path: 'conference-map', diff --git a/src/app/pages/venues-hours/venues-hours.module.ts b/src/app/pages/venues-hours/venues-hours.module.ts index 12c8e219..bb934ebd 100644 --- a/src/app/pages/venues-hours/venues-hours.module.ts +++ b/src/app/pages/venues-hours/venues-hours.module.ts @@ -2,6 +2,8 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { IonicModule } from '@ionic/angular'; +import { LocationMapModule } from '../../location-map/location-map.module'; + import { VenuesHoursPageRoutingModule } from './venues-hours-routing.module'; import { VenuesHoursPage } from './venues-hours.page'; @@ -9,6 +11,7 @@ import { VenuesHoursPage } from './venues-hours.page'; imports: [ CommonModule, IonicModule, + LocationMapModule, VenuesHoursPageRoutingModule ], declarations: [VenuesHoursPage] diff --git a/src/app/pages/venues-hours/venues-hours.page.html b/src/app/pages/venues-hours/venues-hours.page.html index addc2df2..11a8652d 100644 --- a/src/app/pages/venues-hours/venues-hours.page.html +++ b/src/app/pages/venues-hours/venues-hours.page.html @@ -17,7 +17,22 @@

Venues & Hours

300 E Ocean Blvd, Long Beach, CA 90802

-
+
+ +
+
+ + +
+
+
diff --git a/src/app/pages/venues-hours/venues-hours.page.scss b/src/app/pages/venues-hours/venues-hours.page.scss index 815e8970..eafb4aa3 100644 --- a/src/app/pages/venues-hours/venues-hours.page.scss +++ b/src/app/pages/venues-hours/venues-hours.page.scss @@ -58,6 +58,10 @@ ion-title { } } +.venues-section-map { + margin: 8px 0 16px; +} + .venues-content { h2 { font-size: 1.15rem; diff --git a/src/app/pages/venues-hours/venues-hours.page.ts b/src/app/pages/venues-hours/venues-hours.page.ts index 99ecda23..89c12680 100644 --- a/src/app/pages/venues-hours/venues-hours.page.ts +++ b/src/app/pages/venues-hours/venues-hours.page.ts @@ -1,7 +1,14 @@ import { Component, OnInit, ChangeDetectorRef, ViewChild } from '@angular/core'; import { IonContent } from '@ionic/angular'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { ConferenceData } from '../../providers/conference-data'; import { LiveUpdateService } from '../../providers/live-update.service'; +import { VENUE_LOCATIONS, VenueLocation } from '../../location-map/venue-locations'; + +interface VenueSection { + html: SafeHtml; + location?: VenueLocation; +} @Component({ selector: 'app-venues-hours', @@ -12,10 +19,12 @@ export class VenuesHoursPage implements OnInit { @ViewChild(IonContent) ionContent: IonContent; content: any = ''; showTitle = false; + sections: VenueSection[] = []; constructor( private confData: ConferenceData, private changeDetection: ChangeDetectorRef, + private sanitizer: DomSanitizer, public liveUpdateService: LiveUpdateService, ) {} @@ -30,7 +39,70 @@ export class VenuesHoursPage implements OnInit { ngOnInit() { this.confData.getContent().subscribe((content: any) => { this.content = content; + this.sections = this.buildSections(content?.['venues-hours'] || ''); this.changeDetection.detectChanges(); }); } + + private buildSections(html: string): VenueSection[] { + if (!html) return []; + + const parser = new DOMParser(); + const doc = parser.parseFromString(`
${html}
`, 'text/html'); + const root = doc.getElementById('root'); + if (!root) { + return [{ html: this.sanitizer.bypassSecurityTrustHtml(html) }]; + } + + const sections: VenueSection[] = []; + let current = document.createElement('div'); + + const flush = () => { + if (current.innerHTML.trim()) { + const heading = current.querySelector('h1, h2, h3, h4, h5, h6'); + const text = (heading?.textContent || '').toLowerCase(); + sections.push({ + html: this.sanitizer.bypassSecurityTrustHtml(current.innerHTML), + location: this.matchLocation(text), + }); + } + }; + + const isSectionHeading = (node: Node): boolean => { + if (node.nodeType !== Node.ELEMENT_NODE) return false; + const tag = (node as Element).tagName; + return tag === 'H1' || tag === 'H2' || tag === 'H3'; + }; + + Array.from(root.childNodes).forEach((node) => { + if (isSectionHeading(node)) { + flush(); + current = document.createElement('div'); + } + current.appendChild(node.cloneNode(true)); + }); + flush(); + + return sections; + } + + private matchLocation(headingText: string): VenueLocation | undefined { + if (!headingText) return undefined; + if (headingText.includes('information desk') || headingText.includes('info desk')) { + return VENUE_LOCATIONS['infoDesk']; + } + if (headingText.includes('registration')) { + return VENUE_LOCATIONS['registration']; + } + if (headingText.includes('swag pickup') || headingText.includes('t-shirt')) { + return VENUE_LOCATIONS['swagPickup']; + } + if (headingText.includes('quiet room')) { + return VENUE_LOCATIONS['quietRoom']; + } + if (headingText.includes('lost') && headingText.includes('found')) { + return VENUE_LOCATIONS['lostAndFound']; + } + return undefined; + } } diff --git a/src/assets/img/floor-plans/campus-map-thumb.jpg b/src/assets/img/floor-plans/campus-map-thumb.jpg new file mode 100644 index 00000000..95469588 Binary files /dev/null and b/src/assets/img/floor-plans/campus-map-thumb.jpg differ diff --git a/src/assets/img/floor-plans/campus-map.jpg b/src/assets/img/floor-plans/campus-map.jpg new file mode 100644 index 00000000..25b995b8 Binary files /dev/null and b/src/assets/img/floor-plans/campus-map.jpg differ diff --git a/src/assets/img/floor-plans/floor-plan-1-thumb.jpg b/src/assets/img/floor-plans/floor-plan-1-thumb.jpg index 7840cd58..646c6e34 100644 Binary files a/src/assets/img/floor-plans/floor-plan-1-thumb.jpg and b/src/assets/img/floor-plans/floor-plan-1-thumb.jpg differ diff --git a/src/assets/img/floor-plans/floor-plan-1.jpg b/src/assets/img/floor-plans/floor-plan-1.jpg index 9e352865..551cb6af 100644 Binary files a/src/assets/img/floor-plans/floor-plan-1.jpg and b/src/assets/img/floor-plans/floor-plan-1.jpg differ diff --git a/src/assets/img/floor-plans/floor-plan-2-thumb.jpg b/src/assets/img/floor-plans/floor-plan-2-thumb.jpg index 78d8df6f..7b72cea9 100644 Binary files a/src/assets/img/floor-plans/floor-plan-2-thumb.jpg and b/src/assets/img/floor-plans/floor-plan-2-thumb.jpg differ diff --git a/src/assets/img/floor-plans/floor-plan-2.jpg b/src/assets/img/floor-plans/floor-plan-2.jpg index 5bc9a74d..359351ff 100644 Binary files a/src/assets/img/floor-plans/floor-plan-2.jpg and b/src/assets/img/floor-plans/floor-plan-2.jpg differ diff --git a/src/assets/img/floor-plans/floor-plan-3-thumb.jpg b/src/assets/img/floor-plans/floor-plan-3-thumb.jpg index 411f1d3a..df2e9b98 100644 Binary files a/src/assets/img/floor-plans/floor-plan-3-thumb.jpg and b/src/assets/img/floor-plans/floor-plan-3-thumb.jpg differ diff --git a/src/assets/img/floor-plans/floor-plan-3.jpg b/src/assets/img/floor-plans/floor-plan-3.jpg index be7212a4..21c8d01e 100644 Binary files a/src/assets/img/floor-plans/floor-plan-3.jpg and b/src/assets/img/floor-plans/floor-plan-3.jpg differ diff --git a/src/assets/img/floor-plans/floor-plan-4-thumb.jpg b/src/assets/img/floor-plans/floor-plan-4-thumb.jpg index a84dc953..eaa7f4a3 100644 Binary files a/src/assets/img/floor-plans/floor-plan-4-thumb.jpg and b/src/assets/img/floor-plans/floor-plan-4-thumb.jpg differ diff --git a/src/assets/img/floor-plans/floor-plan-4.jpg b/src/assets/img/floor-plans/floor-plan-4.jpg index 67d58d2e..ec051fe9 100644 Binary files a/src/assets/img/floor-plans/floor-plan-4.jpg and b/src/assets/img/floor-plans/floor-plan-4.jpg differ