diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 3200db8..84b43fa 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -21,7 +21,15 @@ "WebFetch(domain:unpkg.com)", "Bash(node -e:*)", "Bash(ls:*)", - "Bash(find:*)" + "Bash(find:*)", + "WebFetch(domain:www.elegantthemes.com)", + "Bash(npm publish:*)", + "Bash(npm init:*)", + "Bash(node dist/bundle.js:*)", + "Bash(node build.js:*)", + "Bash(npm ls:*)", + "Bash(npm view:*)", + "Bash(npm update:*)" ], "deny": [], "ask": [] diff --git a/.gitignore b/.gitignore index a0905c1..9bbe200 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # Build outputs bin/ obj/ -wwwroot/js/ # Node modules node_modules/ @@ -30,4 +29,5 @@ Thumbs.db *.suo *.userosscache *.sln.docstates -js/ + +packages/calendar/dist/ diff --git a/.workbench/image.png b/.workbench/image.png index cbecbe4..196aaeb 100644 Binary files a/.workbench/image.png and b/.workbench/image.png differ diff --git a/.workbench/projectstructure.txt b/.workbench/projectstructure.txt new file mode 100644 index 0000000..8507e45 --- /dev/null +++ b/.workbench/projectstructure.txt @@ -0,0 +1,86 @@ + + +Organizing Project Folder Structure: Function-Based vs Feature-Based +Ina Lopez +Ina Lopez + +Follow +2 min read +· +Sep 3, 2024 +12 + + + + +When setting up a project, one of the most crucial decisions is how to organize the folder structure. The structure you choose can significantly impact productivity, scalability, and collaboration among developers. Two common approaches are function-based and feature-based organization. Both have their advantages, and the choice between them often depends on the size of the project and the number of developers involved. + +Function-Based Organization + +In a function-based folder structure, directories are organized based on the functions they provide. This is a popular approach, especially for smaller projects or teams. The idea is to group similar functionalities together, making it easy to locate specific files or components. + +For example, in a React project, the src directory might look like this: + +src/ +├── components/ +├── hooks/ +├── utils/ + +Each folder contains files related to a specific function. Components, hooks, reducers, and utilities are neatly separated, making it easy to find and manage related code. This structure works well when the codebase is relatively small and developers need to quickly find and reuse functions. + +Pros: + +Easy to find similar functions. +Encourages reuse of components and utilities. +Clean and straightforward structure. +Cons: + +As the project grows, it can become difficult to manage. +Dependencies between different folders can increase complexity. +Not ideal for teams working on different features simultaneously. +Feature-Based Organization +In larger projects with many developers, a feature-based folder structure can be more effective. Instead of organizing files by function, the top-level directories in the src folder are based on features or modules of the application. This approach allows teams to work on separate features independently without interfering with other parts of the codebase. + +Get Ina Lopez’s stories in your inbox +Join Medium for free to get updates from this writer. + +Enter your email +Subscribe +For example, a feature-based structure might look like this: + +src/ +├─ signup/ +│ ├── components/ +│ ├── hooks/ +│ └── utils/ +├─ checkout/ +│ ├── components/ +│ ├── hooks/ +│ └── utils/ +├─ dashboard/ +│ ├── components/ +│ ├── hooks/ +│ └── utils/ +└─ profile/ +├── components/ +├── hooks/ +└── utils/ + +Each folder contains all the components, hooks, reducers, and utilities specific to that feature. This structure makes it easier for developers to focus on specific features, reduces conflicts, and simplifies the onboarding process for new team members. + +Pros: + +Better suited for larger projects with multiple developers. +Encourages modularity and separation of concerns. +Easier to manage and scale as the project grows. +Reduces the risk of conflicts between different teams. +Cons: + +Some duplication of code across features is possible. +Finding reusable components can be more challenging. +Can be overwhelming if the project has too many small features. +Conclusion +Choosing the right folder structure depends on your project’s size and team dynamics. Function-based organization is ideal for small to medium projects with fewer developers, offering simplicity and ease of reuse. However, as your project grows and more developers are involved, a feature-based approach becomes more effective, enabling modularity, better collaboration, and easier scaling. + +For some projects, a hybrid approach might work best, combining both methods to balance flexibility and organization. Ultimately, the key is to select a structure that supports the current and future needs of your project and team. + \ No newline at end of file diff --git a/.workbench/spec-salary.html b/.workbench/spec-salary.html new file mode 100644 index 0000000..41ee387 --- /dev/null +++ b/.workbench/spec-salary.html @@ -0,0 +1,564 @@ + + + + + + Lønspecifikation – Januar 2026 (2 sider) + + + + + + +
+
+
+

Lønspecifikation

+

Periode: Januar 2026

+
+ +
+
Medarbejdernr.: EMP-001
+
+ Medarbejder:Emma Larsen + Afdeling:Frisør + Ansættelse:Fuldtid (37 t/uge) +
+
+
+ +
+
+
Bruttoløn (Januar 2026)
+

34.063,50 kr

+
+
+
Side 1: Overblik
+
+ Kort opsummering til udlevering.
+ Detaljer findes på side 2. +
+
+
+ +
+
+
+

Samlet lønopgørelse

+ Alle beløb i DKK +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
LøndelBeløb
Grundløn inkl. overarbejde29.322,50 kr
Provision i alt3.685,00 kr
Tillæg i alt1.056,00 kr
Bruttoløn34.063,50 kr
+

+ (Hvis du senere vil have skat/AM-bidrag/nettoløn med, kan det tilføjes som ekstra blok her.) +

+
+
+ +
+
+

Saldi

+ Ved periodens slut +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
TypeOptjentAfholdtRest
Ferie (dage)18,56,012,5
Afspadsering (timer)12,04,08,0
+

Saldi er opgjort som angivet på lønspecifikationen.

+
+
+
+ +
+
+

Hurtigt resumé

+ Det vigtigste +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
NøglepunktVærdi
Normaltimer148,0 t
Overarbejde7,0 t
Provision (services + produkter)3.685,00 kr
Tillæg (aften + lørdag + søndag)1.056,00 kr
+
+
+ +
+
Side 1/2 · Overblik
+
Lønspecifikation · Januar 2026
+
+ +
+ + +
+
+

Lønspecifikation – Detaljer

+

Periode: Januar 2026 · Medarbejder: Emma Larsen

+
+ +
+
Bruttoløn: 34.063,50 kr
+
+ Ansættelse:Fuldtid (37 t/uge) + Afdeling:Frisør + Medarb.nr.:EMP-001 +
+
+
+ +
+
+

Arbejdstid pr. uge

+ Normal + overtid +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
UgeNormaltimerOvertidBeløb
Uge 1 (30. dec – 5. jan)37,0 t2,0 t7.400,00 kr
Uge 2 (6. – 12. jan)37,0 t3,5 t7.816,25 kr
Uge 3 (13. – 19. jan)37,0 t0,0 t6.845,00 kr
Uge 4 (20. – 26. jan)37,0 t1,5 t7.261,25 kr
I alt148,0 t7,0 t29.322,50 kr
+

+ Satser: Normal 185,00 kr/time. Overtid (50%) 277,50 kr/time. +

+
+
+ +
+
+

Provision

+ Services & produkter +
+
+

+ Services: 15% af omsætning over minimum (220 kr/time).
+ Produkter: 10% af salg. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
UgeService prov.Produkt prov.I alt
Uge 1573,00 kr210,00 kr783,00 kr
Uge 2883,50 kr320,00 kr1.203,50 kr
Uge 3459,00 kr180,00 kr639,00 kr
Uge 4769,50 kr290,00 kr1.059,50 kr
I alt2.685,00 kr1.000,00 kr3.685,00 kr
+
+
+ +
+
+

Tillæg & fravær

+ Opsummering +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TillægTimerBeløb
Aftentillæg (hverdage 18–21)12,0336,00 kr
Lørdagstillæg (før kl. 14)16,0720,00 kr
Søndagstillæg0,00,00 kr
Tillæg i alt1.056,00 kr
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FraværDageBeløb
Ferie med løn00,00 kr
Sygdom00,00 kr
Barns sygedag00,00 kr
+

Ingen fravær registreret i perioden.

+
+
+
+
+ +
+
Side 2/2 · Detaljer
+
Lønspecifikation · Januar 2026
+
+ +
+ Tip: I Chrome/Edge: Ctrl/Cmd + P → Destination: Gem som PDF → slå “Headers and footers” fra. +
+
+ + diff --git a/CalendarServer.csproj b/CalendarServer.csproj deleted file mode 100644 index 2447542..0000000 --- a/CalendarServer.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - - net8.0 - enable - enable - - - \ No newline at end of file diff --git a/Program.cs b/Program.cs deleted file mode 100644 index 10cbdaa..0000000 --- a/Program.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.FileProviders; - -var builder = WebApplication.CreateBuilder(args); - -var app = builder.Build(); - -// Configure static files to serve from current directory -app.UseStaticFiles(new StaticFileOptions -{ - FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot")), - RequestPath = "" -}); - -// Fallback to index.html for SPA routing -app.MapFallbackToFile("index.html"); - -app.Run("http://localhost:8000"); \ No newline at end of file diff --git a/docs/design-system.md b/docs/design-system.md new file mode 100644 index 0000000..6acead2 --- /dev/null +++ b/docs/design-system.md @@ -0,0 +1,221 @@ +# SWP Design System - UI/UX Dokumentation + +## Oversigt + +Dette dokument beskriver det komponent-baserede design system udviklet til Salon OS SaaS platformen gennem POC-udvikling. Systemet består af **150+ custom HTML elementer** med `swp-` prefix. + +--- + +## Design Principper + +- **Custom Elements**: Alle komponenter bruger semantiske `swp-*` tags +- **CSS Variables**: Theming via `--color-*` variabler (light/dark mode) +- **Responsive**: Mobile-first med grid breakpoints +- **Konsistent spacing**: 4px base unit (4, 8, 12, 16, 20, 24px) + +--- + +## Farvepalette (CSS Variables) + +```css +--color-surface: #fff / #1e1e1e +--color-background: #f5f5f5 / #121212 +--color-border: #e0e0e0 / #333 +--color-text: #333 / #e0e0e0 +--color-text-secondary: #666 / #999 +--color-teal: #00897b (primary) +--color-blue: #1976d2 +--color-green: #43a047 (success) +--color-amber: #f59e0b (warning) +--color-red: #e53935 (error) +--color-purple: #8b5cf6 +``` + +--- + +## Typografi + +```css +--font-family: 'Poppins', sans-serif +--font-mono: 'JetBrains Mono', monospace +``` + +| Størrelse | Brug | +|-----------|------| +| 22px | Stat values | +| 16px | Page titles | +| 14px | Body text | +| 13px | Table cells, inputs | +| 12px | Labels, hints | +| 11px | Table headers (uppercase) | + +--- + +## Komponent-katalog + +### 1. Layout & Container + +| Element | Beskrivelse | +|---------|-------------| +| `swp-page` | Hovedpage wrapper | +| `swp-page-container` | Max-width container (1400px) | +| `swp-card` | Kortkomponent med border og shadow | +| `swp-card-header` | Kortheader med titel | +| `swp-drawer` | Slide-in panel fra højre | +| `swp-drawer-overlay` | Mørk overlay bag drawer | +| `swp-two-columns` | To-kolonne layout | + +### 2. Navigation + +| Element | Beskrivelse | +|---------|-------------| +| `swp-topbar` | Sticky header bar | +| `swp-topbar-left/right` | Flex containers i topbar | +| `swp-page-title` | Sidetitel i topbar | +| `swp-back-link` | Tilbage-navigation med ikon | +| `swp-tabs` | Tab navigation | +| `swp-tab` | Enkelt tab | + +### 3. Formularer + +| Element | Beskrivelse | +|---------|-------------| +| `swp-form-field` | Wrapper for label + input | +| `swp-form-label` | Form label | +| `swp-form-input` | Input wrapper | +| `swp-form-row` | Horisontal gruppe af felter | +| `swp-form-hint` | Hjælpetekst under felt | +| `swp-toggle-slider` | On/off toggle | +| `swp-edit-section` | Redigerbar sektion | +| `swp-edit-row` | Label + værdi row | + +### 4. Tabeller + +| Element | Beskrivelse | +|---------|-------------| +| `swp-table` | Hovedtabel container | +| `swp-table-header` | Header row (grid) | +| `swp-table-body` | Body container | +| `swp-table-row` | Data row (grid) | +| `swp-th` | Header cell | +| `swp-td` | Data cell | +| `swp-table-footer` | Footer med pagination | +| `swp-row-arrow` | Klik-indikator pil | + +### 5. Statistik + +| Element | Beskrivelse | +|---------|-------------| +| `swp-stats-bar` | Grid af stat cards | +| `swp-stat-card` | Enkelt statistik kort | +| `swp-stat-value` | Stor tal (mono font) | +| `swp-stat-label` | Beskrivelse under tal | +| `swp-stat-change` | Ændring indikator (+/-) | + +### 6. Søgning & Filtrering + +| Element | Beskrivelse | +|---------|-------------| +| `swp-filter-bar` | Filterpanel | +| `swp-search-input` | Søgefelt med ikon | +| `swp-filter-group` | Label + select/input | +| `swp-filter-label` | Filter label | + +### 7. Badges & Status + +| Element | Beskrivelse | Klasser | +|---------|-------------|---------| +| `swp-status-badge` | Status indikator | `.paid`, `.pending`, `.credited` | +| `swp-payment-badge` | Betalingsmetode | `.card`, `.cash`, `.mobilepay` | +| `swp-tag` | Inline tag | `.vip`, `.new` | + +### 8. Buttons + +| Element | Beskrivelse | Klasser | +|---------|-------------|---------| +| `swp-btn` | Standard button | `.primary`, `.secondary`, `.danger` | + +### 9. Charts (swp-charting) + +| Element | Beskrivelse | +|---------|-------------| +| `swp-chart-card` | Chart wrapper kort | +| `swp-chart-header` | Titel + hint | +| `swp-chart-container` | Canvas container | + +### 10. Specialiserede Komponenter + +#### Kunde +- `swp-customer-avatar` - Rund avatar +- `swp-customer-cell` - Navn + telefon +- `swp-customer-header` - Profil header + +#### Medarbejder +- `swp-employee-avatar` - Medarbejder billede +- `swp-employee-name` - Navn display + +#### Faktura +- `swp-invoice-cell` - Fakturanummer +- `swp-datetime-cell` - Dato + tid +- `swp-amount-cell` - Beløb (højre-justeret) + +#### Produkt +- `swp-variants-table` - Variant liste +- `swp-stock-display` - Lagerstatus +- `swp-margin-display` - Avance visning + +#### Booking/AI +- `swp-gap-card` - Ledigt hul kort +- `swp-suggestion-item` - AI forslag +- `swp-optimization-score` - Score cirkel + +--- + +## Pagination + +```html + + + 1 + 2 + ... + + +``` + +--- + +## Ikoner + +Bruger **Phosphor Icons** via CDN: +```html + + + + +``` + +--- + +## POC Filer (Reference) + +| Fil | Domæne | +|-----|--------| +| `poc-salg.html` | Salgsoversigt, fakturaer | +| `poc-customer-list.html` | Kundeliste | +| `poc-customer-detail.html` | Kundeprofil | +| `poc-employee.html` | Medarbejderprofil | +| `poc-produkt.html` | Produktdetaljer | +| `poc-gavekort.html` | Fordelskort | +| `poc-kasseafstemninger.html` | Kasseafstemning | +| `poc-rapport.html` | Rapporter | +| `poc-indstillinger.html` | Indstillinger | + +--- + +## Næste Skridt: .NET Core Implementation + +Når design systemet er dokumenteret, er næste fase at implementere: +1. Backend API endpoints +2. Database modeller +3. Razor/Blazor komponenter baseret på swp-* elementer diff --git a/package-lock.json b/package-lock.json index d0ad451..6bc1f15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@novadi/core": "^0.6.0", "@rollup/rollup-win32-x64-msvc": "^4.52.2", - "@sevenweirdpeople/swp-charting": "^0.2.1", + "@sevenweirdpeople/swp-charting": "^0.2.2", "dayjs": "^1.11.19", "fuse.js": "^7.1.0", "json-diff-ts": "^4.8.2", @@ -1177,9 +1177,9 @@ ] }, "node_modules/@sevenweirdpeople/swp-charting": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@sevenweirdpeople/swp-charting/-/swp-charting-0.2.1.tgz", - "integrity": "sha512-QtY77Dyv4Vs/rWfBVSDTmuxgD4L8tGu4pmTF0l3i8HDwK6qtT2wEtH35UHD1RDFE1VtOGcnU0/dTdqjNWCqzxA==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@sevenweirdpeople/swp-charting/-/swp-charting-0.2.2.tgz", + "integrity": "sha512-q9p7TOSMAq6I0t6jGEWpmjR7l2H8q8G0TnXbIpDutCz5a2JEqMDFe0NGBGcCwze2rvvRnRvCz8P2zGMQlHmphw==", "license": "MIT" }, "node_modules/@types/chai": { diff --git a/package.json b/package.json index 36c19f2..8e2f69f 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "dependencies": { "@novadi/core": "^0.6.0", "@rollup/rollup-win32-x64-msvc": "^4.52.2", - "@sevenweirdpeople/swp-charting": "^0.2.1", + "@sevenweirdpeople/swp-charting": "^0.2.2", "dayjs": "^1.11.19", "fuse.js": "^7.1.0", "json-diff-ts": "^4.8.2", diff --git a/packages/calendar/README.md b/packages/calendar/README.md new file mode 100644 index 0000000..5fecd34 --- /dev/null +++ b/packages/calendar/README.md @@ -0,0 +1,620 @@ +# Calendar + +Professional TypeScript calendar component with offline-first architecture, drag-and-drop functionality, and real-time synchronization capabilities. + +## Features + +- **Multiple View Modes**: Date-based (day/week/month) and resource-based (people, rooms) views +- **Drag & Drop**: Smooth event dragging with snap-to-grid, cross-column movement, and timed/all-day conversion +- **Event Resizing**: Intuitive resize handles for adjusting event duration +- **Offline-First**: IndexedDB storage with automatic background sync +- **Event-Driven Architecture**: Decoupled components via centralized EventBus +- **Dependency Injection**: Built on NovaDI for clean, testable architecture +- **Extensions**: Modular extensions for teams, departments, bookings, customers, schedules, and audit logging + +## Installation + +```bash +npm install calendar +``` + +## Quick Start (AI-Friendly Setup Guide) + +### Step 1: Create HTML Structure + +```html + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +``` + +### Step 2: Initialize Calendar + +```typescript +import { Container } from '@novadi/core'; +import { + registerCoreServices, + CalendarApp, + IndexedDBContext, + SettingsService, + ViewConfigService, + EventService, + EventBus, + CalendarEvents +} from 'calendar'; + +async function init() { + // 1. Create DI container and register services + const container = new Container(); + const builder = container.builder(); + registerCoreServices(builder, { + dbConfig: { dbName: 'MyCalendarDB', dbVersion: 1 } + }); + const app = builder.build(); + + // 2. Initialize IndexedDB + const dbContext = app.resolveType(); + await dbContext.initialize(); + + // 3. Seed required settings (first time only) + const settingsService = app.resolveType(); + const viewConfigService = app.resolveType(); + + await settingsService.save({ + id: 'grid', + dayStartHour: 8, + dayEndHour: 17, + workStartHour: 9, + workEndHour: 16, + hourHeight: 64, + snapInterval: 15, + syncStatus: 'synced' + }); + + await settingsService.save({ + id: 'workweek', + presets: { + standard: { id: 'standard', label: 'Standard', workDays: [1, 2, 3, 4, 5], periodDays: 7 } + }, + defaultPreset: 'standard', + firstDayOfWeek: 1, + syncStatus: 'synced' + }); + + await viewConfigService.save({ + id: 'simple', + groupings: [{ type: 'date', values: [], idProperty: 'date', derivedFrom: 'start' }], + syncStatus: 'synced' + }); + + // 4. Initialize CalendarApp + const calendarApp = app.resolveType(); + const containerEl = document.querySelector('swp-calendar-container') as HTMLElement; + await calendarApp.init(containerEl); + + // 5. Render a view + const eventBus = app.resolveType(); + eventBus.emit(CalendarEvents.CMD_RENDER, { viewId: 'simple' }); +} + +init().catch(console.error); +``` + +### Step 3: Add Events + +```typescript +const eventService = app.resolveType(); + +await eventService.save({ + id: crypto.randomUUID(), + title: 'Meeting', + start: new Date('2024-01-15T09:00:00'), + end: new Date('2024-01-15T10:00:00'), + type: 'meeting', + allDay: false, + syncStatus: 'synced' +}); +``` + +--- + +## Architecture + +### Core Components + +| Component | Description | +|-----------|-------------| +| `CalendarApp` | Main application entry point | +| `CalendarOrchestrator` | Coordinates rendering pipeline | +| `EventBus` | Central event dispatcher for all inter-component communication | +| `DateService` | Date calculations and formatting | +| `IndexedDBContext` | Offline storage infrastructure | + +### Managers + +| Manager | Description | +|---------|-------------| +| `DragDropManager` | Event drag-drop with smooth animations and snap-to-grid | +| `EdgeScrollManager` | Automatic scrolling at viewport edges during drag | +| `ResizeManager` | Event resizing with visual feedback | +| `ScrollManager` | Scroll behavior and position management | +| `HeaderDrawerManager` | All-day events drawer toggle | +| `EventPersistenceManager` | Saves drag/resize changes to storage | + +### Renderers + +| Renderer | Description | +|----------|-------------| +| `DateRenderer` | Renders date-based column groupings | +| `ResourceRenderer` | Renders resource-based column groupings | +| `EventRenderer` | Renders timed events in columns | +| `ScheduleRenderer` | Renders working hours backgrounds | +| `HeaderDrawerRenderer` | Renders all-day events in header | +| `TimeAxisRenderer` | Renders time labels on the left axis | + +### Storage + +| Service | Description | +|---------|-------------| +| `EventService` / `EventStore` | Calendar event CRUD | +| `ResourceService` / `ResourceStore` | Resource management | +| `SettingsService` / `SettingsStore` | Tenant settings | +| `ViewConfigService` / `ViewConfigStore` | View configurations | + +--- + +## Events Reference + +### Lifecycle Events + +| Event | Constant | Payload | Description | +|-------|----------|---------|-------------| +| `core:initialized` | `CoreEvents.INITIALIZED` | - | Calendar core initialized | +| `core:ready` | `CoreEvents.READY` | - | Calendar ready for interaction | +| `core:destroyed` | `CoreEvents.DESTROYED` | - | Calendar destroyed | + +### View Events + +| Event | Constant | Payload | Description | +|-------|----------|---------|-------------| +| `view:changed` | `CoreEvents.VIEW_CHANGED` | `{ viewId: string }` | View type changed | +| `view:rendered` | `CoreEvents.VIEW_RENDERED` | - | View finished rendering | + +### Navigation Events + +| Event | Constant | Payload | Description | +|-------|----------|---------|-------------| +| `nav:date-changed` | `CoreEvents.DATE_CHANGED` | `{ date: Date }` | Current date changed | +| `nav:navigation-completed` | `CoreEvents.NAVIGATION_COMPLETED` | - | Navigation animation completed | + +### Data Events + +| Event | Constant | Payload | Description | +|-------|----------|---------|-------------| +| `data:loading` | `CoreEvents.DATA_LOADING` | - | Data loading started | +| `data:loaded` | `CoreEvents.DATA_LOADED` | - | Data loading completed | +| `data:error` | `CoreEvents.DATA_ERROR` | `{ error: Error }` | Data loading error | + +### Grid Events + +| Event | Constant | Payload | Description | +|-------|----------|---------|-------------| +| `grid:rendered` | `CoreEvents.GRID_RENDERED` | - | Grid finished rendering | +| `grid:clicked` | `CoreEvents.GRID_CLICKED` | `{ time: Date, columnKey: string }` | Grid area clicked | + +### Event Management + +| Event | Constant | Payload | Description | +|-------|----------|---------|-------------| +| `event:created` | `CoreEvents.EVENT_CREATED` | `ICalendarEvent` | Event created | +| `event:updated` | `CoreEvents.EVENT_UPDATED` | `IEventUpdatedPayload` | Event updated | +| `event:deleted` | `CoreEvents.EVENT_DELETED` | `{ eventId: string }` | Event deleted | +| `event:selected` | `CoreEvents.EVENT_SELECTED` | `{ eventId: string }` | Event selected | + +### Drag-Drop Events + +| Event | Constant | Payload | Description | +|-------|----------|---------|-------------| +| `event:drag-start` | `CoreEvents.EVENT_DRAG_START` | `IDragStartPayload` | Drag started | +| `event:drag-move` | `CoreEvents.EVENT_DRAG_MOVE` | `IDragMovePayload` | Dragging (throttled) | +| `event:drag-end` | `CoreEvents.EVENT_DRAG_END` | `IDragEndPayload` | Drag completed | +| `event:drag-cancel` | `CoreEvents.EVENT_DRAG_CANCEL` | `IDragCancelPayload` | Drag cancelled | +| `event:drag-column-change` | `CoreEvents.EVENT_DRAG_COLUMN_CHANGE` | `IDragColumnChangePayload` | Moved to different column | + +### Header Drag Events (Timed to All-Day Conversion) + +| Event | Constant | Payload | Description | +|-------|----------|---------|-------------| +| `event:drag-enter-header` | `CoreEvents.EVENT_DRAG_ENTER_HEADER` | `IDragEnterHeaderPayload` | Entered header area | +| `event:drag-move-header` | `CoreEvents.EVENT_DRAG_MOVE_HEADER` | `IDragMoveHeaderPayload` | Moving in header area | +| `event:drag-leave-header` | `CoreEvents.EVENT_DRAG_LEAVE_HEADER` | `IDragLeaveHeaderPayload` | Left header area | + +### Resize Events + +| Event | Constant | Payload | Description | +|-------|----------|---------|-------------| +| `event:resize-start` | `CoreEvents.EVENT_RESIZE_START` | `IResizeStartPayload` | Resize started | +| `event:resize-end` | `CoreEvents.EVENT_RESIZE_END` | `IResizeEndPayload` | Resize completed | + +### Edge Scroll Events + +| Event | Constant | Payload | Description | +|-------|----------|---------|-------------| +| `edge-scroll:tick` | `CoreEvents.EDGE_SCROLL_TICK` | `{ deltaY: number }` | Scroll tick during edge scroll | +| `edge-scroll:started` | `CoreEvents.EDGE_SCROLL_STARTED` | - | Edge scrolling started | +| `edge-scroll:stopped` | `CoreEvents.EDGE_SCROLL_STOPPED` | - | Edge scrolling stopped | + +### Sync Events + +| Event | Constant | Payload | Description | +|-------|----------|---------|-------------| +| `sync:started` | `CoreEvents.SYNC_STARTED` | - | Background sync started | +| `sync:completed` | `CoreEvents.SYNC_COMPLETED` | - | Background sync completed | +| `sync:failed` | `CoreEvents.SYNC_FAILED` | `{ error: Error }` | Background sync failed | + +### Entity Events + +| Event | Constant | Payload | Description | +|-------|----------|---------|-------------| +| `entity:saved` | `CoreEvents.ENTITY_SAVED` | `IEntitySavedPayload` | Entity saved to storage | +| `entity:deleted` | `CoreEvents.ENTITY_DELETED` | `IEntityDeletedPayload` | Entity deleted from storage | + +### Audit Events + +| Event | Constant | Payload | Description | +|-------|----------|---------|-------------| +| `audit:logged` | `CoreEvents.AUDIT_LOGGED` | `IAuditLoggedPayload` | Audit entry logged | + +### Rendering Events + +| Event | Constant | Payload | Description | +|-------|----------|---------|-------------| +| `events:rendered` | `CoreEvents.EVENTS_RENDERED` | - | Events finished rendering | + +### System Events + +| Event | Constant | Payload | Description | +|-------|----------|---------|-------------| +| `system:error` | `CoreEvents.ERROR` | `{ error: Error, context?: string }` | System error occurred | + +--- + +## Command Events (Host to Calendar) + +Use these to control the calendar from your application: + +```typescript +import { EventBus, CalendarEvents } from 'calendar'; + +const eventBus = app.resolveType(); + +// Navigate +eventBus.emit(CalendarEvents.CMD_NAVIGATE_PREV); +eventBus.emit(CalendarEvents.CMD_NAVIGATE_NEXT); + +// Render a view +eventBus.emit(CalendarEvents.CMD_RENDER, { viewId: 'simple' }); + +// Toggle header drawer +eventBus.emit(CalendarEvents.CMD_DRAWER_TOGGLE); + +// Change workweek preset +eventBus.emit(CalendarEvents.CMD_WORKWEEK_CHANGE, { presetId: 'standard' }); + +// Update view grouping +eventBus.emit(CalendarEvents.CMD_VIEW_UPDATE, { type: 'resource', values: ['r1', 'r2'] }); +``` + +| Command | Constant | Payload | Description | +|---------|----------|---------|-------------| +| Navigate Previous | `CalendarEvents.CMD_NAVIGATE_PREV` | - | Go to previous period | +| Navigate Next | `CalendarEvents.CMD_NAVIGATE_NEXT` | - | Go to next period | +| Render View | `CalendarEvents.CMD_RENDER` | `{ viewId: string }` | Render specified view | +| Toggle Drawer | `CalendarEvents.CMD_DRAWER_TOGGLE` | - | Toggle all-day drawer | +| Change Workweek | `CalendarEvents.CMD_WORKWEEK_CHANGE` | `{ presetId: string }` | Change workweek preset | +| Update View | `CalendarEvents.CMD_VIEW_UPDATE` | `{ type: string, values: string[] }` | Update grouping values | + +--- + +## Types + +### Core Types + +```typescript +// Event types +type CalendarEventType = 'customer' | 'vacation' | 'break' | 'meeting' | 'blocked'; + +interface ICalendarEvent { + id: string; + title: string; + description?: string; + start: Date; + end: Date; + type: CalendarEventType; + allDay: boolean; + bookingId?: string; + resourceId?: string; + customerId?: string; + recurringId?: string; + syncStatus: SyncStatus; + metadata?: Record; +} + +// Resource types +type ResourceType = 'person' | 'room' | 'equipment' | 'vehicle' | 'custom'; + +interface IResource { + id: string; + name: string; + displayName: string; + type: ResourceType; + avatarUrl?: string; + color?: string; + isActive?: boolean; + defaultSchedule?: IWeekSchedule; + syncStatus: SyncStatus; +} + +// Sync status +type SyncStatus = 'synced' | 'pending' | 'error'; +``` + +### Settings Types + +```typescript +interface IGridSettings { + id: 'grid'; + dayStartHour: number; + dayEndHour: number; + workStartHour: number; + workEndHour: number; + hourHeight: number; + snapInterval: number; +} + +interface IWorkweekPreset { + id: string; + workDays: number[]; // ISO weekdays: 1=Monday, 7=Sunday + label: string; + periodDays: number; // Navigation step (1=day, 7=week) +} + +interface IWeekSchedule { + [day: number]: ITimeSlot | null; // null = off that day +} + +interface ITimeSlot { + start: string; // "HH:mm" + end: string; // "HH:mm" +} +``` + +### Drag-Drop Payloads + +```typescript +interface IDragStartPayload { + eventId: string; + element: HTMLElement; + ghostElement: HTMLElement; + startY: number; + mouseOffset: { x: number; y: number }; + columnElement: HTMLElement; +} + +interface IDragEndPayload { + swpEvent: SwpEvent; + sourceColumnKey: string; + target: 'grid' | 'header'; +} +``` + +--- + +## Extensions + +Import extensions separately to keep bundle size minimal: + +```typescript +// Teams extension +import { registerTeams, TeamService, TeamStore, TeamRenderer } from 'calendar/teams'; + +// Departments extension +import { registerDepartments, DepartmentService, DepartmentStore } from 'calendar/departments'; + +// Bookings extension +import { registerBookings, BookingService, BookingStore } from 'calendar/bookings'; + +// Customers extension +import { registerCustomers, CustomerService, CustomerStore } from 'calendar/customers'; + +// Schedules extension (working hours) +import { registerSchedules, ResourceScheduleService, ScheduleOverrideService } from 'calendar/schedules'; + +// Audit extension +import { registerAudit, AuditService, AuditStore } from 'calendar/audit'; + +// Register with container builder +const builder = container.builder(); +registerCoreServices(builder); +registerTeams(builder); +registerSchedules(builder); +// ... etc +``` + +--- + +## Configuration + +### Calendar Options + +```typescript +interface ICalendarOptions { + timeConfig?: ITimeFormatConfig; + gridConfig?: IGridConfig; + dbConfig?: IDBConfig; +} + +// Time format configuration +interface ITimeFormatConfig { + timezone: string; // e.g., 'Europe/Copenhagen' + use24HourFormat: boolean; + locale: string; // e.g., 'da-DK' + dateFormat: string; + showSeconds: boolean; +} + +// Grid configuration +interface IGridConfig { + hourHeight: number; // Pixels per hour (default: 64) + dayStartHour: number; // Grid start hour (default: 6) + dayEndHour: number; // Grid end hour (default: 18) + snapInterval: number; // Minutes to snap to (default: 15) + gridStartThresholdMinutes: number; +} + +// Database configuration +interface IDBConfig { + dbName: string; // IndexedDB database name + dbVersion: number; // Schema version +} +``` + +### Default Configuration + +```typescript +import { + defaultTimeFormatConfig, + defaultGridConfig, + defaultDBConfig +} from 'calendar'; + +// Defaults: +// timezone: Intl.DateTimeFormat().resolvedOptions().timeZone +// use24HourFormat: true +// locale: 'da-DK' +// hourHeight: 64 +// dayStartHour: 6 +// dayEndHour: 18 +// snapInterval: 15 +``` + +--- + +## Utilities + +### Position Utilities + +```typescript +import { + calculateEventPosition, + minutesToPixels, + pixelsToMinutes, + snapToGrid +} from 'calendar'; + +// Convert time to pixels +const pixels = minutesToPixels(120, 64); // 120 mins at 64px/hour = 128px + +// Snap to 15-minute grid +const snapped = snapToGrid(new Date(), 15); +``` + +### Event Layout Engine + +```typescript +import { eventsOverlap, calculateColumnLayout } from 'calendar'; + +// Check if two events overlap +const overlap = eventsOverlap(event1, event2); + +// Calculate layout for overlapping events +const layout = calculateColumnLayout(events); +``` + +--- + +## Listening to Events + +```typescript +import { EventBus, CoreEvents } from 'calendar'; + +const eventBus = app.resolveType(); + +// Subscribe to event updates +eventBus.on(CoreEvents.EVENT_UPDATED, (e: Event) => { + const { eventId, sourceColumnKey, targetColumnKey } = (e as CustomEvent).detail; + console.log(`Event ${eventId} moved from ${sourceColumnKey} to ${targetColumnKey}`); +}); + +// Subscribe to drag events +eventBus.on(CoreEvents.EVENT_DRAG_END, (e: Event) => { + const { swpEvent, target } = (e as CustomEvent).detail; + console.log(`Dropped on ${target}:`, swpEvent); +}); + +// One-time listener +eventBus.once(CoreEvents.READY, () => { + console.log('Calendar is ready!'); +}); +``` + +--- + +## CSS Customization + +The calendar uses CSS custom properties for theming. Override these in your CSS: + +```css +:root { + --calendar-hour-height: 64px; + --calendar-header-height: 48px; + --calendar-column-min-width: 120px; + --calendar-event-border-radius: 4px; +} +``` + +--- + +## Dependencies + +- `@novadi/core` - Dependency injection framework (peer dependency) +- `dayjs` - Date manipulation and formatting + +--- + +## License + +Proprietary - SWP diff --git a/packages/calendar/build.js b/packages/calendar/build.js new file mode 100644 index 0000000..0570894 --- /dev/null +++ b/packages/calendar/build.js @@ -0,0 +1,47 @@ +import * as esbuild from 'esbuild'; +import { NovadiUnplugin } from '@novadi/core/unplugin'; +import * as fs from 'fs'; +import * as path from 'path'; + +const entryPoints = [ + 'src/index.ts', + 'src/extensions/teams/index.ts', + 'src/extensions/departments/index.ts', + 'src/extensions/bookings/index.ts', + 'src/extensions/customers/index.ts', + 'src/extensions/schedules/index.ts', + 'src/extensions/audit/index.ts' +]; + +async function build() { + await esbuild.build({ + entryPoints, + bundle: true, + outdir: 'dist', + format: 'esm', + platform: 'browser', + external: ['@novadi/core', 'dayjs'], + splitting: true, + sourcemap: true, + target: 'es2020', + plugins: [NovadiUnplugin.esbuild({ debug: false, enableAutowiring: true })] + }); + + console.log('Build complete: dist/'); + + // Bundle CSS + const cssDir = 'dist/css'; + if (!fs.existsSync(cssDir)) { + fs.mkdirSync(cssDir, { recursive: true }); + } + const cssFiles = [ + '../../wwwroot/css/calendar-base.css', + '../../wwwroot/css/calendar-layout.css', + '../../wwwroot/css/calendar-events.css' + ]; + const bundledCss = cssFiles.map(f => fs.readFileSync(f, 'utf8')).join('\n'); + fs.writeFileSync(path.join(cssDir, 'calendar.css'), bundledCss); + console.log('CSS bundled: dist/css/calendar.css'); +} + +build(); diff --git a/packages/calendar/package-lock.json b/packages/calendar/package-lock.json new file mode 100644 index 0000000..c00dabb --- /dev/null +++ b/packages/calendar/package-lock.json @@ -0,0 +1,167 @@ +{ + "name": "@plantempus/calendar", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@plantempus/calendar", + "version": "0.1.0", + "dependencies": { + "dayjs": "^1.11.0" + }, + "peerDependencies": { + "@novadi/core": "^0.6.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT", + "peer": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@novadi/core": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@novadi/core/-/core-0.6.0.tgz", + "integrity": "sha512-CU1134Nd7ULMg9OQbID5oP+FLtrMkNiLJ17+dmy4jjmPDcPK/dVzKTFxvJmbBvEfZEc9WtmkmJjqw11ABU7Jxw==", + "license": "MIT", + "peer": true, + "dependencies": { + "unplugin": "^2.3.10" + }, + "optionalDependencies": { + "@rollup/rollup-win32-x64-msvc": "^4.52.5" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.56.0.tgz", + "integrity": "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unplugin": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "license": "MIT", + "peer": true + } + } +} diff --git a/packages/calendar/package.json b/packages/calendar/package.json new file mode 100644 index 0000000..9a7374b --- /dev/null +++ b/packages/calendar/package.json @@ -0,0 +1,57 @@ +{ + "name": "calendar", + "version": "0.1.7", + "description": "Calendar library", + "author": "SWP", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + }, + "./teams": { + "import": "./dist/extensions/teams/index.js", + "types": "./dist/extensions/teams/index.d.ts" + }, + "./departments": { + "import": "./dist/extensions/departments/index.js", + "types": "./dist/extensions/departments/index.d.ts" + }, + "./bookings": { + "import": "./dist/extensions/bookings/index.js", + "types": "./dist/extensions/bookings/index.d.ts" + }, + "./customers": { + "import": "./dist/extensions/customers/index.js", + "types": "./dist/extensions/customers/index.d.ts" + }, + "./schedules": { + "import": "./dist/extensions/schedules/index.js", + "types": "./dist/extensions/schedules/index.d.ts" + }, + "./audit": { + "import": "./dist/extensions/audit/index.js", + "types": "./dist/extensions/audit/index.d.ts" + }, + "./styles": "./dist/css/calendar.css" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "node build.js && npm run build:types", + "build:types": "tsc --emitDeclarationOnly --outDir dist" + }, + "peerDependencies": { + "@novadi/core": "^0.6.0" + }, + "dependencies": { + "dayjs": "^1.11.0" + }, + "devDependencies": { + "esbuild": "^0.24.0", + "typescript": "^5.0.0" + } +} diff --git a/packages/calendar/src/CompositionRoot.ts b/packages/calendar/src/CompositionRoot.ts new file mode 100644 index 0000000..e2b429a --- /dev/null +++ b/packages/calendar/src/CompositionRoot.ts @@ -0,0 +1,163 @@ +import { Container, Builder } from '@novadi/core'; + +// Core +import { EventBus } from './core/EventBus'; +import { DateService } from './core/DateService'; +import { CalendarOrchestrator } from './core/CalendarOrchestrator'; +import { CalendarApp } from './core/CalendarApp'; +import { TimeAxisRenderer } from './features/timeaxis/TimeAxisRenderer'; +import { ScrollManager } from './core/ScrollManager'; +import { HeaderDrawerManager } from './core/HeaderDrawerManager'; +import { ITimeFormatConfig } from './core/ITimeFormatConfig'; +import { IGridConfig } from './core/IGridConfig'; + +// Types +import { IEventBus, ICalendarEvent, ISync, IResource } from './types/CalendarTypes'; +import { TenantSetting } from './types/SettingsTypes'; +import { ViewConfig } from './core/ViewConfig'; + +// Renderers +import { IRenderer } from './core/IGroupingRenderer'; +import { DateRenderer } from './features/date/DateRenderer'; +import { ResourceRenderer } from './features/resource/ResourceRenderer'; +import { EventRenderer } from './features/event/EventRenderer'; +import { ScheduleRenderer } from './features/schedule/ScheduleRenderer'; +import { HeaderDrawerRenderer } from './features/headerdrawer/HeaderDrawerRenderer'; + +// Storage +import { IndexedDBContext, IDBConfig, defaultDBConfig } from './storage/IndexedDBContext'; +import { IStore } from './storage/IStore'; +import { IEntityService } from './storage/IEntityService'; +import { EventStore } from './storage/events/EventStore'; +import { EventService } from './storage/events/EventService'; +import { ResourceStore } from './storage/resources/ResourceStore'; +import { ResourceService } from './storage/resources/ResourceService'; +import { SettingsStore } from './storage/settings/SettingsStore'; +import { SettingsService } from './storage/settings/SettingsService'; +import { ViewConfigStore } from './storage/viewconfigs/ViewConfigStore'; +import { ViewConfigService } from './storage/viewconfigs/ViewConfigService'; + +// Managers +import { DragDropManager } from './managers/DragDropManager'; +import { EdgeScrollManager } from './managers/EdgeScrollManager'; +import { ResizeManager } from './managers/ResizeManager'; +import { EventPersistenceManager } from './managers/EventPersistenceManager'; + +/** + * Default configuration values + */ +export const defaultTimeFormatConfig: ITimeFormatConfig = { + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + use24HourFormat: true, + locale: 'da-DK', + dateFormat: 'locale', + showSeconds: false +}; + +export const defaultGridConfig: IGridConfig = { + hourHeight: 64, + dayStartHour: 6, + dayEndHour: 18, + snapInterval: 15, + gridStartThresholdMinutes: 30 +}; + +/** + * Calendar configuration options + */ +export interface ICalendarOptions { + timeConfig?: ITimeFormatConfig; + gridConfig?: IGridConfig; + dbConfig?: IDBConfig; +} + +/** + * Creates a configured DI container with all core calendar services registered. + * Call this to get a ready-to-use container for the calendar. + * + * @param options - Optional calendar configuration options + * @returns Configured Container instance + */ +export function createCalendarContainer(options?: ICalendarOptions): Container { + const container = new Container(); + const builder = container.builder(); + + registerCoreServices(builder, options); + + return builder.build(); +} + +/** + * Registers all core calendar services with the DI container builder. + * Use this when you need to customize the container or add extensions. + * + * @param builder - ContainerBuilder to register services with + * @param options - Optional calendar configuration options + */ +export function registerCoreServices( + builder: Builder, + options?: ICalendarOptions +): void { + const timeConfig = options?.timeConfig ?? defaultTimeFormatConfig; + const gridConfig = options?.gridConfig ?? defaultGridConfig; + const dbConfig = options?.dbConfig ?? defaultDBConfig; + // Configuration instances + builder.registerInstance(timeConfig).as(); + builder.registerInstance(gridConfig).as(); + builder.registerInstance(dbConfig).as(); + + // Core - EventBus (singleton pattern via dual registration) + builder.registerType(EventBus).as(); + builder.registerType(EventBus).as(); + + // Core Services + builder.registerType(DateService).as(); + + // Storage infrastructure + builder.registerType(IndexedDBContext).as(); + + // Core Stores (for IndexedDB schema creation via IStore[] array injection) + builder.registerType(EventStore).as(); + builder.registerType(ResourceStore).as(); + builder.registerType(SettingsStore).as(); + builder.registerType(ViewConfigStore).as(); + + // Core Entity Services (polymorphic via IEntityService) + builder.registerType(EventService).as>(); + builder.registerType(EventService).as>(); + builder.registerType(EventService).as(); + + builder.registerType(ResourceService).as>(); + builder.registerType(ResourceService).as>(); + builder.registerType(ResourceService).as(); + + builder.registerType(SettingsService).as>(); + builder.registerType(SettingsService).as>(); + builder.registerType(SettingsService).as(); + + builder.registerType(ViewConfigService).as>(); + builder.registerType(ViewConfigService).as>(); + builder.registerType(ViewConfigService).as(); + + // Core Renderers + builder.registerType(EventRenderer).as(); + builder.registerType(ScheduleRenderer).as(); + builder.registerType(HeaderDrawerRenderer).as(); + builder.registerType(TimeAxisRenderer).as(); + + // Grouping Renderers (registered as IRenderer[] for CalendarOrchestrator) + builder.registerType(DateRenderer).as(); + builder.registerType(ResourceRenderer).as(); + + // Core Managers + builder.registerType(ScrollManager).as(); + builder.registerType(HeaderDrawerManager).as(); + builder.registerType(DragDropManager).as(); + builder.registerType(EdgeScrollManager).as(); + builder.registerType(ResizeManager).as(); + builder.registerType(EventPersistenceManager).as(); + + // Orchestrator and App + builder.registerType(CalendarOrchestrator).as(); + builder.registerType(CalendarApp).as(); +} diff --git a/packages/calendar/src/constants/CoreEvents.ts b/packages/calendar/src/constants/CoreEvents.ts new file mode 100644 index 0000000..7363138 --- /dev/null +++ b/packages/calendar/src/constants/CoreEvents.ts @@ -0,0 +1,71 @@ +/** + * CoreEvents - Consolidated essential events for the calendar + */ +export const CoreEvents = { + // Lifecycle events + INITIALIZED: 'core:initialized', + READY: 'core:ready', + DESTROYED: 'core:destroyed', + + // View events + VIEW_CHANGED: 'view:changed', + VIEW_RENDERED: 'view:rendered', + + // Navigation events + DATE_CHANGED: 'nav:date-changed', + NAVIGATION_COMPLETED: 'nav:navigation-completed', + + // Data events + DATA_LOADING: 'data:loading', + DATA_LOADED: 'data:loaded', + DATA_ERROR: 'data:error', + + // Grid events + GRID_RENDERED: 'grid:rendered', + GRID_CLICKED: 'grid:clicked', + + // Event management + EVENT_CREATED: 'event:created', + EVENT_UPDATED: 'event:updated', + EVENT_DELETED: 'event:deleted', + EVENT_SELECTED: 'event:selected', + + // Event drag-drop + EVENT_DRAG_START: 'event:drag-start', + EVENT_DRAG_MOVE: 'event:drag-move', + EVENT_DRAG_END: 'event:drag-end', + EVENT_DRAG_CANCEL: 'event:drag-cancel', + EVENT_DRAG_COLUMN_CHANGE: 'event:drag-column-change', + + // Header drag (timed → header conversion) + EVENT_DRAG_ENTER_HEADER: 'event:drag-enter-header', + EVENT_DRAG_MOVE_HEADER: 'event:drag-move-header', + EVENT_DRAG_LEAVE_HEADER: 'event:drag-leave-header', + + // Event resize + EVENT_RESIZE_START: 'event:resize-start', + EVENT_RESIZE_END: 'event:resize-end', + + // Edge scroll + EDGE_SCROLL_TICK: 'edge-scroll:tick', + EDGE_SCROLL_STARTED: 'edge-scroll:started', + EDGE_SCROLL_STOPPED: 'edge-scroll:stopped', + + // System events + ERROR: 'system:error', + + // Sync events + SYNC_STARTED: 'sync:started', + SYNC_COMPLETED: 'sync:completed', + SYNC_FAILED: 'sync:failed', + + // Entity events - for audit and sync + ENTITY_SAVED: 'entity:saved', + ENTITY_DELETED: 'entity:deleted', + + // Audit events + AUDIT_LOGGED: 'audit:logged', + + // Rendering events + EVENTS_RENDERED: 'events:rendered' +} as const; diff --git a/packages/calendar/src/core/BaseGroupingRenderer.ts b/packages/calendar/src/core/BaseGroupingRenderer.ts new file mode 100644 index 0000000..60c9abf --- /dev/null +++ b/packages/calendar/src/core/BaseGroupingRenderer.ts @@ -0,0 +1,91 @@ +import { IRenderer, IRenderContext } from './IGroupingRenderer'; + +/** + * Entity must have id + */ +export interface IGroupingEntity { + id: string; +} + +/** + * Configuration for a grouping renderer + */ +export interface IGroupingRendererConfig { + elementTag: string; // e.g., 'swp-team-header' + idAttribute: string; // e.g., 'teamId' -> data-team-id + colspanVar: string; // e.g., '--team-cols' +} + +/** + * Abstract base class for grouping renderers + * + * Handles: + * - Fetching entities by IDs + * - Calculating colspan from parentChildMap + * - Creating header elements + * - Appending to container + * + * Subclasses override: + * - renderHeader() for custom content + * - getDisplayName() for entity display text + */ +export abstract class BaseGroupingRenderer implements IRenderer { + abstract readonly type: string; + protected abstract readonly config: IGroupingRendererConfig; + + /** + * Fetch entities from service + */ + protected abstract getEntities(ids: string[]): Promise; + + /** + * Get display name for entity + */ + protected abstract getDisplayName(entity: T): string; + + /** + * Main render method - handles common logic + */ + async render(context: IRenderContext): Promise { + const allowedIds = context.filter[this.type] || []; + if (allowedIds.length === 0) return; + + const entities = await this.getEntities(allowedIds); + const dateCount = context.filter['date']?.length || 1; + const childIds = context.childType ? context.filter[context.childType] || [] : []; + + for (const entity of entities) { + const entityChildIds = context.parentChildMap?.[entity.id] || []; + const childCount = entityChildIds.filter(id => childIds.includes(id)).length; + const colspan = childCount * dateCount; + + const header = document.createElement(this.config.elementTag); + header.dataset[this.config.idAttribute] = entity.id; + header.style.setProperty(this.config.colspanVar, String(colspan)); + + // Allow subclass to customize header content + this.renderHeader(entity, header, context); + + context.headerContainer.appendChild(header); + } + } + + /** + * Override this method for custom header rendering + * Default: just sets textContent to display name + */ + protected renderHeader(entity: T, header: HTMLElement, _context: IRenderContext): void { + header.textContent = this.getDisplayName(entity); + } + + /** + * Helper to render a single entity header. + * Can be used by subclasses that override render() but want consistent header creation. + */ + protected createHeader(entity: T, context: IRenderContext): HTMLElement { + const header = document.createElement(this.config.elementTag); + header.dataset[this.config.idAttribute] = entity.id; + this.renderHeader(entity, header, context); + return header; + } +} diff --git a/packages/calendar/src/core/CalendarApp.ts b/packages/calendar/src/core/CalendarApp.ts new file mode 100644 index 0000000..246b257 --- /dev/null +++ b/packages/calendar/src/core/CalendarApp.ts @@ -0,0 +1,201 @@ +import { CalendarOrchestrator } from './CalendarOrchestrator'; +import { TimeAxisRenderer } from '../features/timeaxis/TimeAxisRenderer'; +import { NavigationAnimator } from './NavigationAnimator'; +import { DateService } from './DateService'; +import { ScrollManager } from './ScrollManager'; +import { HeaderDrawerManager } from './HeaderDrawerManager'; +import { ViewConfig } from './ViewConfig'; +import { DragDropManager } from '../managers/DragDropManager'; +import { EdgeScrollManager } from '../managers/EdgeScrollManager'; +import { ResizeManager } from '../managers/ResizeManager'; +import { EventPersistenceManager } from '../managers/EventPersistenceManager'; +import { HeaderDrawerRenderer } from '../features/headerdrawer/HeaderDrawerRenderer'; +import { SettingsService } from '../storage/settings/SettingsService'; +import { ViewConfigService } from '../storage/viewconfigs/ViewConfigService'; +import { IWorkweekPreset } from '../types/SettingsTypes'; +import { IEventBus } from '../types/CalendarTypes'; +import { + CalendarEvents, + RenderPayload, + WorkweekChangePayload, + ViewUpdatePayload +} from './CalendarEvents'; + +export class CalendarApp { + private animator!: NavigationAnimator; + private container!: HTMLElement; + private dayOffset = 0; + private currentViewId = 'simple'; + private workweekPreset: IWorkweekPreset | null = null; + private groupingOverrides: Map = new Map(); + + constructor( + private orchestrator: CalendarOrchestrator, + private timeAxisRenderer: TimeAxisRenderer, + private dateService: DateService, + private scrollManager: ScrollManager, + private headerDrawerManager: HeaderDrawerManager, + private dragDropManager: DragDropManager, + private edgeScrollManager: EdgeScrollManager, + private resizeManager: ResizeManager, + private headerDrawerRenderer: HeaderDrawerRenderer, + private eventPersistenceManager: EventPersistenceManager, + private settingsService: SettingsService, + private viewConfigService: ViewConfigService, + private eventBus: IEventBus + ) {} + + async init(container: HTMLElement): Promise { + this.container = container; + + // Load settings + const gridSettings = await this.settingsService.getGridSettings(); + if (!gridSettings) { + throw new Error('GridSettings not found'); + } + + this.workweekPreset = await this.settingsService.getDefaultWorkweekPreset(); + + // Create NavigationAnimator with DOM elements + this.animator = new NavigationAnimator( + container.querySelector('swp-header-track') as HTMLElement, + container.querySelector('swp-content-track') as HTMLElement, + container.querySelector('swp-header-drawer') + ); + + // Render time axis from settings + this.timeAxisRenderer.render( + container.querySelector('#time-axis') as HTMLElement, + gridSettings.dayStartHour, + gridSettings.dayEndHour + ); + + // Init managers + this.scrollManager.init(container); + this.headerDrawerManager.init(container); + this.dragDropManager.init(container); + this.resizeManager.init(container); + + const scrollableContent = container.querySelector('swp-scrollable-content') as HTMLElement; + this.edgeScrollManager.init(scrollableContent); + + // Setup command event listeners + this.setupEventListeners(); + + // Emit ready status + this.emitStatus('ready'); + } + + private setupEventListeners(): void { + // Navigation commands via EventBus + this.eventBus.on(CalendarEvents.CMD_NAVIGATE_PREV, () => { + this.handleNavigatePrev(); + }); + + this.eventBus.on(CalendarEvents.CMD_NAVIGATE_NEXT, () => { + this.handleNavigateNext(); + }); + + // Drawer toggle via EventBus + this.eventBus.on(CalendarEvents.CMD_DRAWER_TOGGLE, () => { + this.headerDrawerManager.toggle(); + }); + + // Render command via EventBus + this.eventBus.on(CalendarEvents.CMD_RENDER, (e: Event) => { + const { viewId } = (e as CustomEvent).detail; + this.handleRenderCommand(viewId); + }); + + // Workweek change via EventBus + this.eventBus.on(CalendarEvents.CMD_WORKWEEK_CHANGE, (e: Event) => { + const { presetId } = (e as CustomEvent).detail; + this.handleWorkweekChange(presetId); + }); + + // View update via EventBus + this.eventBus.on(CalendarEvents.CMD_VIEW_UPDATE, (e: Event) => { + const { type, values } = (e as CustomEvent).detail; + this.handleViewUpdate(type, values); + }); + } + + private async handleRenderCommand(viewId: string): Promise { + this.currentViewId = viewId; + await this.render(); + this.emitStatus('rendered', { viewId }); + } + + private async handleNavigatePrev(): Promise { + const step = this.workweekPreset?.periodDays ?? 7; + this.dayOffset -= step; + await this.animator.slide('right', () => this.render()); + this.emitStatus('rendered', { viewId: this.currentViewId }); + } + + private async handleNavigateNext(): Promise { + const step = this.workweekPreset?.periodDays ?? 7; + this.dayOffset += step; + await this.animator.slide('left', () => this.render()); + this.emitStatus('rendered', { viewId: this.currentViewId }); + } + + private async handleWorkweekChange(presetId: string): Promise { + const preset = await this.settingsService.getWorkweekPreset(presetId); + if (preset) { + this.workweekPreset = preset; + await this.render(); + this.emitStatus('rendered', { viewId: this.currentViewId }); + } + } + + private async handleViewUpdate(type: string, values: string[]): Promise { + this.groupingOverrides.set(type, values); + await this.render(); + this.emitStatus('rendered', { viewId: this.currentViewId }); + } + + private async render(): Promise { + const storedConfig = await this.viewConfigService.getById(this.currentViewId); + if (!storedConfig) { + this.emitStatus('error', { message: `ViewConfig not found: ${this.currentViewId}` }); + return; + } + + // Populate date values based on workweek preset and day offset + const workDays = this.workweekPreset?.workDays || [1, 2, 3, 4, 5]; + const periodDays = this.workweekPreset?.periodDays ?? 7; + + // For single-day navigation (periodDays=1), show consecutive days from offset + // For week navigation (periodDays=7), show workDays from the week containing offset + const dates = periodDays === 1 + ? this.dateService.getDatesFromOffset(this.dayOffset, workDays.length) + : this.dateService.getWorkDaysFromOffset(this.dayOffset, workDays); + + // Clone config and apply overrides + const viewConfig: ViewConfig = { + ...storedConfig, + groupings: storedConfig.groupings.map(g => { + // Apply date values + if (g.type === 'date') { + return { ...g, values: dates }; + } + // Apply grouping overrides + const override = this.groupingOverrides.get(g.type); + if (override) { + return { ...g, values: override }; + } + return g; + }) + }; + + await this.orchestrator.render(viewConfig, this.container); + } + + private emitStatus(status: string, detail?: object): void { + this.container.dispatchEvent(new CustomEvent(`calendar:status:${status}`, { + detail, + bubbles: true + })); + } +} diff --git a/packages/calendar/src/core/CalendarEvents.ts b/packages/calendar/src/core/CalendarEvents.ts new file mode 100644 index 0000000..4cf553e --- /dev/null +++ b/packages/calendar/src/core/CalendarEvents.ts @@ -0,0 +1,28 @@ +/** + * CalendarEvents - Command and status events for CalendarApp + */ +export const CalendarEvents = { + // Command events (host → calendar) + CMD_NAVIGATE_PREV: 'calendar:cmd:navigate:prev', + CMD_NAVIGATE_NEXT: 'calendar:cmd:navigate:next', + CMD_DRAWER_TOGGLE: 'calendar:cmd:drawer:toggle', + CMD_RENDER: 'calendar:cmd:render', + CMD_WORKWEEK_CHANGE: 'calendar:cmd:workweek:change', + CMD_VIEW_UPDATE: 'calendar:cmd:view:update' +} as const; + +/** + * Payload interfaces for CalendarEvents + */ +export interface RenderPayload { + viewId: string; +} + +export interface WorkweekChangePayload { + presetId: string; +} + +export interface ViewUpdatePayload { + type: string; + values: string[]; +} diff --git a/packages/calendar/src/core/CalendarOrchestrator.ts b/packages/calendar/src/core/CalendarOrchestrator.ts new file mode 100644 index 0000000..933e8a5 --- /dev/null +++ b/packages/calendar/src/core/CalendarOrchestrator.ts @@ -0,0 +1,124 @@ +import { IRenderer, IRenderContext } from './IGroupingRenderer'; +import { buildPipeline } from './RenderBuilder'; +import { EventRenderer } from '../features/event/EventRenderer'; +import { ScheduleRenderer } from '../features/schedule/ScheduleRenderer'; +import { HeaderDrawerRenderer } from '../features/headerdrawer/HeaderDrawerRenderer'; +import { ViewConfig, GroupingConfig } from './ViewConfig'; +import { FilterTemplate } from './FilterTemplate'; +import { DateService } from './DateService'; +import { IEntityService } from '../storage/IEntityService'; +import { ISync } from '../types/CalendarTypes'; + +export class CalendarOrchestrator { + constructor( + private allRenderers: IRenderer[], + private eventRenderer: EventRenderer, + private scheduleRenderer: ScheduleRenderer, + private headerDrawerRenderer: HeaderDrawerRenderer, + private dateService: DateService, + private entityServices: IEntityService[] + ) {} + + async render(viewConfig: ViewConfig, container: HTMLElement): Promise { + const headerContainer = container.querySelector('swp-calendar-header') as HTMLElement; + const columnContainer = container.querySelector('swp-day-columns') as HTMLElement; + if (!headerContainer || !columnContainer) { + throw new Error('Missing swp-calendar-header or swp-day-columns'); + } + + // Byg filter fra viewConfig + const filter: Record = {}; + for (const grouping of viewConfig.groupings) { + filter[grouping.type] = grouping.values; + } + + // Byg FilterTemplate fra viewConfig groupings (kun de med idProperty) + const filterTemplate = new FilterTemplate(this.dateService); + for (const grouping of viewConfig.groupings) { + if (grouping.idProperty) { + filterTemplate.addField(grouping.idProperty, grouping.derivedFrom); + } + } + + // Resolve belongsTo relations (e.g., team.resourceIds) + const { parentChildMap, childType } = await this.resolveBelongsTo(viewConfig.groupings, filter); + + const context: IRenderContext = { headerContainer, columnContainer, filter, groupings: viewConfig.groupings, parentChildMap, childType }; + + // Clear + headerContainer.innerHTML = ''; + columnContainer.innerHTML = ''; + + // Sæt data-levels attribut for CSS grid-row styling + const levels = viewConfig.groupings.map(g => g.type).join(' '); + headerContainer.dataset.levels = levels; + + // Vælg renderers baseret på groupings types + const activeRenderers = this.selectRenderers(viewConfig); + + // Byg og kør pipeline + const pipeline = buildPipeline(activeRenderers); + await pipeline.run(context); + + // Render schedule unavailable zones (før events) + await this.scheduleRenderer.render(container, filter); + + // Render timed events in grid (med filterTemplate til matching) + await this.eventRenderer.render(container, filter, filterTemplate); + + // Render allDay events in header drawer (med filterTemplate til matching) + await this.headerDrawerRenderer.render(container, filter, filterTemplate); + } + + private selectRenderers(viewConfig: ViewConfig): IRenderer[] { + const types = viewConfig.groupings.map(g => g.type); + // Sortér renderers i samme rækkefølge som viewConfig.groupings + return types + .map(type => this.allRenderers.find(r => r.type === type)) + .filter((r): r is IRenderer => r !== undefined); + } + + /** + * Resolve belongsTo relations to build parent-child map + * e.g., belongsTo: 'team.resourceIds' → { team1: ['EMP001', 'EMP002'], team2: [...] } + * Also returns the childType (the grouping type that has belongsTo) + */ + private async resolveBelongsTo( + groupings: GroupingConfig[], + filter: Record + ): Promise<{ parentChildMap?: Record; childType?: string }> { + // Find grouping with belongsTo + const childGrouping = groupings.find(g => g.belongsTo); + if (!childGrouping?.belongsTo) return {}; + + // Parse belongsTo: 'team.resourceIds' + const [entityType, property] = childGrouping.belongsTo.split('.'); + if (!entityType || !property) return {}; + + // Get parent IDs from filter + const parentIds = filter[entityType] || []; + if (parentIds.length === 0) return {}; + + // Find service dynamisk baseret på entityType (ingen hardcoded type check) + const service = this.entityServices.find(s => + s.entityType.toLowerCase() === entityType + ); + if (!service) return {}; + + // Hent alle entities og filtrer på parentIds + const allEntities = await service.getAll(); + const entities = allEntities.filter(e => + parentIds.includes((e as unknown as Record).id as string) + ); + + // Byg parent-child map + const map: Record = {}; + for (const entity of entities) { + const entityRecord = entity as unknown as Record; + const children = (entityRecord[property] as string[]) || []; + map[entityRecord.id as string] = children; + } + + return { parentChildMap: map, childType: childGrouping.type }; + } +} diff --git a/packages/calendar/src/core/DateService.ts b/packages/calendar/src/core/DateService.ts new file mode 100644 index 0000000..1d3c44d --- /dev/null +++ b/packages/calendar/src/core/DateService.ts @@ -0,0 +1,195 @@ +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import timezone from 'dayjs/plugin/timezone'; +import isoWeek from 'dayjs/plugin/isoWeek'; +import { ITimeFormatConfig } from './ITimeFormatConfig'; + +// Enable dayjs plugins +dayjs.extend(utc); +dayjs.extend(timezone); +dayjs.extend(isoWeek); + +export class DateService { + private timezone: string; + private baseDate: dayjs.Dayjs; + + constructor(private config: ITimeFormatConfig, baseDate?: Date) { + this.timezone = config.timezone; + // Allow setting a fixed base date for demo/testing purposes + this.baseDate = baseDate ? dayjs(baseDate) : dayjs(); + } + + /** + * Set a fixed base date (useful for demos with static mock data) + */ + setBaseDate(date: Date): void { + this.baseDate = dayjs(date); + } + + /** + * Get the current base date (either fixed or today) + */ + getBaseDate(): Date { + return this.baseDate.toDate(); + } + + parseISO(isoString: string): Date { + return dayjs(isoString).toDate(); + } + + getDayName(date: Date, format: 'short' | 'long' = 'short'): string { + return new Intl.DateTimeFormat(this.config.locale, { weekday: format }).format(date); + } + + /** + * Get dates starting from a day offset + * @param dayOffset - Day offset from base date + * @param count - Number of consecutive days to return + * @returns Array of date strings in YYYY-MM-DD format + */ + getDatesFromOffset(dayOffset: number, count: number): string[] { + const startDate = this.baseDate.add(dayOffset, 'day'); + return Array.from({ length: count }, (_, i) => + startDate.add(i, 'day').format('YYYY-MM-DD') + ); + } + + /** + * Get specific weekdays from the week containing the offset date + * @param dayOffset - Day offset from base date + * @param workDays - Array of ISO weekday numbers (1=Monday, 7=Sunday) + * @returns Array of date strings in YYYY-MM-DD format + */ + getWorkDaysFromOffset(dayOffset: number, workDays: number[]): string[] { + // Get the date at offset, then find its week's Monday + const targetDate = this.baseDate.add(dayOffset, 'day'); + const monday = targetDate.startOf('week').add(1, 'day'); + + return workDays.map(isoDay => { + // ISO: 1=Monday, 7=Sunday → days from Monday: 0-6 + const daysFromMonday = isoDay === 7 ? 6 : isoDay - 1; + return monday.add(daysFromMonday, 'day').format('YYYY-MM-DD'); + }); + } + + // Legacy methods for backwards compatibility + getWeekDates(weekOffset = 0, days = 7): string[] { + return this.getDatesFromOffset(weekOffset * 7, days); + } + + getWorkWeekDates(weekOffset: number, workDays: number[]): string[] { + return this.getWorkDaysFromOffset(weekOffset * 7, workDays); + } + + // ============================================ + // FORMATTING + // ============================================ + + formatTime(date: Date, showSeconds = false): string { + const pattern = showSeconds ? 'HH:mm:ss' : 'HH:mm'; + return dayjs(date).format(pattern); + } + + formatTimeRange(start: Date, end: Date): string { + return `${this.formatTime(start)} - ${this.formatTime(end)}`; + } + + formatDate(date: Date): string { + return dayjs(date).format('YYYY-MM-DD'); + } + + getDateKey(date: Date): string { + return this.formatDate(date); + } + + // ============================================ + // COLUMN KEY + // ============================================ + + /** + * Build a uniform columnKey from grouping segments + * Handles any combination of date, resource, team, etc. + * + * @example + * buildColumnKey({ date: '2025-12-09' }) → "2025-12-09" + * buildColumnKey({ date: '2025-12-09', resource: 'EMP001' }) → "2025-12-09:EMP001" + */ + buildColumnKey(segments: Record): string { + // Always put date first if present, then other segments alphabetically + const date = segments.date; + const others = Object.entries(segments) + .filter(([k]) => k !== 'date') + .sort(([a], [b]) => a.localeCompare(b)) + .map(([, v]) => v); + + return date ? [date, ...others].join(':') : others.join(':'); + } + + /** + * Parse a columnKey back into segments + * Assumes format: "date:resource:..." or just "date" + */ + parseColumnKey(columnKey: string): { date: string; resource?: string } { + const parts = columnKey.split(':'); + return { + date: parts[0], + resource: parts[1] + }; + } + + /** + * Extract dateKey from columnKey (first segment) + */ + getDateFromColumnKey(columnKey: string): string { + return columnKey.split(':')[0]; + } + + // ============================================ + // TIME CALCULATIONS + // ============================================ + + timeToMinutes(timeString: string): number { + const parts = timeString.split(':').map(Number); + const hours = parts[0] || 0; + const minutes = parts[1] || 0; + return hours * 60 + minutes; + } + + minutesToTime(totalMinutes: number): string { + const hours = Math.floor(totalMinutes / 60); + const minutes = totalMinutes % 60; + return dayjs().hour(hours).minute(minutes).format('HH:mm'); + } + + getMinutesSinceMidnight(date: Date): number { + const d = dayjs(date); + return d.hour() * 60 + d.minute(); + } + + // ============================================ + // UTC CONVERSIONS + // ============================================ + + toUTC(localDate: Date): string { + return dayjs.tz(localDate, this.timezone).utc().toISOString(); + } + + fromUTC(utcString: string): Date { + return dayjs.utc(utcString).tz(this.timezone).toDate(); + } + + // ============================================ + // DATE CREATION + // ============================================ + + createDateAtTime(baseDate: Date | string, timeString: string): Date { + const totalMinutes = this.timeToMinutes(timeString); + const hours = Math.floor(totalMinutes / 60); + const minutes = totalMinutes % 60; + return dayjs(baseDate).startOf('day').hour(hours).minute(minutes).toDate(); + } + + getISOWeekDay(date: Date | string): number { + return dayjs(date).isoWeekday(); // 1=Monday, 7=Sunday + } +} diff --git a/packages/calendar/src/core/EntityResolver.ts b/packages/calendar/src/core/EntityResolver.ts new file mode 100644 index 0000000..7161c30 --- /dev/null +++ b/packages/calendar/src/core/EntityResolver.ts @@ -0,0 +1,48 @@ +import { IEntityResolver } from './IEntityResolver'; + +/** + * EntityResolver - Resolves entities from pre-loaded cache + * + * Entities must be loaded before use (typically at render time). + * This allows synchronous lookups during filtering. + */ +export class EntityResolver implements IEntityResolver { + private cache: Map>> = new Map(); + + /** + * Load entities into cache for a given type + * @param entityType - The entity type (e.g., 'resource') + * @param entities - Array of entities with 'id' property + */ + load(entityType: string, entities: T[]): void { + const typeCache = new Map>(); + for (const entity of entities) { + // Cast to Record for storage while preserving original data + typeCache.set(entity.id, entity as unknown as Record); + } + this.cache.set(entityType, typeCache); + } + + /** + * Resolve an entity by type and ID + */ + resolve(entityType: string, id: string): Record | undefined { + const typeCache = this.cache.get(entityType); + if (!typeCache) return undefined; + return typeCache.get(id); + } + + /** + * Clear all cached entities + */ + clear(): void { + this.cache.clear(); + } + + /** + * Clear cache for a specific entity type + */ + clearType(entityType: string): void { + this.cache.delete(entityType); + } +} diff --git a/packages/calendar/src/core/EventBus.ts b/packages/calendar/src/core/EventBus.ts new file mode 100644 index 0000000..469a73e --- /dev/null +++ b/packages/calendar/src/core/EventBus.ts @@ -0,0 +1,174 @@ +import { IEventLogEntry, IListenerEntry, IEventBus } from '../types/CalendarTypes'; + +/** + * Central event dispatcher for calendar using DOM CustomEvents + * Provides logging and debugging capabilities + */ +export class EventBus implements IEventBus { + private eventLog: IEventLogEntry[] = []; + private debug: boolean = false; + private listeners: Set = new Set(); + + // Log configuration for different categories + private logConfig: { [key: string]: boolean } = { + calendar: true, + grid: true, + event: true, + scroll: true, + navigation: true, + view: true, + default: true + }; + + /** + * Subscribe to an event via DOM addEventListener + */ + on(eventType: string, handler: EventListener, options?: AddEventListenerOptions): () => void { + document.addEventListener(eventType, handler, options); + + // Track for cleanup + this.listeners.add({ eventType, handler, options }); + + // Return unsubscribe function + return () => this.off(eventType, handler); + } + + /** + * Subscribe to an event once + */ + once(eventType: string, handler: EventListener): () => void { + return this.on(eventType, handler, { once: true }); + } + + /** + * Unsubscribe from an event + */ + off(eventType: string, handler: EventListener): void { + document.removeEventListener(eventType, handler); + + // Remove from tracking + for (const listener of this.listeners) { + if (listener.eventType === eventType && listener.handler === handler) { + this.listeners.delete(listener); + break; + } + } + } + + /** + * Emit an event via DOM CustomEvent + */ + emit(eventType: string, detail: unknown = {}): boolean { + // Validate eventType + if (!eventType) { + return false; + } + + const event = new CustomEvent(eventType, { + detail: detail ?? {}, + bubbles: true, + cancelable: true + }); + + // Log event with grouping + if (this.debug) { + this.logEventWithGrouping(eventType, detail); + } + + this.eventLog.push({ + type: eventType, + detail: detail ?? {}, + timestamp: Date.now() + }); + + // Emit on document (only DOM events now) + return !document.dispatchEvent(event); + } + + /** + * Log event with console grouping + */ + private logEventWithGrouping(eventType: string, _detail: unknown): void { + // Extract category from event type (e.g., 'calendar:datechanged' → 'calendar') + const category = this.extractCategory(eventType); + + // Only log if category is enabled + if (!this.logConfig[category]) { + return; + } + + // Get category emoji and color (used for future console styling) + this.getCategoryStyle(category); + } + + /** + * Extract category from event type + */ + private extractCategory(eventType: string): string { + if (!eventType) { + return 'unknown'; + } + + if (eventType.includes(':')) { + return eventType.split(':')[0]; + } + + // Fallback: try to detect category from event name patterns + const lowerType = eventType.toLowerCase(); + if (lowerType.includes('grid') || lowerType.includes('rendered')) return 'grid'; + if (lowerType.includes('event') || lowerType.includes('sync')) return 'event'; + if (lowerType.includes('scroll')) return 'scroll'; + if (lowerType.includes('nav') || lowerType.includes('date')) return 'navigation'; + if (lowerType.includes('view')) return 'view'; + + return 'default'; + } + + /** + * Get styling for different categories + */ + private getCategoryStyle(category: string): { emoji: string; color: string } { + const styles: { [key: string]: { emoji: string; color: string } } = { + calendar: { emoji: '📅', color: '#2196F3' }, + grid: { emoji: '📊', color: '#4CAF50' }, + event: { emoji: '📌', color: '#FF9800' }, + scroll: { emoji: '📜', color: '#9C27B0' }, + navigation: { emoji: '🧭', color: '#F44336' }, + view: { emoji: '👁', color: '#00BCD4' }, + default: { emoji: '📢', color: '#607D8B' } + }; + + return styles[category] || styles.default; + } + + /** + * Configure logging for specific categories + */ + setLogConfig(config: { [key: string]: boolean }): void { + this.logConfig = { ...this.logConfig, ...config }; + } + + /** + * Get current log configuration + */ + getLogConfig(): { [key: string]: boolean } { + return { ...this.logConfig }; + } + + /** + * Get event history + */ + getEventLog(eventType?: string): IEventLogEntry[] { + if (eventType) { + return this.eventLog.filter(e => e.type === eventType); + } + return this.eventLog; + } + + /** + * Enable/disable debug mode + */ + setDebug(enabled: boolean): void { + this.debug = enabled; + } +} diff --git a/packages/calendar/src/core/FilterTemplate.ts b/packages/calendar/src/core/FilterTemplate.ts new file mode 100644 index 0000000..00451b1 --- /dev/null +++ b/packages/calendar/src/core/FilterTemplate.ts @@ -0,0 +1,149 @@ +import { ICalendarEvent } from '../types/CalendarTypes'; +import { DateService } from './DateService'; +import { IEntityResolver } from './IEntityResolver'; + +/** + * Field definition for FilterTemplate + */ +interface IFilterField { + idProperty: string; + derivedFrom?: string; +} + +/** + * Parsed dot-notation reference + */ +interface IDotNotation { + entityType: string; // e.g., 'resource' + property: string; // e.g., 'teamId' + foreignKey: string; // e.g., 'resourceId' +} + +/** + * FilterTemplate - Bygger nøgler til event-kolonne matching + * + * ViewConfig definerer hvilke felter (idProperties) der indgår i kolonnens nøgle. + * Samme template bruges til at bygge nøgle for både kolonne og event. + * + * Supports dot-notation for hierarchical relations: + * - 'resource.teamId' → looks up event.resourceId → resource entity → teamId + * + * Princip: Kolonnens nøgle-template bestemmer hvad der matches på. + * + * @see docs/filter-template.md + */ +export class FilterTemplate { + private fields: IFilterField[] = []; + + constructor( + private dateService: DateService, + private entityResolver?: IEntityResolver + ) {} + + /** + * Tilføj felt til template + * @param idProperty - Property-navn (bruges på både event og column.dataset) + * @param derivedFrom - Hvis feltet udledes fra anden property (f.eks. date fra start) + */ + addField(idProperty: string, derivedFrom?: string): this { + this.fields.push({ idProperty, derivedFrom }); + return this; + } + + /** + * Parse dot-notation string into components + * @example 'resource.teamId' → { entityType: 'resource', property: 'teamId', foreignKey: 'resourceId' } + */ + private parseDotNotation(idProperty: string): IDotNotation | null { + if (!idProperty.includes('.')) return null; + const [entityType, property] = idProperty.split('.'); + return { + entityType, + property, + foreignKey: entityType + 'Id' // Convention: resource → resourceId + }; + } + + /** + * Get dataset key for column lookup + * For dot-notation 'resource.teamId', we look for 'teamId' in dataset + */ + private getDatasetKey(idProperty: string): string { + const dotNotation = this.parseDotNotation(idProperty); + if (dotNotation) { + return dotNotation.property; // 'teamId' + } + return idProperty; + } + + /** + * Byg nøgle fra kolonne + * Læser værdier fra column.dataset[idProperty] + * For dot-notation, uses the property part (resource.teamId → teamId) + */ + buildKeyFromColumn(column: HTMLElement): string { + return this.fields + .map(f => { + const key = this.getDatasetKey(f.idProperty); + return column.dataset[key] || ''; + }) + .join(':'); + } + + /** + * Byg nøgle fra event + * Læser værdier fra event[idProperty] eller udleder fra derivedFrom + * For dot-notation, resolves via EntityResolver + */ + buildKeyFromEvent(event: ICalendarEvent): string { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const eventRecord = event as any; + return this.fields + .map(f => { + // Check for dot-notation (e.g., 'resource.teamId') + const dotNotation = this.parseDotNotation(f.idProperty); + if (dotNotation) { + return this.resolveDotNotation(eventRecord, dotNotation); + } + + if (f.derivedFrom) { + // Udled værdi (f.eks. date fra start) + const sourceValue = eventRecord[f.derivedFrom]; + if (sourceValue instanceof Date) { + return this.dateService.getDateKey(sourceValue); + } + return String(sourceValue || ''); + } + return String(eventRecord[f.idProperty] || ''); + }) + .join(':'); + } + + /** + * Resolve dot-notation reference via EntityResolver + */ + private resolveDotNotation(eventRecord: Record, dotNotation: IDotNotation): string { + if (!this.entityResolver) { + console.warn(`FilterTemplate: EntityResolver required for dot-notation '${dotNotation.entityType}.${dotNotation.property}'`); + return ''; + } + + // Get foreign key value from event (e.g., resourceId) + const foreignId = eventRecord[dotNotation.foreignKey]; + if (!foreignId) return ''; + + // Resolve entity + const entity = this.entityResolver.resolve(dotNotation.entityType, String(foreignId)); + if (!entity) return ''; + + // Return property value from entity + return String(entity[dotNotation.property] || ''); + } + + /** + * Match event mod kolonne + */ + matches(event: ICalendarEvent, column: HTMLElement): boolean { + return this.buildKeyFromEvent(event) === this.buildKeyFromColumn(column); + } +} diff --git a/packages/calendar/src/core/HeaderDrawerManager.ts b/packages/calendar/src/core/HeaderDrawerManager.ts new file mode 100644 index 0000000..445bb23 --- /dev/null +++ b/packages/calendar/src/core/HeaderDrawerManager.ts @@ -0,0 +1,70 @@ +export class HeaderDrawerManager { + private drawer!: HTMLElement; + private expanded = false; + private currentRows = 0; + private readonly rowHeight = 25; + private readonly duration = 200; + + init(container: HTMLElement): void { + this.drawer = container.querySelector('swp-header-drawer')!; + + if (!this.drawer) console.error('HeaderDrawerManager: swp-header-drawer not found'); + } + + toggle(): void { + this.expanded ? this.collapse() : this.expand(); + } + + /** + * Expand drawer to single row (legacy support) + */ + expand(): void { + this.expandToRows(1); + } + + /** + * Expand drawer to fit specified number of rows + */ + expandToRows(rowCount: number): void { + const targetHeight = rowCount * this.rowHeight; + const currentHeight = this.expanded ? this.currentRows * this.rowHeight : 0; + + // Skip if already at target + if (this.expanded && this.currentRows === rowCount) return; + + this.currentRows = rowCount; + this.expanded = true; + this.animate(currentHeight, targetHeight); + } + + collapse(): void { + if (!this.expanded) return; + const currentHeight = this.currentRows * this.rowHeight; + this.expanded = false; + this.currentRows = 0; + this.animate(currentHeight, 0); + } + + private animate(from: number, to: number): void { + const keyframes = [ + { height: `${from}px` }, + { height: `${to}px` } + ]; + const options: KeyframeAnimationOptions = { + duration: this.duration, + easing: 'ease', + fill: 'forwards' + }; + + // Kun animér drawer - ScrollManager synkroniserer header-spacer via ResizeObserver + this.drawer.animate(keyframes, options); + } + + isExpanded(): boolean { + return this.expanded; + } + + getRowCount(): number { + return this.currentRows; + } +} diff --git a/packages/calendar/src/core/IEntityResolver.ts b/packages/calendar/src/core/IEntityResolver.ts new file mode 100644 index 0000000..b825c0f --- /dev/null +++ b/packages/calendar/src/core/IEntityResolver.ts @@ -0,0 +1,15 @@ +/** + * IEntityResolver - Resolves entities by type and ID + * + * Used by FilterTemplate to resolve dot-notation references like 'resource.teamId' + * where the value needs to be looked up from a related entity. + */ +export interface IEntityResolver { + /** + * Resolve an entity by type and ID + * @param entityType - The entity type (e.g., 'resource', 'booking', 'customer') + * @param id - The entity ID + * @returns The entity record or undefined if not found + */ + resolve(entityType: string, id: string): Record | undefined; +} diff --git a/packages/calendar/src/core/IGridConfig.ts b/packages/calendar/src/core/IGridConfig.ts new file mode 100644 index 0000000..03c6a2f --- /dev/null +++ b/packages/calendar/src/core/IGridConfig.ts @@ -0,0 +1,7 @@ +export interface IGridConfig { + hourHeight: number; // pixels per hour + dayStartHour: number; // e.g. 6 + dayEndHour: number; // e.g. 18 + snapInterval: number; // minutes, e.g. 15 + gridStartThresholdMinutes?: number; // threshold for GRID grouping (default 10) +} diff --git a/packages/calendar/src/core/IGroupingRenderer.ts b/packages/calendar/src/core/IGroupingRenderer.ts new file mode 100644 index 0000000..a1bc507 --- /dev/null +++ b/packages/calendar/src/core/IGroupingRenderer.ts @@ -0,0 +1,15 @@ +import { GroupingConfig } from './ViewConfig'; + +export interface IRenderContext { + headerContainer: HTMLElement; + columnContainer: HTMLElement; + filter: Record; // { team: ['alpha'], resource: ['alice', 'bob'], date: [...] } + groupings?: GroupingConfig[]; // Full grouping configs (for hideHeader etc.) + parentChildMap?: Record; // { team1: ['EMP001', 'EMP002'], team2: ['EMP003', 'EMP004'] } + childType?: string; // The type of the child grouping (e.g., 'resource' when team has belongsTo) +} + +export interface IRenderer { + readonly type: string; + render(context: IRenderContext): void | Promise; +} diff --git a/packages/calendar/src/core/IGroupingStore.ts b/packages/calendar/src/core/IGroupingStore.ts new file mode 100644 index 0000000..8abc837 --- /dev/null +++ b/packages/calendar/src/core/IGroupingStore.ts @@ -0,0 +1,4 @@ +export interface IGroupingStore { + readonly type: string; + getByIds(ids: string[]): T[]; +} diff --git a/packages/calendar/src/core/ITimeFormatConfig.ts b/packages/calendar/src/core/ITimeFormatConfig.ts new file mode 100644 index 0000000..1a401d5 --- /dev/null +++ b/packages/calendar/src/core/ITimeFormatConfig.ts @@ -0,0 +1,7 @@ +export interface ITimeFormatConfig { + timezone: string; + use24HourFormat: boolean; + locale: string; + dateFormat: 'locale' | 'technical'; + showSeconds: boolean; +} diff --git a/packages/calendar/src/core/NavigationAnimator.ts b/packages/calendar/src/core/NavigationAnimator.ts new file mode 100644 index 0000000..cf173ad --- /dev/null +++ b/packages/calendar/src/core/NavigationAnimator.ts @@ -0,0 +1,64 @@ +export class NavigationAnimator { + constructor( + private headerTrack: HTMLElement, + private contentTrack: HTMLElement, + private headerDrawer: HTMLElement | null + ) {} + + async slide(direction: 'left' | 'right', renderFn: () => Promise): Promise { + const out = direction === 'left' ? '-100%' : '100%'; + const into = direction === 'left' ? '100%' : '-100%'; + + await this.animateOut(out); + await renderFn(); + await this.animateIn(into); + } + + private async animateOut(translate: string): Promise { + const animations = [ + this.headerTrack.animate( + [{ transform: 'translateX(0)' }, { transform: `translateX(${translate})` }], + { duration: 200, easing: 'ease-in' } + ).finished, + this.contentTrack.animate( + [{ transform: 'translateX(0)' }, { transform: `translateX(${translate})` }], + { duration: 200, easing: 'ease-in' } + ).finished + ]; + + if (this.headerDrawer) { + animations.push( + this.headerDrawer.animate( + [{ transform: 'translateX(0)' }, { transform: `translateX(${translate})` }], + { duration: 200, easing: 'ease-in' } + ).finished + ); + } + + await Promise.all(animations); + } + + private async animateIn(translate: string): Promise { + const animations = [ + this.headerTrack.animate( + [{ transform: `translateX(${translate})` }, { transform: 'translateX(0)' }], + { duration: 200, easing: 'ease-out' } + ).finished, + this.contentTrack.animate( + [{ transform: `translateX(${translate})` }, { transform: 'translateX(0)' }], + { duration: 200, easing: 'ease-out' } + ).finished + ]; + + if (this.headerDrawer) { + animations.push( + this.headerDrawer.animate( + [{ transform: `translateX(${translate})` }, { transform: 'translateX(0)' }], + { duration: 200, easing: 'ease-out' } + ).finished + ); + } + + await Promise.all(animations); + } +} diff --git a/packages/calendar/src/core/RenderBuilder.ts b/packages/calendar/src/core/RenderBuilder.ts new file mode 100644 index 0000000..68f0ee3 --- /dev/null +++ b/packages/calendar/src/core/RenderBuilder.ts @@ -0,0 +1,15 @@ +import { IRenderer, IRenderContext } from './IGroupingRenderer'; + +export interface Pipeline { + run(context: IRenderContext): Promise; +} + +export function buildPipeline(renderers: IRenderer[]): Pipeline { + return { + async run(context: IRenderContext) { + for (const renderer of renderers) { + await renderer.render(context); + } + } + }; +} diff --git a/packages/calendar/src/core/ScrollManager.ts b/packages/calendar/src/core/ScrollManager.ts new file mode 100644 index 0000000..bb4f490 --- /dev/null +++ b/packages/calendar/src/core/ScrollManager.ts @@ -0,0 +1,42 @@ +export class ScrollManager { + private scrollableContent!: HTMLElement; + private timeAxisContent!: HTMLElement; + private calendarHeader!: HTMLElement; + private headerDrawer!: HTMLElement; + private headerViewport!: HTMLElement; + private headerSpacer!: HTMLElement; + private resizeObserver!: ResizeObserver; + + init(container: HTMLElement): void { + this.scrollableContent = container.querySelector('swp-scrollable-content')!; + this.timeAxisContent = container.querySelector('swp-time-axis-content')!; + this.calendarHeader = container.querySelector('swp-calendar-header')!; + this.headerDrawer = container.querySelector('swp-header-drawer')!; + this.headerViewport = container.querySelector('swp-header-viewport')!; + this.headerSpacer = container.querySelector('swp-header-spacer')!; + + this.scrollableContent.addEventListener('scroll', () => this.onScroll()); + + // Synkroniser header-spacer højde med header-viewport + this.resizeObserver = new ResizeObserver(() => this.syncHeaderSpacerHeight()); + this.resizeObserver.observe(this.headerViewport); + this.syncHeaderSpacerHeight(); + } + + private syncHeaderSpacerHeight(): void { + // Kopier den faktiske computed height direkte fra header-viewport + const computedHeight = getComputedStyle(this.headerViewport).height; + this.headerSpacer.style.height = computedHeight; + } + + private onScroll(): void { + const { scrollTop, scrollLeft } = this.scrollableContent; + + // Synkroniser time-axis vertikalt + this.timeAxisContent.style.transform = `translateY(-${scrollTop}px)`; + + // Synkroniser header og drawer horisontalt + this.calendarHeader.style.transform = `translateX(-${scrollLeft}px)`; + this.headerDrawer.style.transform = `translateX(-${scrollLeft}px)`; + } +} diff --git a/packages/calendar/src/core/ViewConfig.ts b/packages/calendar/src/core/ViewConfig.ts new file mode 100644 index 0000000..8ecd79b --- /dev/null +++ b/packages/calendar/src/core/ViewConfig.ts @@ -0,0 +1,21 @@ +import { ISync } from '../types/CalendarTypes'; + +export interface ViewTemplate { + id: string; + name: string; + groupingTypes: string[]; +} + +export interface ViewConfig extends ISync { + id: string; // templateId (e.g. 'day', 'simple', 'resource') + groupings: GroupingConfig[]; +} + +export interface GroupingConfig { + type: string; + values: string[]; + idProperty?: string; // Property-navn på event (f.eks. 'resourceId') - kun for event matching + derivedFrom?: string; // Hvis feltet udledes fra anden property (f.eks. 'date' fra 'start') + belongsTo?: string; // Parent-child relation (f.eks. 'team.resourceIds') + hideHeader?: boolean; // Skjul header-rækken for denne grouping (f.eks. dato i day-view) +} diff --git a/packages/calendar/src/extensions/audit/AuditService.ts b/packages/calendar/src/extensions/audit/AuditService.ts new file mode 100644 index 0000000..ccdb2b4 --- /dev/null +++ b/packages/calendar/src/extensions/audit/AuditService.ts @@ -0,0 +1,167 @@ +import { BaseEntityService } from '../../storage/BaseEntityService'; +import { IndexedDBContext } from '../../storage/IndexedDBContext'; +import { IAuditEntry, IAuditLoggedPayload } from '../../types/AuditTypes'; +import { EntityType, IEventBus, IEntitySavedPayload, IEntityDeletedPayload } from '../../types/CalendarTypes'; +import { CoreEvents } from '../../constants/CoreEvents'; + +/** + * AuditService - Entity service for audit entries + * + * RESPONSIBILITIES: + * - Store audit entries in IndexedDB + * - Listen for ENTITY_SAVED/ENTITY_DELETED events + * - Create audit entries for all entity changes + * - Emit AUDIT_LOGGED after saving (for SyncManager to listen) + * + * OVERRIDE PATTERN: + * - Overrides save() to NOT emit events (prevents infinite loops) + * - AuditService saves audit entries without triggering more audits + * + * EVENT CHAIN: + * Entity change → ENTITY_SAVED/DELETED → AuditService → AUDIT_LOGGED → SyncManager + */ +export class AuditService extends BaseEntityService { + readonly storeName = 'audit'; + readonly entityType: EntityType = 'Audit'; + + // Hardcoded userId for now - will come from session later + private static readonly DEFAULT_USER_ID = '00000000-0000-0000-0000-000000000001'; + + constructor(context: IndexedDBContext, eventBus: IEventBus) { + super(context, eventBus); + this.setupEventListeners(); + } + + /** + * Setup listeners for ENTITY_SAVED and ENTITY_DELETED events + */ + private setupEventListeners(): void { + // Listen for entity saves (create/update) + this.eventBus.on(CoreEvents.ENTITY_SAVED, (event: Event) => { + const detail = (event as CustomEvent).detail; + this.handleEntitySaved(detail); + }); + + // Listen for entity deletes + this.eventBus.on(CoreEvents.ENTITY_DELETED, (event: Event) => { + const detail = (event as CustomEvent).detail; + this.handleEntityDeleted(detail); + }); + } + + /** + * Handle ENTITY_SAVED event - create audit entry + */ + private async handleEntitySaved(payload: IEntitySavedPayload): Promise { + // Don't audit audit entries (prevent infinite loops) + if (payload.entityType === 'Audit') return; + + const auditEntry: IAuditEntry = { + id: crypto.randomUUID(), + entityType: payload.entityType, + entityId: payload.entityId, + operation: payload.operation, + userId: AuditService.DEFAULT_USER_ID, + timestamp: payload.timestamp, + changes: payload.changes, + synced: false, + syncStatus: 'pending' + }; + + await this.save(auditEntry); + } + + /** + * Handle ENTITY_DELETED event - create audit entry + */ + private async handleEntityDeleted(payload: IEntityDeletedPayload): Promise { + // Don't audit audit entries (prevent infinite loops) + if (payload.entityType === 'Audit') return; + + const auditEntry: IAuditEntry = { + id: crypto.randomUUID(), + entityType: payload.entityType, + entityId: payload.entityId, + operation: 'delete', + userId: AuditService.DEFAULT_USER_ID, + timestamp: payload.timestamp, + changes: { id: payload.entityId }, // For delete, just store the ID + synced: false, + syncStatus: 'pending' + }; + + await this.save(auditEntry); + } + + /** + * Override save to NOT trigger ENTITY_SAVED event + * Instead, emits AUDIT_LOGGED for SyncManager to listen + * + * This prevents infinite loops: + * - BaseEntityService.save() emits ENTITY_SAVED + * - AuditService listens to ENTITY_SAVED and creates audit + * - If AuditService.save() also emitted ENTITY_SAVED, it would loop + */ + async save(entity: IAuditEntry): Promise { + const serialized = this.serialize(entity); + + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], 'readwrite'); + const store = transaction.objectStore(this.storeName); + const request = store.put(serialized); + + request.onsuccess = () => { + // Emit AUDIT_LOGGED instead of ENTITY_SAVED + const payload: IAuditLoggedPayload = { + auditId: entity.id, + entityType: entity.entityType, + entityId: entity.entityId, + operation: entity.operation, + timestamp: entity.timestamp + }; + this.eventBus.emit(CoreEvents.AUDIT_LOGGED, payload); + resolve(); + }; + + request.onerror = () => { + reject(new Error(`Failed to save audit entry ${entity.id}: ${request.error}`)); + }; + }); + } + + /** + * Override delete to NOT trigger ENTITY_DELETED event + * Audit entries should never be deleted (compliance requirement) + */ + async delete(_id: string): Promise { + throw new Error('Audit entries cannot be deleted (compliance requirement)'); + } + + /** + * Get pending audit entries (for sync) + */ + async getPendingAudits(): Promise { + return this.getBySyncStatus('pending'); + } + + /** + * Get audit entries for a specific entity + */ + async getByEntityId(entityId: string): Promise { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], 'readonly'); + const store = transaction.objectStore(this.storeName); + const index = store.index('entityId'); + const request = index.getAll(entityId); + + request.onsuccess = () => { + const entries = request.result as IAuditEntry[]; + resolve(entries); + }; + + request.onerror = () => { + reject(new Error(`Failed to get audit entries for entity ${entityId}: ${request.error}`)); + }; + }); + } +} diff --git a/packages/calendar/src/extensions/audit/AuditStore.ts b/packages/calendar/src/extensions/audit/AuditStore.ts new file mode 100644 index 0000000..769b3b9 --- /dev/null +++ b/packages/calendar/src/extensions/audit/AuditStore.ts @@ -0,0 +1,27 @@ +import { IStore } from '../../storage/IStore'; + +/** + * AuditStore - IndexedDB store configuration for audit entries + * + * Stores all entity changes for: + * - Compliance and audit trail + * - Sync tracking with backend + * - Change history + * + * Indexes: + * - syncStatus: For finding pending entries to sync + * - synced: Boolean flag for quick sync queries + * - entityId: For getting all audits for a specific entity + * - timestamp: For chronological queries + */ +export class AuditStore implements IStore { + readonly storeName = 'audit'; + + create(db: IDBDatabase): void { + const store = db.createObjectStore(this.storeName, { keyPath: 'id' }); + store.createIndex('syncStatus', 'syncStatus', { unique: false }); + store.createIndex('synced', 'synced', { unique: false }); + store.createIndex('entityId', 'entityId', { unique: false }); + store.createIndex('timestamp', 'timestamp', { unique: false }); + } +} diff --git a/packages/calendar/src/extensions/audit/index.ts b/packages/calendar/src/extensions/audit/index.ts new file mode 100644 index 0000000..48100e1 --- /dev/null +++ b/packages/calendar/src/extensions/audit/index.ts @@ -0,0 +1,14 @@ +export { AuditService } from './AuditService'; +export { AuditStore } from './AuditStore'; +export type { IAuditEntry, IAuditLoggedPayload } from '../../types/AuditTypes'; + +// DI registration helper +import type { Builder } from '@novadi/core'; +import { IStore } from '../../storage/IStore'; +import { AuditStore } from './AuditStore'; +import { AuditService } from './AuditService'; + +export function registerAudit(builder: Builder): void { + builder.registerType(AuditStore).as(); + builder.registerType(AuditService).as(); +} diff --git a/packages/calendar/src/extensions/bookings/BookingService.ts b/packages/calendar/src/extensions/bookings/BookingService.ts new file mode 100644 index 0000000..d12eb57 --- /dev/null +++ b/packages/calendar/src/extensions/bookings/BookingService.ts @@ -0,0 +1,75 @@ +import { IBooking, EntityType, IEventBus, BookingStatus } from '../../types/CalendarTypes'; +import { BookingStore } from './BookingStore'; +import { BaseEntityService } from '../../storage/BaseEntityService'; +import { IndexedDBContext } from '../../storage/IndexedDBContext'; + +/** + * BookingService - CRUD operations for bookings in IndexedDB + */ +export class BookingService extends BaseEntityService { + readonly storeName = BookingStore.STORE_NAME; + readonly entityType: EntityType = 'Booking'; + + constructor(context: IndexedDBContext, eventBus: IEventBus) { + super(context, eventBus); + } + + protected serialize(booking: IBooking): unknown { + return { + ...booking, + createdAt: booking.createdAt.toISOString() + }; + } + + protected deserialize(data: unknown): IBooking { + const raw = data as Record; + return { + ...raw, + createdAt: new Date(raw.createdAt as string) + } as IBooking; + } + + /** + * Get bookings for a customer + */ + async getByCustomer(customerId: string): Promise { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], 'readonly'); + const store = transaction.objectStore(this.storeName); + const index = store.index('customerId'); + const request = index.getAll(customerId); + + request.onsuccess = () => { + const data = request.result as unknown[]; + const bookings = data.map(item => this.deserialize(item)); + resolve(bookings); + }; + + request.onerror = () => { + reject(new Error(`Failed to get bookings for customer ${customerId}: ${request.error}`)); + }; + }); + } + + /** + * Get bookings by status + */ + async getByStatus(status: BookingStatus): Promise { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], 'readonly'); + const store = transaction.objectStore(this.storeName); + const index = store.index('status'); + const request = index.getAll(status); + + request.onsuccess = () => { + const data = request.result as unknown[]; + const bookings = data.map(item => this.deserialize(item)); + resolve(bookings); + }; + + request.onerror = () => { + reject(new Error(`Failed to get bookings with status ${status}: ${request.error}`)); + }; + }); + } +} diff --git a/packages/calendar/src/extensions/bookings/BookingStore.ts b/packages/calendar/src/extensions/bookings/BookingStore.ts new file mode 100644 index 0000000..5e64ad3 --- /dev/null +++ b/packages/calendar/src/extensions/bookings/BookingStore.ts @@ -0,0 +1,18 @@ +import { IStore } from '../../storage/IStore'; + +/** + * BookingStore - IndexedDB ObjectStore definition for bookings + */ +export class BookingStore implements IStore { + static readonly STORE_NAME = 'bookings'; + readonly storeName = BookingStore.STORE_NAME; + + create(db: IDBDatabase): void { + const store = db.createObjectStore(BookingStore.STORE_NAME, { keyPath: 'id' }); + + store.createIndex('customerId', 'customerId', { unique: false }); + store.createIndex('status', 'status', { unique: false }); + store.createIndex('syncStatus', 'syncStatus', { unique: false }); + store.createIndex('createdAt', 'createdAt', { unique: false }); + } +} diff --git a/packages/calendar/src/extensions/bookings/index.ts b/packages/calendar/src/extensions/bookings/index.ts new file mode 100644 index 0000000..4bfe7d2 --- /dev/null +++ b/packages/calendar/src/extensions/bookings/index.ts @@ -0,0 +1,18 @@ +export { BookingService } from './BookingService'; +export { BookingStore } from './BookingStore'; +export type { IBooking, BookingStatus, IBookingService } from '../../types/CalendarTypes'; + +// DI registration helper +import type { Builder } from '@novadi/core'; +import { IStore } from '../../storage/IStore'; +import { IEntityService } from '../../storage/IEntityService'; +import type { IBooking, ISync } from '../../types/CalendarTypes'; +import { BookingStore } from './BookingStore'; +import { BookingService } from './BookingService'; + +export function registerBookings(builder: Builder): void { + builder.registerType(BookingStore).as(); + builder.registerType(BookingService).as>(); + builder.registerType(BookingService).as>(); + builder.registerType(BookingService).as(); +} diff --git a/packages/calendar/src/extensions/customers/CustomerService.ts b/packages/calendar/src/extensions/customers/CustomerService.ts new file mode 100644 index 0000000..d1225e1 --- /dev/null +++ b/packages/calendar/src/extensions/customers/CustomerService.ts @@ -0,0 +1,46 @@ +import { ICustomer, EntityType, IEventBus } from '../../types/CalendarTypes'; +import { CustomerStore } from './CustomerStore'; +import { BaseEntityService } from '../../storage/BaseEntityService'; +import { IndexedDBContext } from '../../storage/IndexedDBContext'; + +/** + * CustomerService - CRUD operations for customers in IndexedDB + */ +export class CustomerService extends BaseEntityService { + readonly storeName = CustomerStore.STORE_NAME; + readonly entityType: EntityType = 'Customer'; + + constructor(context: IndexedDBContext, eventBus: IEventBus) { + super(context, eventBus); + } + + /** + * Search customers by name (case-insensitive contains) + */ + async searchByName(query: string): Promise { + const all = await this.getAll(); + const lowerQuery = query.toLowerCase(); + return all.filter(c => c.name.toLowerCase().includes(lowerQuery)); + } + + /** + * Find customer by phone + */ + async getByPhone(phone: string): Promise { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], 'readonly'); + const store = transaction.objectStore(this.storeName); + const index = store.index('phone'); + const request = index.get(phone); + + request.onsuccess = () => { + const data = request.result; + resolve(data ? (data as ICustomer) : null); + }; + + request.onerror = () => { + reject(new Error(`Failed to find customer by phone ${phone}: ${request.error}`)); + }; + }); + } +} diff --git a/packages/calendar/src/extensions/customers/CustomerStore.ts b/packages/calendar/src/extensions/customers/CustomerStore.ts new file mode 100644 index 0000000..b53cd7e --- /dev/null +++ b/packages/calendar/src/extensions/customers/CustomerStore.ts @@ -0,0 +1,17 @@ +import { IStore } from '../../storage/IStore'; + +/** + * CustomerStore - IndexedDB ObjectStore definition for customers + */ +export class CustomerStore implements IStore { + static readonly STORE_NAME = 'customers'; + readonly storeName = CustomerStore.STORE_NAME; + + create(db: IDBDatabase): void { + const store = db.createObjectStore(CustomerStore.STORE_NAME, { keyPath: 'id' }); + + store.createIndex('name', 'name', { unique: false }); + store.createIndex('phone', 'phone', { unique: false }); + store.createIndex('syncStatus', 'syncStatus', { unique: false }); + } +} diff --git a/packages/calendar/src/extensions/customers/index.ts b/packages/calendar/src/extensions/customers/index.ts new file mode 100644 index 0000000..6d47df5 --- /dev/null +++ b/packages/calendar/src/extensions/customers/index.ts @@ -0,0 +1,18 @@ +export { CustomerService } from './CustomerService'; +export { CustomerStore } from './CustomerStore'; +export type { ICustomer } from '../../types/CalendarTypes'; + +// DI registration helper +import type { Builder } from '@novadi/core'; +import { IStore } from '../../storage/IStore'; +import { IEntityService } from '../../storage/IEntityService'; +import type { ICustomer, ISync } from '../../types/CalendarTypes'; +import { CustomerStore } from './CustomerStore'; +import { CustomerService } from './CustomerService'; + +export function registerCustomers(builder: Builder): void { + builder.registerType(CustomerStore).as(); + builder.registerType(CustomerService).as>(); + builder.registerType(CustomerService).as>(); + builder.registerType(CustomerService).as(); +} diff --git a/packages/calendar/src/extensions/departments/DepartmentRenderer.ts b/packages/calendar/src/extensions/departments/DepartmentRenderer.ts new file mode 100644 index 0000000..16bb161 --- /dev/null +++ b/packages/calendar/src/extensions/departments/DepartmentRenderer.ts @@ -0,0 +1,25 @@ +import { BaseGroupingRenderer, IGroupingRendererConfig } from '../../core/BaseGroupingRenderer'; +import { DepartmentService } from './DepartmentService'; +import { IDepartment } from '../../types/CalendarTypes'; + +export class DepartmentRenderer extends BaseGroupingRenderer { + readonly type = 'department'; + + protected readonly config: IGroupingRendererConfig = { + elementTag: 'swp-department-header', + idAttribute: 'departmentId', + colspanVar: '--department-cols' + }; + + constructor(private departmentService: DepartmentService) { + super(); + } + + protected getEntities(ids: string[]): Promise { + return this.departmentService.getByIds(ids); + } + + protected getDisplayName(entity: IDepartment): string { + return entity.name; + } +} diff --git a/packages/calendar/src/extensions/departments/DepartmentService.ts b/packages/calendar/src/extensions/departments/DepartmentService.ts new file mode 100644 index 0000000..4b4c8b9 --- /dev/null +++ b/packages/calendar/src/extensions/departments/DepartmentService.ts @@ -0,0 +1,25 @@ +import { IDepartment, EntityType, IEventBus } from '../../types/CalendarTypes'; +import { DepartmentStore } from './DepartmentStore'; +import { BaseEntityService } from '../../storage/BaseEntityService'; +import { IndexedDBContext } from '../../storage/IndexedDBContext'; + +/** + * DepartmentService - CRUD operations for departments in IndexedDB + */ +export class DepartmentService extends BaseEntityService { + readonly storeName = DepartmentStore.STORE_NAME; + readonly entityType: EntityType = 'Department'; + + constructor(context: IndexedDBContext, eventBus: IEventBus) { + super(context, eventBus); + } + + /** + * Get departments by IDs + */ + async getByIds(ids: string[]): Promise { + if (ids.length === 0) return []; + const results = await Promise.all(ids.map(id => this.get(id))); + return results.filter((d): d is IDepartment => d !== null); + } +} diff --git a/packages/calendar/src/extensions/departments/DepartmentStore.ts b/packages/calendar/src/extensions/departments/DepartmentStore.ts new file mode 100644 index 0000000..0e9c6a3 --- /dev/null +++ b/packages/calendar/src/extensions/departments/DepartmentStore.ts @@ -0,0 +1,13 @@ +import { IStore } from '../../storage/IStore'; + +/** + * DepartmentStore - IndexedDB ObjectStore definition for departments + */ +export class DepartmentStore implements IStore { + static readonly STORE_NAME = 'departments'; + readonly storeName = DepartmentStore.STORE_NAME; + + create(db: IDBDatabase): void { + db.createObjectStore(DepartmentStore.STORE_NAME, { keyPath: 'id' }); + } +} diff --git a/packages/calendar/src/extensions/departments/index.ts b/packages/calendar/src/extensions/departments/index.ts new file mode 100644 index 0000000..d50130a --- /dev/null +++ b/packages/calendar/src/extensions/departments/index.ts @@ -0,0 +1,22 @@ +export { DepartmentRenderer } from './DepartmentRenderer'; +export { DepartmentService } from './DepartmentService'; +export { DepartmentStore } from './DepartmentStore'; +export type { IDepartment } from '../../types/CalendarTypes'; + +// DI registration helper +import type { Builder } from '@novadi/core'; +import { IRenderer } from '../../core/IGroupingRenderer'; +import { IStore } from '../../storage/IStore'; +import { IEntityService } from '../../storage/IEntityService'; +import type { IDepartment, ISync } from '../../types/CalendarTypes'; +import { DepartmentStore } from './DepartmentStore'; +import { DepartmentService } from './DepartmentService'; +import { DepartmentRenderer } from './DepartmentRenderer'; + +export function registerDepartments(builder: Builder): void { + builder.registerType(DepartmentStore).as(); + builder.registerType(DepartmentService).as>(); + builder.registerType(DepartmentService).as>(); + builder.registerType(DepartmentService).as(); + builder.registerType(DepartmentRenderer).as(); +} diff --git a/packages/calendar/src/extensions/schedules/ResourceScheduleService.ts b/packages/calendar/src/extensions/schedules/ResourceScheduleService.ts new file mode 100644 index 0000000..a548865 --- /dev/null +++ b/packages/calendar/src/extensions/schedules/ResourceScheduleService.ts @@ -0,0 +1,84 @@ +import { ITimeSlot } from '../../types/ScheduleTypes'; +import { ResourceService } from '../../storage/resources/ResourceService'; +import { ScheduleOverrideService } from './ScheduleOverrideService'; +import { DateService } from '../../core/DateService'; + +/** + * ResourceScheduleService - Get effective schedule for a resource on a date + * + * Logic: + * 1. Check for override on this date + * 2. Fall back to default schedule for the weekday + */ +export class ResourceScheduleService { + constructor( + private resourceService: ResourceService, + private overrideService: ScheduleOverrideService, + private dateService: DateService + ) {} + + /** + * Get effective schedule for a resource on a specific date + * + * @param resourceId - Resource ID + * @param date - Date string "YYYY-MM-DD" + * @returns ITimeSlot or null (fri/closed) + */ + async getScheduleForDate(resourceId: string, date: string): Promise { + // 1. Check for override + const override = await this.overrideService.getOverride(resourceId, date); + if (override) { + return override.schedule; + } + + // 2. Use default schedule for weekday + const resource = await this.resourceService.get(resourceId); + if (!resource || !resource.defaultSchedule) { + return null; + } + + const weekDay = this.dateService.getISOWeekDay(date); + return resource.defaultSchedule[weekDay] || null; + } + + /** + * Get schedules for multiple dates + * + * @param resourceId - Resource ID + * @param dates - Array of date strings "YYYY-MM-DD" + * @returns Map of date -> ITimeSlot | null + */ + async getSchedulesForDates(resourceId: string, dates: string[]): Promise> { + const result = new Map(); + + // Get resource once + const resource = await this.resourceService.get(resourceId); + + // Get all overrides in date range + const overrides = dates.length > 0 + ? await this.overrideService.getByDateRange(resourceId, dates[0], dates[dates.length - 1]) + : []; + + // Build override map + const overrideMap = new Map(overrides.map(o => [o.date, o.schedule])); + + // Resolve each date + for (const date of dates) { + // Check override first + if (overrideMap.has(date)) { + result.set(date, overrideMap.get(date)!); + continue; + } + + // Fall back to default + if (resource?.defaultSchedule) { + const weekDay = this.dateService.getISOWeekDay(date); + result.set(date, resource.defaultSchedule[weekDay] || null); + } else { + result.set(date, null); + } + } + + return result; + } +} diff --git a/packages/calendar/src/extensions/schedules/ScheduleOverrideService.ts b/packages/calendar/src/extensions/schedules/ScheduleOverrideService.ts new file mode 100644 index 0000000..184e354 --- /dev/null +++ b/packages/calendar/src/extensions/schedules/ScheduleOverrideService.ts @@ -0,0 +1,100 @@ +import { IScheduleOverride } from '../../types/ScheduleTypes'; +import { IndexedDBContext } from '../../storage/IndexedDBContext'; +import { ScheduleOverrideStore } from './ScheduleOverrideStore'; + +/** + * ScheduleOverrideService - CRUD for schedule overrides + * + * Provides access to date-specific schedule overrides for resources. + */ +export class ScheduleOverrideService { + private context: IndexedDBContext; + + constructor(context: IndexedDBContext) { + this.context = context; + } + + private get db(): IDBDatabase { + return this.context.getDatabase(); + } + + /** + * Get override for a specific resource and date + */ + async getOverride(resourceId: string, date: string): Promise { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([ScheduleOverrideStore.STORE_NAME], 'readonly'); + const store = transaction.objectStore(ScheduleOverrideStore.STORE_NAME); + const index = store.index('resourceId_date'); + const request = index.get([resourceId, date]); + + request.onsuccess = () => { + resolve(request.result || null); + }; + + request.onerror = () => { + reject(new Error(`Failed to get override for ${resourceId} on ${date}: ${request.error}`)); + }; + }); + } + + /** + * Get all overrides for a resource + */ + async getByResource(resourceId: string): Promise { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([ScheduleOverrideStore.STORE_NAME], 'readonly'); + const store = transaction.objectStore(ScheduleOverrideStore.STORE_NAME); + const index = store.index('resourceId'); + const request = index.getAll(resourceId); + + request.onsuccess = () => { + resolve(request.result || []); + }; + + request.onerror = () => { + reject(new Error(`Failed to get overrides for ${resourceId}: ${request.error}`)); + }; + }); + } + + /** + * Get overrides for a date range + */ + async getByDateRange(resourceId: string, startDate: string, endDate: string): Promise { + const all = await this.getByResource(resourceId); + return all.filter(o => o.date >= startDate && o.date <= endDate); + } + + /** + * Save an override + */ + async save(override: IScheduleOverride): Promise { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([ScheduleOverrideStore.STORE_NAME], 'readwrite'); + const store = transaction.objectStore(ScheduleOverrideStore.STORE_NAME); + const request = store.put(override); + + request.onsuccess = () => resolve(); + request.onerror = () => { + reject(new Error(`Failed to save override ${override.id}: ${request.error}`)); + }; + }); + } + + /** + * Delete an override + */ + async delete(id: string): Promise { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([ScheduleOverrideStore.STORE_NAME], 'readwrite'); + const store = transaction.objectStore(ScheduleOverrideStore.STORE_NAME); + const request = store.delete(id); + + request.onsuccess = () => resolve(); + request.onerror = () => { + reject(new Error(`Failed to delete override ${id}: ${request.error}`)); + }; + }); + } +} diff --git a/packages/calendar/src/extensions/schedules/ScheduleOverrideStore.ts b/packages/calendar/src/extensions/schedules/ScheduleOverrideStore.ts new file mode 100644 index 0000000..0fe4f09 --- /dev/null +++ b/packages/calendar/src/extensions/schedules/ScheduleOverrideStore.ts @@ -0,0 +1,21 @@ +import { IStore } from '../../storage/IStore'; + +/** + * ScheduleOverrideStore - IndexedDB ObjectStore for schedule overrides + * + * Stores date-specific schedule overrides for resources. + * Indexes: resourceId, date, compound (resourceId + date) + */ +export class ScheduleOverrideStore implements IStore { + static readonly STORE_NAME = 'scheduleOverrides'; + readonly storeName = ScheduleOverrideStore.STORE_NAME; + + create(db: IDBDatabase): void { + const store = db.createObjectStore(ScheduleOverrideStore.STORE_NAME, { keyPath: 'id' }); + + store.createIndex('resourceId', 'resourceId', { unique: false }); + store.createIndex('date', 'date', { unique: false }); + store.createIndex('resourceId_date', ['resourceId', 'date'], { unique: true }); + store.createIndex('syncStatus', 'syncStatus', { unique: false }); + } +} diff --git a/packages/calendar/src/extensions/schedules/index.ts b/packages/calendar/src/extensions/schedules/index.ts new file mode 100644 index 0000000..e8a94f0 --- /dev/null +++ b/packages/calendar/src/extensions/schedules/index.ts @@ -0,0 +1,17 @@ +export { ScheduleOverrideService } from './ScheduleOverrideService'; +export { ScheduleOverrideStore } from './ScheduleOverrideStore'; +export { ResourceScheduleService } from './ResourceScheduleService'; +export type { IScheduleOverride, ITimeSlot, IWeekSchedule, WeekDay } from '../../types/ScheduleTypes'; + +// DI registration helper +import type { Builder } from '@novadi/core'; +import { IStore } from '../../storage/IStore'; +import { ScheduleOverrideStore } from './ScheduleOverrideStore'; +import { ScheduleOverrideService } from './ScheduleOverrideService'; +import { ResourceScheduleService } from './ResourceScheduleService'; + +export function registerSchedules(builder: Builder): void { + builder.registerType(ScheduleOverrideStore).as(); + builder.registerType(ScheduleOverrideService).as(); + builder.registerType(ResourceScheduleService).as(); +} diff --git a/packages/calendar/src/extensions/teams/TeamRenderer.ts b/packages/calendar/src/extensions/teams/TeamRenderer.ts new file mode 100644 index 0000000..090d8c9 --- /dev/null +++ b/packages/calendar/src/extensions/teams/TeamRenderer.ts @@ -0,0 +1,25 @@ +import { BaseGroupingRenderer, IGroupingRendererConfig } from '../../core/BaseGroupingRenderer'; +import { TeamService } from './TeamService'; +import { ITeam } from '../../types/CalendarTypes'; + +export class TeamRenderer extends BaseGroupingRenderer { + readonly type = 'team'; + + protected readonly config: IGroupingRendererConfig = { + elementTag: 'swp-team-header', + idAttribute: 'teamId', + colspanVar: '--team-cols' + }; + + constructor(private teamService: TeamService) { + super(); + } + + protected getEntities(ids: string[]): Promise { + return this.teamService.getByIds(ids); + } + + protected getDisplayName(entity: ITeam): string { + return entity.name; + } +} diff --git a/packages/calendar/src/extensions/teams/TeamService.ts b/packages/calendar/src/extensions/teams/TeamService.ts new file mode 100644 index 0000000..655dd1f --- /dev/null +++ b/packages/calendar/src/extensions/teams/TeamService.ts @@ -0,0 +1,44 @@ +import { ITeam, EntityType, IEventBus } from '../../types/CalendarTypes'; +import { TeamStore } from './TeamStore'; +import { BaseEntityService } from '../../storage/BaseEntityService'; +import { IndexedDBContext } from '../../storage/IndexedDBContext'; + +/** + * TeamService - CRUD operations for teams in IndexedDB + * + * Teams define which resources belong together for hierarchical grouping. + * Extends BaseEntityService for standard entity operations. + */ +export class TeamService extends BaseEntityService { + readonly storeName = TeamStore.STORE_NAME; + readonly entityType: EntityType = 'Team'; + + constructor(context: IndexedDBContext, eventBus: IEventBus) { + super(context, eventBus); + } + + /** + * Get teams by IDs + */ + async getByIds(ids: string[]): Promise { + if (ids.length === 0) return []; + const results = await Promise.all(ids.map(id => this.get(id))); + return results.filter((t): t is ITeam => t !== null); + } + + /** + * Build reverse lookup: resourceId → teamId + */ + async buildResourceToTeamMap(): Promise> { + const teams = await this.getAll(); + const map: Record = {}; + + for (const team of teams) { + for (const resourceId of team.resourceIds) { + map[resourceId] = team.id; + } + } + + return map; + } +} diff --git a/packages/calendar/src/extensions/teams/TeamStore.ts b/packages/calendar/src/extensions/teams/TeamStore.ts new file mode 100644 index 0000000..af515e0 --- /dev/null +++ b/packages/calendar/src/extensions/teams/TeamStore.ts @@ -0,0 +1,13 @@ +import { IStore } from '../../storage/IStore'; + +/** + * TeamStore - IndexedDB ObjectStore definition for teams + */ +export class TeamStore implements IStore { + static readonly STORE_NAME = 'teams'; + readonly storeName = TeamStore.STORE_NAME; + + create(db: IDBDatabase): void { + db.createObjectStore(TeamStore.STORE_NAME, { keyPath: 'id' }); + } +} diff --git a/packages/calendar/src/extensions/teams/index.ts b/packages/calendar/src/extensions/teams/index.ts new file mode 100644 index 0000000..f9d231e --- /dev/null +++ b/packages/calendar/src/extensions/teams/index.ts @@ -0,0 +1,22 @@ +export { TeamRenderer } from './TeamRenderer'; +export { TeamService } from './TeamService'; +export { TeamStore } from './TeamStore'; +export type { ITeam } from '../../types/CalendarTypes'; + +// DI registration helper +import type { Builder } from '@novadi/core'; +import { IRenderer } from '../../core/IGroupingRenderer'; +import { IStore } from '../../storage/IStore'; +import { IEntityService } from '../../storage/IEntityService'; +import type { ITeam, ISync } from '../../types/CalendarTypes'; +import { TeamStore } from './TeamStore'; +import { TeamService } from './TeamService'; +import { TeamRenderer } from './TeamRenderer'; + +export function registerTeams(builder: Builder): void { + builder.registerType(TeamStore).as(); + builder.registerType(TeamService).as>(); + builder.registerType(TeamService).as>(); + builder.registerType(TeamService).as(); + builder.registerType(TeamRenderer).as(); +} diff --git a/packages/calendar/src/features/date/DateRenderer.ts b/packages/calendar/src/features/date/DateRenderer.ts new file mode 100644 index 0000000..4f1cfad --- /dev/null +++ b/packages/calendar/src/features/date/DateRenderer.ts @@ -0,0 +1,68 @@ +import { IRenderer, IRenderContext } from '../../core/IGroupingRenderer'; +import { DateService } from '../../core/DateService'; + +export class DateRenderer implements IRenderer { + readonly type = 'date'; + + constructor(private dateService: DateService) {} + + render(context: IRenderContext): void { + const dates = context.filter['date'] || []; + const resourceIds = context.filter['resource'] || []; + + // Check if date headers should be hidden (e.g., in day view) + const dateGrouping = context.groupings?.find(g => g.type === 'date'); + const hideHeader = dateGrouping?.hideHeader === true; + + // Render dates for HVER resource (eller 1 gang hvis ingen resources) + const iterations = resourceIds.length || 1; + let columnCount = 0; + + for (let r = 0; r < iterations; r++) { + const resourceId = resourceIds[r]; // undefined hvis ingen resources + + for (const dateStr of dates) { + const date = this.dateService.parseISO(dateStr); + + // Build columnKey for uniform identification + const segments: Record = { date: dateStr }; + if (resourceId) segments.resource = resourceId; + const columnKey = this.dateService.buildColumnKey(segments); + + // Header + const header = document.createElement('swp-day-header'); + header.dataset.date = dateStr; + header.dataset.columnKey = columnKey; + if (resourceId) { + header.dataset.resourceId = resourceId; + } + if (hideHeader) { + header.dataset.hidden = 'true'; + } + header.innerHTML = ` + ${this.dateService.getDayName(date, 'short')} + ${date.getDate()} + `; + context.headerContainer.appendChild(header); + + // Column + const column = document.createElement('swp-day-column'); + column.dataset.date = dateStr; + column.dataset.columnKey = columnKey; + if (resourceId) { + column.dataset.resourceId = resourceId; + } + column.innerHTML = ''; + context.columnContainer.appendChild(column); + + columnCount++; + } + } + + // Set grid columns on container + const container = context.columnContainer.closest('swp-calendar-container'); + if (container) { + (container as HTMLElement).style.setProperty('--grid-columns', String(columnCount)); + } + } +} diff --git a/packages/calendar/src/features/date/index.ts b/packages/calendar/src/features/date/index.ts new file mode 100644 index 0000000..7bf37b3 --- /dev/null +++ b/packages/calendar/src/features/date/index.ts @@ -0,0 +1 @@ +export { DateRenderer } from './DateRenderer'; diff --git a/packages/calendar/src/features/event/EventLayoutEngine.ts b/packages/calendar/src/features/event/EventLayoutEngine.ts new file mode 100644 index 0000000..0b10905 --- /dev/null +++ b/packages/calendar/src/features/event/EventLayoutEngine.ts @@ -0,0 +1,279 @@ +/** + * EventLayoutEngine - Simplified stacking/grouping algorithm + * + * Supports two layout modes: + * - GRID: Events starting at same time rendered side-by-side + * - STACKING: Overlapping events with margin-left offset (15px per level) + * + * No prev/next chains, single-pass greedy algorithm + */ + +import { ICalendarEvent } from '../../types/CalendarTypes'; +import { IGridConfig } from '../../core/IGridConfig'; +import { calculateEventPosition } from '../../utils/PositionUtils'; +import { IColumnLayout, IGridGroupLayout, IStackedEventLayout } from './EventLayoutTypes'; + +/** + * Check if two events overlap (strict - touching at boundary = NOT overlapping) + * This matches Scenario 8: end===start is NOT overlap + */ +export function eventsOverlap(a: ICalendarEvent, b: ICalendarEvent): boolean { + return a.start < b.end && a.end > b.start; +} + +/** + * Check if two events are within threshold for grid grouping. + * This includes: + * 1. Start-to-start: Events start within threshold of each other + * 2. End-to-start: One event starts within threshold before another ends + */ +function eventsWithinThreshold(a: ICalendarEvent, b: ICalendarEvent, thresholdMinutes: number): boolean { + const thresholdMs = thresholdMinutes * 60 * 1000; + + // Start-to-start: both events start within threshold + const startToStartDiff = Math.abs(a.start.getTime() - b.start.getTime()); + if (startToStartDiff <= thresholdMs) return true; + + // End-to-start: one event starts within threshold before the other ends + // B starts within threshold before A ends + const bStartsBeforeAEnds = a.end.getTime() - b.start.getTime(); + if (bStartsBeforeAEnds > 0 && bStartsBeforeAEnds <= thresholdMs) return true; + + // A starts within threshold before B ends + const aStartsBeforeBEnds = b.end.getTime() - a.start.getTime(); + if (aStartsBeforeBEnds > 0 && aStartsBeforeBEnds <= thresholdMs) return true; + + return false; +} + +/** + * Check if all events in a group start within threshold of each other + */ +function allStartWithinThreshold(events: ICalendarEvent[], thresholdMinutes: number): boolean { + if (events.length <= 1) return true; + + // Find earliest and latest start times + let earliest = events[0].start.getTime(); + let latest = events[0].start.getTime(); + + for (const event of events) { + const time = event.start.getTime(); + if (time < earliest) earliest = time; + if (time > latest) latest = time; + } + + const diffMinutes = (latest - earliest) / (1000 * 60); + return diffMinutes <= thresholdMinutes; +} + +/** + * Find groups of overlapping events (connected by overlap chain) + * Events are grouped if they overlap with any event in the group + */ +function findOverlapGroups(events: ICalendarEvent[]): ICalendarEvent[][] { + if (events.length === 0) return []; + + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + const used = new Set(); + const groups: ICalendarEvent[][] = []; + + for (const event of sorted) { + if (used.has(event.id)) continue; + + // Start a new group with this event + const group: ICalendarEvent[] = [event]; + used.add(event.id); + + // Expand group by finding all connected events (via overlap) + let expanded = true; + while (expanded) { + expanded = false; + for (const candidate of sorted) { + if (used.has(candidate.id)) continue; + + // Check if candidate overlaps with any event in group + const connects = group.some(member => eventsOverlap(member, candidate)); + + if (connects) { + group.push(candidate); + used.add(candidate.id); + expanded = true; + } + } + } + + groups.push(group); + } + + return groups; +} + +/** + * Find grid candidates within a group - events connected via threshold chain + * Uses V1 logic: events are connected if within threshold (no overlap requirement) + */ +function findGridCandidates( + events: ICalendarEvent[], + thresholdMinutes: number +): ICalendarEvent[][] { + if (events.length === 0) return []; + + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + const used = new Set(); + const groups: ICalendarEvent[][] = []; + + for (const event of sorted) { + if (used.has(event.id)) continue; + + const group: ICalendarEvent[] = [event]; + used.add(event.id); + + // Expand by threshold chain (V1 logic: no overlap requirement, just threshold) + let expanded = true; + while (expanded) { + expanded = false; + for (const candidate of sorted) { + if (used.has(candidate.id)) continue; + + const connects = group.some(member => + eventsWithinThreshold(member, candidate, thresholdMinutes) + ); + + if (connects) { + group.push(candidate); + used.add(candidate.id); + expanded = true; + } + } + } + + groups.push(group); + } + + return groups; +} + +/** + * Calculate stack levels for overlapping events using greedy algorithm + * For each event: level = max(overlapping already-processed events) + 1 + */ +function calculateStackLevels(events: ICalendarEvent[]): Map { + const levels = new Map(); + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + + for (const event of sorted) { + let maxOverlappingLevel = -1; + + // Find max level among overlapping events already processed + for (const [id, level] of levels) { + const other = events.find(e => e.id === id); + if (other && eventsOverlap(event, other)) { + maxOverlappingLevel = Math.max(maxOverlappingLevel, level); + } + } + + levels.set(event.id, maxOverlappingLevel + 1); + } + + return levels; +} + +/** + * Allocate events to columns for GRID layout using greedy algorithm + * Non-overlapping events can share a column to minimize total columns + */ +function allocateColumns(events: ICalendarEvent[]): ICalendarEvent[][] { + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + const columns: ICalendarEvent[][] = []; + + for (const event of sorted) { + // Find first column where event doesn't overlap with existing events + let placed = false; + for (const column of columns) { + const canFit = !column.some(e => eventsOverlap(event, e)); + if (canFit) { + column.push(event); + placed = true; + break; + } + } + + // No suitable column found, create new one + if (!placed) { + columns.push([event]); + } + } + + return columns; +} + +/** + * Main entry point: Calculate complete layout for a column's events + * + * Algorithm: + * 1. Find overlap groups (events connected by overlap chain) + * 2. For each overlap group, find grid candidates (events within threshold chain) + * 3. If all events in overlap group form a single grid candidate → GRID mode + * 4. Otherwise → STACKING mode with calculated levels + */ +export function calculateColumnLayout( + events: ICalendarEvent[], + config: IGridConfig +): IColumnLayout { + const thresholdMinutes = config.gridStartThresholdMinutes ?? 10; + + const result: IColumnLayout = { + grids: [], + stacked: [] + }; + + if (events.length === 0) return result; + + // Find all overlapping event groups + const overlapGroups = findOverlapGroups(events); + + for (const overlapGroup of overlapGroups) { + if (overlapGroup.length === 1) { + // Single event - no grouping needed + result.stacked.push({ + event: overlapGroup[0], + stackLevel: 0 + }); + continue; + } + + // Within this overlap group, find grid candidates (threshold-connected subgroups) + const gridSubgroups = findGridCandidates(overlapGroup, thresholdMinutes); + + // Check if the ENTIRE overlap group forms a single grid candidate + // This happens when all events are connected via threshold chain + const largestGridCandidate = gridSubgroups.reduce((max, g) => + g.length > max.length ? g : max, gridSubgroups[0]); + + if (largestGridCandidate.length === overlapGroup.length) { + // All events in overlap group are connected via threshold chain → GRID mode + const columns = allocateColumns(overlapGroup); + const earliest = overlapGroup.reduce((min, e) => + e.start < min.start ? e : min, overlapGroup[0]); + const position = calculateEventPosition(earliest.start, earliest.end, config); + + result.grids.push({ + events: overlapGroup, + columns, + stackLevel: 0, + position: { top: position.top } + }); + } else { + // Not all events connected via threshold → STACKING mode + const levels = calculateStackLevels(overlapGroup); + for (const event of overlapGroup) { + result.stacked.push({ + event, + stackLevel: levels.get(event.id) ?? 0 + }); + } + } + } + + return result; +} diff --git a/packages/calendar/src/features/event/EventLayoutTypes.ts b/packages/calendar/src/features/event/EventLayoutTypes.ts new file mode 100644 index 0000000..c887eaf --- /dev/null +++ b/packages/calendar/src/features/event/EventLayoutTypes.ts @@ -0,0 +1,35 @@ +import { ICalendarEvent } from '../../types/CalendarTypes'; + +/** + * Stack link metadata stored on event elements + * Simplified from V1: No prev/next chains - only stackLevel needed for rendering + */ +export interface IStackLink { + stackLevel: number; +} + +/** + * Layout result for a stacked event (overlapping events with margin offset) + */ +export interface IStackedEventLayout { + event: ICalendarEvent; + stackLevel: number; +} + +/** + * Layout result for a grid group (simultaneous events side-by-side) + */ +export interface IGridGroupLayout { + events: ICalendarEvent[]; + columns: ICalendarEvent[][]; // Events grouped by column (non-overlapping within column) + stackLevel: number; // Stack level for entire group (if nested in another event) + position: { top: number }; // Top position of earliest event in pixels +} + +/** + * Complete layout result for a column's events + */ +export interface IColumnLayout { + grids: IGridGroupLayout[]; + stacked: IStackedEventLayout[]; +} diff --git a/packages/calendar/src/features/event/EventRenderer.ts b/packages/calendar/src/features/event/EventRenderer.ts new file mode 100644 index 0000000..6cc1b96 --- /dev/null +++ b/packages/calendar/src/features/event/EventRenderer.ts @@ -0,0 +1,434 @@ +import { ICalendarEvent, IEventBus, IEventUpdatedPayload } from '../../types/CalendarTypes'; +import { EventService } from '../../storage/events/EventService'; +import { DateService } from '../../core/DateService'; +import { IGridConfig } from '../../core/IGridConfig'; +import { calculateEventPosition, snapToGrid, pixelsToMinutes } from '../../utils/PositionUtils'; +import { CoreEvents } from '../../constants/CoreEvents'; +import { IDragColumnChangePayload, IDragMovePayload, IDragEndPayload, IDragLeaveHeaderPayload } from '../../types/DragTypes'; +import { calculateColumnLayout } from './EventLayoutEngine'; +import { IGridGroupLayout } from './EventLayoutTypes'; +import { FilterTemplate } from '../../core/FilterTemplate'; + +/** + * EventRenderer - Renders calendar events to the DOM + * + * CLEAN approach: + * - Only data-id attribute on event element + * - innerHTML contains only visible content + * - Event data retrieved via EventService when needed + */ +export class EventRenderer { + private container: HTMLElement | null = null; + + constructor( + private eventService: EventService, + private dateService: DateService, + private gridConfig: IGridConfig, + private eventBus: IEventBus + ) { + this.setupListeners(); + } + + /** + * Setup listeners for drag-drop and update events + */ + private setupListeners(): void { + this.eventBus.on(CoreEvents.EVENT_DRAG_COLUMN_CHANGE, (e) => { + const payload = (e as CustomEvent).detail; + this.handleColumnChange(payload); + }); + + this.eventBus.on(CoreEvents.EVENT_DRAG_MOVE, (e) => { + const payload = (e as CustomEvent).detail; + this.updateDragTimestamp(payload); + }); + + this.eventBus.on(CoreEvents.EVENT_UPDATED, (e) => { + const payload = (e as CustomEvent).detail; + this.handleEventUpdated(payload); + }); + + this.eventBus.on(CoreEvents.EVENT_DRAG_END, (e) => { + const payload = (e as CustomEvent).detail; + this.handleDragEnd(payload); + }); + + this.eventBus.on(CoreEvents.EVENT_DRAG_LEAVE_HEADER, (e) => { + const payload = (e as CustomEvent).detail; + this.handleDragLeaveHeader(payload); + }); + } + + /** + * Handle EVENT_DRAG_END - remove element if dropped in header + */ + private handleDragEnd(payload: IDragEndPayload): void { + if (payload.target === 'header') { + // Event was dropped in header drawer - remove from grid + const element = this.container?.querySelector(`swp-content-viewport swp-event[data-event-id="${payload.swpEvent.eventId}"]`); + element?.remove(); + } + } + + /** + * Handle header item leaving header - create swp-event in grid + */ + private handleDragLeaveHeader(payload: IDragLeaveHeaderPayload): void { + // Only handle when source is header (header item dragged to grid) + if (payload.source !== 'header') return; + if (!payload.targetColumn || !payload.start || !payload.end) return; + + // Turn header item into ghost (stays visible but faded) + if (payload.element) { + payload.element.classList.add('drag-ghost'); + payload.element.style.opacity = '0.3'; + payload.element.style.pointerEvents = 'none'; + } + + // Create event object from header item data + const event: ICalendarEvent = { + id: payload.eventId, + title: payload.title || '', + description: '', + start: payload.start, + end: payload.end, + type: 'customer', + allDay: false, + syncStatus: 'pending' + }; + + // Create swp-event element using existing method + const element = this.createEventElement(event); + + // Add to target column + let eventsLayer = payload.targetColumn.querySelector('swp-events-layer'); + if (!eventsLayer) { + eventsLayer = document.createElement('swp-events-layer'); + payload.targetColumn.appendChild(eventsLayer); + } + eventsLayer.appendChild(element); + + // Mark as dragging so DragDropManager can continue with it + element.classList.add('dragging'); + } + + /** + * Handle EVENT_UPDATED - re-render affected columns + */ + private async handleEventUpdated(payload: IEventUpdatedPayload): Promise { + // Re-render source column (if different from target) + if (payload.sourceColumnKey !== payload.targetColumnKey) { + await this.rerenderColumn(payload.sourceColumnKey); + } + + // Re-render target column + await this.rerenderColumn(payload.targetColumnKey); + } + + /** + * Re-render a single column with fresh data from IndexedDB + */ + private async rerenderColumn(columnKey: string): Promise { + const column = this.findColumn(columnKey); + if (!column) return; + + // Read date and resourceId directly from column attributes (columnKey is opaque) + const date = column.dataset.date; + const resourceId = column.dataset.resourceId; + + if (!date) return; + + // Get date range for this day + const startDate = new Date(date); + const endDate = new Date(date); + endDate.setHours(23, 59, 59, 999); + + // Fetch events from IndexedDB + const events = resourceId + ? await this.eventService.getByResourceAndDateRange(resourceId, startDate, endDate) + : await this.eventService.getByDateRange(startDate, endDate); + + // Filter to timed events and match date exactly + const timedEvents = events.filter(event => + !event.allDay && this.dateService.getDateKey(event.start) === date + ); + + // Get or create events layer + let eventsLayer = column.querySelector('swp-events-layer'); + if (!eventsLayer) { + eventsLayer = document.createElement('swp-events-layer'); + column.appendChild(eventsLayer); + } + + // Clear existing events + eventsLayer.innerHTML = ''; + + // Calculate layout with stacking/grouping + const layout = calculateColumnLayout(timedEvents, this.gridConfig); + + // Render GRID groups + layout.grids.forEach(grid => { + const groupEl = this.renderGridGroup(grid); + eventsLayer!.appendChild(groupEl); + }); + + // Render STACKED events + layout.stacked.forEach(item => { + const eventEl = this.renderStackedEvent(item.event, item.stackLevel); + eventsLayer!.appendChild(eventEl); + }); + } + + /** + * Find a column element by columnKey + */ + private findColumn(columnKey: string): HTMLElement | null { + if (!this.container) return null; + return this.container.querySelector(`swp-day-column[data-column-key="${columnKey}"]`) as HTMLElement; + } + + /** + * Handle event moving to a new column during drag + */ + private handleColumnChange(payload: IDragColumnChangePayload): void { + const eventsLayer = payload.newColumn.querySelector('swp-events-layer'); + if (!eventsLayer) return; + + // Move element to new column + eventsLayer.appendChild(payload.element); + + // Preserve Y position + payload.element.style.top = `${payload.currentY}px`; + } + + /** + * Update timestamp display during drag (snapped to grid) + */ + private updateDragTimestamp(payload: IDragMovePayload): void { + const timeEl = payload.element.querySelector('swp-event-time'); + if (!timeEl) return; + + // Snap position to grid interval + const snappedY = snapToGrid(payload.currentY, this.gridConfig); + + // Calculate new start time + const minutesFromGridStart = pixelsToMinutes(snappedY, this.gridConfig); + const startMinutes = (this.gridConfig.dayStartHour * 60) + minutesFromGridStart; + + // Keep original duration (from element height) + const height = parseFloat(payload.element.style.height) || this.gridConfig.hourHeight; + const durationMinutes = pixelsToMinutes(height, this.gridConfig); + + // Create Date objects for consistent formatting via DateService + const start = this.minutesToDate(startMinutes); + const end = this.minutesToDate(startMinutes + durationMinutes); + + timeEl.textContent = this.dateService.formatTimeRange(start, end); + } + + /** + * Convert minutes since midnight to a Date object (today) + */ + private minutesToDate(minutes: number): Date { + const date = new Date(); + date.setHours(Math.floor(minutes / 60) % 24, minutes % 60, 0, 0); + return date; + } + + /** + * Render events for visible dates into day columns + * @param container - Calendar container element + * @param filter - Filter with 'date' and optionally 'resource' arrays + * @param filterTemplate - Template for matching events to columns + */ + async render(container: HTMLElement, filter: Record, filterTemplate: FilterTemplate): Promise { + // Store container reference for later re-renders + this.container = container; + + const visibleDates = filter['date'] || []; + + if (visibleDates.length === 0) return; + + // Get date range for query + const startDate = new Date(visibleDates[0]); + const endDate = new Date(visibleDates[visibleDates.length - 1]); + endDate.setHours(23, 59, 59, 999); + + // Fetch events from IndexedDB + const events = await this.eventService.getByDateRange(startDate, endDate); + + // Find day columns + const dayColumns = container.querySelector('swp-day-columns'); + if (!dayColumns) return; + + const columns = dayColumns.querySelectorAll('swp-day-column'); + + // Render events into each column based on FilterTemplate matching + columns.forEach(column => { + const columnEl = column as HTMLElement; + + // Use FilterTemplate for matching - only fields in template are checked + const columnEvents = events.filter(event => filterTemplate.matches(event, columnEl)); + + // Get or create events layer + let eventsLayer = column.querySelector('swp-events-layer'); + if (!eventsLayer) { + eventsLayer = document.createElement('swp-events-layer'); + column.appendChild(eventsLayer); + } + + // Clear existing events + eventsLayer.innerHTML = ''; + + // Filter to timed events only + const timedEvents = columnEvents.filter(event => !event.allDay); + + // Calculate layout with stacking/grouping + const layout = calculateColumnLayout(timedEvents, this.gridConfig); + + // Render GRID groups (simultaneous events side-by-side) + layout.grids.forEach(grid => { + const groupEl = this.renderGridGroup(grid); + eventsLayer!.appendChild(groupEl); + }); + + // Render STACKED events (overlapping with margin offset) + layout.stacked.forEach(item => { + const eventEl = this.renderStackedEvent(item.event, item.stackLevel); + eventsLayer!.appendChild(eventEl); + }); + }); + } + + /** + * Create a single event element + * + * CLEAN approach: + * - Only data-id for lookup + * - Visible content in innerHTML only + */ + private createEventElement(event: ICalendarEvent): HTMLElement { + const element = document.createElement('swp-event'); + + // Data attributes for SwpEvent compatibility + element.dataset.eventId = event.id; + if (event.resourceId) { + element.dataset.resourceId = event.resourceId; + } + + // Calculate position + const position = calculateEventPosition(event.start, event.end, this.gridConfig); + element.style.top = `${position.top}px`; + element.style.height = `${position.height}px`; + + // Color class based on event type + const colorClass = this.getColorClass(event); + if (colorClass) { + element.classList.add(colorClass); + } + + // Visible content only + element.innerHTML = ` + ${this.dateService.formatTimeRange(event.start, event.end)} + ${this.escapeHtml(event.title)} + ${event.description ? `${this.escapeHtml(event.description)}` : ''} + `; + + return element; + } + + /** + * Get color class based on metadata.color or event type + */ + private getColorClass(event: ICalendarEvent): string { + // Check metadata.color first + if (event.metadata?.color) { + return `is-${event.metadata.color}`; + } + + // Fallback to type-based color + const typeColors: Record = { + 'customer': 'is-blue', + 'vacation': 'is-green', + 'break': 'is-amber', + 'meeting': 'is-purple', + 'blocked': 'is-red' + }; + return typeColors[event.type] || 'is-blue'; + } + + /** + * Escape HTML to prevent XSS + */ + private escapeHtml(text: string): string { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + + /** + * Render a GRID group with side-by-side columns + * Used when multiple events start at the same time + */ + private renderGridGroup(layout: IGridGroupLayout): HTMLElement { + const group = document.createElement('swp-event-group'); + group.classList.add(`cols-${layout.columns.length}`); + group.style.top = `${layout.position.top}px`; + + // Stack level styling for entire group (if nested in another event) + if (layout.stackLevel > 0) { + group.style.marginLeft = `${layout.stackLevel * 15}px`; + group.style.zIndex = `${100 + layout.stackLevel}`; + } + + // Calculate the height needed for the group (tallest event) + let maxBottom = 0; + for (const event of layout.events) { + const pos = calculateEventPosition(event.start, event.end, this.gridConfig); + const eventBottom = pos.top + pos.height; + if (eventBottom > maxBottom) maxBottom = eventBottom; + } + const groupHeight = maxBottom - layout.position.top; + group.style.height = `${groupHeight}px`; + + // Create wrapper div for each column + layout.columns.forEach(columnEvents => { + const wrapper = document.createElement('div'); + wrapper.style.position = 'relative'; + + columnEvents.forEach(event => { + const eventEl = this.createEventElement(event); + // Position relative to group top + const pos = calculateEventPosition(event.start, event.end, this.gridConfig); + eventEl.style.top = `${pos.top - layout.position.top}px`; + eventEl.style.position = 'absolute'; + eventEl.style.left = '0'; + eventEl.style.right = '0'; + wrapper.appendChild(eventEl); + }); + + group.appendChild(wrapper); + }); + + return group; + } + + /** + * Render a STACKED event with margin-left offset + * Used for overlapping events that don't start at the same time + */ + private renderStackedEvent(event: ICalendarEvent, stackLevel: number): HTMLElement { + const element = this.createEventElement(event); + + // Add stack metadata for drag-drop and other features + element.dataset.stackLink = JSON.stringify({ stackLevel }); + + // Visual styling based on stack level + if (stackLevel > 0) { + element.style.marginLeft = `${stackLevel * 15}px`; + element.style.zIndex = `${100 + stackLevel}`; + } + + return element; + } +} diff --git a/packages/calendar/src/features/event/index.ts b/packages/calendar/src/features/event/index.ts new file mode 100644 index 0000000..7b8f118 --- /dev/null +++ b/packages/calendar/src/features/event/index.ts @@ -0,0 +1 @@ +export { EventRenderer } from './EventRenderer'; diff --git a/packages/calendar/src/features/headerdrawer/HeaderDrawerLayoutEngine.ts b/packages/calendar/src/features/headerdrawer/HeaderDrawerLayoutEngine.ts new file mode 100644 index 0000000..b407a58 --- /dev/null +++ b/packages/calendar/src/features/headerdrawer/HeaderDrawerLayoutEngine.ts @@ -0,0 +1,135 @@ +/** + * HeaderDrawerLayoutEngine - Calculates row placement for header items + * + * Prevents visual overlap by assigning items to different rows when + * they occupy the same columns. Uses a track-based algorithm similar + * to V1's AllDayLayoutEngine. + * + * Each row can hold multiple items as long as they don't overlap in columns. + * When an item spans columns that are already occupied, it's placed in the + * next available row. + */ + +export interface IHeaderItemLayout { + itemId: string; + gridArea: string; // "row / col-start / row+1 / col-end" + startColumn: number; + endColumn: number; + row: number; +} + +export interface IHeaderItemInput { + id: string; + columnStart: number; // 0-based column index + columnEnd: number; // 0-based end column (inclusive) +} + +export class HeaderDrawerLayoutEngine { + private tracks: boolean[][] = []; + private columnCount: number; + + constructor(columnCount: number) { + this.columnCount = columnCount; + this.reset(); + } + + /** + * Reset tracks for new layout calculation + */ + reset(): void { + this.tracks = [new Array(this.columnCount).fill(false)]; + } + + /** + * Calculate layout for all items + * Items should be sorted by start column for optimal packing + */ + calculateLayout(items: IHeaderItemInput[]): IHeaderItemLayout[] { + this.reset(); + const layouts: IHeaderItemLayout[] = []; + + for (const item of items) { + const row = this.findAvailableRow(item.columnStart, item.columnEnd); + + // Mark columns as occupied in this row + for (let col = item.columnStart; col <= item.columnEnd; col++) { + this.tracks[row][col] = true; + } + + // gridArea format: "row / col-start / row+1 / col-end" + // CSS grid uses 1-based indices + layouts.push({ + itemId: item.id, + gridArea: `${row + 1} / ${item.columnStart + 1} / ${row + 2} / ${item.columnEnd + 2}`, + startColumn: item.columnStart, + endColumn: item.columnEnd, + row: row + 1 // 1-based for CSS + }); + } + + return layouts; + } + + /** + * Calculate layout for a single new item + * Useful for real-time drag operations + */ + calculateSingleLayout(item: IHeaderItemInput): IHeaderItemLayout { + const row = this.findAvailableRow(item.columnStart, item.columnEnd); + + // Mark columns as occupied + for (let col = item.columnStart; col <= item.columnEnd; col++) { + this.tracks[row][col] = true; + } + + return { + itemId: item.id, + gridArea: `${row + 1} / ${item.columnStart + 1} / ${row + 2} / ${item.columnEnd + 2}`, + startColumn: item.columnStart, + endColumn: item.columnEnd, + row: row + 1 + }; + } + + /** + * Find the first row where all columns in range are available + */ + private findAvailableRow(startCol: number, endCol: number): number { + for (let row = 0; row < this.tracks.length; row++) { + if (this.isRowAvailable(row, startCol, endCol)) { + return row; + } + } + + // Add new row if all existing rows are occupied + this.tracks.push(new Array(this.columnCount).fill(false)); + return this.tracks.length - 1; + } + + /** + * Check if columns in range are all available in given row + */ + private isRowAvailable(row: number, startCol: number, endCol: number): boolean { + for (let col = startCol; col <= endCol; col++) { + if (this.tracks[row][col]) { + return false; + } + } + return true; + } + + /** + * Get the number of rows currently in use + */ + getRowCount(): number { + return this.tracks.length; + } + + /** + * Update column count (e.g., when view changes) + */ + setColumnCount(count: number): void { + this.columnCount = count; + this.reset(); + } +} diff --git a/packages/calendar/src/features/headerdrawer/HeaderDrawerRenderer.ts b/packages/calendar/src/features/headerdrawer/HeaderDrawerRenderer.ts new file mode 100644 index 0000000..1b528cd --- /dev/null +++ b/packages/calendar/src/features/headerdrawer/HeaderDrawerRenderer.ts @@ -0,0 +1,419 @@ +import { IEventBus, ICalendarEvent } from '../../types/CalendarTypes'; +import { IGridConfig } from '../../core/IGridConfig'; +import { CoreEvents } from '../../constants/CoreEvents'; +import { HeaderDrawerManager } from '../../core/HeaderDrawerManager'; +import { EventService } from '../../storage/events/EventService'; +import { DateService } from '../../core/DateService'; +import { FilterTemplate } from '../../core/FilterTemplate'; +import { + IDragEnterHeaderPayload, + IDragMoveHeaderPayload, + IDragLeaveHeaderPayload, + IDragEndPayload +} from '../../types/DragTypes'; + +/** + * Layout information for a header item + */ +interface IHeaderItemLayout { + event: ICalendarEvent; + columnKey: string; // Opaque column identifier + row: number; // 1-indexed + colStart: number; // 1-indexed + colEnd: number; // exclusive +} + +/** + * HeaderDrawerRenderer - Handles rendering of items in the header drawer + * + * Listens to drag events from DragDropManager and creates/manages + * swp-header-item elements in the header drawer. + * + * Uses subgrid for column alignment with parent swp-calendar-header. + * Position items via gridArea for explicit row/column placement. + */ +export class HeaderDrawerRenderer { + private currentItem: HTMLElement | null = null; + private container: HTMLElement | null = null; + private sourceElement: HTMLElement | null = null; + private wasExpandedBeforeDrag = false; + private filterTemplate: FilterTemplate | null = null; + + constructor( + private eventBus: IEventBus, + private gridConfig: IGridConfig, + private headerDrawerManager: HeaderDrawerManager, + private eventService: EventService, + private dateService: DateService + ) { + this.setupListeners(); + } + + /** + * Render allDay events into the header drawer with row stacking + * @param filterTemplate - Template for matching events to columns + */ + async render(container: HTMLElement, filter: Record, filterTemplate: FilterTemplate): Promise { + // Store filterTemplate for buildColumnKeyFromEvent + this.filterTemplate = filterTemplate; + + const drawer = container.querySelector('swp-header-drawer'); + if (!drawer) return; + + const visibleDates = filter['date'] || []; + if (visibleDates.length === 0) return; + + // Get column keys from DOM for correct multi-resource positioning + const visibleColumnKeys = this.getVisibleColumnKeysFromDOM(); + if (visibleColumnKeys.length === 0) return; + + // Fetch events for date range + const startDate = new Date(visibleDates[0]); + const endDate = new Date(visibleDates[visibleDates.length - 1]); + endDate.setHours(23, 59, 59, 999); + + const events = await this.eventService.getByDateRange(startDate, endDate); + + // Filter to allDay events only (allDay !== false) + const allDayEvents = events.filter(event => event.allDay !== false); + + // Clear existing items + drawer.innerHTML = ''; + + if (allDayEvents.length === 0) return; + + // Calculate layout with row stacking using columnKeys + const layouts = this.calculateLayout(allDayEvents, visibleColumnKeys); + const rowCount = Math.max(1, ...layouts.map(l => l.row)); + + // Render each item with layout + layouts.forEach(layout => { + const item = this.createHeaderItem(layout); + drawer.appendChild(item); + }); + + // Expand drawer to fit all rows + this.headerDrawerManager.expandToRows(rowCount); + } + + /** + * Create a header item element from layout + */ + private createHeaderItem(layout: IHeaderItemLayout): HTMLElement { + const { event, columnKey, row, colStart, colEnd } = layout; + + const item = document.createElement('swp-header-item'); + item.dataset.eventId = event.id; + item.dataset.itemType = 'event'; + item.dataset.start = event.start.toISOString(); + item.dataset.end = event.end.toISOString(); + item.dataset.columnKey = columnKey; + item.textContent = event.title; + + // Color class + const colorClass = this.getColorClass(event); + if (colorClass) item.classList.add(colorClass); + + // Grid position from layout + item.style.gridArea = `${row} / ${colStart} / ${row + 1} / ${colEnd}`; + + return item; + } + + /** + * Calculate layout for all events with row stacking + * Uses track-based algorithm to find available rows for overlapping events + */ + private calculateLayout(events: ICalendarEvent[], visibleColumnKeys: string[]): IHeaderItemLayout[] { + // tracks[row][col] = occupied + const tracks: boolean[][] = [new Array(visibleColumnKeys.length).fill(false)]; + const layouts: IHeaderItemLayout[] = []; + + for (const event of events) { + // Build columnKey from event fields (only place we need to construct it) + const columnKey = this.buildColumnKeyFromEvent(event); + const startCol = visibleColumnKeys.indexOf(columnKey); + const endColumnKey = this.buildColumnKeyFromEvent(event, event.end); + const endCol = visibleColumnKeys.indexOf(endColumnKey); + if (startCol === -1 && endCol === -1) continue; + + // Clamp til synlige kolonner + const colStart = Math.max(0, startCol); + const colEnd = (endCol !== -1 ? endCol : visibleColumnKeys.length - 1) + 1; + + // Find ledig række + const row = this.findAvailableRow(tracks, colStart, colEnd); + + // Marker som optaget + for (let c = colStart; c < colEnd; c++) { + tracks[row][c] = true; + } + + layouts.push({ event, columnKey, row: row + 1, colStart: colStart + 1, colEnd: colEnd + 1 }); + } + + return layouts; + } + + /** + * Build columnKey from event using FilterTemplate + * Uses the same template that columns use for matching + */ + private buildColumnKeyFromEvent(event: ICalendarEvent, date?: Date): string { + if (!this.filterTemplate) { + // Fallback if no template - shouldn't happen in normal flow + const dateStr = this.dateService.getDateKey(date || event.start); + return dateStr; + } + + // For multi-day events, we need to override the date in the event + if (date && date.getTime() !== event.start.getTime()) { + // Create temporary event with overridden start for key generation + const tempEvent = { ...event, start: date }; + return this.filterTemplate.buildKeyFromEvent(tempEvent); + } + + return this.filterTemplate.buildKeyFromEvent(event); + } + + /** + * Find available row for event spanning columns [colStart, colEnd) + */ + private findAvailableRow(tracks: boolean[][], colStart: number, colEnd: number): number { + for (let row = 0; row < tracks.length; row++) { + let available = true; + for (let c = colStart; c < colEnd; c++) { + if (tracks[row][c]) { available = false; break; } + } + if (available) return row; + } + // Ny række + tracks.push(new Array(tracks[0].length).fill(false)); + return tracks.length - 1; + } + + /** + * Get color class based on event metadata or type + */ + private getColorClass(event: ICalendarEvent): string { + if (event.metadata?.color) { + return `is-${event.metadata.color}`; + } + const typeColors: Record = { + 'customer': 'is-blue', + 'vacation': 'is-green', + 'break': 'is-amber', + 'meeting': 'is-purple', + 'blocked': 'is-red' + }; + return typeColors[event.type] || 'is-blue'; + } + + /** + * Setup event listeners for drag events + */ + private setupListeners(): void { + this.eventBus.on(CoreEvents.EVENT_DRAG_ENTER_HEADER, (e) => { + const payload = (e as CustomEvent).detail; + this.handleDragEnter(payload); + }); + + this.eventBus.on(CoreEvents.EVENT_DRAG_MOVE_HEADER, (e) => { + const payload = (e as CustomEvent).detail; + this.handleDragMove(payload); + }); + + this.eventBus.on(CoreEvents.EVENT_DRAG_LEAVE_HEADER, (e) => { + const payload = (e as CustomEvent).detail; + this.handleDragLeave(payload); + }); + + this.eventBus.on(CoreEvents.EVENT_DRAG_END, (e) => { + const payload = (e as CustomEvent).detail; + this.handleDragEnd(payload); + }); + + this.eventBus.on(CoreEvents.EVENT_DRAG_CANCEL, () => { + this.cleanup(); + }); + } + + /** + * Handle drag entering header zone - create preview item + */ + private handleDragEnter(payload: IDragEnterHeaderPayload): void { + this.container = document.querySelector('swp-header-drawer'); + if (!this.container) return; + + // Remember if drawer was already expanded + this.wasExpandedBeforeDrag = this.headerDrawerManager.isExpanded(); + + // Expand to at least 1 row if collapsed, otherwise keep current height + if (!this.wasExpandedBeforeDrag) { + this.headerDrawerManager.expandToRows(1); + } + + // Store reference to source element + this.sourceElement = payload.element; + + // Create header item + const item = document.createElement('swp-header-item'); + item.dataset.eventId = payload.eventId; + item.dataset.itemType = payload.itemType; + item.dataset.duration = String(payload.duration); + item.dataset.columnKey = payload.sourceColumnKey; + item.textContent = payload.title; + + // Apply color class if present + if (payload.colorClass) { + item.classList.add(payload.colorClass); + } + + // Add dragging state + item.classList.add('dragging'); + + // Initial placement (duration determines column span) + // gridArea format: "row / col-start / row+1 / col-end" + const col = payload.sourceColumnIndex + 1; + const endCol = col + payload.duration; + item.style.gridArea = `1 / ${col} / 2 / ${endCol}`; + + this.container.appendChild(item); + this.currentItem = item; + + // Hide original element while in header + payload.element.style.visibility = 'hidden'; + } + + /** + * Handle drag moving within header - update column position + */ + private handleDragMove(payload: IDragMoveHeaderPayload): void { + if (!this.currentItem) return; + + // Update column position + const col = payload.columnIndex + 1; + const duration = parseInt(this.currentItem.dataset.duration || '1', 10); + const endCol = col + duration; + + this.currentItem.style.gridArea = `1 / ${col} / 2 / ${endCol}`; + + // Update columnKey to new position + this.currentItem.dataset.columnKey = payload.columnKey; + } + + /** + * Handle drag leaving header - cleanup for grid→header drag only + */ + private handleDragLeave(payload: IDragLeaveHeaderPayload): void { + // Only cleanup for grid→header drag (when grid event leaves header back to grid) + // For header→grid drag, the header item stays as ghost until drop + if (payload.source === 'grid') { + this.cleanup(); + } + // For header source, do nothing - ghost stays until EVENT_DRAG_END + } + + /** + * Handle drag end - finalize based on drop target + */ + private handleDragEnd(payload: IDragEndPayload): void { + if (payload.target === 'header') { + // Grid→Header: Finalize the header item (it stays in header) + if (this.currentItem) { + this.currentItem.classList.remove('dragging'); + this.recalculateDrawerLayout(); + this.currentItem = null; + this.sourceElement = null; + } + } else { + // Header→Grid: Remove ghost header item and recalculate + const ghost = document.querySelector(`swp-header-item.drag-ghost[data-event-id="${payload.swpEvent.eventId}"]`); + ghost?.remove(); + this.recalculateDrawerLayout(); + } + } + + /** + * Recalculate layout for all items currently in the drawer + * Called after drop to reposition items and adjust height + */ + private recalculateDrawerLayout(): void { + const drawer = document.querySelector('swp-header-drawer'); + if (!drawer) return; + + const items = Array.from(drawer.querySelectorAll('swp-header-item')) as HTMLElement[]; + if (items.length === 0) return; + + // Get visible column keys for correct multi-resource positioning + const visibleColumnKeys = this.getVisibleColumnKeysFromDOM(); + if (visibleColumnKeys.length === 0) return; + + // Build layout data from DOM items - use columnKey directly (opaque matching) + const itemData = items.map(item => ({ + element: item, + columnKey: item.dataset.columnKey || '', + duration: parseInt(item.dataset.duration || '1', 10) + })); + + // Calculate new layout using track algorithm + const tracks: boolean[][] = [new Array(visibleColumnKeys.length).fill(false)]; + + for (const item of itemData) { + // Direct columnKey matching - no parsing or construction needed + const startCol = visibleColumnKeys.indexOf(item.columnKey); + if (startCol === -1) continue; + + const colStart = startCol; + const colEnd = Math.min(startCol + item.duration, visibleColumnKeys.length); + + const row = this.findAvailableRow(tracks, colStart, colEnd); + + for (let c = colStart; c < colEnd; c++) { + tracks[row][c] = true; + } + + // Update element position + item.element.style.gridArea = `${row + 1} / ${colStart + 1} / ${row + 2} / ${colEnd + 1}`; + } + + // Update drawer height + const rowCount = tracks.length; + this.headerDrawerManager.expandToRows(rowCount); + } + + /** + * Get visible column keys from DOM (preserves order for multi-resource views) + * Uses filterTemplate.buildKeyFromColumn() for consistent key format with events + */ + private getVisibleColumnKeysFromDOM(): string[] { + if (!this.filterTemplate) return []; + const columns = document.querySelectorAll('swp-day-column'); + const columnKeys: string[] = []; + columns.forEach(col => { + const columnKey = this.filterTemplate!.buildKeyFromColumn(col as HTMLElement); + if (columnKey) columnKeys.push(columnKey); + }); + return columnKeys; + } + + /** + * Cleanup preview item and restore source visibility + */ + private cleanup(): void { + // Remove preview item + this.currentItem?.remove(); + this.currentItem = null; + + // Restore source element visibility + if (this.sourceElement) { + this.sourceElement.style.visibility = ''; + this.sourceElement = null; + } + + // Collapse drawer if it wasn't expanded before drag + if (!this.wasExpandedBeforeDrag) { + this.headerDrawerManager.collapse(); + } + } +} diff --git a/packages/calendar/src/features/headerdrawer/index.ts b/packages/calendar/src/features/headerdrawer/index.ts new file mode 100644 index 0000000..7b19757 --- /dev/null +++ b/packages/calendar/src/features/headerdrawer/index.ts @@ -0,0 +1,2 @@ +export { HeaderDrawerRenderer } from './HeaderDrawerRenderer'; +export { HeaderDrawerLayoutEngine, type IHeaderItemLayout, type IHeaderItemInput } from './HeaderDrawerLayoutEngine'; diff --git a/packages/calendar/src/features/resource/ResourceRenderer.ts b/packages/calendar/src/features/resource/ResourceRenderer.ts new file mode 100644 index 0000000..2bf565f --- /dev/null +++ b/packages/calendar/src/features/resource/ResourceRenderer.ts @@ -0,0 +1,69 @@ +import { IRenderContext } from '../../core/IGroupingRenderer'; +import { BaseGroupingRenderer, IGroupingRendererConfig } from '../../core/BaseGroupingRenderer'; +import { ResourceService } from '../../storage/resources/ResourceService'; +import { IResource } from '../../types/CalendarTypes'; + +export class ResourceRenderer extends BaseGroupingRenderer { + readonly type = 'resource'; + + protected readonly config: IGroupingRendererConfig = { + elementTag: 'swp-resource-header', + idAttribute: 'resourceId', + colspanVar: '--resource-cols' + }; + + constructor(private resourceService: ResourceService) { + super(); + } + + protected getEntities(ids: string[]): Promise { + return this.resourceService.getByIds(ids); + } + + protected getDisplayName(entity: IResource): string { + return entity.displayName; + } + + /** + * Override render to handle: + * 1. Special ordering when parentChildMap exists (resources grouped by parent) + * 2. Different colspan calculation (just dateCount, not childCount * dateCount) + */ + async render(context: IRenderContext): Promise { + const resourceIds = context.filter['resource'] || []; + const dateCount = context.filter['date']?.length || 1; + + // Determine render order based on parentChildMap + // If parentChildMap exists, render resources grouped by parent (e.g., team) + // Otherwise, render in filter order + let orderedResourceIds: string[]; + + if (context.parentChildMap) { + // Render resources in parent-child order + orderedResourceIds = []; + for (const childIds of Object.values(context.parentChildMap)) { + for (const childId of childIds) { + if (resourceIds.includes(childId)) { + orderedResourceIds.push(childId); + } + } + } + } else { + orderedResourceIds = resourceIds; + } + + const resources = await this.getEntities(orderedResourceIds); + + // Create a map for quick lookup to preserve order + const resourceMap = new Map(resources.map(r => [r.id, r])); + + for (const resourceId of orderedResourceIds) { + const resource = resourceMap.get(resourceId); + if (!resource) continue; + + const header = this.createHeader(resource, context); + header.style.gridColumn = `span ${dateCount}`; + context.headerContainer.appendChild(header); + } + } +} diff --git a/packages/calendar/src/features/resource/index.ts b/packages/calendar/src/features/resource/index.ts new file mode 100644 index 0000000..3bbd0d9 --- /dev/null +++ b/packages/calendar/src/features/resource/index.ts @@ -0,0 +1 @@ +export { ResourceRenderer } from './ResourceRenderer'; diff --git a/packages/calendar/src/features/schedule/ScheduleRenderer.ts b/packages/calendar/src/features/schedule/ScheduleRenderer.ts new file mode 100644 index 0000000..d1d3349 --- /dev/null +++ b/packages/calendar/src/features/schedule/ScheduleRenderer.ts @@ -0,0 +1,106 @@ +import { ResourceScheduleService } from '../../extensions/schedules/ResourceScheduleService'; +import { DateService } from '../../core/DateService'; +import { IGridConfig } from '../../core/IGridConfig'; +import { ITimeSlot } from '../../types/ScheduleTypes'; + +/** + * ScheduleRenderer - Renders unavailable time zones in day columns + * + * Creates visual indicators for times outside the resource's working hours: + * - Before work start (e.g., 06:00 - 09:00) + * - After work end (e.g., 17:00 - 18:00) + * - Full day if resource is off (schedule = null) + */ +export class ScheduleRenderer { + constructor( + private scheduleService: ResourceScheduleService, + private dateService: DateService, + private gridConfig: IGridConfig + ) {} + + /** + * Render unavailable zones for visible columns + * @param container - Calendar container element + * @param filter - Filter with 'date' and 'resource' arrays + */ + async render(container: HTMLElement, filter: Record): Promise { + const dates = filter['date'] || []; + const resourceIds = filter['resource'] || []; + + if (dates.length === 0) return; + + // Find day columns + const dayColumns = container.querySelector('swp-day-columns'); + if (!dayColumns) return; + + const columns = dayColumns.querySelectorAll('swp-day-column'); + + for (const column of columns) { + const date = (column as HTMLElement).dataset.date; + const resourceId = (column as HTMLElement).dataset.resourceId; + + if (!date || !resourceId) continue; + + // Get or create unavailable layer + let unavailableLayer = column.querySelector('swp-unavailable-layer'); + if (!unavailableLayer) { + unavailableLayer = document.createElement('swp-unavailable-layer'); + column.insertBefore(unavailableLayer, column.firstChild); + } + + // Clear existing + unavailableLayer.innerHTML = ''; + + // Get schedule for this resource/date + const schedule = await this.scheduleService.getScheduleForDate(resourceId, date); + + // Render unavailable zones + this.renderUnavailableZones(unavailableLayer as HTMLElement, schedule); + } + } + + /** + * Render unavailable time zones based on schedule + */ + private renderUnavailableZones(layer: HTMLElement, schedule: ITimeSlot | null): void { + const dayStartMinutes = this.gridConfig.dayStartHour * 60; + const dayEndMinutes = this.gridConfig.dayEndHour * 60; + const minuteHeight = this.gridConfig.hourHeight / 60; + + if (schedule === null) { + // Full day unavailable + const zone = this.createUnavailableZone(0, (dayEndMinutes - dayStartMinutes) * minuteHeight); + layer.appendChild(zone); + return; + } + + const workStartMinutes = this.dateService.timeToMinutes(schedule.start); + const workEndMinutes = this.dateService.timeToMinutes(schedule.end); + + // Before work start + if (workStartMinutes > dayStartMinutes) { + const top = 0; + const height = (workStartMinutes - dayStartMinutes) * minuteHeight; + const zone = this.createUnavailableZone(top, height); + layer.appendChild(zone); + } + + // After work end + if (workEndMinutes < dayEndMinutes) { + const top = (workEndMinutes - dayStartMinutes) * minuteHeight; + const height = (dayEndMinutes - workEndMinutes) * minuteHeight; + const zone = this.createUnavailableZone(top, height); + layer.appendChild(zone); + } + } + + /** + * Create an unavailable zone element + */ + private createUnavailableZone(top: number, height: number): HTMLElement { + const zone = document.createElement('swp-unavailable-zone'); + zone.style.top = `${top}px`; + zone.style.height = `${height}px`; + return zone; + } +} diff --git a/packages/calendar/src/features/schedule/index.ts b/packages/calendar/src/features/schedule/index.ts new file mode 100644 index 0000000..c6ca514 --- /dev/null +++ b/packages/calendar/src/features/schedule/index.ts @@ -0,0 +1 @@ +export { ScheduleRenderer } from './ScheduleRenderer'; diff --git a/packages/calendar/src/features/timeaxis/TimeAxisRenderer.ts b/packages/calendar/src/features/timeaxis/TimeAxisRenderer.ts new file mode 100644 index 0000000..80279be --- /dev/null +++ b/packages/calendar/src/features/timeaxis/TimeAxisRenderer.ts @@ -0,0 +1,10 @@ +export class TimeAxisRenderer { + render(container: HTMLElement, startHour = 6, endHour = 20): void { + container.innerHTML = ''; + for (let hour = startHour; hour <= endHour; hour++) { + const marker = document.createElement('swp-hour-marker'); + marker.textContent = `${hour.toString().padStart(2, '0')}:00`; + container.appendChild(marker); + } + } +} diff --git a/packages/calendar/src/index.ts b/packages/calendar/src/index.ts new file mode 100644 index 0000000..a0bee9c --- /dev/null +++ b/packages/calendar/src/index.ts @@ -0,0 +1,164 @@ +// === CORE === + +// App +export { CalendarApp } from './core/CalendarApp'; +export { CalendarOrchestrator } from './core/CalendarOrchestrator'; +export { CalendarEvents } from './core/CalendarEvents'; +export type { + RenderPayload, + WorkweekChangePayload, + ViewUpdatePayload +} from './core/CalendarEvents'; + +// Infrastructure +export { EventBus } from './core/EventBus'; +export { DateService } from './core/DateService'; +export { ViewTemplate, ViewConfig, GroupingConfig } from './core/ViewConfig'; +export { IRenderer, IRenderContext } from './core/IGroupingRenderer'; +export { IGroupingStore } from './core/IGroupingStore'; +export { BaseGroupingRenderer, IGroupingRendererConfig } from './core/BaseGroupingRenderer'; +export { buildPipeline, Pipeline } from './core/RenderBuilder'; +export { NavigationAnimator } from './core/NavigationAnimator'; +export { ScrollManager } from './core/ScrollManager'; +export { HeaderDrawerManager } from './core/HeaderDrawerManager'; +export { FilterTemplate } from './core/FilterTemplate'; +export { EntityResolver } from './core/EntityResolver'; +export type { IEntityResolver } from './core/IEntityResolver'; + +// Configuration interfaces +export type { ITimeFormatConfig } from './core/ITimeFormatConfig'; +export type { IGridConfig } from './core/IGridConfig'; + +// Core Features +export { DateRenderer } from './features/date'; +export { ResourceRenderer } from './features/resource'; +export { EventRenderer } from './features/event'; +export { eventsOverlap, calculateColumnLayout } from './features/event/EventLayoutEngine'; +export type { + IStackLink, + IStackedEventLayout, + IGridGroupLayout, + IColumnLayout +} from './features/event/EventLayoutTypes'; +export { TimeAxisRenderer } from './features/timeaxis/TimeAxisRenderer'; +export { HeaderDrawerRenderer, HeaderDrawerLayoutEngine } from './features/headerdrawer'; +export { ScheduleRenderer } from './features/schedule'; + +// Core Storage +export { IndexedDBContext, defaultDBConfig } from './storage/IndexedDBContext'; +export type { IDBConfig } from './storage/IndexedDBContext'; +export { BaseEntityService } from './storage/BaseEntityService'; +export type { IEntityService } from './storage/IEntityService'; +export type { IStore } from './storage/IStore'; +export { SyncPlugin } from './storage/SyncPlugin'; + +// Event storage +export { EventService } from './storage/events/EventService'; +export { EventStore } from './storage/events/EventStore'; +export { EventSerialization } from './storage/events/EventSerialization'; + +// Resource storage +export { ResourceService } from './storage/resources/ResourceService'; +export { ResourceStore } from './storage/resources/ResourceStore'; + +// Settings storage +export { SettingsService } from './storage/settings/SettingsService'; +export { SettingsStore } from './storage/settings/SettingsStore'; + +// ViewConfig storage +export { ViewConfigService } from './storage/viewconfigs/ViewConfigService'; +export { ViewConfigStore } from './storage/viewconfigs/ViewConfigStore'; + +// Core Managers +export { DragDropManager } from './managers/DragDropManager'; +export { EdgeScrollManager } from './managers/EdgeScrollManager'; +export { ResizeManager } from './managers/ResizeManager'; +export { EventPersistenceManager } from './managers/EventPersistenceManager'; + +// Position utilities +export { + calculateEventPosition, + minutesToPixels, + pixelsToMinutes, + snapToGrid +} from './utils/PositionUtils'; +export type { EventPosition } from './utils/PositionUtils'; + +// Types +export type { + ICalendarEvent, + IResource, + IEventBus, + ISync, + SyncStatus, + EntityType, + CalendarEventType, + ResourceType, + ITeam, + IDepartment, + IBooking, + BookingStatus, + IBookingService, + ICustomer, + IDataEntity, + IEventLogEntry, + IListenerEntry, + IEntitySavedPayload, + IEntityDeletedPayload, + IEventUpdatedPayload +} from './types/CalendarTypes'; + +export type { + TenantSetting, + IGridSettings, + IWorkweekPreset +} from './types/SettingsTypes'; + +export type { + IScheduleOverride, + ITimeSlot, + IWeekSchedule, + WeekDay +} from './types/ScheduleTypes'; + +// Drag types +export type { + IMousePosition, + IDragStartPayload, + IDragMovePayload, + IDragEndPayload, + IDragCancelPayload, + IDragColumnChangePayload, + IDragEnterHeaderPayload, + IDragMoveHeaderPayload, + IDragLeaveHeaderPayload +} from './types/DragTypes'; + +// Resize types +export type { + IResizeStartPayload, + IResizeEndPayload +} from './types/ResizeTypes'; + +// Audit types +export type { + IAuditEntry, + IAuditLoggedPayload +} from './types/AuditTypes'; + +export { SwpEvent } from './types/SwpEvent'; + +// Core Events constants +export { CoreEvents } from './constants/CoreEvents'; + +// Repository interface (for custom implementations) +export type { IApiRepository } from './repositories/IApiRepository'; + +// DI helpers +export { + createCalendarContainer, + registerCoreServices, + defaultTimeFormatConfig, + defaultGridConfig +} from './CompositionRoot'; +export type { ICalendarOptions } from './CompositionRoot'; diff --git a/packages/calendar/src/managers/DragDropManager.ts b/packages/calendar/src/managers/DragDropManager.ts new file mode 100644 index 0000000..4bf50b9 --- /dev/null +++ b/packages/calendar/src/managers/DragDropManager.ts @@ -0,0 +1,581 @@ +import { IEventBus } from '../types/CalendarTypes'; +import { IGridConfig } from '../core/IGridConfig'; +import { CoreEvents } from '../constants/CoreEvents'; +import { snapToGrid } from '../utils/PositionUtils'; +import { + IMousePosition, + IDragStartPayload, + IDragMovePayload, + IDragEndPayload, + IDragCancelPayload, + IDragColumnChangePayload, + IDragEnterHeaderPayload, + IDragMoveHeaderPayload, + IDragLeaveHeaderPayload +} from '../types/DragTypes'; +import { SwpEvent } from '../types/SwpEvent'; + +interface DragState { + eventId: string; + element: HTMLElement; + ghostElement: HTMLElement | null; // Null for header items + startY: number; + mouseOffset: IMousePosition; + columnElement: HTMLElement | null; // Null when starting from header + currentColumn: HTMLElement | null; // Null when in header + targetY: number; + currentY: number; + animationId: number; + sourceColumnKey: string; // Source column key (where drag started) + dragSource: 'grid' | 'header'; // Where drag originated +} + +/** + * DragDropManager - Handles drag-drop for calendar events + * + * Strategy: Drag original element, leave ghost-clone in place + * - mousedown: Store initial state, wait for movement + * - mousemove (>5px): Create ghost, start dragging original + * - mouseup: Snap to grid, remove ghost, emit drag:end + * - cancel: Animate back to startY, remove ghost + */ +export class DragDropManager { + private dragState: DragState | null = null; + private mouseDownPosition: IMousePosition | null = null; + private pendingElement: HTMLElement | null = null; + private pendingMouseOffset: IMousePosition | null = null; + private container: HTMLElement | null = null; + private inHeader = false; + + private readonly DRAG_THRESHOLD = 5; + private readonly INTERPOLATION_FACTOR = 0.3; + + constructor( + private eventBus: IEventBus, + private gridConfig: IGridConfig + ) { + this.setupScrollListener(); + } + + private setupScrollListener(): void { + this.eventBus.on(CoreEvents.EDGE_SCROLL_TICK, (e) => { + if (!this.dragState) return; + const { scrollDelta } = (e as CustomEvent<{ scrollDelta: number }>).detail; + + // Element skal flytte med scroll for at forblive under musen + // (elementets top er relativ til kolonnen, som scroller med viewport) + this.dragState.targetY += scrollDelta; + this.dragState.currentY += scrollDelta; + this.dragState.element.style.top = `${this.dragState.currentY}px`; + }); + } + + /** + * Initialize drag-drop on a container element + */ + init(container: HTMLElement): void { + this.container = container; + container.addEventListener('pointerdown', this.handlePointerDown); + document.addEventListener('pointermove', this.handlePointerMove); + document.addEventListener('pointerup', this.handlePointerUp); + } + + private handlePointerDown = (e: PointerEvent): void => { + const target = e.target as HTMLElement; + + // Ignore if clicking on resize handle + if (target.closest('swp-resize-handle')) return; + + // Match both swp-event and swp-header-item + const eventElement = target.closest('swp-event') as HTMLElement; + const headerItem = target.closest('swp-header-item') as HTMLElement; + const draggable = eventElement || headerItem; + + if (!draggable) return; + + // Store for potential drag + this.mouseDownPosition = { x: e.clientX, y: e.clientY }; + this.pendingElement = draggable; + + // Calculate mouse offset within element + const rect = draggable.getBoundingClientRect(); + this.pendingMouseOffset = { + x: e.clientX - rect.left, + y: e.clientY - rect.top + }; + + // Capture pointer for reliable tracking + draggable.setPointerCapture(e.pointerId); + }; + + private handlePointerMove = (e: PointerEvent): void => { + // Not in potential drag state + if (!this.mouseDownPosition || !this.pendingElement) { + // Already dragging - update target + if (this.dragState) { + this.updateDragTarget(e); + } + return; + } + + // Check threshold + const deltaX = Math.abs(e.clientX - this.mouseDownPosition.x); + const deltaY = Math.abs(e.clientY - this.mouseDownPosition.y); + const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + + if (distance < this.DRAG_THRESHOLD) return; + + // Start drag + this.initializeDrag(this.pendingElement, this.pendingMouseOffset!, e); + this.mouseDownPosition = null; + this.pendingElement = null; + this.pendingMouseOffset = null; + }; + + private handlePointerUp = (_e: PointerEvent): void => { + // Clear pending state + this.mouseDownPosition = null; + this.pendingElement = null; + this.pendingMouseOffset = null; + + if (!this.dragState) return; + + // Stop animation + cancelAnimationFrame(this.dragState.animationId); + + // Handle based on drag source and target + if (this.dragState.dragSource === 'header') { + // Header item drag end + this.handleHeaderItemDragEnd(); + } else { + // Grid event drag end + this.handleGridEventDragEnd(); + } + + // Cleanup + this.dragState.element.classList.remove('dragging'); + this.dragState = null; + this.inHeader = false; + }; + + /** + * Handle drag end for header items + */ + private handleHeaderItemDragEnd(): void { + if (!this.dragState) return; + + // If dropped in grid (not in header), the swp-event was already created + // by EventRenderer listening to EVENT_DRAG_LEAVE_HEADER + // Just emit drag:end for persistence + + if (!this.inHeader && this.dragState.currentColumn) { + // Dropped in grid - emit drag:end with the new swp-event element + const gridEvent = this.dragState.currentColumn.querySelector( + `swp-event[data-event-id="${this.dragState.eventId}"]` + ) as HTMLElement; + + if (gridEvent) { + const columnKey = this.dragState.currentColumn.dataset.columnKey || ''; + const date = this.dragState.currentColumn.dataset.date || ''; + const swpEvent = SwpEvent.fromElement(gridEvent, columnKey, date, this.gridConfig); + + const payload: IDragEndPayload = { + swpEvent, + sourceColumnKey: this.dragState.sourceColumnKey, + target: 'grid' + }; + + this.eventBus.emit(CoreEvents.EVENT_DRAG_END, payload); + } + } + // If still in header, no persistence needed (stayed in header) + } + + /** + * Handle drag end for grid events + */ + private handleGridEventDragEnd(): void { + if (!this.dragState || !this.dragState.columnElement) return; + + // Snap to grid + const snappedY = snapToGrid(this.dragState.currentY, this.gridConfig); + this.dragState.element.style.top = `${snappedY}px`; + + // Remove ghost + this.dragState.ghostElement?.remove(); + + // Get columnKey and date from target column + const columnKey = this.dragState.columnElement.dataset.columnKey || ''; + const date = this.dragState.columnElement.dataset.date || ''; + + // Create SwpEvent from element (reads top/height/eventId from element) + const swpEvent = SwpEvent.fromElement( + this.dragState.element, + columnKey, + date, + this.gridConfig + ); + + // Emit drag:end + const payload: IDragEndPayload = { + swpEvent, + sourceColumnKey: this.dragState.sourceColumnKey, + target: this.inHeader ? 'header' : 'grid' + }; + + this.eventBus.emit(CoreEvents.EVENT_DRAG_END, payload); + } + + private initializeDrag(element: HTMLElement, mouseOffset: IMousePosition, e: PointerEvent): void { + const eventId = element.dataset.eventId || ''; + const isHeaderItem = element.tagName.toLowerCase() === 'swp-header-item'; + const columnElement = element.closest('swp-day-column') as HTMLElement; + + // For grid events, we need a column + if (!isHeaderItem && !columnElement) return; + + if (isHeaderItem) { + // Header item drag initialization + this.initializeHeaderItemDrag(element, mouseOffset, eventId); + } else { + // Grid event drag initialization + this.initializeGridEventDrag(element, mouseOffset, e, columnElement, eventId); + } + } + + /** + * Initialize drag for a header item (allDay event) + */ + private initializeHeaderItemDrag(element: HTMLElement, mouseOffset: IMousePosition, eventId: string): void { + // Mark as dragging + element.classList.add('dragging'); + + // Initialize drag state for header item + this.dragState = { + eventId, + element, + ghostElement: null, // No ghost for header items + startY: 0, + mouseOffset, + columnElement: null, + currentColumn: null, + targetY: 0, + currentY: 0, + animationId: 0, + sourceColumnKey: '', // Will be set from header item data + dragSource: 'header' + }; + + // Start in header mode + this.inHeader = true; + } + + /** + * Initialize drag for a grid event + */ + private initializeGridEventDrag(element: HTMLElement, mouseOffset: IMousePosition, e: PointerEvent, columnElement: HTMLElement, eventId: string): void { + // Calculate absolute Y position using getBoundingClientRect + const elementRect = element.getBoundingClientRect(); + const columnRect = columnElement.getBoundingClientRect(); + const startY = elementRect.top - columnRect.top; + + // If event is inside a group, move it to events-layer for correct positioning during drag + const group = element.closest('swp-event-group'); + if (group) { + const eventsLayer = columnElement.querySelector('swp-events-layer'); + if (eventsLayer) { + eventsLayer.appendChild(element); + } + } + + // Set consistent positioning for drag (works for both grouped and stacked events) + element.style.position = 'absolute'; + element.style.top = `${startY}px`; + element.style.left = '2px'; + element.style.right = '2px'; + element.style.marginLeft = '0'; // Reset stacking margin + + // Create ghost clone + const ghostElement = element.cloneNode(true) as HTMLElement; + ghostElement.classList.add('drag-ghost'); + ghostElement.style.opacity = '0.3'; + ghostElement.style.pointerEvents = 'none'; + + // Insert ghost before original + element.parentNode?.insertBefore(ghostElement, element); + + // Setup element for dragging + element.classList.add('dragging'); + + // Calculate initial target from mouse position + const targetY = e.clientY - columnRect.top - mouseOffset.y; + + // Initialize drag state + this.dragState = { + eventId, + element, + ghostElement, + startY, + mouseOffset, + columnElement, + currentColumn: columnElement, + targetY: Math.max(0, targetY), + currentY: startY, + animationId: 0, + sourceColumnKey: columnElement.dataset.columnKey || '', + dragSource: 'grid' + }; + + // Emit drag:start + const payload: IDragStartPayload = { + eventId, + element, + ghostElement, + startY, + mouseOffset, + columnElement + }; + + this.eventBus.emit(CoreEvents.EVENT_DRAG_START, payload); + + // Start animation loop + this.animateDrag(); + } + + private updateDragTarget(e: PointerEvent): void { + if (!this.dragState) return; + + // Check header zone first + this.checkHeaderZone(e); + + // Skip normal grid handling if in header + if (this.inHeader) return; + + // Check for column change + const columnAtPoint = this.getColumnAtPoint(e.clientX); + + // For header items entering grid, set initial column + if (this.dragState.dragSource === 'header' && columnAtPoint && !this.dragState.currentColumn) { + this.dragState.currentColumn = columnAtPoint; + this.dragState.columnElement = columnAtPoint; + } + + if (columnAtPoint && columnAtPoint !== this.dragState.currentColumn && this.dragState.currentColumn) { + const payload: IDragColumnChangePayload = { + eventId: this.dragState.eventId, + element: this.dragState.element, + previousColumn: this.dragState.currentColumn, + newColumn: columnAtPoint, + currentY: this.dragState.currentY + }; + + this.eventBus.emit(CoreEvents.EVENT_DRAG_COLUMN_CHANGE, payload); + this.dragState.currentColumn = columnAtPoint; + this.dragState.columnElement = columnAtPoint; + } + + // Skip grid position updates if no column yet + if (!this.dragState.columnElement) return; + + const columnRect = this.dragState.columnElement.getBoundingClientRect(); + const targetY = e.clientY - columnRect.top - this.dragState.mouseOffset.y; + + this.dragState.targetY = Math.max(0, targetY); + + // Start animation if not running + if (!this.dragState.animationId) { + this.animateDrag(); + } + } + + /** + * Check if pointer is in header zone and emit appropriate events + */ + private checkHeaderZone(e: PointerEvent): void { + if (!this.dragState) return; + + const headerViewport = document.querySelector('swp-header-viewport'); + if (!headerViewport) return; + + const rect = headerViewport.getBoundingClientRect(); + const isInHeader = e.clientY < rect.bottom; + + if (isInHeader && !this.inHeader) { + // Entered header (from grid) + this.inHeader = true; + + if (this.dragState.dragSource === 'grid' && this.dragState.columnElement) { + const payload: IDragEnterHeaderPayload = { + eventId: this.dragState.eventId, + element: this.dragState.element, + sourceColumnIndex: this.getColumnIndex(this.dragState.columnElement), + sourceColumnKey: this.dragState.columnElement.dataset.columnKey || '', + title: this.dragState.element.querySelector('swp-event-title')?.textContent || '', + colorClass: [...this.dragState.element.classList].find(c => c.startsWith('is-')), + itemType: 'event', + duration: 1 + }; + + this.eventBus.emit(CoreEvents.EVENT_DRAG_ENTER_HEADER, payload); + } + // For header source re-entering header, just update inHeader flag + } else if (!isInHeader && this.inHeader) { + // Left header (entering grid) + this.inHeader = false; + + const targetColumn = this.getColumnAtPoint(e.clientX); + + if (this.dragState.dragSource === 'header') { + // Header item leaving header → create swp-event in grid + const payload: IDragLeaveHeaderPayload = { + eventId: this.dragState.eventId, + source: 'header', + element: this.dragState.element, + targetColumn: targetColumn || undefined, + start: this.dragState.element.dataset.start ? new Date(this.dragState.element.dataset.start) : undefined, + end: this.dragState.element.dataset.end ? new Date(this.dragState.element.dataset.end) : undefined, + title: this.dragState.element.textContent || '', + colorClass: [...this.dragState.element.classList].find(c => c.startsWith('is-')) + }; + + this.eventBus.emit(CoreEvents.EVENT_DRAG_LEAVE_HEADER, payload); + + // Re-attach to the new swp-event created by EventRenderer + if (targetColumn) { + const newElement = targetColumn.querySelector( + `swp-event[data-event-id="${this.dragState.eventId}"]` + ) as HTMLElement; + + if (newElement) { + this.dragState.element = newElement; + this.dragState.columnElement = targetColumn; + this.dragState.currentColumn = targetColumn; + + // Start animation for the new element + this.animateDrag(); + } + } + } else { + // Grid event leaving header → restore to grid + const payload: IDragLeaveHeaderPayload = { + eventId: this.dragState.eventId, + source: 'grid' + }; + + this.eventBus.emit(CoreEvents.EVENT_DRAG_LEAVE_HEADER, payload); + } + } else if (isInHeader) { + // Moving within header + const column = this.getColumnAtX(e.clientX); + if (column) { + const payload: IDragMoveHeaderPayload = { + eventId: this.dragState.eventId, + columnIndex: this.getColumnIndex(column), + columnKey: column.dataset.columnKey || '' + }; + + this.eventBus.emit(CoreEvents.EVENT_DRAG_MOVE_HEADER, payload); + } + } + } + + /** + * Get column index (0-based) for a column element + */ + private getColumnIndex(column: HTMLElement | null): number { + if (!this.container || !column) return 0; + const columns = Array.from(this.container.querySelectorAll('swp-day-column')); + return columns.indexOf(column); + } + + /** + * Get column at X coordinate (alias for getColumnAtPoint) + */ + private getColumnAtX(clientX: number): HTMLElement | null { + return this.getColumnAtPoint(clientX); + } + + /** + * Find column element at given X coordinate + */ + private getColumnAtPoint(clientX: number): HTMLElement | null { + if (!this.container) return null; + + const columns = this.container.querySelectorAll('swp-day-column'); + for (const col of columns) { + const rect = col.getBoundingClientRect(); + if (clientX >= rect.left && clientX <= rect.right) { + return col as HTMLElement; + } + } + return null; + } + + private animateDrag = (): void => { + if (!this.dragState) return; + + const diff = this.dragState.targetY - this.dragState.currentY; + + // Stop animation when close enough to target + if (Math.abs(diff) <= 0.5) { + this.dragState.animationId = 0; + return; + } + + // Interpolate towards target + this.dragState.currentY += diff * this.INTERPOLATION_FACTOR; + + // Update element position + this.dragState.element.style.top = `${this.dragState.currentY}px`; + + // Emit drag:move (only if we have a column) + if (this.dragState.columnElement) { + const payload: IDragMovePayload = { + eventId: this.dragState.eventId, + element: this.dragState.element, + currentY: this.dragState.currentY, + columnElement: this.dragState.columnElement + }; + + this.eventBus.emit(CoreEvents.EVENT_DRAG_MOVE, payload); + } + + // Continue animation + this.dragState.animationId = requestAnimationFrame(this.animateDrag); + }; + + /** + * Cancel drag and animate back to start position + */ + cancelDrag(): void { + if (!this.dragState) return; + + // Stop animation + cancelAnimationFrame(this.dragState.animationId); + + const { element, ghostElement, startY, eventId } = this.dragState; + + // Animate back to start + element.style.transition = 'top 200ms ease-out'; + element.style.top = `${startY}px`; + + // Remove ghost after animation (if exists) + setTimeout(() => { + ghostElement?.remove(); + element.style.transition = ''; + element.classList.remove('dragging'); + }, 200); + + // Emit drag:cancel + const payload: IDragCancelPayload = { + eventId, + element, + startY + }; + + this.eventBus.emit(CoreEvents.EVENT_DRAG_CANCEL, payload); + + this.dragState = null; + this.inHeader = false; + } +} diff --git a/packages/calendar/src/managers/EdgeScrollManager.ts b/packages/calendar/src/managers/EdgeScrollManager.ts new file mode 100644 index 0000000..d1b5584 --- /dev/null +++ b/packages/calendar/src/managers/EdgeScrollManager.ts @@ -0,0 +1,140 @@ +/** + * EdgeScrollManager - Auto-scroll when dragging near viewport edges + * + * 2-zone system: + * - Inner zone (0-50px): Fast scroll (640 px/sec) + * - Outer zone (50-100px): Slow scroll (140 px/sec) + */ + +import { IEventBus } from '../types/CalendarTypes'; +import { CoreEvents } from '../constants/CoreEvents'; + +export class EdgeScrollManager { + private scrollableContent: HTMLElement | null = null; + private timeGrid: HTMLElement | null = null; + private draggedElement: HTMLElement | null = null; + private scrollRAF: number | null = null; + private mouseY = 0; + private isDragging = false; + private isScrolling = false; + private lastTs = 0; + private rect: DOMRect | null = null; + private initialScrollTop = 0; + + private readonly OUTER_ZONE = 100; + private readonly INNER_ZONE = 50; + private readonly SLOW_SPEED = 140; + private readonly FAST_SPEED = 640; + + constructor(private eventBus: IEventBus) { + this.subscribeToEvents(); + document.addEventListener('pointermove', this.trackMouse); + } + + init(scrollableContent: HTMLElement): void { + this.scrollableContent = scrollableContent; + this.timeGrid = scrollableContent.querySelector('swp-time-grid'); + this.scrollableContent.style.scrollBehavior = 'auto'; + } + + private trackMouse = (e: PointerEvent): void => { + if (this.isDragging) { + this.mouseY = e.clientY; + } + }; + + private subscribeToEvents(): void { + this.eventBus.on(CoreEvents.EVENT_DRAG_START, (event: Event) => { + const payload = (event as CustomEvent).detail; + this.draggedElement = payload.element; + this.startDrag(); + }); + + this.eventBus.on(CoreEvents.EVENT_DRAG_END, () => this.stopDrag()); + this.eventBus.on(CoreEvents.EVENT_DRAG_CANCEL, () => this.stopDrag()); + } + + private startDrag(): void { + this.isDragging = true; + this.isScrolling = false; + this.lastTs = 0; + this.initialScrollTop = this.scrollableContent?.scrollTop ?? 0; + + if (this.scrollRAF === null) { + this.scrollRAF = requestAnimationFrame(this.scrollTick); + } + } + + private stopDrag(): void { + this.isDragging = false; + this.setScrollingState(false); + + if (this.scrollRAF !== null) { + cancelAnimationFrame(this.scrollRAF); + this.scrollRAF = null; + } + + this.rect = null; + this.lastTs = 0; + this.initialScrollTop = 0; + } + + private calculateVelocity(): number { + if (!this.rect) return 0; + + const distTop = this.mouseY - this.rect.top; + const distBot = this.rect.bottom - this.mouseY; + + if (distTop < this.INNER_ZONE) return -this.FAST_SPEED; + if (distTop < this.OUTER_ZONE) return -this.SLOW_SPEED; + if (distBot < this.INNER_ZONE) return this.FAST_SPEED; + if (distBot < this.OUTER_ZONE) return this.SLOW_SPEED; + + return 0; + } + + private isAtBoundary(velocity: number): boolean { + if (!this.scrollableContent || !this.timeGrid || !this.draggedElement) return false; + + const atTop = this.scrollableContent.scrollTop <= 0 && velocity < 0; + const atBottom = velocity > 0 && + this.draggedElement.getBoundingClientRect().bottom >= + this.timeGrid.getBoundingClientRect().bottom; + + return atTop || atBottom; + } + + private setScrollingState(scrolling: boolean): void { + if (this.isScrolling === scrolling) return; + + this.isScrolling = scrolling; + if (scrolling) { + this.eventBus.emit(CoreEvents.EDGE_SCROLL_STARTED, {}); + } else { + this.initialScrollTop = this.scrollableContent?.scrollTop ?? 0; + this.eventBus.emit(CoreEvents.EDGE_SCROLL_STOPPED, {}); + } + } + + private scrollTick = (ts: number): void => { + if (!this.isDragging || !this.scrollableContent) return; + + const dt = this.lastTs ? (ts - this.lastTs) / 1000 : 0; + this.lastTs = ts; + this.rect ??= this.scrollableContent.getBoundingClientRect(); + + const velocity = this.calculateVelocity(); + + if (velocity !== 0 && !this.isAtBoundary(velocity)) { + const scrollDelta = velocity * dt; + this.scrollableContent.scrollTop += scrollDelta; + this.rect = null; + this.eventBus.emit(CoreEvents.EDGE_SCROLL_TICK, { scrollDelta }); + this.setScrollingState(true); + } else { + this.setScrollingState(false); + } + + this.scrollRAF = requestAnimationFrame(this.scrollTick); + }; +} diff --git a/packages/calendar/src/managers/EventPersistenceManager.ts b/packages/calendar/src/managers/EventPersistenceManager.ts new file mode 100644 index 0000000..ae59df9 --- /dev/null +++ b/packages/calendar/src/managers/EventPersistenceManager.ts @@ -0,0 +1,102 @@ +/** + * EventPersistenceManager - Persists event changes to IndexedDB + * + * Listens to drag/resize events and updates IndexedDB via EventService. + * This bridges the gap between UI interactions and data persistence. + */ + +import { ICalendarEvent, IEventBus, IEventUpdatedPayload } from '../types/CalendarTypes'; +import { EventService } from '../storage/events/EventService'; +import { DateService } from '../core/DateService'; +import { CoreEvents } from '../constants/CoreEvents'; +import { IDragEndPayload } from '../types/DragTypes'; +import { IResizeEndPayload } from '../types/ResizeTypes'; + +export class EventPersistenceManager { + constructor( + private eventService: EventService, + private eventBus: IEventBus, + private dateService: DateService + ) { + this.setupListeners(); + } + + private setupListeners(): void { + this.eventBus.on(CoreEvents.EVENT_DRAG_END, this.handleDragEnd); + this.eventBus.on(CoreEvents.EVENT_RESIZE_END, this.handleResizeEnd); + } + + /** + * Handle drag end - update event position in IndexedDB + */ + private handleDragEnd = async (e: Event): Promise => { + const payload = (e as CustomEvent).detail; + const { swpEvent } = payload; + + // Get existing event to merge with + const event = await this.eventService.get(swpEvent.eventId); + if (!event) { + console.warn(`EventPersistenceManager: Event ${swpEvent.eventId} not found`); + return; + } + + // Parse resourceId from columnKey if present + const { resource } = this.dateService.parseColumnKey(swpEvent.columnKey); + + // Update and save - start/end already calculated in SwpEvent + // Set allDay based on drop target: + // - header: allDay = true + // - grid: allDay = false (converts allDay event to timed) + const updatedEvent: ICalendarEvent = { + ...event, + start: swpEvent.start, + end: swpEvent.end, + resourceId: resource ?? event.resourceId, + allDay: payload.target === 'header', + syncStatus: 'pending' + }; + + await this.eventService.save(updatedEvent); + + // Emit EVENT_UPDATED for EventRenderer to re-render affected columns + const updatePayload: IEventUpdatedPayload = { + eventId: updatedEvent.id, + sourceColumnKey: payload.sourceColumnKey, + targetColumnKey: swpEvent.columnKey + }; + this.eventBus.emit(CoreEvents.EVENT_UPDATED, updatePayload); + }; + + /** + * Handle resize end - update event duration in IndexedDB + */ + private handleResizeEnd = async (e: Event): Promise => { + const payload = (e as CustomEvent).detail; + const { swpEvent } = payload; + + // Get existing event to merge with + const event = await this.eventService.get(swpEvent.eventId); + if (!event) { + console.warn(`EventPersistenceManager: Event ${swpEvent.eventId} not found`); + return; + } + + // Update and save - end already calculated in SwpEvent + const updatedEvent: ICalendarEvent = { + ...event, + end: swpEvent.end, + syncStatus: 'pending' + }; + + await this.eventService.save(updatedEvent); + + // Emit EVENT_UPDATED for EventRenderer to re-render the column + // Resize stays in same column, so source and target are the same + const updatePayload: IEventUpdatedPayload = { + eventId: updatedEvent.id, + sourceColumnKey: swpEvent.columnKey, + targetColumnKey: swpEvent.columnKey + }; + this.eventBus.emit(CoreEvents.EVENT_UPDATED, updatePayload); + }; +} diff --git a/packages/calendar/src/managers/ResizeManager.ts b/packages/calendar/src/managers/ResizeManager.ts new file mode 100644 index 0000000..5448def --- /dev/null +++ b/packages/calendar/src/managers/ResizeManager.ts @@ -0,0 +1,290 @@ +import { IEventBus } from '../types/CalendarTypes'; +import { IGridConfig } from '../core/IGridConfig'; +import { pixelsToMinutes, minutesToPixels, snapToGrid } from '../utils/PositionUtils'; +import { DateService } from '../core/DateService'; +import { CoreEvents } from '../constants/CoreEvents'; +import { IResizeStartPayload, IResizeEndPayload } from '../types/ResizeTypes'; +import { SwpEvent } from '../types/SwpEvent'; + +/** + * ResizeManager - Handles resize of calendar events + * + * Step 1: Handle creation on mouseover (CSS handles visibility) + * Step 2: Pointer events + resize start + * Step 3: RAF animation for smooth height update + * Step 4: Grid snapping + timestamp update + */ + +interface ResizeState { + eventId: string; + element: HTMLElement; + handleElement: HTMLElement; + startY: number; + startHeight: number; + startDurationMinutes: number; + pointerId: number; + prevZIndex: string; + // Animation state + currentHeight: number; + targetHeight: number; + animationId: number | null; +} + +export class ResizeManager { + private container: HTMLElement | null = null; + private resizeState: ResizeState | null = null; + + private readonly Z_INDEX_RESIZING = '1000'; + private readonly ANIMATION_SPEED = 0.35; + private readonly MIN_HEIGHT_MINUTES = 15; + + constructor( + private eventBus: IEventBus, + private gridConfig: IGridConfig, + private dateService: DateService + ) {} + + /** + * Initialize resize functionality on container + */ + init(container: HTMLElement): void { + this.container = container; + + // Mouseover listener for handle creation (capture phase like V1) + container.addEventListener('mouseover', this.handleMouseOver, true); + + // Pointer listeners for resize (capture phase like V1) + document.addEventListener('pointerdown', this.handlePointerDown, true); + document.addEventListener('pointermove', this.handlePointerMove, true); + document.addEventListener('pointerup', this.handlePointerUp, true); + } + + /** + * Create resize handle element + */ + private createResizeHandle(): HTMLElement { + const handle = document.createElement('swp-resize-handle'); + handle.setAttribute('aria-label', 'Resize event'); + handle.setAttribute('role', 'separator'); + return handle; + } + + /** + * Handle mouseover - create resize handle if not exists + */ + private handleMouseOver = (e: Event): void => { + const target = e.target as HTMLElement; + const eventElement = target.closest('swp-event') as HTMLElement; + + if (!eventElement || this.resizeState) return; + + // Check if handle already exists + if (!eventElement.querySelector(':scope > swp-resize-handle')) { + const handle = this.createResizeHandle(); + eventElement.appendChild(handle); + } + }; + + /** + * Handle pointerdown - start resize if on handle + */ + private handlePointerDown = (e: PointerEvent): void => { + const handle = (e.target as HTMLElement).closest('swp-resize-handle') as HTMLElement; + if (!handle) return; + + const element = handle.parentElement as HTMLElement; + if (!element) return; + + const eventId = element.dataset.eventId || ''; + const startHeight = element.offsetHeight; + const startDurationMinutes = pixelsToMinutes(startHeight, this.gridConfig); + + // Store previous z-index + const container = element.closest('swp-event-group') as HTMLElement ?? element; + const prevZIndex = container.style.zIndex; + + // Set resize state + this.resizeState = { + eventId, + element, + handleElement: handle, + startY: e.clientY, + startHeight, + startDurationMinutes, + pointerId: e.pointerId, + prevZIndex, + // Animation state + currentHeight: startHeight, + targetHeight: startHeight, + animationId: null + }; + + // Elevate z-index + container.style.zIndex = this.Z_INDEX_RESIZING; + + // Capture pointer for smooth tracking + try { + handle.setPointerCapture(e.pointerId); + } catch (err) { + console.warn('Pointer capture failed:', err); + } + + // Add global resizing class + document.documentElement.classList.add('swp--resizing'); + + // Emit resize start event + this.eventBus.emit(CoreEvents.EVENT_RESIZE_START, { + eventId, + element, + startHeight + } as IResizeStartPayload); + + e.preventDefault(); + }; + + /** + * Handle pointermove - update target height during resize + */ + private handlePointerMove = (e: PointerEvent): void => { + if (!this.resizeState) return; + + const deltaY = e.clientY - this.resizeState.startY; + const minHeight = (this.MIN_HEIGHT_MINUTES / 60) * this.gridConfig.hourHeight; + const newHeight = Math.max(minHeight, this.resizeState.startHeight + deltaY); + + // Set target height for animation + this.resizeState.targetHeight = newHeight; + + // Start animation if not running + if (this.resizeState.animationId === null) { + this.animateHeight(); + } + }; + + /** + * RAF animation loop for smooth height interpolation + */ + private animateHeight = (): void => { + if (!this.resizeState) return; + + const diff = this.resizeState.targetHeight - this.resizeState.currentHeight; + + // Stop animation when close enough + if (Math.abs(diff) < 0.5) { + this.resizeState.animationId = null; + return; + } + + // Interpolate towards target (35% per frame like V1) + this.resizeState.currentHeight += diff * this.ANIMATION_SPEED; + this.resizeState.element.style.height = `${this.resizeState.currentHeight}px`; + + // Update timestamp display (snapped) + this.updateTimestampDisplay(); + + // Continue animation + this.resizeState.animationId = requestAnimationFrame(this.animateHeight); + }; + + /** + * Update timestamp display with snapped end time + */ + private updateTimestampDisplay(): void { + if (!this.resizeState) return; + + const timeEl = this.resizeState.element.querySelector('swp-event-time'); + if (!timeEl) return; + + // Get start time from element position + const top = parseFloat(this.resizeState.element.style.top) || 0; + const startMinutesFromGrid = pixelsToMinutes(top, this.gridConfig); + const startMinutes = (this.gridConfig.dayStartHour * 60) + startMinutesFromGrid; + + // Calculate snapped end time from current height + const snappedHeight = snapToGrid(this.resizeState.currentHeight, this.gridConfig); + const durationMinutes = pixelsToMinutes(snappedHeight, this.gridConfig); + const endMinutes = startMinutes + durationMinutes; + + // Format and update + const start = this.minutesToDate(startMinutes); + const end = this.minutesToDate(endMinutes); + timeEl.textContent = this.dateService.formatTimeRange(start, end); + } + + /** + * Convert minutes since midnight to Date + */ + private minutesToDate(minutes: number): Date { + const date = new Date(); + date.setHours(Math.floor(minutes / 60) % 24, minutes % 60, 0, 0); + return date; + }; + + /** + * Handle pointerup - finish resize + */ + private handlePointerUp = (e: PointerEvent): void => { + if (!this.resizeState) return; + + // Cancel any pending animation + if (this.resizeState.animationId !== null) { + cancelAnimationFrame(this.resizeState.animationId); + } + + // Release pointer capture + try { + this.resizeState.handleElement.releasePointerCapture(e.pointerId); + } catch (err) { + console.warn('Pointer release failed:', err); + } + + // Snap final height to grid + this.snapToGridFinal(); + + // Update timestamp one final time + this.updateTimestampDisplay(); + + // Restore z-index + const container = this.resizeState.element.closest('swp-event-group') as HTMLElement ?? this.resizeState.element; + container.style.zIndex = this.resizeState.prevZIndex; + + // Remove global resizing class + document.documentElement.classList.remove('swp--resizing'); + + // Get columnKey and date from parent column + const column = this.resizeState.element.closest('swp-day-column') as HTMLElement; + const columnKey = column?.dataset.columnKey || ''; + const date = column?.dataset.date || ''; + + // Create SwpEvent from element (reads top/height/eventId from element) + const swpEvent = SwpEvent.fromElement( + this.resizeState.element, + columnKey, + date, + this.gridConfig + ); + + // Emit resize end event + this.eventBus.emit(CoreEvents.EVENT_RESIZE_END, { + swpEvent + } as IResizeEndPayload); + + // Reset state + this.resizeState = null; + }; + + /** + * Snap final height to grid interval + */ + private snapToGridFinal(): void { + if (!this.resizeState) return; + + const currentHeight = this.resizeState.element.offsetHeight; + const snappedHeight = snapToGrid(currentHeight, this.gridConfig); + const minHeight = minutesToPixels(this.MIN_HEIGHT_MINUTES, this.gridConfig); + const finalHeight = Math.max(minHeight, snappedHeight); + + this.resizeState.element.style.height = `${finalHeight}px`; + this.resizeState.currentHeight = finalHeight; + } +} diff --git a/packages/calendar/src/repositories/IApiRepository.ts b/packages/calendar/src/repositories/IApiRepository.ts new file mode 100644 index 0000000..a50791f --- /dev/null +++ b/packages/calendar/src/repositories/IApiRepository.ts @@ -0,0 +1,33 @@ +import { EntityType } from '../types/CalendarTypes'; + +/** + * IApiRepository - Generic interface for backend API communication + * + * Used by DataSeeder to fetch initial data and by SyncManager for sync operations. + */ +export interface IApiRepository { + /** + * Entity type discriminator - used for runtime routing + */ + readonly entityType: EntityType; + + /** + * Send create operation to backend API + */ + sendCreate(data: T): Promise; + + /** + * Send update operation to backend API + */ + sendUpdate(id: string, updates: Partial): Promise; + + /** + * Send delete operation to backend API + */ + sendDelete(id: string): Promise; + + /** + * Fetch all entities from backend API + */ + fetchAll(): Promise; +} diff --git a/packages/calendar/src/storage/BaseEntityService.ts b/packages/calendar/src/storage/BaseEntityService.ts new file mode 100644 index 0000000..ed8d3a1 --- /dev/null +++ b/packages/calendar/src/storage/BaseEntityService.ts @@ -0,0 +1,181 @@ +import { ISync, EntityType, SyncStatus, IEventBus, IEntitySavedPayload, IEntityDeletedPayload } from '../types/CalendarTypes'; +import { IEntityService } from './IEntityService'; +import { SyncPlugin } from './SyncPlugin'; +import { IndexedDBContext } from './IndexedDBContext'; +import { CoreEvents } from '../constants/CoreEvents'; +import { diff } from 'json-diff-ts'; + +/** + * BaseEntityService - Abstract base class for all entity services + * + * PROVIDES: + * - Generic CRUD operations (get, getAll, save, delete) + * - Sync status management (delegates to SyncPlugin) + * - Serialization hooks (override in subclass if needed) + */ +export abstract class BaseEntityService implements IEntityService { + abstract readonly storeName: string; + abstract readonly entityType: EntityType; + + private syncPlugin: SyncPlugin; + private context: IndexedDBContext; + protected eventBus: IEventBus; + + constructor(context: IndexedDBContext, eventBus: IEventBus) { + this.context = context; + this.eventBus = eventBus; + this.syncPlugin = new SyncPlugin(this); + } + + protected get db(): IDBDatabase { + return this.context.getDatabase(); + } + + /** + * Serialize entity before storing in IndexedDB + */ + protected serialize(entity: T): unknown { + return entity; + } + + /** + * Deserialize data from IndexedDB back to entity + */ + protected deserialize(data: unknown): T { + return data as T; + } + + /** + * Get a single entity by ID + */ + async get(id: string): Promise { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], 'readonly'); + const store = transaction.objectStore(this.storeName); + const request = store.get(id); + + request.onsuccess = () => { + const data = request.result; + resolve(data ? this.deserialize(data) : null); + }; + + request.onerror = () => { + reject(new Error(`Failed to get ${this.entityType} ${id}: ${request.error}`)); + }; + }); + } + + /** + * Get all entities + */ + async getAll(): Promise { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], 'readonly'); + const store = transaction.objectStore(this.storeName); + const request = store.getAll(); + + request.onsuccess = () => { + const data = request.result as unknown[]; + const entities = data.map(item => this.deserialize(item)); + resolve(entities); + }; + + request.onerror = () => { + reject(new Error(`Failed to get all ${this.entityType}s: ${request.error}`)); + }; + }); + } + + /** + * Save an entity (create or update) + * Emits ENTITY_SAVED event with operation type and changes (diff for updates) + * @param entity - Entity to save + * @param silent - If true, skip event emission (used for seeding) + */ + async save(entity: T, silent = false): Promise { + const entityId = (entity as unknown as { id: string }).id; + const existingEntity = await this.get(entityId); + const isCreate = existingEntity === null; + + // Calculate changes: full entity for create, diff for update + let changes: unknown; + if (isCreate) { + changes = entity; + } else { + const existingSerialized = this.serialize(existingEntity); + const newSerialized = this.serialize(entity); + changes = diff(existingSerialized, newSerialized); + } + + const serialized = this.serialize(entity); + + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], 'readwrite'); + const store = transaction.objectStore(this.storeName); + const request = store.put(serialized); + + request.onsuccess = () => { + // Only emit event if not silent (silent used for seeding) + if (!silent) { + const payload: IEntitySavedPayload = { + entityType: this.entityType, + entityId, + operation: isCreate ? 'create' : 'update', + changes, + timestamp: Date.now() + }; + this.eventBus.emit(CoreEvents.ENTITY_SAVED, payload); + } + resolve(); + }; + + request.onerror = () => { + reject(new Error(`Failed to save ${this.entityType} ${entityId}: ${request.error}`)); + }; + }); + } + + /** + * Delete an entity + * Emits ENTITY_DELETED event + */ + async delete(id: string): Promise { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], 'readwrite'); + const store = transaction.objectStore(this.storeName); + const request = store.delete(id); + + request.onsuccess = () => { + const payload: IEntityDeletedPayload = { + entityType: this.entityType, + entityId: id, + operation: 'delete', + timestamp: Date.now() + }; + this.eventBus.emit(CoreEvents.ENTITY_DELETED, payload); + resolve(); + }; + + request.onerror = () => { + reject(new Error(`Failed to delete ${this.entityType} ${id}: ${request.error}`)); + }; + }); + } + + // Sync methods - delegate to SyncPlugin + async markAsSynced(id: string): Promise { + return this.syncPlugin.markAsSynced(id); + } + + async markAsError(id: string): Promise { + return this.syncPlugin.markAsError(id); + } + + async getSyncStatus(id: string): Promise { + return this.syncPlugin.getSyncStatus(id); + } + + async getBySyncStatus(syncStatus: string): Promise { + return this.syncPlugin.getBySyncStatus(syncStatus); + } +} diff --git a/packages/calendar/src/storage/IEntityService.ts b/packages/calendar/src/storage/IEntityService.ts new file mode 100644 index 0000000..800ea62 --- /dev/null +++ b/packages/calendar/src/storage/IEntityService.ts @@ -0,0 +1,40 @@ +import { ISync, EntityType, SyncStatus } from '../types/CalendarTypes'; + +/** + * IEntityService - Generic interface for entity services with sync capabilities + * + * All entity services implement this interface to enable polymorphic operations. + */ +export interface IEntityService { + /** + * Entity type discriminator for runtime routing + */ + readonly entityType: EntityType; + + /** + * Get all entities from IndexedDB + */ + getAll(): Promise; + + /** + * Save an entity (create or update) to IndexedDB + * @param entity - Entity to save + * @param silent - If true, skip event emission (used for seeding) + */ + save(entity: T, silent?: boolean): Promise; + + /** + * Mark entity as successfully synced + */ + markAsSynced(id: string): Promise; + + /** + * Mark entity as sync error + */ + markAsError(id: string): Promise; + + /** + * Get current sync status for an entity + */ + getSyncStatus(id: string): Promise; +} diff --git a/packages/calendar/src/storage/IStore.ts b/packages/calendar/src/storage/IStore.ts new file mode 100644 index 0000000..91ac873 --- /dev/null +++ b/packages/calendar/src/storage/IStore.ts @@ -0,0 +1,18 @@ +/** + * IStore - Interface for IndexedDB ObjectStore definitions + * + * Each entity store implements this interface to define its schema. + * Enables Open/Closed Principle: IndexedDBContext works with any IStore. + */ +export interface IStore { + /** + * The name of the ObjectStore in IndexedDB + */ + readonly storeName: string; + + /** + * Create the ObjectStore with its schema (indexes, keyPath, etc.) + * Called during database upgrade (onupgradeneeded event) + */ + create(db: IDBDatabase): void; +} diff --git a/packages/calendar/src/storage/IndexedDBContext.ts b/packages/calendar/src/storage/IndexedDBContext.ts new file mode 100644 index 0000000..ab9e9ab --- /dev/null +++ b/packages/calendar/src/storage/IndexedDBContext.ts @@ -0,0 +1,108 @@ +import { IStore } from './IStore'; + +/** + * Database configuration + */ +export interface IDBConfig { + dbName: string; + dbVersion?: number; +} + +export const defaultDBConfig: IDBConfig = { + dbName: 'CalendarDB', + dbVersion: 4 +}; + +/** + * IndexedDBContext - Database connection manager + * + * RESPONSIBILITY: + * - Opens and manages IDBDatabase connection lifecycle + * - Creates object stores via injected IStore implementations + * - Provides shared IDBDatabase instance to all services + */ +export class IndexedDBContext { + private db: IDBDatabase | null = null; + private initialized: boolean = false; + private stores: IStore[]; + private config: IDBConfig; + + constructor(stores: IStore[], config: IDBConfig) { + this.stores = stores; + this.config = config; + } + + get dbName(): string { + return this.config.dbName; + } + + /** + * Initialize and open the database + */ + async initialize(): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open(this.config.dbName, this.config.dbVersion); + + request.onerror = () => { + reject(new Error(`Failed to open IndexedDB: ${request.error}`)); + }; + + request.onsuccess = () => { + this.db = request.result; + this.initialized = true; + resolve(); + }; + + request.onupgradeneeded = (event) => { + const db = (event.target as IDBOpenDBRequest).result; + + // Create all entity stores via injected IStore implementations + this.stores.forEach(store => { + if (!db.objectStoreNames.contains(store.storeName)) { + store.create(db); + } + }); + }; + }); + } + + /** + * Check if database is initialized + */ + public isInitialized(): boolean { + return this.initialized; + } + + /** + * Get IDBDatabase instance + */ + public getDatabase(): IDBDatabase { + if (!this.db) { + throw new Error('IndexedDB not initialized. Call initialize() first.'); + } + return this.db; + } + + /** + * Close database connection + */ + close(): void { + if (this.db) { + this.db.close(); + this.db = null; + this.initialized = false; + } + } + + /** + * Delete entire database (for testing/reset) + */ + static async deleteDatabase(dbName: string = defaultDBConfig.dbName): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.deleteDatabase(dbName); + + request.onsuccess = () => resolve(); + request.onerror = () => reject(new Error(`Failed to delete database: ${request.error}`)); + }); + } +} diff --git a/packages/calendar/src/storage/SyncPlugin.ts b/packages/calendar/src/storage/SyncPlugin.ts new file mode 100644 index 0000000..7774da6 --- /dev/null +++ b/packages/calendar/src/storage/SyncPlugin.ts @@ -0,0 +1,64 @@ +import { ISync, SyncStatus } from '../types/CalendarTypes'; + +/** + * SyncPlugin - Pluggable sync functionality for entity services + * + * COMPOSITION PATTERN: + * - Encapsulates all sync-related logic in separate class + * - Composed into BaseEntityService (not inheritance) + */ +export class SyncPlugin { + constructor(private service: any) {} + + /** + * Mark entity as successfully synced + */ + async markAsSynced(id: string): Promise { + const entity = await this.service.get(id); + if (entity) { + entity.syncStatus = 'synced'; + await this.service.save(entity); + } + } + + /** + * Mark entity as sync error + */ + async markAsError(id: string): Promise { + const entity = await this.service.get(id); + if (entity) { + entity.syncStatus = 'error'; + await this.service.save(entity); + } + } + + /** + * Get current sync status for an entity + */ + async getSyncStatus(id: string): Promise { + const entity = await this.service.get(id); + return entity ? entity.syncStatus : null; + } + + /** + * Get entities by sync status using IndexedDB index + */ + async getBySyncStatus(syncStatus: string): Promise { + return new Promise((resolve, reject) => { + const transaction = this.service.db.transaction([this.service.storeName], 'readonly'); + const store = transaction.objectStore(this.service.storeName); + const index = store.index('syncStatus'); + const request = index.getAll(syncStatus); + + request.onsuccess = () => { + const data = request.result as unknown[]; + const entities = data.map(item => this.service.deserialize(item)); + resolve(entities); + }; + + request.onerror = () => { + reject(new Error(`Failed to get by sync status ${syncStatus}: ${request.error}`)); + }; + }); + } +} diff --git a/packages/calendar/src/storage/events/EventSerialization.ts b/packages/calendar/src/storage/events/EventSerialization.ts new file mode 100644 index 0000000..583fa79 --- /dev/null +++ b/packages/calendar/src/storage/events/EventSerialization.ts @@ -0,0 +1,32 @@ +import { ICalendarEvent } from '../../types/CalendarTypes'; + +/** + * EventSerialization - Handles Date field serialization for IndexedDB + * + * IndexedDB doesn't store Date objects directly, so we convert: + * - Date → ISO string (serialize) when writing + * - ISO string → Date (deserialize) when reading + */ +export class EventSerialization { + /** + * Serialize event for IndexedDB storage + */ + static serialize(event: ICalendarEvent): unknown { + return { + ...event, + start: event.start instanceof Date ? event.start.toISOString() : event.start, + end: event.end instanceof Date ? event.end.toISOString() : event.end + }; + } + + /** + * Deserialize event from IndexedDB storage + */ + static deserialize(data: Record): ICalendarEvent { + return { + ...data, + start: typeof data.start === 'string' ? new Date(data.start) : data.start, + end: typeof data.end === 'string' ? new Date(data.end) : data.end + } as ICalendarEvent; + } +} diff --git a/packages/calendar/src/storage/events/EventService.ts b/packages/calendar/src/storage/events/EventService.ts new file mode 100644 index 0000000..0ccd5a5 --- /dev/null +++ b/packages/calendar/src/storage/events/EventService.ts @@ -0,0 +1,84 @@ +import { ICalendarEvent, EntityType, IEventBus } from '../../types/CalendarTypes'; +import { EventStore } from './EventStore'; +import { EventSerialization } from './EventSerialization'; +import { BaseEntityService } from '../BaseEntityService'; +import { IndexedDBContext } from '../IndexedDBContext'; + +/** + * EventService - CRUD operations for calendar events in IndexedDB + * + * Extends BaseEntityService for shared CRUD and sync logic. + * Provides event-specific query methods. + */ +export class EventService extends BaseEntityService { + readonly storeName = EventStore.STORE_NAME; + readonly entityType: EntityType = 'Event'; + + constructor(context: IndexedDBContext, eventBus: IEventBus) { + super(context, eventBus); + } + + protected serialize(event: ICalendarEvent): unknown { + return EventSerialization.serialize(event); + } + + protected deserialize(data: unknown): ICalendarEvent { + return EventSerialization.deserialize(data as Record); + } + + /** + * Get events within a date range + */ + async getByDateRange(start: Date, end: Date): Promise { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], 'readonly'); + const store = transaction.objectStore(this.storeName); + const index = store.index('start'); + + const range = IDBKeyRange.lowerBound(start.toISOString()); + const request = index.getAll(range); + + request.onsuccess = () => { + const data = request.result as unknown[]; + const events = data + .map(item => this.deserialize(item)) + .filter(event => event.start <= end); + resolve(events); + }; + + request.onerror = () => { + reject(new Error(`Failed to get events by date range: ${request.error}`)); + }; + }); + } + + /** + * Get events for a specific resource + */ + async getByResource(resourceId: string): Promise { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], 'readonly'); + const store = transaction.objectStore(this.storeName); + const index = store.index('resourceId'); + const request = index.getAll(resourceId); + + request.onsuccess = () => { + const data = request.result as unknown[]; + const events = data.map(item => this.deserialize(item)); + resolve(events); + }; + + request.onerror = () => { + reject(new Error(`Failed to get events for resource ${resourceId}: ${request.error}`)); + }; + }); + } + + /** + * Get events for a resource within a date range + */ + async getByResourceAndDateRange(resourceId: string, start: Date, end: Date): Promise { + const resourceEvents = await this.getByResource(resourceId); + return resourceEvents.filter(event => event.start >= start && event.start <= end); + } +} diff --git a/packages/calendar/src/storage/events/EventStore.ts b/packages/calendar/src/storage/events/EventStore.ts new file mode 100644 index 0000000..21c7be0 --- /dev/null +++ b/packages/calendar/src/storage/events/EventStore.ts @@ -0,0 +1,37 @@ +import { IStore } from '../IStore'; + +/** + * EventStore - IndexedDB ObjectStore definition for calendar events + */ +export class EventStore implements IStore { + static readonly STORE_NAME = 'events'; + readonly storeName = EventStore.STORE_NAME; + + /** + * Create the events ObjectStore with indexes + */ + create(db: IDBDatabase): void { + const store = db.createObjectStore(EventStore.STORE_NAME, { keyPath: 'id' }); + + // Index: start (for date range queries) + store.createIndex('start', 'start', { unique: false }); + + // Index: end (for date range queries) + store.createIndex('end', 'end', { unique: false }); + + // Index: syncStatus (for filtering by sync state) + store.createIndex('syncStatus', 'syncStatus', { unique: false }); + + // Index: resourceId (for resource-mode filtering) + store.createIndex('resourceId', 'resourceId', { unique: false }); + + // Index: customerId (for customer-centric queries) + store.createIndex('customerId', 'customerId', { unique: false }); + + // Index: bookingId (for event-to-booking lookups) + store.createIndex('bookingId', 'bookingId', { unique: false }); + + // Compound index: startEnd (for optimized range queries) + store.createIndex('startEnd', ['start', 'end'], { unique: false }); + } +} diff --git a/packages/calendar/src/storage/resources/ResourceService.ts b/packages/calendar/src/storage/resources/ResourceService.ts new file mode 100644 index 0000000..769210c --- /dev/null +++ b/packages/calendar/src/storage/resources/ResourceService.ts @@ -0,0 +1,55 @@ +import { IResource, EntityType, IEventBus } from '../../types/CalendarTypes'; +import { ResourceStore } from './ResourceStore'; +import { BaseEntityService } from '../BaseEntityService'; +import { IndexedDBContext } from '../IndexedDBContext'; + +/** + * ResourceService - CRUD operations for resources in IndexedDB + */ +export class ResourceService extends BaseEntityService { + readonly storeName = ResourceStore.STORE_NAME; + readonly entityType: EntityType = 'Resource'; + + constructor(context: IndexedDBContext, eventBus: IEventBus) { + super(context, eventBus); + } + + /** + * Get all active resources + */ + async getActive(): Promise { + const all = await this.getAll(); + return all.filter(r => r.isActive !== false); + } + + /** + * Get resources by IDs + */ + async getByIds(ids: string[]): Promise { + if (ids.length === 0) return []; + + const results = await Promise.all(ids.map(id => this.get(id))); + return results.filter((r): r is IResource => r !== null); + } + + /** + * Get resources by type + */ + async getByType(type: string): Promise { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], 'readonly'); + const store = transaction.objectStore(this.storeName); + const index = store.index('type'); + const request = index.getAll(type); + + request.onsuccess = () => { + const data = request.result as IResource[]; + resolve(data); + }; + + request.onerror = () => { + reject(new Error(`Failed to get resources by type ${type}: ${request.error}`)); + }; + }); + } +} diff --git a/packages/calendar/src/storage/resources/ResourceStore.ts b/packages/calendar/src/storage/resources/ResourceStore.ts new file mode 100644 index 0000000..38e39b6 --- /dev/null +++ b/packages/calendar/src/storage/resources/ResourceStore.ts @@ -0,0 +1,17 @@ +import { IStore } from '../IStore'; + +/** + * ResourceStore - IndexedDB ObjectStore definition for resources + */ +export class ResourceStore implements IStore { + static readonly STORE_NAME = 'resources'; + readonly storeName = ResourceStore.STORE_NAME; + + create(db: IDBDatabase): void { + const store = db.createObjectStore(ResourceStore.STORE_NAME, { keyPath: 'id' }); + + store.createIndex('type', 'type', { unique: false }); + store.createIndex('syncStatus', 'syncStatus', { unique: false }); + store.createIndex('isActive', 'isActive', { unique: false }); + } +} diff --git a/packages/calendar/src/storage/settings/SettingsService.ts b/packages/calendar/src/storage/settings/SettingsService.ts new file mode 100644 index 0000000..5bc57b4 --- /dev/null +++ b/packages/calendar/src/storage/settings/SettingsService.ts @@ -0,0 +1,83 @@ +import { EntityType, IEventBus } from '../../types/CalendarTypes'; +import { + TenantSetting, + IWorkweekSettings, + IGridSettings, + ITimeFormatSettings, + IViewSettings, + IWorkweekPreset, + SettingsIds +} from '../../types/SettingsTypes'; +import { SettingsStore } from './SettingsStore'; +import { BaseEntityService } from '../BaseEntityService'; +import { IndexedDBContext } from '../IndexedDBContext'; + +/** + * SettingsService - CRUD operations for tenant settings + * + * Settings are stored as separate records per section. + * This service provides typed methods for accessing specific settings. + */ +export class SettingsService extends BaseEntityService { + readonly storeName = SettingsStore.STORE_NAME; + readonly entityType: EntityType = 'Settings'; + + constructor(context: IndexedDBContext, eventBus: IEventBus) { + super(context, eventBus); + } + + /** + * Get workweek settings + */ + async getWorkweekSettings(): Promise { + return this.get(SettingsIds.WORKWEEK) as Promise; + } + + /** + * Get grid settings + */ + async getGridSettings(): Promise { + return this.get(SettingsIds.GRID) as Promise; + } + + /** + * Get time format settings + */ + async getTimeFormatSettings(): Promise { + return this.get(SettingsIds.TIME_FORMAT) as Promise; + } + + /** + * Get view settings + */ + async getViewSettings(): Promise { + return this.get(SettingsIds.VIEWS) as Promise; + } + + /** + * Get workweek preset by ID + */ + async getWorkweekPreset(presetId: string): Promise { + const settings = await this.getWorkweekSettings(); + if (!settings) return null; + return settings.presets[presetId] || null; + } + + /** + * Get the default workweek preset + */ + async getDefaultWorkweekPreset(): Promise { + const settings = await this.getWorkweekSettings(); + if (!settings) return null; + return settings.presets[settings.defaultPreset] || null; + } + + /** + * Get all available workweek presets + */ + async getWorkweekPresets(): Promise { + const settings = await this.getWorkweekSettings(); + if (!settings) return []; + return Object.values(settings.presets); + } +} diff --git a/packages/calendar/src/storage/settings/SettingsStore.ts b/packages/calendar/src/storage/settings/SettingsStore.ts new file mode 100644 index 0000000..a28cc79 --- /dev/null +++ b/packages/calendar/src/storage/settings/SettingsStore.ts @@ -0,0 +1,16 @@ +import { IStore } from '../IStore'; + +/** + * SettingsStore - IndexedDB ObjectStore definition for tenant settings + * + * Single store for all settings sections. Settings are stored as one document + * per tenant with id='tenant-settings'. + */ +export class SettingsStore implements IStore { + static readonly STORE_NAME = 'settings'; + readonly storeName = SettingsStore.STORE_NAME; + + create(db: IDBDatabase): void { + db.createObjectStore(SettingsStore.STORE_NAME, { keyPath: 'id' }); + } +} diff --git a/packages/calendar/src/storage/viewconfigs/ViewConfigService.ts b/packages/calendar/src/storage/viewconfigs/ViewConfigService.ts new file mode 100644 index 0000000..03a42f7 --- /dev/null +++ b/packages/calendar/src/storage/viewconfigs/ViewConfigService.ts @@ -0,0 +1,18 @@ +import { EntityType, IEventBus } from '../../types/CalendarTypes'; +import { ViewConfig } from '../../core/ViewConfig'; +import { ViewConfigStore } from './ViewConfigStore'; +import { BaseEntityService } from '../BaseEntityService'; +import { IndexedDBContext } from '../IndexedDBContext'; + +export class ViewConfigService extends BaseEntityService { + readonly storeName = ViewConfigStore.STORE_NAME; + readonly entityType: EntityType = 'ViewConfig'; + + constructor(context: IndexedDBContext, eventBus: IEventBus) { + super(context, eventBus); + } + + async getById(id: string): Promise { + return this.get(id); + } +} diff --git a/packages/calendar/src/storage/viewconfigs/ViewConfigStore.ts b/packages/calendar/src/storage/viewconfigs/ViewConfigStore.ts new file mode 100644 index 0000000..fb02d07 --- /dev/null +++ b/packages/calendar/src/storage/viewconfigs/ViewConfigStore.ts @@ -0,0 +1,10 @@ +import { IStore } from '../IStore'; + +export class ViewConfigStore implements IStore { + static readonly STORE_NAME = 'viewconfigs'; + readonly storeName = ViewConfigStore.STORE_NAME; + + create(db: IDBDatabase): void { + db.createObjectStore(ViewConfigStore.STORE_NAME, { keyPath: 'id' }); + } +} diff --git a/packages/calendar/src/types/AuditTypes.ts b/packages/calendar/src/types/AuditTypes.ts new file mode 100644 index 0000000..3c0eb9f --- /dev/null +++ b/packages/calendar/src/types/AuditTypes.ts @@ -0,0 +1,46 @@ +import { ISync, EntityType } from './CalendarTypes'; + +/** + * IAuditEntry - Audit log entry for tracking all entity changes + * + * Used for: + * - Compliance and audit trail + * - Sync tracking with backend + * - Change history + */ +export interface IAuditEntry extends ISync { + /** Unique audit entry ID */ + id: string; + + /** Type of entity that was changed */ + entityType: EntityType; + + /** ID of the entity that was changed */ + entityId: string; + + /** Type of operation performed */ + operation: 'create' | 'update' | 'delete'; + + /** User who made the change */ + userId: string; + + /** Timestamp when change was made */ + timestamp: number; + + /** Changes made (full entity for create, diff for update, { id } for delete) */ + changes: unknown; + + /** Whether this audit entry has been synced to backend */ + synced: boolean; +} + +/** + * IAuditLoggedPayload - Event payload when audit entry is logged + */ +export interface IAuditLoggedPayload { + auditId: string; + entityType: EntityType; + entityId: string; + operation: 'create' | 'update' | 'delete'; + timestamp: number; +} diff --git a/packages/calendar/src/types/CalendarTypes.ts b/packages/calendar/src/types/CalendarTypes.ts new file mode 100644 index 0000000..c7aa21c --- /dev/null +++ b/packages/calendar/src/types/CalendarTypes.ts @@ -0,0 +1,170 @@ +/** + * Calendar Type Definitions + */ + +import { IWeekSchedule } from './ScheduleTypes'; + +export type SyncStatus = 'synced' | 'pending' | 'error'; + +export type EntityType = 'Event' | 'Booking' | 'Customer' | 'Resource' | 'Team' | 'Department' | 'Audit' | 'Settings' | 'ViewConfig'; + +/** + * CalendarEventType - Used by ICalendarEvent.type + * Note: Only 'customer' events have associated IBooking + */ +export type CalendarEventType = + | 'customer' // Customer appointment (HAS booking) + | 'vacation' // Vacation/time off (NO booking) + | 'break' // Lunch/break (NO booking) + | 'meeting' // Meeting (NO booking) + | 'blocked'; // Blocked time (NO booking) + +/** + * ISync - Interface for sync status tracking + * All syncable entities should extend this interface + */ +export interface ISync { + syncStatus: SyncStatus; +} + +/** + * IDataEntity - Wrapper for entity data with typename discriminator + */ +export interface IDataEntity { + typename: EntityType; + data: unknown; +} + +export interface ICalendarEvent extends ISync { + id: string; + title: string; + description?: string; + start: Date; + end: Date; + type: CalendarEventType; + allDay: boolean; + + // References (denormalized for IndexedDB performance) + bookingId?: string; // Reference to booking (only if type = 'customer') + resourceId?: string; // Resource who owns this time slot + customerId?: string; // Denormalized from Booking.customerId + + recurringId?: string; + metadata?: Record; +} + +// EventBus types +export interface IEventLogEntry { + type: string; + detail: unknown; + timestamp: number; +} + +export interface IListenerEntry { + eventType: string; + handler: EventListener; + options?: AddEventListenerOptions; +} + +export interface IEventBus { + on(eventType: string, handler: EventListener, options?: AddEventListenerOptions): () => void; + once(eventType: string, handler: EventListener): () => void; + off(eventType: string, handler: EventListener): void; + emit(eventType: string, detail?: unknown): boolean; + getEventLog(eventType?: string): IEventLogEntry[]; + setDebug(enabled: boolean): void; +} + +// Entity event payloads +export interface IEntitySavedPayload { + entityType: EntityType; + entityId: string; + operation: 'create' | 'update'; + changes: unknown; + timestamp: number; +} + +export interface IEntityDeletedPayload { + entityType: EntityType; + entityId: string; + operation: 'delete'; + timestamp: number; +} + +// Event update payload (for re-rendering columns after drag/resize) +export interface IEventUpdatedPayload { + eventId: string; + sourceColumnKey: string; // Source column key (where event came from) + targetColumnKey: string; // Target column key (where event landed) +} + +// Resource types +export type ResourceType = + | 'person' + | 'room' + | 'equipment' + | 'vehicle' + | 'custom'; + +export interface IResource extends ISync { + id: string; + name: string; + displayName: string; + type: ResourceType; + avatarUrl?: string; + color?: string; + isActive?: boolean; + defaultSchedule?: IWeekSchedule; // Default arbejdstider per ugedag + metadata?: Record; +} + +// Team types +export interface ITeam extends ISync { + id: string; + name: string; + resourceIds: string[]; +} + +// Department types +export interface IDepartment extends ISync { + id: string; + name: string; + resourceIds: string[]; +} + +// Booking types +export type BookingStatus = + | 'created' + | 'arrived' + | 'paid' + | 'noshow' + | 'cancelled'; + +export interface IBookingService { + serviceId: string; + serviceName: string; + baseDuration: number; + basePrice: number; + customPrice?: number; + resourceId: string; +} + +export interface IBooking extends ISync { + id: string; + customerId: string; + status: BookingStatus; + createdAt: Date; + services: IBookingService[]; + totalPrice?: number; + tags?: string[]; + notes?: string; +} + +// Customer types +export interface ICustomer extends ISync { + id: string; + name: string; + phone: string; + email?: string; + metadata?: Record; +} diff --git a/packages/calendar/src/types/DragTypes.ts b/packages/calendar/src/types/DragTypes.ts new file mode 100644 index 0000000..d63c1a8 --- /dev/null +++ b/packages/calendar/src/types/DragTypes.ts @@ -0,0 +1,76 @@ +/** + * DragTypes - Event payloads for drag-drop operations + */ + +import { SwpEvent } from './SwpEvent'; + +export interface IMousePosition { + x: number; + y: number; +} + +export interface IDragStartPayload { + eventId: string; + element: HTMLElement; // Original element (being dragged) + ghostElement: HTMLElement; // Ghost clone (stays in place) + startY: number; // Original Y position + mouseOffset: IMousePosition; // Click position within element + columnElement: HTMLElement; +} + +export interface IDragMovePayload { + eventId: string; + element: HTMLElement; + currentY: number; // Interpolated Y position (smooth) + columnElement: HTMLElement; +} + +export interface IDragEndPayload { + swpEvent: SwpEvent; // Wrapper with element, start, end, eventId, columnKey + sourceColumnKey: string; // Source column key (where drag started) + target: 'grid' | 'header'; // Where the event was dropped +} + +export interface IDragCancelPayload { + eventId: string; + element: HTMLElement; + startY: number; // Position to animate back to +} + +export interface IDragColumnChangePayload { + eventId: string; + element: HTMLElement; + previousColumn: HTMLElement; + newColumn: HTMLElement; + currentY: number; +} + +// Header drag payloads +export interface IDragEnterHeaderPayload { + eventId: string; + element: HTMLElement; // Original dragged element + sourceColumnIndex: number; + sourceColumnKey: string; // Opaque column identifier (for matching only) + title: string; + colorClass?: string; + itemType: 'event' | 'reminder'; + duration: number; // Antal dage (default 1) +} + +export interface IDragMoveHeaderPayload { + eventId: string; + columnIndex: number; + columnKey: string; // Opaque column identifier (for matching only) +} + +export interface IDragLeaveHeaderPayload { + eventId: string; + source: 'grid' | 'header'; // Where drag originated + // Header→grid fields (when source === 'header') + element?: HTMLElement; // Header item element + targetColumn?: HTMLElement; // Target column in grid + start?: Date; // Event start from header item + end?: Date; // Event end from header item + title?: string; + colorClass?: string; +} diff --git a/packages/calendar/src/types/ResizeTypes.ts b/packages/calendar/src/types/ResizeTypes.ts new file mode 100644 index 0000000..75caf6b --- /dev/null +++ b/packages/calendar/src/types/ResizeTypes.ts @@ -0,0 +1,15 @@ +/** + * ResizeTypes - Event payloads for resize operations + */ + +import { SwpEvent } from './SwpEvent'; + +export interface IResizeStartPayload { + eventId: string; + element: HTMLElement; + startHeight: number; +} + +export interface IResizeEndPayload { + swpEvent: SwpEvent; // Wrapper with element, start, end, eventId, resourceId +} diff --git a/packages/calendar/src/types/ScheduleTypes.ts b/packages/calendar/src/types/ScheduleTypes.ts new file mode 100644 index 0000000..65bfee7 --- /dev/null +++ b/packages/calendar/src/types/ScheduleTypes.ts @@ -0,0 +1,27 @@ +/** + * Schedule Types - Resource arbejdstider + */ + +// Genbrugelig tidsslot +export interface ITimeSlot { + start: string; // "HH:mm" + end: string; // "HH:mm" +} + +// Ugedag: 1=mandag, 7=søndag (ISO 8601) +export type WeekDay = 1 | 2 | 3 | 4 | 5 | 6 | 7; + +// Default arbejdstider per ugedag +export interface IWeekSchedule { + [day: number]: ITimeSlot | null; // null = fri den dag +} + +// Override for specifik dato +export interface IScheduleOverride { + id: string; + resourceId: string; + date: string; // "YYYY-MM-DD" + schedule: ITimeSlot | null; // null = fri den dag + breaks?: ITimeSlot[]; + syncStatus?: 'synced' | 'pending' | 'error'; +} diff --git a/packages/calendar/src/types/SettingsTypes.ts b/packages/calendar/src/types/SettingsTypes.ts new file mode 100644 index 0000000..5ae47aa --- /dev/null +++ b/packages/calendar/src/types/SettingsTypes.ts @@ -0,0 +1,78 @@ +/** + * Tenant Settings Type Definitions + * + * Settings are tenant-specific configuration that comes from the backend + * and is stored in IndexedDB for offline access. + * + * Each settings section is stored as a separate record with its own id. + */ + +import { ISync } from './CalendarTypes'; + +/** + * Workweek preset - defines which ISO weekdays to display + * ISO: 1=Monday, 7=Sunday + */ +export interface IWorkweekPreset { + id: string; + workDays: number[]; + label: string; + periodDays: number; // Navigation step in days (1 = day, 7 = week) +} + +/** + * Workweek settings - stored as separate record + */ +export interface IWorkweekSettings extends ISync { + id: 'workweek'; + presets: Record; + defaultPreset: string; + firstDayOfWeek: number; // ISO: 1=Monday +} + +/** + * Grid display settings - stored as separate record + */ +export interface IGridSettings extends ISync { + id: 'grid'; + dayStartHour: number; + dayEndHour: number; + workStartHour: number; + workEndHour: number; + hourHeight: number; + snapInterval: number; +} + +/** + * Time format settings - stored as separate record + */ +export interface ITimeFormatSettings extends ISync { + id: 'timeFormat'; + timezone: string; + locale: string; + use24HourFormat: boolean; +} + +/** + * View settings - stored as separate record + */ +export interface IViewSettings extends ISync { + id: 'views'; + availableViews: string[]; + defaultView: string; +} + +/** + * Union type for all tenant settings records + */ +export type TenantSetting = IWorkweekSettings | IGridSettings | ITimeFormatSettings | IViewSettings; + +/** + * Settings IDs as const for type safety + */ +export const SettingsIds = { + WORKWEEK: 'workweek', + GRID: 'grid', + TIME_FORMAT: 'timeFormat', + VIEWS: 'views' +} as const; diff --git a/packages/calendar/src/types/SwpEvent.ts b/packages/calendar/src/types/SwpEvent.ts new file mode 100644 index 0000000..ff8373b --- /dev/null +++ b/packages/calendar/src/types/SwpEvent.ts @@ -0,0 +1,79 @@ +import { IGridConfig } from '../core/IGridConfig'; + +/** + * SwpEvent - Wrapper class for calendar event elements + * + * Encapsulates an HTMLElement and provides computed properties + * for start/end times based on element position and grid config. + * + * Usage: + * - eventId is read from element.dataset + * - columnKey identifies the column uniformly + * - Position (top, height) is read from element.style + * - Factory method `fromElement()` calculates Date objects + */ +export class SwpEvent { + readonly element: HTMLElement; + readonly columnKey: string; + private _start: Date; + private _end: Date; + + constructor(element: HTMLElement, columnKey: string, start: Date, end: Date) { + this.element = element; + this.columnKey = columnKey; + this._start = start; + this._end = end; + } + + /** Event ID from element.dataset.eventId */ + get eventId(): string { + return this.element.dataset.eventId || ''; + } + + get start(): Date { + return this._start; + } + + get end(): Date { + return this._end; + } + + /** Duration in minutes */ + get durationMinutes(): number { + return (this._end.getTime() - this._start.getTime()) / (1000 * 60); + } + + /** Duration in milliseconds */ + get durationMs(): number { + return this._end.getTime() - this._start.getTime(); + } + + /** + * Factory: Create SwpEvent from element + columnKey + * Reads top/height from element.style to calculate start/end + * @param columnKey - Opaque column identifier (do NOT parse - use only for matching) + * @param date - Date string (YYYY-MM-DD) for time calculations + */ + static fromElement( + element: HTMLElement, + columnKey: string, + date: string, + gridConfig: IGridConfig + ): SwpEvent { + const topPixels = parseFloat(element.style.top) || 0; + const heightPixels = parseFloat(element.style.height) || 0; + + // Calculate start from top position + const startMinutesFromGrid = (topPixels / gridConfig.hourHeight) * 60; + const totalMinutes = (gridConfig.dayStartHour * 60) + startMinutesFromGrid; + + const start = new Date(date); + start.setHours(Math.floor(totalMinutes / 60), totalMinutes % 60, 0, 0); + + // Calculate end from height + const durationMinutes = (heightPixels / gridConfig.hourHeight) * 60; + const end = new Date(start.getTime() + durationMinutes * 60 * 1000); + + return new SwpEvent(element, columnKey, start, end); + } +} diff --git a/packages/calendar/src/utils/PositionUtils.ts b/packages/calendar/src/utils/PositionUtils.ts new file mode 100644 index 0000000..5c99e4b --- /dev/null +++ b/packages/calendar/src/utils/PositionUtils.ts @@ -0,0 +1,55 @@ +/** + * PositionUtils - Pixel/position calculations for calendar grid + * + * RESPONSIBILITY: Convert between time and pixel positions + * NOTE: Date formatting belongs in DateService, not here + */ + +import { IGridConfig } from '../core/IGridConfig'; + +export interface EventPosition { + top: number; // pixels from day start + height: number; // pixels +} + +/** + * Calculate pixel position for an event based on its times + */ +export function calculateEventPosition( + start: Date, + end: Date, + config: IGridConfig +): EventPosition { + const startMinutes = start.getHours() * 60 + start.getMinutes(); + const endMinutes = end.getHours() * 60 + end.getMinutes(); + + const dayStartMinutes = config.dayStartHour * 60; + const minuteHeight = config.hourHeight / 60; + + const top = (startMinutes - dayStartMinutes) * minuteHeight; + const height = (endMinutes - startMinutes) * minuteHeight; + + return { top, height }; +} + +/** + * Convert minutes to pixels + */ +export function minutesToPixels(minutes: number, config: IGridConfig): number { + return (minutes / 60) * config.hourHeight; +} + +/** + * Convert pixels to minutes + */ +export function pixelsToMinutes(pixels: number, config: IGridConfig): number { + return (pixels / config.hourHeight) * 60; +} + +/** + * Snap pixel position to grid interval + */ +export function snapToGrid(pixels: number, config: IGridConfig): number { + const snapPixels = minutesToPixels(config.snapInterval, config); + return Math.round(pixels / snapPixels) * snapPixels; +} diff --git a/packages/calendar/tsconfig.json b/packages/calendar/tsconfig.json new file mode 100644 index 0000000..c1c4978 --- /dev/null +++ b/packages/calendar/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "node", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationDir": "./dist", + "outDir": "./dist", + "rootDir": "./src", + "sourceMap": true, + "lib": ["ES2024", "DOM", "DOM.Iterable"], + "experimentalDecorators": true, + "emitDecoratorMetadata": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/test-package/build.js b/test-package/build.js new file mode 100644 index 0000000..07cf9e7 --- /dev/null +++ b/test-package/build.js @@ -0,0 +1,23 @@ +import esbuild from 'esbuild'; +import { NovadiUnplugin } from '@novadi/core/unplugin'; +import { copyFileSync, mkdirSync } from 'fs'; + +// Ensure dist/css directory exists +mkdirSync('dist/css', { recursive: true }); + +// Copy calendar CSS +copyFileSync( + 'node_modules/calendar/dist/css/calendar.css', + 'dist/css/calendar.css' +); + +await esbuild.build({ + entryPoints: ['src/index.ts'], + bundle: true, + outfile: 'dist/bundle.js', + format: 'esm', + platform: 'browser', + plugins: [NovadiUnplugin.esbuild()] +}); + +console.log('Build complete'); diff --git a/test-package/dist/bundle.js b/test-package/dist/bundle.js new file mode 100644 index 0000000..71cc1e8 --- /dev/null +++ b/test-package/dist/bundle.js @@ -0,0 +1,5289 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); + +// node_modules/dayjs/dayjs.min.js +var require_dayjs_min = __commonJS({ + "node_modules/dayjs/dayjs.min.js"(exports, module) { + !(function(t, e) { + "object" == typeof exports && "undefined" != typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define(e) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs = e(); + })(exports, (function() { + "use strict"; + var t = 1e3, e = 6e4, n = 36e5, r = "millisecond", i = "second", s = "minute", u = "hour", a = "day", o = "week", c = "month", f = "quarter", h = "year", d = "date", l = "Invalid Date", $ = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/, y = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g, M = { name: "en", weekdays: "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), months: "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), ordinal: function(t2) { + var e2 = ["th", "st", "nd", "rd"], n2 = t2 % 100; + return "[" + t2 + (e2[(n2 - 20) % 10] || e2[n2] || e2[0]) + "]"; + } }, m = function(t2, e2, n2) { + var r2 = String(t2); + return !r2 || r2.length >= e2 ? t2 : "" + Array(e2 + 1 - r2.length).join(n2) + t2; + }, v = { s: m, z: function(t2) { + var e2 = -t2.utcOffset(), n2 = Math.abs(e2), r2 = Math.floor(n2 / 60), i2 = n2 % 60; + return (e2 <= 0 ? "+" : "-") + m(r2, 2, "0") + ":" + m(i2, 2, "0"); + }, m: function t2(e2, n2) { + if (e2.date() < n2.date()) return -t2(n2, e2); + var r2 = 12 * (n2.year() - e2.year()) + (n2.month() - e2.month()), i2 = e2.clone().add(r2, c), s2 = n2 - i2 < 0, u2 = e2.clone().add(r2 + (s2 ? -1 : 1), c); + return +(-(r2 + (n2 - i2) / (s2 ? i2 - u2 : u2 - i2)) || 0); + }, a: function(t2) { + return t2 < 0 ? Math.ceil(t2) || 0 : Math.floor(t2); + }, p: function(t2) { + return { M: c, y: h, w: o, d: a, D: d, h: u, m: s, s: i, ms: r, Q: f }[t2] || String(t2 || "").toLowerCase().replace(/s$/, ""); + }, u: function(t2) { + return void 0 === t2; + } }, g = "en", D = {}; + D[g] = M; + var p = "$isDayjsObject", S = function(t2) { + return t2 instanceof _ || !(!t2 || !t2[p]); + }, w = function t2(e2, n2, r2) { + var i2; + if (!e2) return g; + if ("string" == typeof e2) { + var s2 = e2.toLowerCase(); + D[s2] && (i2 = s2), n2 && (D[s2] = n2, i2 = s2); + var u2 = e2.split("-"); + if (!i2 && u2.length > 1) return t2(u2[0]); + } else { + var a2 = e2.name; + D[a2] = e2, i2 = a2; + } + return !r2 && i2 && (g = i2), i2 || !r2 && g; + }, O = function(t2, e2) { + if (S(t2)) return t2.clone(); + var n2 = "object" == typeof e2 ? e2 : {}; + return n2.date = t2, n2.args = arguments, new _(n2); + }, b = v; + b.l = w, b.i = S, b.w = function(t2, e2) { + return O(t2, { locale: e2.$L, utc: e2.$u, x: e2.$x, $offset: e2.$offset }); + }; + var _ = (function() { + function M2(t2) { + this.$L = w(t2.locale, null, true), this.parse(t2), this.$x = this.$x || t2.x || {}, this[p] = true; + } + var m2 = M2.prototype; + return m2.parse = function(t2) { + this.$d = (function(t3) { + var e2 = t3.date, n2 = t3.utc; + if (null === e2) return /* @__PURE__ */ new Date(NaN); + if (b.u(e2)) return /* @__PURE__ */ new Date(); + if (e2 instanceof Date) return new Date(e2); + if ("string" == typeof e2 && !/Z$/i.test(e2)) { + var r2 = e2.match($); + if (r2) { + var i2 = r2[2] - 1 || 0, s2 = (r2[7] || "0").substring(0, 3); + return n2 ? new Date(Date.UTC(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2)) : new Date(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2); + } + } + return new Date(e2); + })(t2), this.init(); + }, m2.init = function() { + var t2 = this.$d; + this.$y = t2.getFullYear(), this.$M = t2.getMonth(), this.$D = t2.getDate(), this.$W = t2.getDay(), this.$H = t2.getHours(), this.$m = t2.getMinutes(), this.$s = t2.getSeconds(), this.$ms = t2.getMilliseconds(); + }, m2.$utils = function() { + return b; + }, m2.isValid = function() { + return !(this.$d.toString() === l); + }, m2.isSame = function(t2, e2) { + var n2 = O(t2); + return this.startOf(e2) <= n2 && n2 <= this.endOf(e2); + }, m2.isAfter = function(t2, e2) { + return O(t2) < this.startOf(e2); + }, m2.isBefore = function(t2, e2) { + return this.endOf(e2) < O(t2); + }, m2.$g = function(t2, e2, n2) { + return b.u(t2) ? this[e2] : this.set(n2, t2); + }, m2.unix = function() { + return Math.floor(this.valueOf() / 1e3); + }, m2.valueOf = function() { + return this.$d.getTime(); + }, m2.startOf = function(t2, e2) { + var n2 = this, r2 = !!b.u(e2) || e2, f2 = b.p(t2), l2 = function(t3, e3) { + var i2 = b.w(n2.$u ? Date.UTC(n2.$y, e3, t3) : new Date(n2.$y, e3, t3), n2); + return r2 ? i2 : i2.endOf(a); + }, $2 = function(t3, e3) { + return b.w(n2.toDate()[t3].apply(n2.toDate("s"), (r2 ? [0, 0, 0, 0] : [23, 59, 59, 999]).slice(e3)), n2); + }, y2 = this.$W, M3 = this.$M, m3 = this.$D, v2 = "set" + (this.$u ? "UTC" : ""); + switch (f2) { + case h: + return r2 ? l2(1, 0) : l2(31, 11); + case c: + return r2 ? l2(1, M3) : l2(0, M3 + 1); + case o: + var g2 = this.$locale().weekStart || 0, D2 = (y2 < g2 ? y2 + 7 : y2) - g2; + return l2(r2 ? m3 - D2 : m3 + (6 - D2), M3); + case a: + case d: + return $2(v2 + "Hours", 0); + case u: + return $2(v2 + "Minutes", 1); + case s: + return $2(v2 + "Seconds", 2); + case i: + return $2(v2 + "Milliseconds", 3); + default: + return this.clone(); + } + }, m2.endOf = function(t2) { + return this.startOf(t2, false); + }, m2.$set = function(t2, e2) { + var n2, o2 = b.p(t2), f2 = "set" + (this.$u ? "UTC" : ""), l2 = (n2 = {}, n2[a] = f2 + "Date", n2[d] = f2 + "Date", n2[c] = f2 + "Month", n2[h] = f2 + "FullYear", n2[u] = f2 + "Hours", n2[s] = f2 + "Minutes", n2[i] = f2 + "Seconds", n2[r] = f2 + "Milliseconds", n2)[o2], $2 = o2 === a ? this.$D + (e2 - this.$W) : e2; + if (o2 === c || o2 === h) { + var y2 = this.clone().set(d, 1); + y2.$d[l2]($2), y2.init(), this.$d = y2.set(d, Math.min(this.$D, y2.daysInMonth())).$d; + } else l2 && this.$d[l2]($2); + return this.init(), this; + }, m2.set = function(t2, e2) { + return this.clone().$set(t2, e2); + }, m2.get = function(t2) { + return this[b.p(t2)](); + }, m2.add = function(r2, f2) { + var d2, l2 = this; + r2 = Number(r2); + var $2 = b.p(f2), y2 = function(t2) { + var e2 = O(l2); + return b.w(e2.date(e2.date() + Math.round(t2 * r2)), l2); + }; + if ($2 === c) return this.set(c, this.$M + r2); + if ($2 === h) return this.set(h, this.$y + r2); + if ($2 === a) return y2(1); + if ($2 === o) return y2(7); + var M3 = (d2 = {}, d2[s] = e, d2[u] = n, d2[i] = t, d2)[$2] || 1, m3 = this.$d.getTime() + r2 * M3; + return b.w(m3, this); + }, m2.subtract = function(t2, e2) { + return this.add(-1 * t2, e2); + }, m2.format = function(t2) { + var e2 = this, n2 = this.$locale(); + if (!this.isValid()) return n2.invalidDate || l; + var r2 = t2 || "YYYY-MM-DDTHH:mm:ssZ", i2 = b.z(this), s2 = this.$H, u2 = this.$m, a2 = this.$M, o2 = n2.weekdays, c2 = n2.months, f2 = n2.meridiem, h2 = function(t3, n3, i3, s3) { + return t3 && (t3[n3] || t3(e2, r2)) || i3[n3].slice(0, s3); + }, d2 = function(t3) { + return b.s(s2 % 12 || 12, t3, "0"); + }, $2 = f2 || function(t3, e3, n3) { + var r3 = t3 < 12 ? "AM" : "PM"; + return n3 ? r3.toLowerCase() : r3; + }; + return r2.replace(y, (function(t3, r3) { + return r3 || (function(t4) { + switch (t4) { + case "YY": + return String(e2.$y).slice(-2); + case "YYYY": + return b.s(e2.$y, 4, "0"); + case "M": + return a2 + 1; + case "MM": + return b.s(a2 + 1, 2, "0"); + case "MMM": + return h2(n2.monthsShort, a2, c2, 3); + case "MMMM": + return h2(c2, a2); + case "D": + return e2.$D; + case "DD": + return b.s(e2.$D, 2, "0"); + case "d": + return String(e2.$W); + case "dd": + return h2(n2.weekdaysMin, e2.$W, o2, 2); + case "ddd": + return h2(n2.weekdaysShort, e2.$W, o2, 3); + case "dddd": + return o2[e2.$W]; + case "H": + return String(s2); + case "HH": + return b.s(s2, 2, "0"); + case "h": + return d2(1); + case "hh": + return d2(2); + case "a": + return $2(s2, u2, true); + case "A": + return $2(s2, u2, false); + case "m": + return String(u2); + case "mm": + return b.s(u2, 2, "0"); + case "s": + return String(e2.$s); + case "ss": + return b.s(e2.$s, 2, "0"); + case "SSS": + return b.s(e2.$ms, 3, "0"); + case "Z": + return i2; + } + return null; + })(t3) || i2.replace(":", ""); + })); + }, m2.utcOffset = function() { + return 15 * -Math.round(this.$d.getTimezoneOffset() / 15); + }, m2.diff = function(r2, d2, l2) { + var $2, y2 = this, M3 = b.p(d2), m3 = O(r2), v2 = (m3.utcOffset() - this.utcOffset()) * e, g2 = this - m3, D2 = function() { + return b.m(y2, m3); + }; + switch (M3) { + case h: + $2 = D2() / 12; + break; + case c: + $2 = D2(); + break; + case f: + $2 = D2() / 3; + break; + case o: + $2 = (g2 - v2) / 6048e5; + break; + case a: + $2 = (g2 - v2) / 864e5; + break; + case u: + $2 = g2 / n; + break; + case s: + $2 = g2 / e; + break; + case i: + $2 = g2 / t; + break; + default: + $2 = g2; + } + return l2 ? $2 : b.a($2); + }, m2.daysInMonth = function() { + return this.endOf(c).$D; + }, m2.$locale = function() { + return D[this.$L]; + }, m2.locale = function(t2, e2) { + if (!t2) return this.$L; + var n2 = this.clone(), r2 = w(t2, e2, true); + return r2 && (n2.$L = r2), n2; + }, m2.clone = function() { + return b.w(this.$d, this); + }, m2.toDate = function() { + return new Date(this.valueOf()); + }, m2.toJSON = function() { + return this.isValid() ? this.toISOString() : null; + }, m2.toISOString = function() { + return this.$d.toISOString(); + }, m2.toString = function() { + return this.$d.toUTCString(); + }, M2; + })(), k = _.prototype; + return O.prototype = k, [["$ms", r], ["$s", i], ["$m", s], ["$H", u], ["$W", a], ["$M", c], ["$y", h], ["$D", d]].forEach((function(t2) { + k[t2[1]] = function(e2) { + return this.$g(e2, t2[0], t2[1]); + }; + })), O.extend = function(t2, e2) { + return t2.$i || (t2(e2, _, O), t2.$i = true), O; + }, O.locale = w, O.isDayjs = S, O.unix = function(t2) { + return O(1e3 * t2); + }, O.en = D[g], O.Ls = D, O.p = {}, O; + })); + } +}); + +// node_modules/dayjs/plugin/utc.js +var require_utc = __commonJS({ + "node_modules/dayjs/plugin/utc.js"(exports, module) { + !(function(t, i) { + "object" == typeof exports && "undefined" != typeof module ? module.exports = i() : "function" == typeof define && define.amd ? define(i) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs_plugin_utc = i(); + })(exports, (function() { + "use strict"; + var t = "minute", i = /[+-]\d\d(?::?\d\d)?/g, e = /([+-]|\d\d)/g; + return function(s, f, n) { + var u = f.prototype; + n.utc = function(t2) { + var i2 = { date: t2, utc: true, args: arguments }; + return new f(i2); + }, u.utc = function(i2) { + var e2 = n(this.toDate(), { locale: this.$L, utc: true }); + return i2 ? e2.add(this.utcOffset(), t) : e2; + }, u.local = function() { + return n(this.toDate(), { locale: this.$L, utc: false }); + }; + var r = u.parse; + u.parse = function(t2) { + t2.utc && (this.$u = true), this.$utils().u(t2.$offset) || (this.$offset = t2.$offset), r.call(this, t2); + }; + var o = u.init; + u.init = function() { + if (this.$u) { + var t2 = this.$d; + this.$y = t2.getUTCFullYear(), this.$M = t2.getUTCMonth(), this.$D = t2.getUTCDate(), this.$W = t2.getUTCDay(), this.$H = t2.getUTCHours(), this.$m = t2.getUTCMinutes(), this.$s = t2.getUTCSeconds(), this.$ms = t2.getUTCMilliseconds(); + } else o.call(this); + }; + var a = u.utcOffset; + u.utcOffset = function(s2, f2) { + var n2 = this.$utils().u; + if (n2(s2)) return this.$u ? 0 : n2(this.$offset) ? a.call(this) : this.$offset; + if ("string" == typeof s2 && (s2 = (function(t2) { + void 0 === t2 && (t2 = ""); + var s3 = t2.match(i); + if (!s3) return null; + var f3 = ("" + s3[0]).match(e) || ["-", 0, 0], n3 = f3[0], u3 = 60 * +f3[1] + +f3[2]; + return 0 === u3 ? 0 : "+" === n3 ? u3 : -u3; + })(s2), null === s2)) return this; + var u2 = Math.abs(s2) <= 16 ? 60 * s2 : s2; + if (0 === u2) return this.utc(f2); + var r2 = this.clone(); + if (f2) return r2.$offset = u2, r2.$u = false, r2; + var o2 = this.$u ? this.toDate().getTimezoneOffset() : -1 * this.utcOffset(); + return (r2 = this.local().add(u2 + o2, t)).$offset = u2, r2.$x.$localOffset = o2, r2; + }; + var h = u.format; + u.format = function(t2) { + var i2 = t2 || (this.$u ? "YYYY-MM-DDTHH:mm:ss[Z]" : ""); + return h.call(this, i2); + }, u.valueOf = function() { + var t2 = this.$utils().u(this.$offset) ? 0 : this.$offset + (this.$x.$localOffset || this.$d.getTimezoneOffset()); + return this.$d.valueOf() - 6e4 * t2; + }, u.isUTC = function() { + return !!this.$u; + }, u.toISOString = function() { + return this.toDate().toISOString(); + }, u.toString = function() { + return this.toDate().toUTCString(); + }; + var l = u.toDate; + u.toDate = function(t2) { + return "s" === t2 && this.$offset ? n(this.format("YYYY-MM-DD HH:mm:ss:SSS")).toDate() : l.call(this); + }; + var c = u.diff; + u.diff = function(t2, i2, e2) { + if (t2 && this.$u === t2.$u) return c.call(this, t2, i2, e2); + var s2 = this.local(), f2 = n(t2).local(); + return c.call(s2, f2, i2, e2); + }; + }; + })); + } +}); + +// node_modules/dayjs/plugin/timezone.js +var require_timezone = __commonJS({ + "node_modules/dayjs/plugin/timezone.js"(exports, module) { + !(function(t, e) { + "object" == typeof exports && "undefined" != typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define(e) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs_plugin_timezone = e(); + })(exports, (function() { + "use strict"; + var t = { year: 0, month: 1, day: 2, hour: 3, minute: 4, second: 5 }, e = {}; + return function(n, i, o) { + var r, a = function(t2, n2, i2) { + void 0 === i2 && (i2 = {}); + var o2 = new Date(t2), r2 = (function(t3, n3) { + void 0 === n3 && (n3 = {}); + var i3 = n3.timeZoneName || "short", o3 = t3 + "|" + i3, r3 = e[o3]; + return r3 || (r3 = new Intl.DateTimeFormat("en-US", { hour12: false, timeZone: t3, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", timeZoneName: i3 }), e[o3] = r3), r3; + })(n2, i2); + return r2.formatToParts(o2); + }, u = function(e2, n2) { + for (var i2 = a(e2, n2), r2 = [], u2 = 0; u2 < i2.length; u2 += 1) { + var f2 = i2[u2], s2 = f2.type, m = f2.value, c = t[s2]; + c >= 0 && (r2[c] = parseInt(m, 10)); + } + var d = r2[3], l = 24 === d ? 0 : d, h = r2[0] + "-" + r2[1] + "-" + r2[2] + " " + l + ":" + r2[4] + ":" + r2[5] + ":000", v = +e2; + return (o.utc(h).valueOf() - (v -= v % 1e3)) / 6e4; + }, f = i.prototype; + f.tz = function(t2, e2) { + void 0 === t2 && (t2 = r); + var n2, i2 = this.utcOffset(), a2 = this.toDate(), u2 = a2.toLocaleString("en-US", { timeZone: t2 }), f2 = Math.round((a2 - new Date(u2)) / 1e3 / 60), s2 = 15 * -Math.round(a2.getTimezoneOffset() / 15) - f2; + if (!Number(s2)) n2 = this.utcOffset(0, e2); + else if (n2 = o(u2, { locale: this.$L }).$set("millisecond", this.$ms).utcOffset(s2, true), e2) { + var m = n2.utcOffset(); + n2 = n2.add(i2 - m, "minute"); + } + return n2.$x.$timezone = t2, n2; + }, f.offsetName = function(t2) { + var e2 = this.$x.$timezone || o.tz.guess(), n2 = a(this.valueOf(), e2, { timeZoneName: t2 }).find((function(t3) { + return "timezonename" === t3.type.toLowerCase(); + })); + return n2 && n2.value; + }; + var s = f.startOf; + f.startOf = function(t2, e2) { + if (!this.$x || !this.$x.$timezone) return s.call(this, t2, e2); + var n2 = o(this.format("YYYY-MM-DD HH:mm:ss:SSS"), { locale: this.$L }); + return s.call(n2, t2, e2).tz(this.$x.$timezone, true); + }, o.tz = function(t2, e2, n2) { + var i2 = n2 && e2, a2 = n2 || e2 || r, f2 = u(+o(), a2); + if ("string" != typeof t2) return o(t2).tz(a2); + var s2 = (function(t3, e3, n3) { + var i3 = t3 - 60 * e3 * 1e3, o2 = u(i3, n3); + if (e3 === o2) return [i3, e3]; + var r2 = u(i3 -= 60 * (o2 - e3) * 1e3, n3); + return o2 === r2 ? [i3, o2] : [t3 - 60 * Math.min(o2, r2) * 1e3, Math.max(o2, r2)]; + })(o.utc(t2, i2).valueOf(), f2, a2), m = s2[0], c = s2[1], d = o(m).utcOffset(c); + return d.$x.$timezone = a2, d; + }, o.tz.guess = function() { + return Intl.DateTimeFormat().resolvedOptions().timeZone; + }, o.tz.setDefault = function(t2) { + r = t2; + }; + }; + })); + } +}); + +// node_modules/dayjs/plugin/isoWeek.js +var require_isoWeek = __commonJS({ + "node_modules/dayjs/plugin/isoWeek.js"(exports, module) { + !(function(e, t) { + "object" == typeof exports && "undefined" != typeof module ? module.exports = t() : "function" == typeof define && define.amd ? define(t) : (e = "undefined" != typeof globalThis ? globalThis : e || self).dayjs_plugin_isoWeek = t(); + })(exports, (function() { + "use strict"; + var e = "day"; + return function(t, i, s) { + var a = function(t2) { + return t2.add(4 - t2.isoWeekday(), e); + }, d = i.prototype; + d.isoWeekYear = function() { + return a(this).year(); + }, d.isoWeek = function(t2) { + if (!this.$utils().u(t2)) return this.add(7 * (t2 - this.isoWeek()), e); + var i2, d2, n2, o, r = a(this), u = (i2 = this.isoWeekYear(), d2 = this.$u, n2 = (d2 ? s.utc : s)().year(i2).startOf("year"), o = 4 - n2.isoWeekday(), n2.isoWeekday() > 4 && (o += 7), n2.add(o, e)); + return r.diff(u, "week") + 1; + }, d.isoWeekday = function(e2) { + return this.$utils().u(e2) ? this.day() || 7 : this.day(this.day() % 7 ? e2 : e2 - 7); + }; + var n = d.startOf; + d.startOf = function(e2, t2) { + var i2 = this.$utils(), s2 = !!i2.u(t2) || t2; + return "isoweek" === i2.p(e2) ? s2 ? this.date(this.date() - (this.isoWeekday() - 1)).startOf("day") : this.date(this.date() - 1 - (this.isoWeekday() - 1) + 7).endOf("day") : n.bind(this)(e2, t2); + }; + }; + })); + } +}); + +// node_modules/@novadi/core/dist/token.js +var tokenCounter = 0; +function Token(description) { + const id = ++tokenCounter; + const sym = /* @__PURE__ */ Symbol(description ? `Token(${description})` : `Token#${id}`); + const token2 = { + symbol: sym, + description, + toString() { + return description ? `Token<${description}>` : `Token<#${id}>`; + } + }; + return token2; +} + +// node_modules/@novadi/core/dist/errors.js +var ContainerError = class extends Error { + constructor(message) { + super(message); + this.name = "ContainerError"; + } +}; +var BindingNotFoundError = class extends ContainerError { + constructor(tokenDescription, path = []) { + const pathStr = path.length > 0 ? ` + Dependency path: ${path.join(" -> ")}` : ""; + super(`Token "${tokenDescription}" is not bound or registered in the container.${pathStr}`); + this.name = "BindingNotFoundError"; + } +}; +var CircularDependencyError = class extends ContainerError { + constructor(path) { + super(`Circular dependency detected: ${path.join(" -> ")}`); + this.name = "CircularDependencyError"; + } +}; + +// node_modules/@novadi/core/dist/autowire.js +var paramNameCache = /* @__PURE__ */ new WeakMap(); +function extractParameterNames(constructor) { + const cached = paramNameCache.get(constructor); + if (cached) { + return cached; + } + const fnStr = constructor.toString(); + const match = fnStr.match(/constructor\s*\(([^)]*)\)/) || fnStr.match(/^[^(]*\(([^)]*)\)/); + if (!match || !match[1]) { + return []; + } + const params = match[1].split(",").map((param) => param.trim()).filter((param) => param.length > 0).map((param) => { + let name = param.split(/[:=]/)[0].trim(); + name = name.replace(/^((public|private|protected|readonly)\s+)+/, ""); + if (name.includes("{") || name.includes("[")) { + return null; + } + return name; + }).filter((name) => name !== null); + paramNameCache.set(constructor, params); + return params; +} +function resolveByMap(constructor, container, options) { + if (!options.map) { + throw new Error("AutoWire map strategy requires options.map to be defined"); + } + const paramNames = extractParameterNames(constructor); + const resolvedDeps = []; + for (const paramName of paramNames) { + const resolver = options.map[paramName]; + if (resolver === void 0) { + if (options.strict) { + throw new Error(`Cannot resolve parameter "${paramName}" on ${constructor.name}. Not found in autowire map. Add it to the map: .autoWire({ map: { ${paramName}: ... } })`); + } else { + resolvedDeps.push(void 0); + } + continue; + } + if (typeof resolver === "function") { + resolvedDeps.push(resolver(container)); + } else { + resolvedDeps.push(container.resolve(resolver)); + } + } + return resolvedDeps; +} +function resolveByMapResolvers(_constructor, container, options) { + if (!options.mapResolvers || options.mapResolvers.length === 0) { + return []; + } + const resolvedDeps = []; + for (let i = 0; i < options.mapResolvers.length; i++) { + const resolver = options.mapResolvers[i]; + if (resolver === void 0) { + resolvedDeps.push(void 0); + } else if (typeof resolver === "function") { + resolvedDeps.push(resolver(container)); + } else { + resolvedDeps.push(container.resolve(resolver)); + } + } + return resolvedDeps; +} +function autowire(constructor, container, options) { + const opts = { + by: "paramName", + strict: false, + ...options + }; + if (opts.mapResolvers && opts.mapResolvers.length > 0) { + return resolveByMapResolvers(constructor, container, opts); + } + if (opts.map && Object.keys(opts.map).length > 0) { + return resolveByMap(constructor, container, opts); + } + return []; +} + +// node_modules/@novadi/core/dist/builder.js +var RegistrationBuilder = class { + constructor(pending, registrations) { + this.registrations = registrations; + this.configs = []; + this.defaultLifetime = "singleton"; + this.pending = pending; + } + /** + * Bind this registration to a token or interface type + * + * @overload + * @param {Token} token - Explicit token for binding + * + * @overload + * @param {string} typeName - Interface type name (auto-generated by transformer) + */ + as(tokenOrTypeName) { + if (tokenOrTypeName && typeof tokenOrTypeName === "object" && "symbol" in tokenOrTypeName) { + const config = { + token: tokenOrTypeName, + type: this.pending.type, + value: this.pending.value, + factory: this.pending.factory, + constructor: this.pending.constructor, + lifetime: this.defaultLifetime + }; + this.configs.push(config); + this.registrations.push(config); + return this; + } else { + const config = { + token: null, + // Will be set during build() + type: this.pending.type, + value: this.pending.value, + factory: this.pending.factory, + constructor: this.pending.constructor, + lifetime: this.defaultLifetime, + interfaceType: tokenOrTypeName + }; + this.configs.push(config); + this.registrations.push(config); + return this; + } + } + /** + * Register as default implementation for an interface + * Combines as() + asDefault() + */ + asDefaultInterface(typeName) { + this.as("TInterface", typeName); + return this.asDefault(); + } + /** + * Register as a keyed interface implementation + * Combines as() + keyed() + */ + asKeyedInterface(key, typeName) { + this.as("TInterface", typeName); + return this.keyed(key); + } + /** + * Register as multiple implemented interfaces + */ + asImplementedInterfaces(tokens) { + if (tokens.length === 0) { + return this; + } + if (this.configs.length > 0) { + for (const config of this.configs) { + config.lifetime = "singleton"; + config.additionalTokens = config.additionalTokens || []; + config.additionalTokens.push(...tokens); + } + return this; + } + const firstConfig = { + token: tokens[0], + type: this.pending.type, + value: this.pending.value, + factory: this.pending.factory, + constructor: this.pending.constructor, + lifetime: "singleton" + }; + this.configs.push(firstConfig); + this.registrations.push(firstConfig); + for (let i = 1; i < tokens.length; i++) { + firstConfig.additionalTokens = firstConfig.additionalTokens || []; + firstConfig.additionalTokens.push(tokens[i]); + } + return this; + } + /** + * Set singleton lifetime (one instance for entire container) + */ + singleInstance() { + for (const config of this.configs) { + config.lifetime = "singleton"; + } + return this; + } + /** + * Set per-request lifetime (one instance per resolve call tree) + */ + instancePerRequest() { + for (const config of this.configs) { + config.lifetime = "per-request"; + } + return this; + } + /** + * Set transient lifetime (new instance every time) + * Alias for default behavior + */ + instancePerDependency() { + for (const config of this.configs) { + config.lifetime = "transient"; + } + return this; + } + /** + * Name this registration for named resolution + */ + named(name) { + for (const config of this.configs) { + config.name = name; + } + return this; + } + /** + * Key this registration for keyed resolution + */ + keyed(key) { + for (const config of this.configs) { + config.key = key; + } + return this; + } + /** + * Mark this as default registration + * Default registrations don't override existing ones + */ + asDefault() { + for (const config of this.configs) { + config.isDefault = true; + } + return this; + } + /** + * Only register if token not already registered + */ + ifNotRegistered() { + for (const config of this.configs) { + config.ifNotRegistered = true; + } + return this; + } + /** + * Specify parameter values for constructor (primitives and constants) + * Use this for non-DI parameters like strings, numbers, config values + */ + withParameters(parameters) { + for (const config of this.configs) { + config.parameterValues = parameters; + } + return this; + } + /** + * Enable automatic dependency injection (autowiring) + * Supports three strategies: paramName (default), map, and class + * + * @example + * ```ts + * // Strategy 1: paramName (default, requires non-minified code in dev) + * builder.registerType(EventBus).as().autoWire() + * + * // Strategy 2: map (minify-safe, explicit) + * builder.registerType(EventBus).as().autoWire({ + * map: { + * logger: (c) => c.resolveType() + * } + * }) + * + * // Strategy 3: class (requires build-time codegen) + * builder.registerType(EventBus).as().autoWire({ by: 'class' }) + * ``` + */ + autoWire(options) { + for (const config of this.configs) { + config.autowireOptions = options || { by: "paramName", strict: false }; + } + return this; + } +}; +var Builder = class { + constructor(baseContainer) { + this.baseContainer = baseContainer; + this.registrations = []; + } + /** + * Register a class constructor + */ + registerType(constructor) { + const pending = { + type: "type", + value: null, + constructor + }; + return new RegistrationBuilder(pending, this.registrations); + } + /** + * Register a pre-created instance + */ + registerInstance(instance) { + const pending = { + type: "instance", + value: instance, + constructor: void 0 + }; + return new RegistrationBuilder(pending, this.registrations); + } + /** + * Register a factory function + */ + register(factory) { + const pending = { + type: "factory", + value: null, + factory, + constructor: void 0 + }; + return new RegistrationBuilder(pending, this.registrations); + } + /** + * Register a module (function that adds multiple registrations) + */ + module(moduleFunc) { + moduleFunc(this); + return this; + } + /** + * Resolve interface type names to tokens + * @internal + */ + resolveInterfaceTokens(container) { + for (const config of this.registrations) { + if (config.interfaceType !== void 0 && !config.token) { + config.token = container.interfaceToken(config.interfaceType); + } + } + } + /** + * Identify tokens that have non-default registrations + * @internal + */ + identifyNonDefaultTokens() { + const tokensWithNonDefaults = /* @__PURE__ */ new Set(); + for (const config of this.registrations) { + if (!config.isDefault && !config.name && config.key === void 0) { + tokensWithNonDefaults.add(config.token); + } + } + return tokensWithNonDefaults; + } + /** + * Check if registration should be skipped + * @internal + */ + shouldSkipRegistration(config, tokensWithNonDefaults, registeredTokens) { + if (config.isDefault && !config.name && config.key === void 0 && tokensWithNonDefaults.has(config.token)) { + return true; + } + if (config.ifNotRegistered && registeredTokens.has(config.token)) { + return true; + } + if (config.isDefault && registeredTokens.has(config.token)) { + return true; + } + return false; + } + /** + * Create binding token for registration (named, keyed, or multi) + * @internal + */ + createBindingToken(config, namedRegistrations, keyedRegistrations, multiRegistrations) { + if (config.name) { + const bindingToken = Token(`__named_${config.name}`); + namedRegistrations.set(config.name, { ...config, token: bindingToken }); + return bindingToken; + } else if (config.key !== void 0) { + const keyStr = typeof config.key === "symbol" ? config.key.toString() : config.key; + const bindingToken = Token(`__keyed_${keyStr}`); + keyedRegistrations.set(config.key, { ...config, token: bindingToken }); + return bindingToken; + } else { + if (multiRegistrations.has(config.token)) { + const bindingToken = Token(`__multi_${config.token.toString()}_${multiRegistrations.get(config.token).length}`); + multiRegistrations.get(config.token).push(bindingToken); + return bindingToken; + } else { + multiRegistrations.set(config.token, [config.token]); + return config.token; + } + } + } + /** + * Register additional interfaces for a config + * @internal + */ + registerAdditionalInterfaces(container, config, bindingToken, registeredTokens) { + if (config.additionalTokens) { + for (const additionalToken of config.additionalTokens) { + container.bindFactory(additionalToken, (c) => c.resolve(bindingToken), { lifetime: config.lifetime }); + registeredTokens.add(additionalToken); + } + } + } + /** + * Build the container with all registered bindings + */ + build() { + const container = this.baseContainer.createChild(); + this.resolveInterfaceTokens(container); + const registeredTokens = /* @__PURE__ */ new Set(); + const namedRegistrations = /* @__PURE__ */ new Map(); + const keyedRegistrations = /* @__PURE__ */ new Map(); + const multiRegistrations = /* @__PURE__ */ new Map(); + const tokensWithNonDefaults = this.identifyNonDefaultTokens(); + for (const config of this.registrations) { + if (this.shouldSkipRegistration(config, tokensWithNonDefaults, registeredTokens)) { + continue; + } + const bindingToken = this.createBindingToken(config, namedRegistrations, keyedRegistrations, multiRegistrations); + this.applyRegistration(container, { ...config, token: bindingToken }); + registeredTokens.add(config.token); + this.registerAdditionalInterfaces(container, config, bindingToken, registeredTokens); + } + ; + container.__namedRegistrations = namedRegistrations; + container.__keyedRegistrations = keyedRegistrations; + container.__multiRegistrations = multiRegistrations; + return container; + } + /** + * Analyze constructor to detect dependencies + * @internal + */ + analyzeConstructor(constructor) { + const constructorStr = constructor.toString(); + const hasDependencies = /constructor\s*\([^)]+\)/.test(constructorStr); + return { hasDependencies }; + } + /** + * Create optimized factory for zero-dependency constructors + * @internal + */ + createOptimizedFactory(container, config, options) { + if (config.lifetime === "singleton") { + const instance = new config.constructor(); + container.bindValue(config.token, instance); + } else if (config.lifetime === "transient") { + const ctor = config.constructor; + const fastFactory = () => new ctor(); + container.fastTransientCache.set(config.token, fastFactory); + container.bindFactory(config.token, fastFactory, options); + } else { + const factory = () => new config.constructor(); + container.bindFactory(config.token, factory, options); + } + } + /** + * Create autowire factory + * @internal + */ + createAutoWireFactory(container, config, options) { + const factory = (c) => { + const resolvedDeps = autowire(config.constructor, c, config.autowireOptions); + return new config.constructor(...resolvedDeps); + }; + container.bindFactory(config.token, factory, options); + } + /** + * Create withParameters factory + * @internal + */ + createParameterFactory(container, config, options) { + const factory = () => { + const values = Object.values(config.parameterValues); + return new config.constructor(...values); + }; + container.bindFactory(config.token, factory, options); + } + /** + * Apply type registration (class constructor) + * @internal + */ + applyTypeRegistration(container, config, options) { + const { hasDependencies } = this.analyzeConstructor(config.constructor); + if (!hasDependencies && !config.autowireOptions && !config.parameterValues) { + this.createOptimizedFactory(container, config, options); + return; + } + if (config.autowireOptions) { + this.createAutoWireFactory(container, config, options); + return; + } + if (config.parameterValues) { + this.createParameterFactory(container, config, options); + return; + } + if (hasDependencies) { + const className = config.constructor.name || "UnnamedClass"; + throw new Error(`Service "${className}" has constructor dependencies but no autowiring configuration. + +Solutions: + 1. \u2B50 Use the NovaDI transformer (recommended): + - Add "@novadi/core/unplugin" to your build config + - Transformer automatically generates .autoWire() for all dependencies + + 2. Add manual autowiring: + .autoWire({ map: { /* param: resolver */ } }) + + 3. Use a factory function: + .register((c) => new ${className}(...)) + +See docs: https://github.com/janus007/NovaDI#autowire`); + } + const factory = () => new config.constructor(); + container.bindFactory(config.token, factory, options); + } + applyRegistration(container, config) { + const options = { lifetime: config.lifetime }; + switch (config.type) { + case "instance": + container.bindValue(config.token, config.value); + break; + case "factory": + container.bindFactory(config.token, config.factory, options); + break; + case "type": + this.applyTypeRegistration(container, config, options); + break; + } + } +}; + +// node_modules/@novadi/core/dist/container.js +function isDisposable(obj) { + return obj && typeof obj.dispose === "function"; +} +var ResolutionContext = class { + constructor() { + this.resolvingStack = /* @__PURE__ */ new Set(); + this.perRequestCache = /* @__PURE__ */ new Map(); + } + isResolving(token2) { + return this.resolvingStack.has(token2); + } + enterResolve(token2) { + this.resolvingStack.add(token2); + } + exitResolve(token2) { + this.resolvingStack.delete(token2); + this.path = void 0; + } + getPath() { + if (!this.path) { + this.path = Array.from(this.resolvingStack).map((t) => t.toString()); + } + return [...this.path]; + } + cachePerRequest(token2, instance) { + this.perRequestCache.set(token2, instance); + } + getPerRequest(token2) { + return this.perRequestCache.get(token2); + } + hasPerRequest(token2) { + return this.perRequestCache.has(token2); + } + /** + * Reset context for reuse in object pool + * Performance: Reusing contexts avoids heap allocations + */ + reset() { + this.resolvingStack.clear(); + this.perRequestCache.clear(); + this.path = void 0; + } +}; +var ResolutionContextPool = class { + constructor() { + this.pool = []; + this.maxSize = 10; + } + acquire() { + const context = this.pool.pop(); + if (context) { + context.reset(); + return context; + } + return new ResolutionContext(); + } + release(context) { + if (this.pool.length < this.maxSize) { + this.pool.push(context); + } + } +}; +var Container = class _Container { + constructor(parent) { + this.bindings = /* @__PURE__ */ new Map(); + this.singletonCache = /* @__PURE__ */ new Map(); + this.singletonOrder = []; + this.interfaceRegistry = /* @__PURE__ */ new Map(); + this.interfaceTokenCache = /* @__PURE__ */ new Map(); + this.fastTransientCache = /* @__PURE__ */ new Map(); + this.ultraFastSingletonCache = /* @__PURE__ */ new Map(); + this.parent = parent; + } + /** + * Bind a pre-created value to a token + */ + bindValue(token2, value) { + this.bindings.set(token2, { + type: "value", + lifetime: "singleton", + value, + constructor: void 0 + }); + this.invalidateBindingCache(); + } + /** + * Bind a factory function to a token + */ + bindFactory(token2, factory, options) { + this.bindings.set(token2, { + type: "factory", + lifetime: options?.lifetime || "transient", + factory, + dependencies: options?.dependencies, + constructor: void 0 + }); + this.invalidateBindingCache(); + } + /** + * Bind a class constructor to a token + */ + bindClass(token2, constructor, options) { + const binding = { + type: "class", + lifetime: options?.lifetime || "transient", + constructor, + dependencies: options?.dependencies + }; + this.bindings.set(token2, binding); + this.invalidateBindingCache(); + if (binding.lifetime === "transient" && (!binding.dependencies || binding.dependencies.length === 0)) { + this.fastTransientCache.set(token2, () => new constructor()); + } + } + /** + * Resolve a dependency synchronously + * Performance optimized with multiple fast paths + */ + resolve(token2) { + const cached = this.tryGetFromCaches(token2); + if (cached !== void 0) { + return cached; + } + if (this.currentContext) { + return this.resolveWithContext(token2, this.currentContext); + } + const context = _Container.contextPool.acquire(); + this.currentContext = context; + try { + return this.resolveWithContext(token2, context); + } finally { + this.currentContext = void 0; + _Container.contextPool.release(context); + } + } + /** + * SPECIALIZED: Ultra-fast singleton resolve (no safety checks) + * Use ONLY when you're 100% sure the token is a registered singleton + * @internal For performance-critical paths only + */ + resolveSingletonUnsafe(token2) { + return this.ultraFastSingletonCache.get(token2) ?? this.singletonCache.get(token2); + } + /** + * SPECIALIZED: Fast transient resolve for zero-dependency classes + * Skips all context creation and circular dependency checks + * @internal For performance-critical paths only + */ + resolveTransientSimple(token2) { + const factory = this.fastTransientCache.get(token2); + if (factory) { + return factory(); + } + return this.resolve(token2); + } + /** + * SPECIALIZED: Batch resolve multiple dependencies at once + * More efficient than multiple individual resolves + */ + resolveBatch(tokens) { + const wasResolving = !!this.currentContext; + const context = this.currentContext || _Container.contextPool.acquire(); + if (!wasResolving) { + this.currentContext = context; + } + try { + const results = tokens.map((token2) => { + const cached = this.tryGetFromCaches(token2); + if (cached !== void 0) + return cached; + return this.resolveWithContext(token2, context); + }); + return results; + } finally { + if (!wasResolving) { + this.currentContext = void 0; + _Container.contextPool.release(context); + } + } + } + /** + * Resolve a dependency asynchronously (supports async factories) + */ + async resolveAsync(token2) { + if (this.currentContext) { + return this.resolveAsyncWithContext(token2, this.currentContext); + } + const context = _Container.contextPool.acquire(); + this.currentContext = context; + try { + return await this.resolveAsyncWithContext(token2, context); + } finally { + this.currentContext = void 0; + _Container.contextPool.release(context); + } + } + /** + * Try to get instance from all cache levels + * Returns undefined if not cached + * @internal + */ + tryGetFromCaches(token2) { + const ultraFast = this.ultraFastSingletonCache.get(token2); + if (ultraFast !== void 0) { + return ultraFast; + } + if (this.singletonCache.has(token2)) { + const cached = this.singletonCache.get(token2); + this.ultraFastSingletonCache.set(token2, cached); + return cached; + } + const fastFactory = this.fastTransientCache.get(token2); + if (fastFactory) { + return fastFactory(); + } + return void 0; + } + /** + * Cache instance based on lifetime strategy + * @internal + */ + cacheInstance(token2, instance, lifetime, context) { + if (lifetime === "singleton") { + this.singletonCache.set(token2, instance); + this.singletonOrder.push(token2); + this.ultraFastSingletonCache.set(token2, instance); + } else if (lifetime === "per-request" && context) { + context.cachePerRequest(token2, instance); + } + } + /** + * Validate and get binding with circular dependency check + * Returns binding or throws error + * @internal + */ + validateAndGetBinding(token2, context) { + if (context.isResolving(token2)) { + throw new CircularDependencyError([...context.getPath(), token2.toString()]); + } + const binding = this.getBinding(token2); + if (!binding) { + throw new BindingNotFoundError(token2.toString(), context.getPath()); + } + return binding; + } + /** + * Instantiate from binding synchronously + * @internal + */ + instantiateBindingSync(binding, token2, context) { + switch (binding.type) { + case "value": + return binding.value; + case "factory": + const result = binding.factory(this); + if (result instanceof Promise) { + throw new Error(`Async factory detected for ${token2.toString()}. Use resolveAsync() instead.`); + } + return result; + case "class": + const deps = binding.dependencies || []; + const resolvedDeps = deps.map((dep) => this.resolveWithContext(dep, context)); + return new binding.constructor(...resolvedDeps); + case "inline-class": + return new binding.constructor(); + default: + throw new Error(`Unknown binding type: ${binding.type}`); + } + } + /** + * Instantiate from binding asynchronously + * @internal + */ + async instantiateBindingAsync(binding, context) { + switch (binding.type) { + case "value": + return binding.value; + case "factory": + return await Promise.resolve(binding.factory(this)); + case "class": + const deps = binding.dependencies || []; + const resolvedDeps = await Promise.all(deps.map((dep) => this.resolveAsyncWithContext(dep, context))); + return new binding.constructor(...resolvedDeps); + case "inline-class": + return new binding.constructor(); + default: + throw new Error(`Unknown binding type: ${binding.type}`); + } + } + /** + * Create a child container that inherits bindings from this container + */ + createChild() { + return new _Container(this); + } + /** + * Dispose all singleton instances in reverse registration order + */ + async dispose() { + const errors = []; + for (let i = this.singletonOrder.length - 1; i >= 0; i--) { + const token2 = this.singletonOrder[i]; + const instance = this.singletonCache.get(token2); + if (instance && isDisposable(instance)) { + try { + await instance.dispose(); + } catch (error) { + errors.push(error); + } + } + } + this.singletonCache.clear(); + this.singletonOrder.length = 0; + } + /** + * Create a fluent builder for registering dependencies + */ + builder() { + return new Builder(this); + } + /** + * Resolve a named service + */ + resolveNamed(name) { + const namedRegistrations = this.__namedRegistrations; + if (!namedRegistrations) { + throw new Error(`Named service "${name}" not found. No named registrations exist.`); + } + const config = namedRegistrations.get(name); + if (!config) { + throw new Error(`Named service "${name}" not found`); + } + return this.resolve(config.token); + } + /** + * Resolve a keyed service + */ + resolveKeyed(key) { + const keyedRegistrations = this.__keyedRegistrations; + if (!keyedRegistrations) { + throw new Error(`Keyed service not found. No keyed registrations exist.`); + } + const config = keyedRegistrations.get(key); + if (!config) { + const keyStr = typeof key === "symbol" ? key.toString() : `"${key}"`; + throw new Error(`Keyed service ${keyStr} not found`); + } + return this.resolve(config.token); + } + /** + * Resolve all registrations for a token + */ + resolveAll(token2) { + const multiRegistrations = this.__multiRegistrations; + if (!multiRegistrations) { + return []; + } + const tokens = multiRegistrations.get(token2); + if (!tokens || tokens.length === 0) { + return []; + } + return tokens.map((t) => this.resolve(t)); + } + /** + * Get registry information for debugging/visualization + * Returns array of binding information + */ + getRegistry() { + const registry = []; + this.bindings.forEach((binding, token2) => { + registry.push({ + token: token2.description || token2.symbol.toString(), + type: binding.type, + lifetime: binding.lifetime, + dependencies: binding.dependencies?.map((d) => d.description || d.symbol.toString()) + }); + }); + return registry; + } + /** + * Get or create a token for an interface type + * Uses a type name hash as key for the interface registry + */ + interfaceToken(typeName) { + const key = typeName || `Interface_${Math.random().toString(36).substr(2, 9)}`; + if (this.interfaceRegistry.has(key)) { + return this.interfaceRegistry.get(key); + } + if (this.parent) { + const parentToken = this.parent.interfaceToken(key); + return parentToken; + } + const token2 = Token(key); + this.interfaceRegistry.set(key, token2); + return token2; + } + /** + * Resolve a dependency by interface type without explicit token + */ + resolveType(typeName) { + const key = typeName || ""; + let token2 = this.interfaceTokenCache.get(key); + if (!token2) { + token2 = this.interfaceToken(typeName); + this.interfaceTokenCache.set(key, token2); + } + return this.resolve(token2); + } + /** + * Resolve a keyed interface + */ + resolveTypeKeyed(key, _typeName) { + return this.resolveKeyed(key); + } + /** + * Resolve all registrations for an interface type + */ + resolveTypeAll(typeName) { + const token2 = this.interfaceToken(typeName); + return this.resolveAll(token2); + } + /** + * Internal: Resolve with context for circular dependency detection + */ + resolveWithContext(token2, context) { + const binding = this.validateAndGetBinding(token2, context); + if (binding.lifetime === "per-request" && context.hasPerRequest(token2)) { + return context.getPerRequest(token2); + } + if (binding.lifetime === "singleton" && this.singletonCache.has(token2)) { + return this.singletonCache.get(token2); + } + context.enterResolve(token2); + try { + const instance = this.instantiateBindingSync(binding, token2, context); + this.cacheInstance(token2, instance, binding.lifetime, context); + return instance; + } finally { + context.exitResolve(token2); + } + } + /** + * Internal: Async resolve with context + */ + async resolveAsyncWithContext(token2, context) { + const binding = this.validateAndGetBinding(token2, context); + if (binding.lifetime === "per-request" && context.hasPerRequest(token2)) { + return context.getPerRequest(token2); + } + if (binding.lifetime === "singleton" && this.singletonCache.has(token2)) { + return this.singletonCache.get(token2); + } + context.enterResolve(token2); + try { + const instance = await this.instantiateBindingAsync(binding, context); + this.cacheInstance(token2, instance, binding.lifetime, context); + return instance; + } finally { + context.exitResolve(token2); + } + } + /** + * Get binding from this container or parent chain + * Performance optimized: Uses flat cache to avoid recursive parent lookups + */ + getBinding(token2) { + if (!this.bindingCache) { + this.buildBindingCache(); + } + return this.bindingCache.get(token2); + } + /** + * Build flat cache of all bindings including parent chain + * This converts O(n) parent chain traversal to O(1) lookup + */ + buildBindingCache() { + this.bindingCache = /* @__PURE__ */ new Map(); + let current = this; + while (current) { + current.bindings.forEach((binding, token2) => { + if (!this.bindingCache.has(token2)) { + this.bindingCache.set(token2, binding); + } + }); + current = current.parent; + } + } + /** + * Invalidate binding cache when new bindings are added + * Called by bindValue, bindFactory, bindClass + */ + invalidateBindingCache() { + this.bindingCache = void 0; + this.ultraFastSingletonCache.clear(); + } +}; +Container.contextPool = new ResolutionContextPool(); + +// node_modules/calendar/dist/chunk-OPIZ4QQE.js +var BaseGroupingRenderer = class { + /** + * Main render method - handles common logic + */ + async render(context) { + const allowedIds = context.filter[this.type] || []; + if (allowedIds.length === 0) + return; + const entities = await this.getEntities(allowedIds); + const dateCount = context.filter["date"]?.length || 1; + const childIds = context.childType ? context.filter[context.childType] || [] : []; + for (const entity of entities) { + const entityChildIds = context.parentChildMap?.[entity.id] || []; + const childCount = entityChildIds.filter((id) => childIds.includes(id)).length; + const colspan = childCount * dateCount; + const header = document.createElement(this.config.elementTag); + header.dataset[this.config.idAttribute] = entity.id; + header.style.setProperty(this.config.colspanVar, String(colspan)); + this.renderHeader(entity, header, context); + context.headerContainer.appendChild(header); + } + } + /** + * Override this method for custom header rendering + * Default: just sets textContent to display name + */ + renderHeader(entity, header, _context) { + header.textContent = this.getDisplayName(entity); + } + /** + * Helper to render a single entity header. + * Can be used by subclasses that override render() but want consistent header creation. + */ + createHeader(entity, context) { + const header = document.createElement(this.config.elementTag); + header.dataset[this.config.idAttribute] = entity.id; + this.renderHeader(entity, header, context); + return header; + } +}; + +// node_modules/calendar/dist/chunk-CMOI3H5F.js +var CoreEvents = { + // Lifecycle events + INITIALIZED: "core:initialized", + READY: "core:ready", + DESTROYED: "core:destroyed", + // View events + VIEW_CHANGED: "view:changed", + VIEW_RENDERED: "view:rendered", + // Navigation events + DATE_CHANGED: "nav:date-changed", + NAVIGATION_COMPLETED: "nav:navigation-completed", + // Data events + DATA_LOADING: "data:loading", + DATA_LOADED: "data:loaded", + DATA_ERROR: "data:error", + // Grid events + GRID_RENDERED: "grid:rendered", + GRID_CLICKED: "grid:clicked", + // Event management + EVENT_CREATED: "event:created", + EVENT_UPDATED: "event:updated", + EVENT_DELETED: "event:deleted", + EVENT_SELECTED: "event:selected", + // Event drag-drop + EVENT_DRAG_START: "event:drag-start", + EVENT_DRAG_MOVE: "event:drag-move", + EVENT_DRAG_END: "event:drag-end", + EVENT_DRAG_CANCEL: "event:drag-cancel", + EVENT_DRAG_COLUMN_CHANGE: "event:drag-column-change", + // Header drag (timed → header conversion) + EVENT_DRAG_ENTER_HEADER: "event:drag-enter-header", + EVENT_DRAG_MOVE_HEADER: "event:drag-move-header", + EVENT_DRAG_LEAVE_HEADER: "event:drag-leave-header", + // Event resize + EVENT_RESIZE_START: "event:resize-start", + EVENT_RESIZE_END: "event:resize-end", + // Edge scroll + EDGE_SCROLL_TICK: "edge-scroll:tick", + EDGE_SCROLL_STARTED: "edge-scroll:started", + EDGE_SCROLL_STOPPED: "edge-scroll:stopped", + // System events + ERROR: "system:error", + // Sync events + SYNC_STARTED: "sync:started", + SYNC_COMPLETED: "sync:completed", + SYNC_FAILED: "sync:failed", + // Entity events - for audit and sync + ENTITY_SAVED: "entity:saved", + ENTITY_DELETED: "entity:deleted", + // Audit events + AUDIT_LOGGED: "audit:logged", + // Rendering events + EVENTS_RENDERED: "events:rendered" +}; +var SyncPlugin = class { + constructor(service) { + this.service = service; + } + /** + * Mark entity as successfully synced + */ + async markAsSynced(id) { + const entity = await this.service.get(id); + if (entity) { + entity.syncStatus = "synced"; + await this.service.save(entity); + } + } + /** + * Mark entity as sync error + */ + async markAsError(id) { + const entity = await this.service.get(id); + if (entity) { + entity.syncStatus = "error"; + await this.service.save(entity); + } + } + /** + * Get current sync status for an entity + */ + async getSyncStatus(id) { + const entity = await this.service.get(id); + return entity ? entity.syncStatus : null; + } + /** + * Get entities by sync status using IndexedDB index + */ + async getBySyncStatus(syncStatus) { + return new Promise((resolve, reject) => { + const transaction = this.service.db.transaction([this.service.storeName], "readonly"); + const store = transaction.objectStore(this.service.storeName); + const index = store.index("syncStatus"); + const request = index.getAll(syncStatus); + request.onsuccess = () => { + const data = request.result; + const entities = data.map((item) => this.service.deserialize(item)); + resolve(entities); + }; + request.onerror = () => { + reject(new Error(`Failed to get by sync status ${syncStatus}: ${request.error}`)); + }; + }); + } +}; +function arrayDifference(first, second) { + const secondSet = new Set(second); + return first.filter((item) => !secondSet.has(item)); +} +function arrayIntersection(first, second) { + const secondSet = new Set(second); + return first.filter((item) => secondSet.has(item)); +} +function keyBy(arr, getKey2) { + const result = {}; + for (const item of arr) { + result[String(getKey2(item))] = item; + } + return result; +} +function diff(oldObj, newObj, options = {}) { + let { embeddedObjKeys } = options; + const { keysToSkip, treatTypeChangeAsReplace } = options; + if (embeddedObjKeys instanceof Map) { + embeddedObjKeys = new Map( + Array.from(embeddedObjKeys.entries()).map(([key, value]) => [ + key instanceof RegExp ? key : key.replace(/^\./, ""), + value + ]) + ); + } else if (embeddedObjKeys) { + embeddedObjKeys = Object.fromEntries( + Object.entries(embeddedObjKeys).map(([key, value]) => [key.replace(/^\./, ""), value]) + ); + } + return compare(oldObj, newObj, [], [], { + embeddedObjKeys, + keysToSkip: keysToSkip ?? [], + treatTypeChangeAsReplace: treatTypeChangeAsReplace ?? true + }); +} +var getTypeOfObj = (obj) => { + if (typeof obj === "undefined") { + return "undefined"; + } + if (obj === null) { + return null; + } + return Object.prototype.toString.call(obj).match(/^\[object\s(.*)\]$/)[1]; +}; +var getKey = (path) => { + const left = path[path.length - 1]; + return left != null ? left : "$root"; +}; +var compare = (oldObj, newObj, path, keyPath, options) => { + let changes = []; + const currentPath = keyPath.join("."); + if (options.keysToSkip?.some((skipPath) => { + if (currentPath === skipPath) { + return true; + } + if (skipPath.includes(".") && skipPath.startsWith(currentPath + ".")) { + return false; + } + if (skipPath.includes(".")) { + const skipParts = skipPath.split("."); + const currentParts = currentPath.split("."); + if (currentParts.length >= skipParts.length) { + for (let i = 0; i < skipParts.length; i++) { + if (skipParts[i] !== currentParts[i]) { + return false; + } + } + return true; + } + } + return false; + })) { + return changes; + } + const typeOfOldObj = getTypeOfObj(oldObj); + const typeOfNewObj = getTypeOfObj(newObj); + if (options.treatTypeChangeAsReplace && typeOfOldObj !== typeOfNewObj) { + if (typeOfOldObj !== "undefined") { + changes.push({ type: "REMOVE", key: getKey(path), value: oldObj }); + } + if (typeOfNewObj !== "undefined") { + changes.push({ type: "ADD", key: getKey(path), value: newObj }); + } + return changes; + } + if (typeOfNewObj === "undefined" && typeOfOldObj !== "undefined") { + changes.push({ type: "REMOVE", key: getKey(path), value: oldObj }); + return changes; + } + if (typeOfNewObj === "Object" && typeOfOldObj === "Array") { + changes.push({ type: "UPDATE", key: getKey(path), value: newObj, oldValue: oldObj }); + return changes; + } + if (typeOfNewObj === null) { + if (typeOfOldObj !== null) { + changes.push({ type: "UPDATE", key: getKey(path), value: newObj, oldValue: oldObj }); + } + return changes; + } + switch (typeOfOldObj) { + case "Date": + if (typeOfNewObj === "Date") { + changes = changes.concat( + comparePrimitives(oldObj.getTime(), newObj.getTime(), path).map((x) => ({ + ...x, + value: new Date(x.value), + oldValue: new Date(x.oldValue) + })) + ); + } else { + changes = changes.concat(comparePrimitives(oldObj, newObj, path)); + } + break; + case "Object": { + const diffs = compareObject(oldObj, newObj, path, keyPath, false, options); + if (diffs.length) { + if (path.length) { + changes.push({ + type: "UPDATE", + key: getKey(path), + changes: diffs + }); + } else { + changes = changes.concat(diffs); + } + } + break; + } + case "Array": + changes = changes.concat(compareArray(oldObj, newObj, path, keyPath, options)); + break; + case "Function": + break; + default: + changes = changes.concat(comparePrimitives(oldObj, newObj, path)); + } + return changes; +}; +var compareObject = (oldObj, newObj, path, keyPath, skipPath = false, options = {}) => { + let k; + let newKeyPath; + let newPath; + if (skipPath == null) { + skipPath = false; + } + let changes = []; + const oldObjKeys = Object.keys(oldObj); + const newObjKeys = Object.keys(newObj); + const intersectionKeys = arrayIntersection(oldObjKeys, newObjKeys); + for (k of intersectionKeys) { + newPath = path.concat([k]); + newKeyPath = skipPath ? keyPath : keyPath.concat([k]); + const diffs = compare(oldObj[k], newObj[k], newPath, newKeyPath, options); + if (diffs.length) { + changes = changes.concat(diffs); + } + } + const addedKeys = arrayDifference(newObjKeys, oldObjKeys); + for (k of addedKeys) { + newPath = path.concat([k]); + newKeyPath = skipPath ? keyPath : keyPath.concat([k]); + const currentPath = newKeyPath.join("."); + if (options.keysToSkip?.some((skipPath2) => currentPath === skipPath2 || currentPath.startsWith(skipPath2 + "."))) { + continue; + } + changes.push({ + type: "ADD", + key: getKey(newPath), + value: newObj[k] + }); + } + const deletedKeys = arrayDifference(oldObjKeys, newObjKeys); + for (k of deletedKeys) { + newPath = path.concat([k]); + newKeyPath = skipPath ? keyPath : keyPath.concat([k]); + const currentPath = newKeyPath.join("."); + if (options.keysToSkip?.some((skipPath2) => currentPath === skipPath2 || currentPath.startsWith(skipPath2 + "."))) { + continue; + } + changes.push({ + type: "REMOVE", + key: getKey(newPath), + value: oldObj[k] + }); + } + return changes; +}; +var compareArray = (oldObj, newObj, path, keyPath, options) => { + if (getTypeOfObj(newObj) !== "Array") { + return [{ type: "UPDATE", key: getKey(path), value: newObj, oldValue: oldObj }]; + } + const left = getObjectKey(options.embeddedObjKeys, keyPath); + const uniqKey = left != null ? left : "$index"; + const indexedOldObj = convertArrayToObj(oldObj, uniqKey); + const indexedNewObj = convertArrayToObj(newObj, uniqKey); + const diffs = compareObject(indexedOldObj, indexedNewObj, path, keyPath, true, options); + if (diffs.length) { + return [ + { + type: "UPDATE", + key: getKey(path), + embeddedKey: typeof uniqKey === "function" && uniqKey.length === 2 ? uniqKey(newObj[0], true) : uniqKey, + changes: diffs + } + ]; + } else { + return []; + } +}; +var getObjectKey = (embeddedObjKeys, keyPath) => { + if (embeddedObjKeys != null) { + const path = keyPath.join("."); + if (embeddedObjKeys instanceof Map) { + for (const [key2, value] of embeddedObjKeys.entries()) { + if (key2 instanceof RegExp) { + if (path.match(key2)) { + return value; + } + } else if (path === key2) { + return value; + } + } + } + const key = embeddedObjKeys[path]; + if (key != null) { + return key; + } + } + return void 0; +}; +var convertArrayToObj = (arr, uniqKey) => { + let obj = {}; + if (uniqKey === "$value") { + arr.forEach((value) => { + obj[value] = value; + }); + } else if (uniqKey !== "$index") { + const keyFunction = typeof uniqKey === "string" ? (item) => item[uniqKey] : uniqKey; + obj = keyBy(arr, keyFunction); + } else { + for (let i = 0; i < arr.length; i++) { + const value = arr[i]; + obj[i] = value; + } + } + return obj; +}; +var comparePrimitives = (oldObj, newObj, path) => { + const changes = []; + if (oldObj !== newObj) { + changes.push({ + type: "UPDATE", + key: getKey(path), + value: newObj, + oldValue: oldObj + }); + } + return changes; +}; +var BaseEntityService = class { + constructor(context, eventBus) { + this.context = context; + this.eventBus = eventBus; + this.syncPlugin = new SyncPlugin(this); + } + get db() { + return this.context.getDatabase(); + } + /** + * Serialize entity before storing in IndexedDB + */ + serialize(entity) { + return entity; + } + /** + * Deserialize data from IndexedDB back to entity + */ + deserialize(data) { + return data; + } + /** + * Get a single entity by ID + */ + async get(id) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const request = store.get(id); + request.onsuccess = () => { + const data = request.result; + resolve(data ? this.deserialize(data) : null); + }; + request.onerror = () => { + reject(new Error(`Failed to get ${this.entityType} ${id}: ${request.error}`)); + }; + }); + } + /** + * Get all entities + */ + async getAll() { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const request = store.getAll(); + request.onsuccess = () => { + const data = request.result; + const entities = data.map((item) => this.deserialize(item)); + resolve(entities); + }; + request.onerror = () => { + reject(new Error(`Failed to get all ${this.entityType}s: ${request.error}`)); + }; + }); + } + /** + * Save an entity (create or update) + * Emits ENTITY_SAVED event with operation type and changes (diff for updates) + * @param entity - Entity to save + * @param silent - If true, skip event emission (used for seeding) + */ + async save(entity, silent = false) { + const entityId = entity.id; + const existingEntity = await this.get(entityId); + const isCreate = existingEntity === null; + let changes; + if (isCreate) { + changes = entity; + } else { + const existingSerialized = this.serialize(existingEntity); + const newSerialized = this.serialize(entity); + changes = diff(existingSerialized, newSerialized); + } + const serialized = this.serialize(entity); + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readwrite"); + const store = transaction.objectStore(this.storeName); + const request = store.put(serialized); + request.onsuccess = () => { + if (!silent) { + const payload = { + entityType: this.entityType, + entityId, + operation: isCreate ? "create" : "update", + changes, + timestamp: Date.now() + }; + this.eventBus.emit(CoreEvents.ENTITY_SAVED, payload); + } + resolve(); + }; + request.onerror = () => { + reject(new Error(`Failed to save ${this.entityType} ${entityId}: ${request.error}`)); + }; + }); + } + /** + * Delete an entity + * Emits ENTITY_DELETED event + */ + async delete(id) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readwrite"); + const store = transaction.objectStore(this.storeName); + const request = store.delete(id); + request.onsuccess = () => { + const payload = { + entityType: this.entityType, + entityId: id, + operation: "delete", + timestamp: Date.now() + }; + this.eventBus.emit(CoreEvents.ENTITY_DELETED, payload); + resolve(); + }; + request.onerror = () => { + reject(new Error(`Failed to delete ${this.entityType} ${id}: ${request.error}`)); + }; + }); + } + // Sync methods - delegate to SyncPlugin + async markAsSynced(id) { + return this.syncPlugin.markAsSynced(id); + } + async markAsError(id) { + return this.syncPlugin.markAsError(id); + } + async getSyncStatus(id) { + return this.syncPlugin.getSyncStatus(id); + } + async getBySyncStatus(syncStatus) { + return this.syncPlugin.getBySyncStatus(syncStatus); + } +}; + +// node_modules/calendar/dist/index.js +var import_dayjs = __toESM(require_dayjs_min(), 1); +var import_utc = __toESM(require_utc(), 1); +var import_timezone = __toESM(require_timezone(), 1); +var import_isoWeek = __toESM(require_isoWeek(), 1); +var NavigationAnimator = class { + constructor(headerTrack, contentTrack, headerDrawer) { + this.headerTrack = headerTrack; + this.contentTrack = contentTrack; + this.headerDrawer = headerDrawer; + } + async slide(direction, renderFn) { + const out = direction === "left" ? "-100%" : "100%"; + const into = direction === "left" ? "100%" : "-100%"; + await this.animateOut(out); + await renderFn(); + await this.animateIn(into); + } + async animateOut(translate) { + const animations = [ + this.headerTrack.animate([{ transform: "translateX(0)" }, { transform: `translateX(${translate})` }], { duration: 200, easing: "ease-in" }).finished, + this.contentTrack.animate([{ transform: "translateX(0)" }, { transform: `translateX(${translate})` }], { duration: 200, easing: "ease-in" }).finished + ]; + if (this.headerDrawer) { + animations.push(this.headerDrawer.animate([{ transform: "translateX(0)" }, { transform: `translateX(${translate})` }], { duration: 200, easing: "ease-in" }).finished); + } + await Promise.all(animations); + } + async animateIn(translate) { + const animations = [ + this.headerTrack.animate([{ transform: `translateX(${translate})` }, { transform: "translateX(0)" }], { duration: 200, easing: "ease-out" }).finished, + this.contentTrack.animate([{ transform: `translateX(${translate})` }, { transform: "translateX(0)" }], { duration: 200, easing: "ease-out" }).finished + ]; + if (this.headerDrawer) { + animations.push(this.headerDrawer.animate([{ transform: `translateX(${translate})` }, { transform: "translateX(0)" }], { duration: 200, easing: "ease-out" }).finished); + } + await Promise.all(animations); + } +}; +var CalendarEvents = { + // Command events (host → calendar) + CMD_NAVIGATE_PREV: "calendar:cmd:navigate:prev", + CMD_NAVIGATE_NEXT: "calendar:cmd:navigate:next", + CMD_DRAWER_TOGGLE: "calendar:cmd:drawer:toggle", + CMD_RENDER: "calendar:cmd:render", + CMD_WORKWEEK_CHANGE: "calendar:cmd:workweek:change", + CMD_VIEW_UPDATE: "calendar:cmd:view:update" +}; +var CalendarApp = class { + constructor(orchestrator, timeAxisRenderer, dateService, scrollManager, headerDrawerManager, dragDropManager, edgeScrollManager, resizeManager, headerDrawerRenderer, eventPersistenceManager, settingsService, viewConfigService, eventBus) { + this.orchestrator = orchestrator; + this.timeAxisRenderer = timeAxisRenderer; + this.dateService = dateService; + this.scrollManager = scrollManager; + this.headerDrawerManager = headerDrawerManager; + this.dragDropManager = dragDropManager; + this.edgeScrollManager = edgeScrollManager; + this.resizeManager = resizeManager; + this.headerDrawerRenderer = headerDrawerRenderer; + this.eventPersistenceManager = eventPersistenceManager; + this.settingsService = settingsService; + this.viewConfigService = viewConfigService; + this.eventBus = eventBus; + this.dayOffset = 0; + this.currentViewId = "simple"; + this.workweekPreset = null; + this.groupingOverrides = /* @__PURE__ */ new Map(); + } + async init(container) { + this.container = container; + const gridSettings = await this.settingsService.getGridSettings(); + if (!gridSettings) { + throw new Error("GridSettings not found"); + } + this.workweekPreset = await this.settingsService.getDefaultWorkweekPreset(); + this.animator = new NavigationAnimator(container.querySelector("swp-header-track"), container.querySelector("swp-content-track"), container.querySelector("swp-header-drawer")); + this.timeAxisRenderer.render(container.querySelector("#time-axis"), gridSettings.dayStartHour, gridSettings.dayEndHour); + this.scrollManager.init(container); + this.headerDrawerManager.init(container); + this.dragDropManager.init(container); + this.resizeManager.init(container); + const scrollableContent = container.querySelector("swp-scrollable-content"); + this.edgeScrollManager.init(scrollableContent); + this.setupEventListeners(); + this.emitStatus("ready"); + } + setupEventListeners() { + this.eventBus.on(CalendarEvents.CMD_NAVIGATE_PREV, () => { + this.handleNavigatePrev(); + }); + this.eventBus.on(CalendarEvents.CMD_NAVIGATE_NEXT, () => { + this.handleNavigateNext(); + }); + this.eventBus.on(CalendarEvents.CMD_DRAWER_TOGGLE, () => { + this.headerDrawerManager.toggle(); + }); + this.eventBus.on(CalendarEvents.CMD_RENDER, (e) => { + const { viewId } = e.detail; + this.handleRenderCommand(viewId); + }); + this.eventBus.on(CalendarEvents.CMD_WORKWEEK_CHANGE, (e) => { + const { presetId } = e.detail; + this.handleWorkweekChange(presetId); + }); + this.eventBus.on(CalendarEvents.CMD_VIEW_UPDATE, (e) => { + const { type, values } = e.detail; + this.handleViewUpdate(type, values); + }); + } + async handleRenderCommand(viewId) { + this.currentViewId = viewId; + await this.render(); + this.emitStatus("rendered", { viewId }); + } + async handleNavigatePrev() { + const step = this.workweekPreset?.periodDays ?? 7; + this.dayOffset -= step; + await this.animator.slide("right", () => this.render()); + this.emitStatus("rendered", { viewId: this.currentViewId }); + } + async handleNavigateNext() { + const step = this.workweekPreset?.periodDays ?? 7; + this.dayOffset += step; + await this.animator.slide("left", () => this.render()); + this.emitStatus("rendered", { viewId: this.currentViewId }); + } + async handleWorkweekChange(presetId) { + const preset = await this.settingsService.getWorkweekPreset(presetId); + if (preset) { + this.workweekPreset = preset; + await this.render(); + this.emitStatus("rendered", { viewId: this.currentViewId }); + } + } + async handleViewUpdate(type, values) { + this.groupingOverrides.set(type, values); + await this.render(); + this.emitStatus("rendered", { viewId: this.currentViewId }); + } + async render() { + const storedConfig = await this.viewConfigService.getById(this.currentViewId); + if (!storedConfig) { + this.emitStatus("error", { message: `ViewConfig not found: ${this.currentViewId}` }); + return; + } + const workDays = this.workweekPreset?.workDays || [1, 2, 3, 4, 5]; + const periodDays = this.workweekPreset?.periodDays ?? 7; + const dates = periodDays === 1 ? this.dateService.getDatesFromOffset(this.dayOffset, workDays.length) : this.dateService.getWorkDaysFromOffset(this.dayOffset, workDays); + const viewConfig = { + ...storedConfig, + groupings: storedConfig.groupings.map((g) => { + if (g.type === "date") { + return { ...g, values: dates }; + } + const override = this.groupingOverrides.get(g.type); + if (override) { + return { ...g, values: override }; + } + return g; + }) + }; + await this.orchestrator.render(viewConfig, this.container); + } + emitStatus(status, detail) { + this.container.dispatchEvent(new CustomEvent(`calendar:status:${status}`, { + detail, + bubbles: true + })); + } +}; +function buildPipeline(renderers) { + return { + async run(context) { + for (const renderer of renderers) { + await renderer.render(context); + } + } + }; +} +var FilterTemplate = class { + constructor(dateService, entityResolver) { + this.dateService = dateService; + this.entityResolver = entityResolver; + this.fields = []; + } + /** + * Tilføj felt til template + * @param idProperty - Property-navn (bruges på både event og column.dataset) + * @param derivedFrom - Hvis feltet udledes fra anden property (f.eks. date fra start) + */ + addField(idProperty, derivedFrom) { + this.fields.push({ idProperty, derivedFrom }); + return this; + } + /** + * Parse dot-notation string into components + * @example 'resource.teamId' → { entityType: 'resource', property: 'teamId', foreignKey: 'resourceId' } + */ + parseDotNotation(idProperty) { + if (!idProperty.includes(".")) + return null; + const [entityType, property] = idProperty.split("."); + return { + entityType, + property, + foreignKey: entityType + "Id" + // Convention: resource → resourceId + }; + } + /** + * Get dataset key for column lookup + * For dot-notation 'resource.teamId', we look for 'teamId' in dataset + */ + getDatasetKey(idProperty) { + const dotNotation = this.parseDotNotation(idProperty); + if (dotNotation) { + return dotNotation.property; + } + return idProperty; + } + /** + * Byg nøgle fra kolonne + * Læser værdier fra column.dataset[idProperty] + * For dot-notation, uses the property part (resource.teamId → teamId) + */ + buildKeyFromColumn(column) { + return this.fields.map((f) => { + const key = this.getDatasetKey(f.idProperty); + return column.dataset[key] || ""; + }).join(":"); + } + /** + * Byg nøgle fra event + * Læser værdier fra event[idProperty] eller udleder fra derivedFrom + * For dot-notation, resolves via EntityResolver + */ + buildKeyFromEvent(event) { + const eventRecord = event; + return this.fields.map((f) => { + const dotNotation = this.parseDotNotation(f.idProperty); + if (dotNotation) { + return this.resolveDotNotation(eventRecord, dotNotation); + } + if (f.derivedFrom) { + const sourceValue = eventRecord[f.derivedFrom]; + if (sourceValue instanceof Date) { + return this.dateService.getDateKey(sourceValue); + } + return String(sourceValue || ""); + } + return String(eventRecord[f.idProperty] || ""); + }).join(":"); + } + /** + * Resolve dot-notation reference via EntityResolver + */ + resolveDotNotation(eventRecord, dotNotation) { + if (!this.entityResolver) { + console.warn(`FilterTemplate: EntityResolver required for dot-notation '${dotNotation.entityType}.${dotNotation.property}'`); + return ""; + } + const foreignId = eventRecord[dotNotation.foreignKey]; + if (!foreignId) + return ""; + const entity = this.entityResolver.resolve(dotNotation.entityType, String(foreignId)); + if (!entity) + return ""; + return String(entity[dotNotation.property] || ""); + } + /** + * Match event mod kolonne + */ + matches(event, column) { + return this.buildKeyFromEvent(event) === this.buildKeyFromColumn(column); + } +}; +var CalendarOrchestrator = class { + constructor(allRenderers, eventRenderer, scheduleRenderer, headerDrawerRenderer, dateService, entityServices) { + this.allRenderers = allRenderers; + this.eventRenderer = eventRenderer; + this.scheduleRenderer = scheduleRenderer; + this.headerDrawerRenderer = headerDrawerRenderer; + this.dateService = dateService; + this.entityServices = entityServices; + } + async render(viewConfig, container) { + const headerContainer = container.querySelector("swp-calendar-header"); + const columnContainer = container.querySelector("swp-day-columns"); + if (!headerContainer || !columnContainer) { + throw new Error("Missing swp-calendar-header or swp-day-columns"); + } + const filter = {}; + for (const grouping of viewConfig.groupings) { + filter[grouping.type] = grouping.values; + } + const filterTemplate = new FilterTemplate(this.dateService); + for (const grouping of viewConfig.groupings) { + if (grouping.idProperty) { + filterTemplate.addField(grouping.idProperty, grouping.derivedFrom); + } + } + const { parentChildMap, childType } = await this.resolveBelongsTo(viewConfig.groupings, filter); + const context = { headerContainer, columnContainer, filter, groupings: viewConfig.groupings, parentChildMap, childType }; + headerContainer.innerHTML = ""; + columnContainer.innerHTML = ""; + const levels = viewConfig.groupings.map((g) => g.type).join(" "); + headerContainer.dataset.levels = levels; + const activeRenderers = this.selectRenderers(viewConfig); + const pipeline = buildPipeline(activeRenderers); + await pipeline.run(context); + await this.scheduleRenderer.render(container, filter); + await this.eventRenderer.render(container, filter, filterTemplate); + await this.headerDrawerRenderer.render(container, filter, filterTemplate); + } + selectRenderers(viewConfig) { + const types = viewConfig.groupings.map((g) => g.type); + return types.map((type) => this.allRenderers.find((r) => r.type === type)).filter((r) => r !== void 0); + } + /** + * Resolve belongsTo relations to build parent-child map + * e.g., belongsTo: 'team.resourceIds' → { team1: ['EMP001', 'EMP002'], team2: [...] } + * Also returns the childType (the grouping type that has belongsTo) + */ + async resolveBelongsTo(groupings, filter) { + const childGrouping = groupings.find((g) => g.belongsTo); + if (!childGrouping?.belongsTo) + return {}; + const [entityType, property] = childGrouping.belongsTo.split("."); + if (!entityType || !property) + return {}; + const parentIds = filter[entityType] || []; + if (parentIds.length === 0) + return {}; + const service = this.entityServices.find((s) => s.entityType.toLowerCase() === entityType); + if (!service) + return {}; + const allEntities = await service.getAll(); + const entities = allEntities.filter((e) => parentIds.includes(e.id)); + const map = {}; + for (const entity of entities) { + const entityRecord = entity; + const children = entityRecord[property] || []; + map[entityRecord.id] = children; + } + return { parentChildMap: map, childType: childGrouping.type }; + } +}; +var EventBus = class { + constructor() { + this.eventLog = []; + this.debug = false; + this.listeners = /* @__PURE__ */ new Set(); + this.logConfig = { + calendar: true, + grid: true, + event: true, + scroll: true, + navigation: true, + view: true, + default: true + }; + } + /** + * Subscribe to an event via DOM addEventListener + */ + on(eventType, handler, options) { + document.addEventListener(eventType, handler, options); + this.listeners.add({ eventType, handler, options }); + return () => this.off(eventType, handler); + } + /** + * Subscribe to an event once + */ + once(eventType, handler) { + return this.on(eventType, handler, { once: true }); + } + /** + * Unsubscribe from an event + */ + off(eventType, handler) { + document.removeEventListener(eventType, handler); + for (const listener of this.listeners) { + if (listener.eventType === eventType && listener.handler === handler) { + this.listeners.delete(listener); + break; + } + } + } + /** + * Emit an event via DOM CustomEvent + */ + emit(eventType, detail = {}) { + if (!eventType) { + return false; + } + const event = new CustomEvent(eventType, { + detail: detail ?? {}, + bubbles: true, + cancelable: true + }); + if (this.debug) { + this.logEventWithGrouping(eventType, detail); + } + this.eventLog.push({ + type: eventType, + detail: detail ?? {}, + timestamp: Date.now() + }); + return !document.dispatchEvent(event); + } + /** + * Log event with console grouping + */ + logEventWithGrouping(eventType, _detail) { + const category = this.extractCategory(eventType); + if (!this.logConfig[category]) { + return; + } + this.getCategoryStyle(category); + } + /** + * Extract category from event type + */ + extractCategory(eventType) { + if (!eventType) { + return "unknown"; + } + if (eventType.includes(":")) { + return eventType.split(":")[0]; + } + const lowerType = eventType.toLowerCase(); + if (lowerType.includes("grid") || lowerType.includes("rendered")) + return "grid"; + if (lowerType.includes("event") || lowerType.includes("sync")) + return "event"; + if (lowerType.includes("scroll")) + return "scroll"; + if (lowerType.includes("nav") || lowerType.includes("date")) + return "navigation"; + if (lowerType.includes("view")) + return "view"; + return "default"; + } + /** + * Get styling for different categories + */ + getCategoryStyle(category) { + const styles = { + calendar: { emoji: "\u{1F4C5}", color: "#2196F3" }, + grid: { emoji: "\u{1F4CA}", color: "#4CAF50" }, + event: { emoji: "\u{1F4CC}", color: "#FF9800" }, + scroll: { emoji: "\u{1F4DC}", color: "#9C27B0" }, + navigation: { emoji: "\u{1F9ED}", color: "#F44336" }, + view: { emoji: "\u{1F441}", color: "#00BCD4" }, + default: { emoji: "\u{1F4E2}", color: "#607D8B" } + }; + return styles[category] || styles.default; + } + /** + * Configure logging for specific categories + */ + setLogConfig(config) { + this.logConfig = { ...this.logConfig, ...config }; + } + /** + * Get current log configuration + */ + getLogConfig() { + return { ...this.logConfig }; + } + /** + * Get event history + */ + getEventLog(eventType) { + if (eventType) { + return this.eventLog.filter((e) => e.type === eventType); + } + return this.eventLog; + } + /** + * Enable/disable debug mode + */ + setDebug(enabled) { + this.debug = enabled; + } +}; +import_dayjs.default.extend(import_utc.default); +import_dayjs.default.extend(import_timezone.default); +import_dayjs.default.extend(import_isoWeek.default); +var DateService = class { + constructor(config, baseDate) { + this.config = config; + this.timezone = config.timezone; + this.baseDate = baseDate ? (0, import_dayjs.default)(baseDate) : (0, import_dayjs.default)(); + } + /** + * Set a fixed base date (useful for demos with static mock data) + */ + setBaseDate(date) { + this.baseDate = (0, import_dayjs.default)(date); + } + /** + * Get the current base date (either fixed or today) + */ + getBaseDate() { + return this.baseDate.toDate(); + } + parseISO(isoString) { + return (0, import_dayjs.default)(isoString).toDate(); + } + getDayName(date, format = "short") { + return new Intl.DateTimeFormat(this.config.locale, { weekday: format }).format(date); + } + /** + * Get dates starting from a day offset + * @param dayOffset - Day offset from base date + * @param count - Number of consecutive days to return + * @returns Array of date strings in YYYY-MM-DD format + */ + getDatesFromOffset(dayOffset, count) { + const startDate = this.baseDate.add(dayOffset, "day"); + return Array.from({ length: count }, (_, i) => startDate.add(i, "day").format("YYYY-MM-DD")); + } + /** + * Get specific weekdays from the week containing the offset date + * @param dayOffset - Day offset from base date + * @param workDays - Array of ISO weekday numbers (1=Monday, 7=Sunday) + * @returns Array of date strings in YYYY-MM-DD format + */ + getWorkDaysFromOffset(dayOffset, workDays) { + const targetDate = this.baseDate.add(dayOffset, "day"); + const monday = targetDate.startOf("week").add(1, "day"); + return workDays.map((isoDay) => { + const daysFromMonday = isoDay === 7 ? 6 : isoDay - 1; + return monday.add(daysFromMonday, "day").format("YYYY-MM-DD"); + }); + } + // Legacy methods for backwards compatibility + getWeekDates(weekOffset = 0, days = 7) { + return this.getDatesFromOffset(weekOffset * 7, days); + } + getWorkWeekDates(weekOffset, workDays) { + return this.getWorkDaysFromOffset(weekOffset * 7, workDays); + } + // ============================================ + // FORMATTING + // ============================================ + formatTime(date, showSeconds = false) { + const pattern = showSeconds ? "HH:mm:ss" : "HH:mm"; + return (0, import_dayjs.default)(date).format(pattern); + } + formatTimeRange(start, end) { + return `${this.formatTime(start)} - ${this.formatTime(end)}`; + } + formatDate(date) { + return (0, import_dayjs.default)(date).format("YYYY-MM-DD"); + } + getDateKey(date) { + return this.formatDate(date); + } + // ============================================ + // COLUMN KEY + // ============================================ + /** + * Build a uniform columnKey from grouping segments + * Handles any combination of date, resource, team, etc. + * + * @example + * buildColumnKey({ date: '2025-12-09' }) → "2025-12-09" + * buildColumnKey({ date: '2025-12-09', resource: 'EMP001' }) → "2025-12-09:EMP001" + */ + buildColumnKey(segments) { + const date = segments.date; + const others = Object.entries(segments).filter(([k]) => k !== "date").sort(([a], [b]) => a.localeCompare(b)).map(([, v]) => v); + return date ? [date, ...others].join(":") : others.join(":"); + } + /** + * Parse a columnKey back into segments + * Assumes format: "date:resource:..." or just "date" + */ + parseColumnKey(columnKey) { + const parts = columnKey.split(":"); + return { + date: parts[0], + resource: parts[1] + }; + } + /** + * Extract dateKey from columnKey (first segment) + */ + getDateFromColumnKey(columnKey) { + return columnKey.split(":")[0]; + } + // ============================================ + // TIME CALCULATIONS + // ============================================ + timeToMinutes(timeString) { + const parts = timeString.split(":").map(Number); + const hours = parts[0] || 0; + const minutes = parts[1] || 0; + return hours * 60 + minutes; + } + minutesToTime(totalMinutes) { + const hours = Math.floor(totalMinutes / 60); + const minutes = totalMinutes % 60; + return (0, import_dayjs.default)().hour(hours).minute(minutes).format("HH:mm"); + } + getMinutesSinceMidnight(date) { + const d = (0, import_dayjs.default)(date); + return d.hour() * 60 + d.minute(); + } + // ============================================ + // UTC CONVERSIONS + // ============================================ + toUTC(localDate) { + return import_dayjs.default.tz(localDate, this.timezone).utc().toISOString(); + } + fromUTC(utcString) { + return import_dayjs.default.utc(utcString).tz(this.timezone).toDate(); + } + // ============================================ + // DATE CREATION + // ============================================ + createDateAtTime(baseDate, timeString) { + const totalMinutes = this.timeToMinutes(timeString); + const hours = Math.floor(totalMinutes / 60); + const minutes = totalMinutes % 60; + return (0, import_dayjs.default)(baseDate).startOf("day").hour(hours).minute(minutes).toDate(); + } + getISOWeekDay(date) { + return (0, import_dayjs.default)(date).isoWeekday(); + } +}; +var ScrollManager = class { + init(container) { + this.scrollableContent = container.querySelector("swp-scrollable-content"); + this.timeAxisContent = container.querySelector("swp-time-axis-content"); + this.calendarHeader = container.querySelector("swp-calendar-header"); + this.headerDrawer = container.querySelector("swp-header-drawer"); + this.headerViewport = container.querySelector("swp-header-viewport"); + this.headerSpacer = container.querySelector("swp-header-spacer"); + this.scrollableContent.addEventListener("scroll", () => this.onScroll()); + this.resizeObserver = new ResizeObserver(() => this.syncHeaderSpacerHeight()); + this.resizeObserver.observe(this.headerViewport); + this.syncHeaderSpacerHeight(); + } + syncHeaderSpacerHeight() { + const computedHeight = getComputedStyle(this.headerViewport).height; + this.headerSpacer.style.height = computedHeight; + } + onScroll() { + const { scrollTop, scrollLeft } = this.scrollableContent; + this.timeAxisContent.style.transform = `translateY(-${scrollTop}px)`; + this.calendarHeader.style.transform = `translateX(-${scrollLeft}px)`; + this.headerDrawer.style.transform = `translateX(-${scrollLeft}px)`; + } +}; +var HeaderDrawerManager = class { + constructor() { + this.expanded = false; + this.currentRows = 0; + this.rowHeight = 25; + this.duration = 200; + } + init(container) { + this.drawer = container.querySelector("swp-header-drawer"); + if (!this.drawer) + console.error("HeaderDrawerManager: swp-header-drawer not found"); + } + toggle() { + this.expanded ? this.collapse() : this.expand(); + } + /** + * Expand drawer to single row (legacy support) + */ + expand() { + this.expandToRows(1); + } + /** + * Expand drawer to fit specified number of rows + */ + expandToRows(rowCount) { + const targetHeight = rowCount * this.rowHeight; + const currentHeight = this.expanded ? this.currentRows * this.rowHeight : 0; + if (this.expanded && this.currentRows === rowCount) + return; + this.currentRows = rowCount; + this.expanded = true; + this.animate(currentHeight, targetHeight); + } + collapse() { + if (!this.expanded) + return; + const currentHeight = this.currentRows * this.rowHeight; + this.expanded = false; + this.currentRows = 0; + this.animate(currentHeight, 0); + } + animate(from, to) { + const keyframes = [ + { height: `${from}px` }, + { height: `${to}px` } + ]; + const options = { + duration: this.duration, + easing: "ease", + fill: "forwards" + }; + this.drawer.animate(keyframes, options); + } + isExpanded() { + return this.expanded; + } + getRowCount() { + return this.currentRows; + } +}; +var DateRenderer = class { + constructor(dateService) { + this.dateService = dateService; + this.type = "date"; + } + render(context) { + const dates = context.filter["date"] || []; + const resourceIds = context.filter["resource"] || []; + const dateGrouping = context.groupings?.find((g) => g.type === "date"); + const hideHeader = dateGrouping?.hideHeader === true; + const iterations = resourceIds.length || 1; + let columnCount = 0; + for (let r = 0; r < iterations; r++) { + const resourceId = resourceIds[r]; + for (const dateStr of dates) { + const date = this.dateService.parseISO(dateStr); + const segments = { date: dateStr }; + if (resourceId) + segments.resource = resourceId; + const columnKey = this.dateService.buildColumnKey(segments); + const header = document.createElement("swp-day-header"); + header.dataset.date = dateStr; + header.dataset.columnKey = columnKey; + if (resourceId) { + header.dataset.resourceId = resourceId; + } + if (hideHeader) { + header.dataset.hidden = "true"; + } + header.innerHTML = ` + ${this.dateService.getDayName(date, "short")} + ${date.getDate()} + `; + context.headerContainer.appendChild(header); + const column = document.createElement("swp-day-column"); + column.dataset.date = dateStr; + column.dataset.columnKey = columnKey; + if (resourceId) { + column.dataset.resourceId = resourceId; + } + column.innerHTML = ""; + context.columnContainer.appendChild(column); + columnCount++; + } + } + const container = context.columnContainer.closest("swp-calendar-container"); + if (container) { + container.style.setProperty("--grid-columns", String(columnCount)); + } + } +}; +var ResourceRenderer = class extends BaseGroupingRenderer { + constructor(resourceService) { + super(); + this.resourceService = resourceService; + this.type = "resource"; + this.config = { + elementTag: "swp-resource-header", + idAttribute: "resourceId", + colspanVar: "--resource-cols" + }; + } + getEntities(ids) { + return this.resourceService.getByIds(ids); + } + getDisplayName(entity) { + return entity.displayName; + } + /** + * Override render to handle: + * 1. Special ordering when parentChildMap exists (resources grouped by parent) + * 2. Different colspan calculation (just dateCount, not childCount * dateCount) + */ + async render(context) { + const resourceIds = context.filter["resource"] || []; + const dateCount = context.filter["date"]?.length || 1; + let orderedResourceIds; + if (context.parentChildMap) { + orderedResourceIds = []; + for (const childIds of Object.values(context.parentChildMap)) { + for (const childId of childIds) { + if (resourceIds.includes(childId)) { + orderedResourceIds.push(childId); + } + } + } + } else { + orderedResourceIds = resourceIds; + } + const resources = await this.getEntities(orderedResourceIds); + const resourceMap = new Map(resources.map((r) => [r.id, r])); + for (const resourceId of orderedResourceIds) { + const resource = resourceMap.get(resourceId); + if (!resource) + continue; + const header = this.createHeader(resource, context); + header.style.gridColumn = `span ${dateCount}`; + context.headerContainer.appendChild(header); + } + } +}; +function calculateEventPosition(start, end, config) { + const startMinutes = start.getHours() * 60 + start.getMinutes(); + const endMinutes = end.getHours() * 60 + end.getMinutes(); + const dayStartMinutes = config.dayStartHour * 60; + const minuteHeight = config.hourHeight / 60; + const top = (startMinutes - dayStartMinutes) * minuteHeight; + const height = (endMinutes - startMinutes) * minuteHeight; + return { top, height }; +} +function minutesToPixels(minutes, config) { + return minutes / 60 * config.hourHeight; +} +function pixelsToMinutes(pixels, config) { + return pixels / config.hourHeight * 60; +} +function snapToGrid(pixels, config) { + const snapPixels = minutesToPixels(config.snapInterval, config); + return Math.round(pixels / snapPixels) * snapPixels; +} +function eventsOverlap(a, b) { + return a.start < b.end && a.end > b.start; +} +function eventsWithinThreshold(a, b, thresholdMinutes) { + const thresholdMs = thresholdMinutes * 60 * 1e3; + const startToStartDiff = Math.abs(a.start.getTime() - b.start.getTime()); + if (startToStartDiff <= thresholdMs) + return true; + const bStartsBeforeAEnds = a.end.getTime() - b.start.getTime(); + if (bStartsBeforeAEnds > 0 && bStartsBeforeAEnds <= thresholdMs) + return true; + const aStartsBeforeBEnds = b.end.getTime() - a.start.getTime(); + if (aStartsBeforeBEnds > 0 && aStartsBeforeBEnds <= thresholdMs) + return true; + return false; +} +function findOverlapGroups(events) { + if (events.length === 0) + return []; + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + const used = /* @__PURE__ */ new Set(); + const groups = []; + for (const event of sorted) { + if (used.has(event.id)) + continue; + const group = [event]; + used.add(event.id); + let expanded = true; + while (expanded) { + expanded = false; + for (const candidate of sorted) { + if (used.has(candidate.id)) + continue; + const connects = group.some((member) => eventsOverlap(member, candidate)); + if (connects) { + group.push(candidate); + used.add(candidate.id); + expanded = true; + } + } + } + groups.push(group); + } + return groups; +} +function findGridCandidates(events, thresholdMinutes) { + if (events.length === 0) + return []; + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + const used = /* @__PURE__ */ new Set(); + const groups = []; + for (const event of sorted) { + if (used.has(event.id)) + continue; + const group = [event]; + used.add(event.id); + let expanded = true; + while (expanded) { + expanded = false; + for (const candidate of sorted) { + if (used.has(candidate.id)) + continue; + const connects = group.some((member) => eventsWithinThreshold(member, candidate, thresholdMinutes)); + if (connects) { + group.push(candidate); + used.add(candidate.id); + expanded = true; + } + } + } + groups.push(group); + } + return groups; +} +function calculateStackLevels(events) { + const levels = /* @__PURE__ */ new Map(); + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + for (const event of sorted) { + let maxOverlappingLevel = -1; + for (const [id, level] of levels) { + const other = events.find((e) => e.id === id); + if (other && eventsOverlap(event, other)) { + maxOverlappingLevel = Math.max(maxOverlappingLevel, level); + } + } + levels.set(event.id, maxOverlappingLevel + 1); + } + return levels; +} +function allocateColumns(events) { + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + const columns = []; + for (const event of sorted) { + let placed = false; + for (const column of columns) { + const canFit = !column.some((e) => eventsOverlap(event, e)); + if (canFit) { + column.push(event); + placed = true; + break; + } + } + if (!placed) { + columns.push([event]); + } + } + return columns; +} +function calculateColumnLayout(events, config) { + const thresholdMinutes = config.gridStartThresholdMinutes ?? 10; + const result = { + grids: [], + stacked: [] + }; + if (events.length === 0) + return result; + const overlapGroups = findOverlapGroups(events); + for (const overlapGroup of overlapGroups) { + if (overlapGroup.length === 1) { + result.stacked.push({ + event: overlapGroup[0], + stackLevel: 0 + }); + continue; + } + const gridSubgroups = findGridCandidates(overlapGroup, thresholdMinutes); + const largestGridCandidate = gridSubgroups.reduce((max, g) => g.length > max.length ? g : max, gridSubgroups[0]); + if (largestGridCandidate.length === overlapGroup.length) { + const columns = allocateColumns(overlapGroup); + const earliest = overlapGroup.reduce((min, e) => e.start < min.start ? e : min, overlapGroup[0]); + const position = calculateEventPosition(earliest.start, earliest.end, config); + result.grids.push({ + events: overlapGroup, + columns, + stackLevel: 0, + position: { top: position.top } + }); + } else { + const levels = calculateStackLevels(overlapGroup); + for (const event of overlapGroup) { + result.stacked.push({ + event, + stackLevel: levels.get(event.id) ?? 0 + }); + } + } + } + return result; +} +var EventRenderer = class { + constructor(eventService, dateService, gridConfig, eventBus) { + this.eventService = eventService; + this.dateService = dateService; + this.gridConfig = gridConfig; + this.eventBus = eventBus; + this.container = null; + this.setupListeners(); + } + /** + * Setup listeners for drag-drop and update events + */ + setupListeners() { + this.eventBus.on(CoreEvents.EVENT_DRAG_COLUMN_CHANGE, (e) => { + const payload = e.detail; + this.handleColumnChange(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_MOVE, (e) => { + const payload = e.detail; + this.updateDragTimestamp(payload); + }); + this.eventBus.on(CoreEvents.EVENT_UPDATED, (e) => { + const payload = e.detail; + this.handleEventUpdated(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_END, (e) => { + const payload = e.detail; + this.handleDragEnd(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_LEAVE_HEADER, (e) => { + const payload = e.detail; + this.handleDragLeaveHeader(payload); + }); + } + /** + * Handle EVENT_DRAG_END - remove element if dropped in header + */ + handleDragEnd(payload) { + if (payload.target === "header") { + const element = this.container?.querySelector(`swp-content-viewport swp-event[data-event-id="${payload.swpEvent.eventId}"]`); + element?.remove(); + } + } + /** + * Handle header item leaving header - create swp-event in grid + */ + handleDragLeaveHeader(payload) { + if (payload.source !== "header") + return; + if (!payload.targetColumn || !payload.start || !payload.end) + return; + if (payload.element) { + payload.element.classList.add("drag-ghost"); + payload.element.style.opacity = "0.3"; + payload.element.style.pointerEvents = "none"; + } + const event = { + id: payload.eventId, + title: payload.title || "", + description: "", + start: payload.start, + end: payload.end, + type: "customer", + allDay: false, + syncStatus: "pending" + }; + const element = this.createEventElement(event); + let eventsLayer = payload.targetColumn.querySelector("swp-events-layer"); + if (!eventsLayer) { + eventsLayer = document.createElement("swp-events-layer"); + payload.targetColumn.appendChild(eventsLayer); + } + eventsLayer.appendChild(element); + element.classList.add("dragging"); + } + /** + * Handle EVENT_UPDATED - re-render affected columns + */ + async handleEventUpdated(payload) { + if (payload.sourceColumnKey !== payload.targetColumnKey) { + await this.rerenderColumn(payload.sourceColumnKey); + } + await this.rerenderColumn(payload.targetColumnKey); + } + /** + * Re-render a single column with fresh data from IndexedDB + */ + async rerenderColumn(columnKey) { + const column = this.findColumn(columnKey); + if (!column) + return; + const date = column.dataset.date; + const resourceId = column.dataset.resourceId; + if (!date) + return; + const startDate = new Date(date); + const endDate = new Date(date); + endDate.setHours(23, 59, 59, 999); + const events = resourceId ? await this.eventService.getByResourceAndDateRange(resourceId, startDate, endDate) : await this.eventService.getByDateRange(startDate, endDate); + const timedEvents = events.filter((event) => !event.allDay && this.dateService.getDateKey(event.start) === date); + let eventsLayer = column.querySelector("swp-events-layer"); + if (!eventsLayer) { + eventsLayer = document.createElement("swp-events-layer"); + column.appendChild(eventsLayer); + } + eventsLayer.innerHTML = ""; + const layout = calculateColumnLayout(timedEvents, this.gridConfig); + layout.grids.forEach((grid) => { + const groupEl = this.renderGridGroup(grid); + eventsLayer.appendChild(groupEl); + }); + layout.stacked.forEach((item) => { + const eventEl = this.renderStackedEvent(item.event, item.stackLevel); + eventsLayer.appendChild(eventEl); + }); + } + /** + * Find a column element by columnKey + */ + findColumn(columnKey) { + if (!this.container) + return null; + return this.container.querySelector(`swp-day-column[data-column-key="${columnKey}"]`); + } + /** + * Handle event moving to a new column during drag + */ + handleColumnChange(payload) { + const eventsLayer = payload.newColumn.querySelector("swp-events-layer"); + if (!eventsLayer) + return; + eventsLayer.appendChild(payload.element); + payload.element.style.top = `${payload.currentY}px`; + } + /** + * Update timestamp display during drag (snapped to grid) + */ + updateDragTimestamp(payload) { + const timeEl = payload.element.querySelector("swp-event-time"); + if (!timeEl) + return; + const snappedY = snapToGrid(payload.currentY, this.gridConfig); + const minutesFromGridStart = pixelsToMinutes(snappedY, this.gridConfig); + const startMinutes = this.gridConfig.dayStartHour * 60 + minutesFromGridStart; + const height = parseFloat(payload.element.style.height) || this.gridConfig.hourHeight; + const durationMinutes = pixelsToMinutes(height, this.gridConfig); + const start = this.minutesToDate(startMinutes); + const end = this.minutesToDate(startMinutes + durationMinutes); + timeEl.textContent = this.dateService.formatTimeRange(start, end); + } + /** + * Convert minutes since midnight to a Date object (today) + */ + minutesToDate(minutes) { + const date = /* @__PURE__ */ new Date(); + date.setHours(Math.floor(minutes / 60) % 24, minutes % 60, 0, 0); + return date; + } + /** + * Render events for visible dates into day columns + * @param container - Calendar container element + * @param filter - Filter with 'date' and optionally 'resource' arrays + * @param filterTemplate - Template for matching events to columns + */ + async render(container, filter, filterTemplate) { + this.container = container; + const visibleDates = filter["date"] || []; + if (visibleDates.length === 0) + return; + const startDate = new Date(visibleDates[0]); + const endDate = new Date(visibleDates[visibleDates.length - 1]); + endDate.setHours(23, 59, 59, 999); + const events = await this.eventService.getByDateRange(startDate, endDate); + const dayColumns = container.querySelector("swp-day-columns"); + if (!dayColumns) + return; + const columns = dayColumns.querySelectorAll("swp-day-column"); + columns.forEach((column) => { + const columnEl = column; + const columnEvents = events.filter((event) => filterTemplate.matches(event, columnEl)); + let eventsLayer = column.querySelector("swp-events-layer"); + if (!eventsLayer) { + eventsLayer = document.createElement("swp-events-layer"); + column.appendChild(eventsLayer); + } + eventsLayer.innerHTML = ""; + const timedEvents = columnEvents.filter((event) => !event.allDay); + const layout = calculateColumnLayout(timedEvents, this.gridConfig); + layout.grids.forEach((grid) => { + const groupEl = this.renderGridGroup(grid); + eventsLayer.appendChild(groupEl); + }); + layout.stacked.forEach((item) => { + const eventEl = this.renderStackedEvent(item.event, item.stackLevel); + eventsLayer.appendChild(eventEl); + }); + }); + } + /** + * Create a single event element + * + * CLEAN approach: + * - Only data-id for lookup + * - Visible content in innerHTML only + */ + createEventElement(event) { + const element = document.createElement("swp-event"); + element.dataset.eventId = event.id; + if (event.resourceId) { + element.dataset.resourceId = event.resourceId; + } + const position = calculateEventPosition(event.start, event.end, this.gridConfig); + element.style.top = `${position.top}px`; + element.style.height = `${position.height}px`; + const colorClass = this.getColorClass(event); + if (colorClass) { + element.classList.add(colorClass); + } + element.innerHTML = ` + ${this.dateService.formatTimeRange(event.start, event.end)} + ${this.escapeHtml(event.title)} + ${event.description ? `${this.escapeHtml(event.description)}` : ""} + `; + return element; + } + /** + * Get color class based on metadata.color or event type + */ + getColorClass(event) { + if (event.metadata?.color) { + return `is-${event.metadata.color}`; + } + const typeColors = { + "customer": "is-blue", + "vacation": "is-green", + "break": "is-amber", + "meeting": "is-purple", + "blocked": "is-red" + }; + return typeColors[event.type] || "is-blue"; + } + /** + * Escape HTML to prevent XSS + */ + escapeHtml(text) { + const div = document.createElement("div"); + div.textContent = text; + return div.innerHTML; + } + /** + * Render a GRID group with side-by-side columns + * Used when multiple events start at the same time + */ + renderGridGroup(layout) { + const group = document.createElement("swp-event-group"); + group.classList.add(`cols-${layout.columns.length}`); + group.style.top = `${layout.position.top}px`; + if (layout.stackLevel > 0) { + group.style.marginLeft = `${layout.stackLevel * 15}px`; + group.style.zIndex = `${100 + layout.stackLevel}`; + } + let maxBottom = 0; + for (const event of layout.events) { + const pos = calculateEventPosition(event.start, event.end, this.gridConfig); + const eventBottom = pos.top + pos.height; + if (eventBottom > maxBottom) + maxBottom = eventBottom; + } + const groupHeight = maxBottom - layout.position.top; + group.style.height = `${groupHeight}px`; + layout.columns.forEach((columnEvents) => { + const wrapper = document.createElement("div"); + wrapper.style.position = "relative"; + columnEvents.forEach((event) => { + const eventEl = this.createEventElement(event); + const pos = calculateEventPosition(event.start, event.end, this.gridConfig); + eventEl.style.top = `${pos.top - layout.position.top}px`; + eventEl.style.position = "absolute"; + eventEl.style.left = "0"; + eventEl.style.right = "0"; + wrapper.appendChild(eventEl); + }); + group.appendChild(wrapper); + }); + return group; + } + /** + * Render a STACKED event with margin-left offset + * Used for overlapping events that don't start at the same time + */ + renderStackedEvent(event, stackLevel) { + const element = this.createEventElement(event); + element.dataset.stackLink = JSON.stringify({ stackLevel }); + if (stackLevel > 0) { + element.style.marginLeft = `${stackLevel * 15}px`; + element.style.zIndex = `${100 + stackLevel}`; + } + return element; + } +}; +var TimeAxisRenderer = class { + render(container, startHour = 6, endHour = 20) { + container.innerHTML = ""; + for (let hour = startHour; hour <= endHour; hour++) { + const marker = document.createElement("swp-hour-marker"); + marker.textContent = `${hour.toString().padStart(2, "0")}:00`; + container.appendChild(marker); + } + } +}; +var HeaderDrawerRenderer = class { + constructor(eventBus, gridConfig, headerDrawerManager, eventService, dateService) { + this.eventBus = eventBus; + this.gridConfig = gridConfig; + this.headerDrawerManager = headerDrawerManager; + this.eventService = eventService; + this.dateService = dateService; + this.currentItem = null; + this.container = null; + this.sourceElement = null; + this.wasExpandedBeforeDrag = false; + this.filterTemplate = null; + this.setupListeners(); + } + /** + * Render allDay events into the header drawer with row stacking + * @param filterTemplate - Template for matching events to columns + */ + async render(container, filter, filterTemplate) { + this.filterTemplate = filterTemplate; + const drawer = container.querySelector("swp-header-drawer"); + if (!drawer) + return; + const visibleDates = filter["date"] || []; + if (visibleDates.length === 0) + return; + const visibleColumnKeys = this.getVisibleColumnKeysFromDOM(); + if (visibleColumnKeys.length === 0) + return; + const startDate = new Date(visibleDates[0]); + const endDate = new Date(visibleDates[visibleDates.length - 1]); + endDate.setHours(23, 59, 59, 999); + const events = await this.eventService.getByDateRange(startDate, endDate); + const allDayEvents = events.filter((event) => event.allDay !== false); + drawer.innerHTML = ""; + if (allDayEvents.length === 0) + return; + const layouts = this.calculateLayout(allDayEvents, visibleColumnKeys); + const rowCount = Math.max(1, ...layouts.map((l) => l.row)); + layouts.forEach((layout) => { + const item = this.createHeaderItem(layout); + drawer.appendChild(item); + }); + this.headerDrawerManager.expandToRows(rowCount); + } + /** + * Create a header item element from layout + */ + createHeaderItem(layout) { + const { event, columnKey, row, colStart, colEnd } = layout; + const item = document.createElement("swp-header-item"); + item.dataset.eventId = event.id; + item.dataset.itemType = "event"; + item.dataset.start = event.start.toISOString(); + item.dataset.end = event.end.toISOString(); + item.dataset.columnKey = columnKey; + item.textContent = event.title; + const colorClass = this.getColorClass(event); + if (colorClass) + item.classList.add(colorClass); + item.style.gridArea = `${row} / ${colStart} / ${row + 1} / ${colEnd}`; + return item; + } + /** + * Calculate layout for all events with row stacking + * Uses track-based algorithm to find available rows for overlapping events + */ + calculateLayout(events, visibleColumnKeys) { + const tracks = [new Array(visibleColumnKeys.length).fill(false)]; + const layouts = []; + for (const event of events) { + const columnKey = this.buildColumnKeyFromEvent(event); + const startCol = visibleColumnKeys.indexOf(columnKey); + const endColumnKey = this.buildColumnKeyFromEvent(event, event.end); + const endCol = visibleColumnKeys.indexOf(endColumnKey); + if (startCol === -1 && endCol === -1) + continue; + const colStart = Math.max(0, startCol); + const colEnd = (endCol !== -1 ? endCol : visibleColumnKeys.length - 1) + 1; + const row = this.findAvailableRow(tracks, colStart, colEnd); + for (let c = colStart; c < colEnd; c++) { + tracks[row][c] = true; + } + layouts.push({ event, columnKey, row: row + 1, colStart: colStart + 1, colEnd: colEnd + 1 }); + } + return layouts; + } + /** + * Build columnKey from event using FilterTemplate + * Uses the same template that columns use for matching + */ + buildColumnKeyFromEvent(event, date) { + if (!this.filterTemplate) { + const dateStr = this.dateService.getDateKey(date || event.start); + return dateStr; + } + if (date && date.getTime() !== event.start.getTime()) { + const tempEvent = { ...event, start: date }; + return this.filterTemplate.buildKeyFromEvent(tempEvent); + } + return this.filterTemplate.buildKeyFromEvent(event); + } + /** + * Find available row for event spanning columns [colStart, colEnd) + */ + findAvailableRow(tracks, colStart, colEnd) { + for (let row = 0; row < tracks.length; row++) { + let available = true; + for (let c = colStart; c < colEnd; c++) { + if (tracks[row][c]) { + available = false; + break; + } + } + if (available) + return row; + } + tracks.push(new Array(tracks[0].length).fill(false)); + return tracks.length - 1; + } + /** + * Get color class based on event metadata or type + */ + getColorClass(event) { + if (event.metadata?.color) { + return `is-${event.metadata.color}`; + } + const typeColors = { + "customer": "is-blue", + "vacation": "is-green", + "break": "is-amber", + "meeting": "is-purple", + "blocked": "is-red" + }; + return typeColors[event.type] || "is-blue"; + } + /** + * Setup event listeners for drag events + */ + setupListeners() { + this.eventBus.on(CoreEvents.EVENT_DRAG_ENTER_HEADER, (e) => { + const payload = e.detail; + this.handleDragEnter(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_MOVE_HEADER, (e) => { + const payload = e.detail; + this.handleDragMove(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_LEAVE_HEADER, (e) => { + const payload = e.detail; + this.handleDragLeave(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_END, (e) => { + const payload = e.detail; + this.handleDragEnd(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_CANCEL, () => { + this.cleanup(); + }); + } + /** + * Handle drag entering header zone - create preview item + */ + handleDragEnter(payload) { + this.container = document.querySelector("swp-header-drawer"); + if (!this.container) + return; + this.wasExpandedBeforeDrag = this.headerDrawerManager.isExpanded(); + if (!this.wasExpandedBeforeDrag) { + this.headerDrawerManager.expandToRows(1); + } + this.sourceElement = payload.element; + const item = document.createElement("swp-header-item"); + item.dataset.eventId = payload.eventId; + item.dataset.itemType = payload.itemType; + item.dataset.duration = String(payload.duration); + item.dataset.columnKey = payload.sourceColumnKey; + item.textContent = payload.title; + if (payload.colorClass) { + item.classList.add(payload.colorClass); + } + item.classList.add("dragging"); + const col = payload.sourceColumnIndex + 1; + const endCol = col + payload.duration; + item.style.gridArea = `1 / ${col} / 2 / ${endCol}`; + this.container.appendChild(item); + this.currentItem = item; + payload.element.style.visibility = "hidden"; + } + /** + * Handle drag moving within header - update column position + */ + handleDragMove(payload) { + if (!this.currentItem) + return; + const col = payload.columnIndex + 1; + const duration = parseInt(this.currentItem.dataset.duration || "1", 10); + const endCol = col + duration; + this.currentItem.style.gridArea = `1 / ${col} / 2 / ${endCol}`; + this.currentItem.dataset.columnKey = payload.columnKey; + } + /** + * Handle drag leaving header - cleanup for grid→header drag only + */ + handleDragLeave(payload) { + if (payload.source === "grid") { + this.cleanup(); + } + } + /** + * Handle drag end - finalize based on drop target + */ + handleDragEnd(payload) { + if (payload.target === "header") { + if (this.currentItem) { + this.currentItem.classList.remove("dragging"); + this.recalculateDrawerLayout(); + this.currentItem = null; + this.sourceElement = null; + } + } else { + const ghost = document.querySelector(`swp-header-item.drag-ghost[data-event-id="${payload.swpEvent.eventId}"]`); + ghost?.remove(); + this.recalculateDrawerLayout(); + } + } + /** + * Recalculate layout for all items currently in the drawer + * Called after drop to reposition items and adjust height + */ + recalculateDrawerLayout() { + const drawer = document.querySelector("swp-header-drawer"); + if (!drawer) + return; + const items = Array.from(drawer.querySelectorAll("swp-header-item")); + if (items.length === 0) + return; + const visibleColumnKeys = this.getVisibleColumnKeysFromDOM(); + if (visibleColumnKeys.length === 0) + return; + const itemData = items.map((item) => ({ + element: item, + columnKey: item.dataset.columnKey || "", + duration: parseInt(item.dataset.duration || "1", 10) + })); + const tracks = [new Array(visibleColumnKeys.length).fill(false)]; + for (const item of itemData) { + const startCol = visibleColumnKeys.indexOf(item.columnKey); + if (startCol === -1) + continue; + const colStart = startCol; + const colEnd = Math.min(startCol + item.duration, visibleColumnKeys.length); + const row = this.findAvailableRow(tracks, colStart, colEnd); + for (let c = colStart; c < colEnd; c++) { + tracks[row][c] = true; + } + item.element.style.gridArea = `${row + 1} / ${colStart + 1} / ${row + 2} / ${colEnd + 1}`; + } + const rowCount = tracks.length; + this.headerDrawerManager.expandToRows(rowCount); + } + /** + * Get visible column keys from DOM (preserves order for multi-resource views) + * Uses filterTemplate.buildKeyFromColumn() for consistent key format with events + */ + getVisibleColumnKeysFromDOM() { + if (!this.filterTemplate) + return []; + const columns = document.querySelectorAll("swp-day-column"); + const columnKeys = []; + columns.forEach((col) => { + const columnKey = this.filterTemplate.buildKeyFromColumn(col); + if (columnKey) + columnKeys.push(columnKey); + }); + return columnKeys; + } + /** + * Cleanup preview item and restore source visibility + */ + cleanup() { + this.currentItem?.remove(); + this.currentItem = null; + if (this.sourceElement) { + this.sourceElement.style.visibility = ""; + this.sourceElement = null; + } + if (!this.wasExpandedBeforeDrag) { + this.headerDrawerManager.collapse(); + } + } +}; +var ScheduleRenderer = class { + constructor(scheduleService, dateService, gridConfig) { + this.scheduleService = scheduleService; + this.dateService = dateService; + this.gridConfig = gridConfig; + } + /** + * Render unavailable zones for visible columns + * @param container - Calendar container element + * @param filter - Filter with 'date' and 'resource' arrays + */ + async render(container, filter) { + const dates = filter["date"] || []; + const resourceIds = filter["resource"] || []; + if (dates.length === 0) + return; + const dayColumns = container.querySelector("swp-day-columns"); + if (!dayColumns) + return; + const columns = dayColumns.querySelectorAll("swp-day-column"); + for (const column of columns) { + const date = column.dataset.date; + const resourceId = column.dataset.resourceId; + if (!date || !resourceId) + continue; + let unavailableLayer = column.querySelector("swp-unavailable-layer"); + if (!unavailableLayer) { + unavailableLayer = document.createElement("swp-unavailable-layer"); + column.insertBefore(unavailableLayer, column.firstChild); + } + unavailableLayer.innerHTML = ""; + const schedule = await this.scheduleService.getScheduleForDate(resourceId, date); + this.renderUnavailableZones(unavailableLayer, schedule); + } + } + /** + * Render unavailable time zones based on schedule + */ + renderUnavailableZones(layer, schedule) { + const dayStartMinutes = this.gridConfig.dayStartHour * 60; + const dayEndMinutes = this.gridConfig.dayEndHour * 60; + const minuteHeight = this.gridConfig.hourHeight / 60; + if (schedule === null) { + const zone = this.createUnavailableZone(0, (dayEndMinutes - dayStartMinutes) * minuteHeight); + layer.appendChild(zone); + return; + } + const workStartMinutes = this.dateService.timeToMinutes(schedule.start); + const workEndMinutes = this.dateService.timeToMinutes(schedule.end); + if (workStartMinutes > dayStartMinutes) { + const top = 0; + const height = (workStartMinutes - dayStartMinutes) * minuteHeight; + const zone = this.createUnavailableZone(top, height); + layer.appendChild(zone); + } + if (workEndMinutes < dayEndMinutes) { + const top = (workEndMinutes - dayStartMinutes) * minuteHeight; + const height = (dayEndMinutes - workEndMinutes) * minuteHeight; + const zone = this.createUnavailableZone(top, height); + layer.appendChild(zone); + } + } + /** + * Create an unavailable zone element + */ + createUnavailableZone(top, height) { + const zone = document.createElement("swp-unavailable-zone"); + zone.style.top = `${top}px`; + zone.style.height = `${height}px`; + return zone; + } +}; +var defaultDBConfig = { + dbName: "CalendarDB", + dbVersion: 4 +}; +var IndexedDBContext = class { + constructor(stores, config) { + this.db = null; + this.initialized = false; + this.stores = stores; + this.config = config; + } + get dbName() { + return this.config.dbName; + } + /** + * Initialize and open the database + */ + async initialize() { + return new Promise((resolve, reject) => { + const request = indexedDB.open(this.config.dbName, this.config.dbVersion); + request.onerror = () => { + reject(new Error(`Failed to open IndexedDB: ${request.error}`)); + }; + request.onsuccess = () => { + this.db = request.result; + this.initialized = true; + resolve(); + }; + request.onupgradeneeded = (event) => { + const db = event.target.result; + this.stores.forEach((store) => { + if (!db.objectStoreNames.contains(store.storeName)) { + store.create(db); + } + }); + }; + }); + } + /** + * Check if database is initialized + */ + isInitialized() { + return this.initialized; + } + /** + * Get IDBDatabase instance + */ + getDatabase() { + if (!this.db) { + throw new Error("IndexedDB not initialized. Call initialize() first."); + } + return this.db; + } + /** + * Close database connection + */ + close() { + if (this.db) { + this.db.close(); + this.db = null; + this.initialized = false; + } + } + /** + * Delete entire database (for testing/reset) + */ + static async deleteDatabase(dbName = defaultDBConfig.dbName) { + return new Promise((resolve, reject) => { + const request = indexedDB.deleteDatabase(dbName); + request.onsuccess = () => resolve(); + request.onerror = () => reject(new Error(`Failed to delete database: ${request.error}`)); + }); + } +}; +var EventStore = class _EventStore { + constructor() { + this.storeName = _EventStore.STORE_NAME; + } + /** + * Create the events ObjectStore with indexes + */ + create(db) { + const store = db.createObjectStore(_EventStore.STORE_NAME, { keyPath: "id" }); + store.createIndex("start", "start", { unique: false }); + store.createIndex("end", "end", { unique: false }); + store.createIndex("syncStatus", "syncStatus", { unique: false }); + store.createIndex("resourceId", "resourceId", { unique: false }); + store.createIndex("customerId", "customerId", { unique: false }); + store.createIndex("bookingId", "bookingId", { unique: false }); + store.createIndex("startEnd", ["start", "end"], { unique: false }); + } +}; +EventStore.STORE_NAME = "events"; +var EventSerialization = class { + /** + * Serialize event for IndexedDB storage + */ + static serialize(event) { + return { + ...event, + start: event.start instanceof Date ? event.start.toISOString() : event.start, + end: event.end instanceof Date ? event.end.toISOString() : event.end + }; + } + /** + * Deserialize event from IndexedDB storage + */ + static deserialize(data) { + return { + ...data, + start: typeof data.start === "string" ? new Date(data.start) : data.start, + end: typeof data.end === "string" ? new Date(data.end) : data.end + }; + } +}; +var EventService = class extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = EventStore.STORE_NAME; + this.entityType = "Event"; + } + serialize(event) { + return EventSerialization.serialize(event); + } + deserialize(data) { + return EventSerialization.deserialize(data); + } + /** + * Get events within a date range + */ + async getByDateRange(start, end) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const index = store.index("start"); + const range = IDBKeyRange.lowerBound(start.toISOString()); + const request = index.getAll(range); + request.onsuccess = () => { + const data = request.result; + const events = data.map((item) => this.deserialize(item)).filter((event) => event.start <= end); + resolve(events); + }; + request.onerror = () => { + reject(new Error(`Failed to get events by date range: ${request.error}`)); + }; + }); + } + /** + * Get events for a specific resource + */ + async getByResource(resourceId) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const index = store.index("resourceId"); + const request = index.getAll(resourceId); + request.onsuccess = () => { + const data = request.result; + const events = data.map((item) => this.deserialize(item)); + resolve(events); + }; + request.onerror = () => { + reject(new Error(`Failed to get events for resource ${resourceId}: ${request.error}`)); + }; + }); + } + /** + * Get events for a resource within a date range + */ + async getByResourceAndDateRange(resourceId, start, end) { + const resourceEvents = await this.getByResource(resourceId); + return resourceEvents.filter((event) => event.start >= start && event.start <= end); + } +}; +var ResourceStore = class _ResourceStore { + constructor() { + this.storeName = _ResourceStore.STORE_NAME; + } + create(db) { + const store = db.createObjectStore(_ResourceStore.STORE_NAME, { keyPath: "id" }); + store.createIndex("type", "type", { unique: false }); + store.createIndex("syncStatus", "syncStatus", { unique: false }); + store.createIndex("isActive", "isActive", { unique: false }); + } +}; +ResourceStore.STORE_NAME = "resources"; +var ResourceService = class extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = ResourceStore.STORE_NAME; + this.entityType = "Resource"; + } + /** + * Get all active resources + */ + async getActive() { + const all = await this.getAll(); + return all.filter((r) => r.isActive !== false); + } + /** + * Get resources by IDs + */ + async getByIds(ids) { + if (ids.length === 0) + return []; + const results = await Promise.all(ids.map((id) => this.get(id))); + return results.filter((r) => r !== null); + } + /** + * Get resources by type + */ + async getByType(type) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const index = store.index("type"); + const request = index.getAll(type); + request.onsuccess = () => { + const data = request.result; + resolve(data); + }; + request.onerror = () => { + reject(new Error(`Failed to get resources by type ${type}: ${request.error}`)); + }; + }); + } +}; +var SettingsIds = { + WORKWEEK: "workweek", + GRID: "grid", + TIME_FORMAT: "timeFormat", + VIEWS: "views" +}; +var SettingsStore = class _SettingsStore { + constructor() { + this.storeName = _SettingsStore.STORE_NAME; + } + create(db) { + db.createObjectStore(_SettingsStore.STORE_NAME, { keyPath: "id" }); + } +}; +SettingsStore.STORE_NAME = "settings"; +var SettingsService = class extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = SettingsStore.STORE_NAME; + this.entityType = "Settings"; + } + /** + * Get workweek settings + */ + async getWorkweekSettings() { + return this.get(SettingsIds.WORKWEEK); + } + /** + * Get grid settings + */ + async getGridSettings() { + return this.get(SettingsIds.GRID); + } + /** + * Get time format settings + */ + async getTimeFormatSettings() { + return this.get(SettingsIds.TIME_FORMAT); + } + /** + * Get view settings + */ + async getViewSettings() { + return this.get(SettingsIds.VIEWS); + } + /** + * Get workweek preset by ID + */ + async getWorkweekPreset(presetId) { + const settings = await this.getWorkweekSettings(); + if (!settings) + return null; + return settings.presets[presetId] || null; + } + /** + * Get the default workweek preset + */ + async getDefaultWorkweekPreset() { + const settings = await this.getWorkweekSettings(); + if (!settings) + return null; + return settings.presets[settings.defaultPreset] || null; + } + /** + * Get all available workweek presets + */ + async getWorkweekPresets() { + const settings = await this.getWorkweekSettings(); + if (!settings) + return []; + return Object.values(settings.presets); + } +}; +var ViewConfigStore = class _ViewConfigStore { + constructor() { + this.storeName = _ViewConfigStore.STORE_NAME; + } + create(db) { + db.createObjectStore(_ViewConfigStore.STORE_NAME, { keyPath: "id" }); + } +}; +ViewConfigStore.STORE_NAME = "viewconfigs"; +var ViewConfigService = class extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = ViewConfigStore.STORE_NAME; + this.entityType = "ViewConfig"; + } + async getById(id) { + return this.get(id); + } +}; +var SwpEvent = class _SwpEvent { + constructor(element, columnKey, start, end) { + this.element = element; + this.columnKey = columnKey; + this._start = start; + this._end = end; + } + /** Event ID from element.dataset.eventId */ + get eventId() { + return this.element.dataset.eventId || ""; + } + get start() { + return this._start; + } + get end() { + return this._end; + } + /** Duration in minutes */ + get durationMinutes() { + return (this._end.getTime() - this._start.getTime()) / (1e3 * 60); + } + /** Duration in milliseconds */ + get durationMs() { + return this._end.getTime() - this._start.getTime(); + } + /** + * Factory: Create SwpEvent from element + columnKey + * Reads top/height from element.style to calculate start/end + * @param columnKey - Opaque column identifier (do NOT parse - use only for matching) + * @param date - Date string (YYYY-MM-DD) for time calculations + */ + static fromElement(element, columnKey, date, gridConfig) { + const topPixels = parseFloat(element.style.top) || 0; + const heightPixels = parseFloat(element.style.height) || 0; + const startMinutesFromGrid = topPixels / gridConfig.hourHeight * 60; + const totalMinutes = gridConfig.dayStartHour * 60 + startMinutesFromGrid; + const start = new Date(date); + start.setHours(Math.floor(totalMinutes / 60), totalMinutes % 60, 0, 0); + const durationMinutes = heightPixels / gridConfig.hourHeight * 60; + const end = new Date(start.getTime() + durationMinutes * 60 * 1e3); + return new _SwpEvent(element, columnKey, start, end); + } +}; +var DragDropManager = class { + constructor(eventBus, gridConfig) { + this.eventBus = eventBus; + this.gridConfig = gridConfig; + this.dragState = null; + this.mouseDownPosition = null; + this.pendingElement = null; + this.pendingMouseOffset = null; + this.container = null; + this.inHeader = false; + this.DRAG_THRESHOLD = 5; + this.INTERPOLATION_FACTOR = 0.3; + this.handlePointerDown = (e) => { + const target = e.target; + if (target.closest("swp-resize-handle")) + return; + const eventElement = target.closest("swp-event"); + const headerItem = target.closest("swp-header-item"); + const draggable = eventElement || headerItem; + if (!draggable) + return; + this.mouseDownPosition = { x: e.clientX, y: e.clientY }; + this.pendingElement = draggable; + const rect = draggable.getBoundingClientRect(); + this.pendingMouseOffset = { + x: e.clientX - rect.left, + y: e.clientY - rect.top + }; + draggable.setPointerCapture(e.pointerId); + }; + this.handlePointerMove = (e) => { + if (!this.mouseDownPosition || !this.pendingElement) { + if (this.dragState) { + this.updateDragTarget(e); + } + return; + } + const deltaX = Math.abs(e.clientX - this.mouseDownPosition.x); + const deltaY = Math.abs(e.clientY - this.mouseDownPosition.y); + const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + if (distance < this.DRAG_THRESHOLD) + return; + this.initializeDrag(this.pendingElement, this.pendingMouseOffset, e); + this.mouseDownPosition = null; + this.pendingElement = null; + this.pendingMouseOffset = null; + }; + this.handlePointerUp = (_e) => { + this.mouseDownPosition = null; + this.pendingElement = null; + this.pendingMouseOffset = null; + if (!this.dragState) + return; + cancelAnimationFrame(this.dragState.animationId); + if (this.dragState.dragSource === "header") { + this.handleHeaderItemDragEnd(); + } else { + this.handleGridEventDragEnd(); + } + this.dragState.element.classList.remove("dragging"); + this.dragState = null; + this.inHeader = false; + }; + this.animateDrag = () => { + if (!this.dragState) + return; + const diff2 = this.dragState.targetY - this.dragState.currentY; + if (Math.abs(diff2) <= 0.5) { + this.dragState.animationId = 0; + return; + } + this.dragState.currentY += diff2 * this.INTERPOLATION_FACTOR; + this.dragState.element.style.top = `${this.dragState.currentY}px`; + if (this.dragState.columnElement) { + const payload = { + eventId: this.dragState.eventId, + element: this.dragState.element, + currentY: this.dragState.currentY, + columnElement: this.dragState.columnElement + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_MOVE, payload); + } + this.dragState.animationId = requestAnimationFrame(this.animateDrag); + }; + this.setupScrollListener(); + } + setupScrollListener() { + this.eventBus.on(CoreEvents.EDGE_SCROLL_TICK, (e) => { + if (!this.dragState) + return; + const { scrollDelta } = e.detail; + this.dragState.targetY += scrollDelta; + this.dragState.currentY += scrollDelta; + this.dragState.element.style.top = `${this.dragState.currentY}px`; + }); + } + /** + * Initialize drag-drop on a container element + */ + init(container) { + this.container = container; + container.addEventListener("pointerdown", this.handlePointerDown); + document.addEventListener("pointermove", this.handlePointerMove); + document.addEventListener("pointerup", this.handlePointerUp); + } + /** + * Handle drag end for header items + */ + handleHeaderItemDragEnd() { + if (!this.dragState) + return; + if (!this.inHeader && this.dragState.currentColumn) { + const gridEvent = this.dragState.currentColumn.querySelector(`swp-event[data-event-id="${this.dragState.eventId}"]`); + if (gridEvent) { + const columnKey = this.dragState.currentColumn.dataset.columnKey || ""; + const date = this.dragState.currentColumn.dataset.date || ""; + const swpEvent = SwpEvent.fromElement(gridEvent, columnKey, date, this.gridConfig); + const payload = { + swpEvent, + sourceColumnKey: this.dragState.sourceColumnKey, + target: "grid" + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_END, payload); + } + } + } + /** + * Handle drag end for grid events + */ + handleGridEventDragEnd() { + if (!this.dragState || !this.dragState.columnElement) + return; + const snappedY = snapToGrid(this.dragState.currentY, this.gridConfig); + this.dragState.element.style.top = `${snappedY}px`; + this.dragState.ghostElement?.remove(); + const columnKey = this.dragState.columnElement.dataset.columnKey || ""; + const date = this.dragState.columnElement.dataset.date || ""; + const swpEvent = SwpEvent.fromElement(this.dragState.element, columnKey, date, this.gridConfig); + const payload = { + swpEvent, + sourceColumnKey: this.dragState.sourceColumnKey, + target: this.inHeader ? "header" : "grid" + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_END, payload); + } + initializeDrag(element, mouseOffset, e) { + const eventId = element.dataset.eventId || ""; + const isHeaderItem = element.tagName.toLowerCase() === "swp-header-item"; + const columnElement = element.closest("swp-day-column"); + if (!isHeaderItem && !columnElement) + return; + if (isHeaderItem) { + this.initializeHeaderItemDrag(element, mouseOffset, eventId); + } else { + this.initializeGridEventDrag(element, mouseOffset, e, columnElement, eventId); + } + } + /** + * Initialize drag for a header item (allDay event) + */ + initializeHeaderItemDrag(element, mouseOffset, eventId) { + element.classList.add("dragging"); + this.dragState = { + eventId, + element, + ghostElement: null, + // No ghost for header items + startY: 0, + mouseOffset, + columnElement: null, + currentColumn: null, + targetY: 0, + currentY: 0, + animationId: 0, + sourceColumnKey: "", + // Will be set from header item data + dragSource: "header" + }; + this.inHeader = true; + } + /** + * Initialize drag for a grid event + */ + initializeGridEventDrag(element, mouseOffset, e, columnElement, eventId) { + const elementRect = element.getBoundingClientRect(); + const columnRect = columnElement.getBoundingClientRect(); + const startY = elementRect.top - columnRect.top; + const group = element.closest("swp-event-group"); + if (group) { + const eventsLayer = columnElement.querySelector("swp-events-layer"); + if (eventsLayer) { + eventsLayer.appendChild(element); + } + } + element.style.position = "absolute"; + element.style.top = `${startY}px`; + element.style.left = "2px"; + element.style.right = "2px"; + element.style.marginLeft = "0"; + const ghostElement = element.cloneNode(true); + ghostElement.classList.add("drag-ghost"); + ghostElement.style.opacity = "0.3"; + ghostElement.style.pointerEvents = "none"; + element.parentNode?.insertBefore(ghostElement, element); + element.classList.add("dragging"); + const targetY = e.clientY - columnRect.top - mouseOffset.y; + this.dragState = { + eventId, + element, + ghostElement, + startY, + mouseOffset, + columnElement, + currentColumn: columnElement, + targetY: Math.max(0, targetY), + currentY: startY, + animationId: 0, + sourceColumnKey: columnElement.dataset.columnKey || "", + dragSource: "grid" + }; + const payload = { + eventId, + element, + ghostElement, + startY, + mouseOffset, + columnElement + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_START, payload); + this.animateDrag(); + } + updateDragTarget(e) { + if (!this.dragState) + return; + this.checkHeaderZone(e); + if (this.inHeader) + return; + const columnAtPoint = this.getColumnAtPoint(e.clientX); + if (this.dragState.dragSource === "header" && columnAtPoint && !this.dragState.currentColumn) { + this.dragState.currentColumn = columnAtPoint; + this.dragState.columnElement = columnAtPoint; + } + if (columnAtPoint && columnAtPoint !== this.dragState.currentColumn && this.dragState.currentColumn) { + const payload = { + eventId: this.dragState.eventId, + element: this.dragState.element, + previousColumn: this.dragState.currentColumn, + newColumn: columnAtPoint, + currentY: this.dragState.currentY + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_COLUMN_CHANGE, payload); + this.dragState.currentColumn = columnAtPoint; + this.dragState.columnElement = columnAtPoint; + } + if (!this.dragState.columnElement) + return; + const columnRect = this.dragState.columnElement.getBoundingClientRect(); + const targetY = e.clientY - columnRect.top - this.dragState.mouseOffset.y; + this.dragState.targetY = Math.max(0, targetY); + if (!this.dragState.animationId) { + this.animateDrag(); + } + } + /** + * Check if pointer is in header zone and emit appropriate events + */ + checkHeaderZone(e) { + if (!this.dragState) + return; + const headerViewport = document.querySelector("swp-header-viewport"); + if (!headerViewport) + return; + const rect = headerViewport.getBoundingClientRect(); + const isInHeader = e.clientY < rect.bottom; + if (isInHeader && !this.inHeader) { + this.inHeader = true; + if (this.dragState.dragSource === "grid" && this.dragState.columnElement) { + const payload = { + eventId: this.dragState.eventId, + element: this.dragState.element, + sourceColumnIndex: this.getColumnIndex(this.dragState.columnElement), + sourceColumnKey: this.dragState.columnElement.dataset.columnKey || "", + title: this.dragState.element.querySelector("swp-event-title")?.textContent || "", + colorClass: [...this.dragState.element.classList].find((c) => c.startsWith("is-")), + itemType: "event", + duration: 1 + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_ENTER_HEADER, payload); + } + } else if (!isInHeader && this.inHeader) { + this.inHeader = false; + const targetColumn = this.getColumnAtPoint(e.clientX); + if (this.dragState.dragSource === "header") { + const payload = { + eventId: this.dragState.eventId, + source: "header", + element: this.dragState.element, + targetColumn: targetColumn || void 0, + start: this.dragState.element.dataset.start ? new Date(this.dragState.element.dataset.start) : void 0, + end: this.dragState.element.dataset.end ? new Date(this.dragState.element.dataset.end) : void 0, + title: this.dragState.element.textContent || "", + colorClass: [...this.dragState.element.classList].find((c) => c.startsWith("is-")) + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_LEAVE_HEADER, payload); + if (targetColumn) { + const newElement = targetColumn.querySelector(`swp-event[data-event-id="${this.dragState.eventId}"]`); + if (newElement) { + this.dragState.element = newElement; + this.dragState.columnElement = targetColumn; + this.dragState.currentColumn = targetColumn; + this.animateDrag(); + } + } + } else { + const payload = { + eventId: this.dragState.eventId, + source: "grid" + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_LEAVE_HEADER, payload); + } + } else if (isInHeader) { + const column = this.getColumnAtX(e.clientX); + if (column) { + const payload = { + eventId: this.dragState.eventId, + columnIndex: this.getColumnIndex(column), + columnKey: column.dataset.columnKey || "" + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_MOVE_HEADER, payload); + } + } + } + /** + * Get column index (0-based) for a column element + */ + getColumnIndex(column) { + if (!this.container || !column) + return 0; + const columns = Array.from(this.container.querySelectorAll("swp-day-column")); + return columns.indexOf(column); + } + /** + * Get column at X coordinate (alias for getColumnAtPoint) + */ + getColumnAtX(clientX) { + return this.getColumnAtPoint(clientX); + } + /** + * Find column element at given X coordinate + */ + getColumnAtPoint(clientX) { + if (!this.container) + return null; + const columns = this.container.querySelectorAll("swp-day-column"); + for (const col of columns) { + const rect = col.getBoundingClientRect(); + if (clientX >= rect.left && clientX <= rect.right) { + return col; + } + } + return null; + } + /** + * Cancel drag and animate back to start position + */ + cancelDrag() { + if (!this.dragState) + return; + cancelAnimationFrame(this.dragState.animationId); + const { element, ghostElement, startY, eventId } = this.dragState; + element.style.transition = "top 200ms ease-out"; + element.style.top = `${startY}px`; + setTimeout(() => { + ghostElement?.remove(); + element.style.transition = ""; + element.classList.remove("dragging"); + }, 200); + const payload = { + eventId, + element, + startY + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_CANCEL, payload); + this.dragState = null; + this.inHeader = false; + } +}; +var EdgeScrollManager = class { + constructor(eventBus) { + this.eventBus = eventBus; + this.scrollableContent = null; + this.timeGrid = null; + this.draggedElement = null; + this.scrollRAF = null; + this.mouseY = 0; + this.isDragging = false; + this.isScrolling = false; + this.lastTs = 0; + this.rect = null; + this.initialScrollTop = 0; + this.OUTER_ZONE = 100; + this.INNER_ZONE = 50; + this.SLOW_SPEED = 140; + this.FAST_SPEED = 640; + this.trackMouse = (e) => { + if (this.isDragging) { + this.mouseY = e.clientY; + } + }; + this.scrollTick = (ts) => { + if (!this.isDragging || !this.scrollableContent) + return; + const dt = this.lastTs ? (ts - this.lastTs) / 1e3 : 0; + this.lastTs = ts; + this.rect ?? (this.rect = this.scrollableContent.getBoundingClientRect()); + const velocity = this.calculateVelocity(); + if (velocity !== 0 && !this.isAtBoundary(velocity)) { + const scrollDelta = velocity * dt; + this.scrollableContent.scrollTop += scrollDelta; + this.rect = null; + this.eventBus.emit(CoreEvents.EDGE_SCROLL_TICK, { scrollDelta }); + this.setScrollingState(true); + } else { + this.setScrollingState(false); + } + this.scrollRAF = requestAnimationFrame(this.scrollTick); + }; + this.subscribeToEvents(); + document.addEventListener("pointermove", this.trackMouse); + } + init(scrollableContent) { + this.scrollableContent = scrollableContent; + this.timeGrid = scrollableContent.querySelector("swp-time-grid"); + this.scrollableContent.style.scrollBehavior = "auto"; + } + subscribeToEvents() { + this.eventBus.on(CoreEvents.EVENT_DRAG_START, (event) => { + const payload = event.detail; + this.draggedElement = payload.element; + this.startDrag(); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_END, () => this.stopDrag()); + this.eventBus.on(CoreEvents.EVENT_DRAG_CANCEL, () => this.stopDrag()); + } + startDrag() { + this.isDragging = true; + this.isScrolling = false; + this.lastTs = 0; + this.initialScrollTop = this.scrollableContent?.scrollTop ?? 0; + if (this.scrollRAF === null) { + this.scrollRAF = requestAnimationFrame(this.scrollTick); + } + } + stopDrag() { + this.isDragging = false; + this.setScrollingState(false); + if (this.scrollRAF !== null) { + cancelAnimationFrame(this.scrollRAF); + this.scrollRAF = null; + } + this.rect = null; + this.lastTs = 0; + this.initialScrollTop = 0; + } + calculateVelocity() { + if (!this.rect) + return 0; + const distTop = this.mouseY - this.rect.top; + const distBot = this.rect.bottom - this.mouseY; + if (distTop < this.INNER_ZONE) + return -this.FAST_SPEED; + if (distTop < this.OUTER_ZONE) + return -this.SLOW_SPEED; + if (distBot < this.INNER_ZONE) + return this.FAST_SPEED; + if (distBot < this.OUTER_ZONE) + return this.SLOW_SPEED; + return 0; + } + isAtBoundary(velocity) { + if (!this.scrollableContent || !this.timeGrid || !this.draggedElement) + return false; + const atTop = this.scrollableContent.scrollTop <= 0 && velocity < 0; + const atBottom = velocity > 0 && this.draggedElement.getBoundingClientRect().bottom >= this.timeGrid.getBoundingClientRect().bottom; + return atTop || atBottom; + } + setScrollingState(scrolling) { + if (this.isScrolling === scrolling) + return; + this.isScrolling = scrolling; + if (scrolling) { + this.eventBus.emit(CoreEvents.EDGE_SCROLL_STARTED, {}); + } else { + this.initialScrollTop = this.scrollableContent?.scrollTop ?? 0; + this.eventBus.emit(CoreEvents.EDGE_SCROLL_STOPPED, {}); + } + } +}; +var ResizeManager = class { + constructor(eventBus, gridConfig, dateService) { + this.eventBus = eventBus; + this.gridConfig = gridConfig; + this.dateService = dateService; + this.container = null; + this.resizeState = null; + this.Z_INDEX_RESIZING = "1000"; + this.ANIMATION_SPEED = 0.35; + this.MIN_HEIGHT_MINUTES = 15; + this.handleMouseOver = (e) => { + const target = e.target; + const eventElement = target.closest("swp-event"); + if (!eventElement || this.resizeState) + return; + if (!eventElement.querySelector(":scope > swp-resize-handle")) { + const handle = this.createResizeHandle(); + eventElement.appendChild(handle); + } + }; + this.handlePointerDown = (e) => { + const handle = e.target.closest("swp-resize-handle"); + if (!handle) + return; + const element = handle.parentElement; + if (!element) + return; + const eventId = element.dataset.eventId || ""; + const startHeight = element.offsetHeight; + const startDurationMinutes = pixelsToMinutes(startHeight, this.gridConfig); + const container = element.closest("swp-event-group") ?? element; + const prevZIndex = container.style.zIndex; + this.resizeState = { + eventId, + element, + handleElement: handle, + startY: e.clientY, + startHeight, + startDurationMinutes, + pointerId: e.pointerId, + prevZIndex, + // Animation state + currentHeight: startHeight, + targetHeight: startHeight, + animationId: null + }; + container.style.zIndex = this.Z_INDEX_RESIZING; + try { + handle.setPointerCapture(e.pointerId); + } catch (err) { + console.warn("Pointer capture failed:", err); + } + document.documentElement.classList.add("swp--resizing"); + this.eventBus.emit(CoreEvents.EVENT_RESIZE_START, { + eventId, + element, + startHeight + }); + e.preventDefault(); + }; + this.handlePointerMove = (e) => { + if (!this.resizeState) + return; + const deltaY = e.clientY - this.resizeState.startY; + const minHeight = this.MIN_HEIGHT_MINUTES / 60 * this.gridConfig.hourHeight; + const newHeight = Math.max(minHeight, this.resizeState.startHeight + deltaY); + this.resizeState.targetHeight = newHeight; + if (this.resizeState.animationId === null) { + this.animateHeight(); + } + }; + this.animateHeight = () => { + if (!this.resizeState) + return; + const diff2 = this.resizeState.targetHeight - this.resizeState.currentHeight; + if (Math.abs(diff2) < 0.5) { + this.resizeState.animationId = null; + return; + } + this.resizeState.currentHeight += diff2 * this.ANIMATION_SPEED; + this.resizeState.element.style.height = `${this.resizeState.currentHeight}px`; + this.updateTimestampDisplay(); + this.resizeState.animationId = requestAnimationFrame(this.animateHeight); + }; + this.handlePointerUp = (e) => { + if (!this.resizeState) + return; + if (this.resizeState.animationId !== null) { + cancelAnimationFrame(this.resizeState.animationId); + } + try { + this.resizeState.handleElement.releasePointerCapture(e.pointerId); + } catch (err) { + console.warn("Pointer release failed:", err); + } + this.snapToGridFinal(); + this.updateTimestampDisplay(); + const container = this.resizeState.element.closest("swp-event-group") ?? this.resizeState.element; + container.style.zIndex = this.resizeState.prevZIndex; + document.documentElement.classList.remove("swp--resizing"); + const column = this.resizeState.element.closest("swp-day-column"); + const columnKey = column?.dataset.columnKey || ""; + const date = column?.dataset.date || ""; + const swpEvent = SwpEvent.fromElement(this.resizeState.element, columnKey, date, this.gridConfig); + this.eventBus.emit(CoreEvents.EVENT_RESIZE_END, { + swpEvent + }); + this.resizeState = null; + }; + } + /** + * Initialize resize functionality on container + */ + init(container) { + this.container = container; + container.addEventListener("mouseover", this.handleMouseOver, true); + document.addEventListener("pointerdown", this.handlePointerDown, true); + document.addEventListener("pointermove", this.handlePointerMove, true); + document.addEventListener("pointerup", this.handlePointerUp, true); + } + /** + * Create resize handle element + */ + createResizeHandle() { + const handle = document.createElement("swp-resize-handle"); + handle.setAttribute("aria-label", "Resize event"); + handle.setAttribute("role", "separator"); + return handle; + } + /** + * Update timestamp display with snapped end time + */ + updateTimestampDisplay() { + if (!this.resizeState) + return; + const timeEl = this.resizeState.element.querySelector("swp-event-time"); + if (!timeEl) + return; + const top = parseFloat(this.resizeState.element.style.top) || 0; + const startMinutesFromGrid = pixelsToMinutes(top, this.gridConfig); + const startMinutes = this.gridConfig.dayStartHour * 60 + startMinutesFromGrid; + const snappedHeight = snapToGrid(this.resizeState.currentHeight, this.gridConfig); + const durationMinutes = pixelsToMinutes(snappedHeight, this.gridConfig); + const endMinutes = startMinutes + durationMinutes; + const start = this.minutesToDate(startMinutes); + const end = this.minutesToDate(endMinutes); + timeEl.textContent = this.dateService.formatTimeRange(start, end); + } + /** + * Convert minutes since midnight to Date + */ + minutesToDate(minutes) { + const date = /* @__PURE__ */ new Date(); + date.setHours(Math.floor(minutes / 60) % 24, minutes % 60, 0, 0); + return date; + } + /** + * Snap final height to grid interval + */ + snapToGridFinal() { + if (!this.resizeState) + return; + const currentHeight = this.resizeState.element.offsetHeight; + const snappedHeight = snapToGrid(currentHeight, this.gridConfig); + const minHeight = minutesToPixels(this.MIN_HEIGHT_MINUTES, this.gridConfig); + const finalHeight = Math.max(minHeight, snappedHeight); + this.resizeState.element.style.height = `${finalHeight}px`; + this.resizeState.currentHeight = finalHeight; + } +}; +var EventPersistenceManager = class { + constructor(eventService, eventBus, dateService) { + this.eventService = eventService; + this.eventBus = eventBus; + this.dateService = dateService; + this.handleDragEnd = async (e) => { + const payload = e.detail; + const { swpEvent } = payload; + const event = await this.eventService.get(swpEvent.eventId); + if (!event) { + console.warn(`EventPersistenceManager: Event ${swpEvent.eventId} not found`); + return; + } + const { resource } = this.dateService.parseColumnKey(swpEvent.columnKey); + const updatedEvent = { + ...event, + start: swpEvent.start, + end: swpEvent.end, + resourceId: resource ?? event.resourceId, + allDay: payload.target === "header", + syncStatus: "pending" + }; + await this.eventService.save(updatedEvent); + const updatePayload = { + eventId: updatedEvent.id, + sourceColumnKey: payload.sourceColumnKey, + targetColumnKey: swpEvent.columnKey + }; + this.eventBus.emit(CoreEvents.EVENT_UPDATED, updatePayload); + }; + this.handleResizeEnd = async (e) => { + const payload = e.detail; + const { swpEvent } = payload; + const event = await this.eventService.get(swpEvent.eventId); + if (!event) { + console.warn(`EventPersistenceManager: Event ${swpEvent.eventId} not found`); + return; + } + const updatedEvent = { + ...event, + end: swpEvent.end, + syncStatus: "pending" + }; + await this.eventService.save(updatedEvent); + const updatePayload = { + eventId: updatedEvent.id, + sourceColumnKey: swpEvent.columnKey, + targetColumnKey: swpEvent.columnKey + }; + this.eventBus.emit(CoreEvents.EVENT_UPDATED, updatePayload); + }; + this.setupListeners(); + } + setupListeners() { + this.eventBus.on(CoreEvents.EVENT_DRAG_END, this.handleDragEnd); + this.eventBus.on(CoreEvents.EVENT_RESIZE_END, this.handleResizeEnd); + } +}; +var defaultTimeFormatConfig = { + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + use24HourFormat: true, + locale: "da-DK", + dateFormat: "locale", + showSeconds: false +}; +var defaultGridConfig = { + hourHeight: 64, + dayStartHour: 6, + dayEndHour: 18, + snapInterval: 15, + gridStartThresholdMinutes: 30 +}; +function registerCoreServices(builder, options) { + const timeConfig = options?.timeConfig ?? defaultTimeFormatConfig; + const gridConfig = options?.gridConfig ?? defaultGridConfig; + const dbConfig = options?.dbConfig ?? defaultDBConfig; + builder.registerInstance(timeConfig).as("ITimeFormatConfig"); + builder.registerInstance(gridConfig).as("IGridConfig"); + builder.registerInstance(dbConfig).as("IDBConfig"); + builder.registerType(EventBus).as("EventBus"); + builder.registerType(EventBus).as("IEventBus"); + builder.registerType(DateService).as("DateService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("ITimeFormatConfig"), + void 0 + ] + }); + builder.registerType(IndexedDBContext).as("IndexedDBContext").autoWire({ + mapResolvers: [ + (c) => c.resolveTypeAll("IStore"), + (c) => c.resolveType("IDBConfig") + ] + }); + builder.registerType(EventStore).as("IStore"); + builder.registerType(ResourceStore).as("IStore"); + builder.registerType(SettingsStore).as("IStore"); + builder.registerType(ViewConfigStore).as("IStore"); + builder.registerType(EventService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(EventService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(EventService).as("EventService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ResourceService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ResourceService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ResourceService).as("ResourceService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(SettingsService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(SettingsService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(SettingsService).as("SettingsService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ViewConfigService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ViewConfigService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ViewConfigService).as("ViewConfigService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(EventRenderer).as("EventRenderer").autoWire({ + mapResolvers: [ + (c) => c.resolveType("EventService"), + (c) => c.resolveType("DateService"), + (c) => c.resolveType("IGridConfig"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ScheduleRenderer).as("ScheduleRenderer").autoWire({ + mapResolvers: [ + (c) => c.resolveType("ResourceScheduleService"), + (c) => c.resolveType("DateService"), + (c) => c.resolveType("IGridConfig") + ] + }); + builder.registerType(HeaderDrawerRenderer).as("HeaderDrawerRenderer").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IEventBus"), + (c) => c.resolveType("IGridConfig"), + (c) => c.resolveType("HeaderDrawerManager"), + (c) => c.resolveType("EventService"), + (c) => c.resolveType("DateService") + ] + }); + builder.registerType(TimeAxisRenderer).as("TimeAxisRenderer"); + builder.registerType(DateRenderer).as("IRenderer").autoWire({ + mapResolvers: [ + (c) => c.resolveType("DateService") + ] + }); + builder.registerType(ResourceRenderer).as("IRenderer").autoWire({ + mapResolvers: [ + (c) => c.resolveType("ResourceService") + ] + }); + builder.registerType(ScrollManager).as("ScrollManager"); + builder.registerType(HeaderDrawerManager).as("HeaderDrawerManager"); + builder.registerType(DragDropManager).as("DragDropManager").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IEventBus"), + (c) => c.resolveType("IGridConfig") + ] + }); + builder.registerType(EdgeScrollManager).as("EdgeScrollManager").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ResizeManager).as("ResizeManager").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IEventBus"), + (c) => c.resolveType("IGridConfig"), + (c) => c.resolveType("DateService") + ] + }); + builder.registerType(EventPersistenceManager).as("EventPersistenceManager").autoWire({ + mapResolvers: [ + (c) => c.resolveType("EventService"), + (c) => c.resolveType("IEventBus"), + (c) => c.resolveType("DateService") + ] + }); + builder.registerType(CalendarOrchestrator).as("CalendarOrchestrator").autoWire({ + mapResolvers: [ + (c) => c.resolveTypeAll("IRenderer"), + (c) => c.resolveType("EventRenderer"), + (c) => c.resolveType("ScheduleRenderer"), + (c) => c.resolveType("HeaderDrawerRenderer"), + (c) => c.resolveType("DateService"), + (c) => c.resolveTypeAll("IEntityService") + ] + }); + builder.registerType(CalendarApp).as("CalendarApp").autoWire({ + mapResolvers: [ + (c) => c.resolveType("CalendarOrchestrator"), + (c) => c.resolveType("TimeAxisRenderer"), + (c) => c.resolveType("DateService"), + (c) => c.resolveType("ScrollManager"), + (c) => c.resolveType("HeaderDrawerManager"), + (c) => c.resolveType("DragDropManager"), + (c) => c.resolveType("EdgeScrollManager"), + (c) => c.resolveType("ResizeManager"), + (c) => c.resolveType("HeaderDrawerRenderer"), + (c) => c.resolveType("EventPersistenceManager"), + (c) => c.resolveType("SettingsService"), + (c) => c.resolveType("ViewConfigService"), + (c) => c.resolveType("IEventBus") + ] + }); +} + +// node_modules/calendar/dist/extensions/schedules/index.js +var ScheduleOverrideStore = class _ScheduleOverrideStore { + constructor() { + this.storeName = _ScheduleOverrideStore.STORE_NAME; + } + create(db) { + const store = db.createObjectStore(_ScheduleOverrideStore.STORE_NAME, { keyPath: "id" }); + store.createIndex("resourceId", "resourceId", { unique: false }); + store.createIndex("date", "date", { unique: false }); + store.createIndex("resourceId_date", ["resourceId", "date"], { unique: true }); + store.createIndex("syncStatus", "syncStatus", { unique: false }); + } +}; +ScheduleOverrideStore.STORE_NAME = "scheduleOverrides"; +var ScheduleOverrideService = class { + constructor(context) { + this.context = context; + } + get db() { + return this.context.getDatabase(); + } + /** + * Get override for a specific resource and date + */ + async getOverride(resourceId, date) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([ScheduleOverrideStore.STORE_NAME], "readonly"); + const store = transaction.objectStore(ScheduleOverrideStore.STORE_NAME); + const index = store.index("resourceId_date"); + const request = index.get([resourceId, date]); + request.onsuccess = () => { + resolve(request.result || null); + }; + request.onerror = () => { + reject(new Error(`Failed to get override for ${resourceId} on ${date}: ${request.error}`)); + }; + }); + } + /** + * Get all overrides for a resource + */ + async getByResource(resourceId) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([ScheduleOverrideStore.STORE_NAME], "readonly"); + const store = transaction.objectStore(ScheduleOverrideStore.STORE_NAME); + const index = store.index("resourceId"); + const request = index.getAll(resourceId); + request.onsuccess = () => { + resolve(request.result || []); + }; + request.onerror = () => { + reject(new Error(`Failed to get overrides for ${resourceId}: ${request.error}`)); + }; + }); + } + /** + * Get overrides for a date range + */ + async getByDateRange(resourceId, startDate, endDate) { + const all = await this.getByResource(resourceId); + return all.filter((o) => o.date >= startDate && o.date <= endDate); + } + /** + * Save an override + */ + async save(override) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([ScheduleOverrideStore.STORE_NAME], "readwrite"); + const store = transaction.objectStore(ScheduleOverrideStore.STORE_NAME); + const request = store.put(override); + request.onsuccess = () => resolve(); + request.onerror = () => { + reject(new Error(`Failed to save override ${override.id}: ${request.error}`)); + }; + }); + } + /** + * Delete an override + */ + async delete(id) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([ScheduleOverrideStore.STORE_NAME], "readwrite"); + const store = transaction.objectStore(ScheduleOverrideStore.STORE_NAME); + const request = store.delete(id); + request.onsuccess = () => resolve(); + request.onerror = () => { + reject(new Error(`Failed to delete override ${id}: ${request.error}`)); + }; + }); + } +}; +var ResourceScheduleService = class { + constructor(resourceService, overrideService, dateService) { + this.resourceService = resourceService; + this.overrideService = overrideService; + this.dateService = dateService; + } + /** + * Get effective schedule for a resource on a specific date + * + * @param resourceId - Resource ID + * @param date - Date string "YYYY-MM-DD" + * @returns ITimeSlot or null (fri/closed) + */ + async getScheduleForDate(resourceId, date) { + const override = await this.overrideService.getOverride(resourceId, date); + if (override) { + return override.schedule; + } + const resource = await this.resourceService.get(resourceId); + if (!resource || !resource.defaultSchedule) { + return null; + } + const weekDay = this.dateService.getISOWeekDay(date); + return resource.defaultSchedule[weekDay] || null; + } + /** + * Get schedules for multiple dates + * + * @param resourceId - Resource ID + * @param dates - Array of date strings "YYYY-MM-DD" + * @returns Map of date -> ITimeSlot | null + */ + async getSchedulesForDates(resourceId, dates) { + const result = /* @__PURE__ */ new Map(); + const resource = await this.resourceService.get(resourceId); + const overrides = dates.length > 0 ? await this.overrideService.getByDateRange(resourceId, dates[0], dates[dates.length - 1]) : []; + const overrideMap = new Map(overrides.map((o) => [o.date, o.schedule])); + for (const date of dates) { + if (overrideMap.has(date)) { + result.set(date, overrideMap.get(date)); + continue; + } + if (resource?.defaultSchedule) { + const weekDay = this.dateService.getISOWeekDay(date); + result.set(date, resource.defaultSchedule[weekDay] || null); + } else { + result.set(date, null); + } + } + return result; + } +}; +function registerSchedules(builder) { + builder.registerType(ScheduleOverrideStore).as("IStore"); + builder.registerType(ScheduleOverrideService).as("ScheduleOverrideService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext") + ] + }); + builder.registerType(ResourceScheduleService).as("ResourceScheduleService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("ResourceService"), + (c) => c.resolveType("ScheduleOverrideService"), + (c) => c.resolveType("DateService") + ] + }); +} + +// src/index.ts +async function init() { + const databases = await indexedDB.databases(); + const dbExists = databases.some((db) => db.name === "CalendarTestDB"); + const container = new Container(); + const builder = container.builder(); + registerCoreServices(builder, { + dbConfig: { dbName: "CalendarTestDB", dbVersion: 4 } + }); + registerSchedules(builder); + const app = builder.build(); + console.log("Container created"); + const dbContext = app.resolveType("IndexedDBContext"); + await dbContext.initialize(); + console.log("IndexedDB initialized"); + const settingsService = app.resolveType("SettingsService"); + const viewConfigService = app.resolveType("ViewConfigService"); + const eventService = app.resolveType("EventService"); + if (dbExists) { + console.log("Database exists, skipping seed"); + } else { + console.log("Seeding data..."); + await seedData(settingsService, viewConfigService, eventService); + } + const calendarApp = app.resolveType("CalendarApp"); + const containerEl = document.querySelector("swp-calendar-container"); + await calendarApp.init(containerEl); + const eventBus = app.resolveType("EventBus"); + eventBus.emit(CalendarEvents.CMD_RENDER, { viewId: "simple" }); + console.log("Calendar rendered"); + document.addEventListener("event:drag-end", (e) => { + console.log("event:drag-end:", e.detail); + }); + document.addEventListener("event:updated", (e) => { + console.log("event:updated:", e.detail); + }); + const persistenceManager = app.resolveType("EventPersistenceManager"); + console.log("EventPersistenceManager resolved:", persistenceManager); +} +async function seedData(settingsService, viewConfigService, eventService) { + await settingsService.save({ + id: "grid", + dayStartHour: 8, + dayEndHour: 17, + workStartHour: 9, + workEndHour: 16, + hourHeight: 64, + snapInterval: 15, + syncStatus: "synced" + }); + await settingsService.save({ + id: "workweek", + presets: { + standard: { id: "standard", label: "Standard", workDays: [1, 2, 3, 4, 5], periodDays: 7 } + }, + defaultPreset: "standard", + firstDayOfWeek: 1, + syncStatus: "synced" + }); + await viewConfigService.save({ + id: "simple", + groupings: [{ type: "date", values: [], idProperty: "date", derivedFrom: "start" }], + syncStatus: "synced" + }); + const today = /* @__PURE__ */ new Date(); + today.setHours(0, 0, 0, 0); + console.log("Event date:", today.toISOString()); + const start1 = new Date(today); + start1.setHours(9, 0, 0, 0); + const end1 = new Date(today); + end1.setHours(10, 0, 0, 0); + await eventService.save({ + id: "1", + title: "Morgenm\xF8de", + start: start1, + end: end1, + type: "meeting", + allDay: false, + syncStatus: "synced" + }); + const start2 = new Date(today); + start2.setHours(12, 0, 0, 0); + const end2 = new Date(today); + end2.setHours(13, 0, 0, 0); + await eventService.save({ + id: "2", + title: "Frokost", + start: start2, + end: end2, + type: "break", + allDay: false, + syncStatus: "synced" + }); +} +init().catch(console.error); diff --git a/test-package/dist/css/calendar.css b/test-package/dist/css/calendar.css new file mode 100644 index 0000000..5a06eed --- /dev/null +++ b/test-package/dist/css/calendar.css @@ -0,0 +1,877 @@ +/* V2 Base - Shared variables */ + +:root { + /* Grid measurements */ + --hour-height: 64px; + --time-axis-width: 60px; + --grid-columns: 7; + --day-column-min-width: 200px; + --day-start-hour: 6; + --day-end-hour: 18; + --header-height: 70px; + + /* Colors - UI */ + --color-border: #e0e0e0; + --color-surface: #fff; + --color-background: #f5f5f5; + --color-background-hover: #f0f0f0; + --color-background-alt: #fafafa; + --color-text: #333333; + --color-text-secondary: #666; + --color-primary: #1976d2; + --color-team-bg: #e3f2fd; + --color-team-text: #1565c0; + + /* Colors - Grid */ + --color-hour-line: rgba(0, 0, 0, 0.2); + --color-grid-line-light: rgba(0, 0, 0, 0.05); + --color-unavailable: rgba(0, 0, 0, 0.02); + + /* Named color palette for events (fra V1) */ + --b-color-red: #e53935; + --b-color-pink: #d81b60; + --b-color-magenta: #c200c2; + --b-color-purple: #8e24aa; + --b-color-violet: #5e35b1; + --b-color-deep-purple: #4527a0; + --b-color-indigo: #3949ab; + --b-color-blue: #1e88e5; + --b-color-light-blue: #03a9f4; + --b-color-cyan: #3bc9db; + --b-color-teal: #00897b; + --b-color-green: #43a047; + --b-color-light-green: #8bc34a; + --b-color-lime: #c0ca33; + --b-color-yellow: #fdd835; + --b-color-amber: #ffb300; + --b-color-orange: #fb8c00; + --b-color-deep-orange: #f4511e; + + /* Base mix for color-mix() function */ + --b-mix: #fff; + + /* Shadows */ + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1); + --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); + + /* Transitions */ + --transition-fast: 150ms ease; +} + +* { box-sizing: border-box; margin: 0; padding: 0; } + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background: var(--color-background); +} + +/* V2 Layout - Calendar structure, grid, navigation */ + +.calendar-wrapper { + height: 100vh; + display: flex; + flex-direction: column; +} + +swp-calendar { + display: grid; + grid-template-rows: auto 1fr; + height: 100%; + background: var(--color-surface); +} + +/* Nav */ +swp-calendar-nav { + display: flex; + gap: 12px; + padding: 8px 16px; + border-bottom: 1px solid var(--color-border); + align-items: center; + font-size: 13px; +} + +/* View switcher - small chips */ +swp-view-switcher { + display: flex; + gap: 4px; + background: var(--color-background-alt); + padding: 3px; + border-radius: 6px; +} + +.view-chip { + padding: 4px 10px; + border: none; + border-radius: 4px; + cursor: pointer; + background: transparent; + color: var(--color-text-secondary); + font-size: 12px; + font-weight: 500; + transition: all 0.15s ease; + + &:hover { + background: var(--color-surface); + color: var(--color-text); + } + + &.active { + background: var(--color-surface); + color: var(--color-text); + box-shadow: 0 1px 2px rgba(0,0,0,0.1); + } +} + +/* Workweek dropdown */ +.workweek-dropdown { + padding: 4px 8px; + border: 1px solid var(--color-border); + border-radius: 4px; + background: var(--color-surface); + font-size: 12px; + cursor: pointer; + + &:hover { border-color: var(--color-text-secondary); } + &:focus { outline: 2px solid var(--color-primary); outline-offset: 1px; } +} + +/* Resource selector (picker view) */ +swp-resource-selector { + &.hidden { display: none; } + + fieldset { + border: 1px solid var(--color-border); + border-radius: 6px; + padding: 6px 12px; + margin: 0; + } + + legend { + font-size: 11px; + font-weight: 500; + color: var(--color-text-secondary); + padding: 0 6px; + } + + .resource-checkboxes { + display: flex; + flex-wrap: wrap; + gap: 4px 16px; + } + + label { + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + cursor: pointer; + white-space: nowrap; + + &:hover { color: var(--color-primary); } + } + + input[type="checkbox"] { + width: 14px; + height: 14px; + cursor: pointer; + } +} + +/* Navigation group */ +swp-nav-group { + display: flex; + gap: 2px; +} + +swp-nav-button { + padding: 6px 12px; + border: 1px solid var(--color-border); + border-radius: 4px; + cursor: pointer; + background: var(--color-surface); + font-size: 12px; + + &:hover { background: var(--color-background-hover); } + + &.btn-small { + padding: 4px 8px; + font-size: 11px; + } +} + +swp-week-info { + margin-left: auto; + text-align: right; + + swp-week-number { + font-weight: 600; + font-size: 12px; + display: block; + } + + swp-date-range { + font-size: 11px; + color: var(--color-text-secondary); + } +} + +/* Container */ +swp-calendar-container { + display: grid; + grid-template-columns: var(--time-axis-width) 1fr; + grid-template-rows: auto 1fr; + overflow: hidden; + height: 100%; +} + +/* Time axis */ +swp-time-axis { + grid-column: 1; + grid-row: 1 / 3; + display: grid; + grid-template-rows: auto 1fr; + border-right: 1px solid var(--color-border); + background: var(--color-surface); + overflow: hidden; + user-select: none; +} + +swp-header-spacer { + border-bottom: 1px solid var(--color-border); + background: var(--color-surface); + z-index: 1; +} + +swp-header-drawer { + display: grid; + grid-template-columns: subgrid; + grid-column: 1 / -1; + grid-row: 2; + overflow: hidden; + background: var(--color-background-alt); + border-bottom: 1px solid var(--color-border); +} + +swp-time-axis-content { + display: flex; + flex-direction: column; + position: relative; +} + +swp-hour-marker { + height: var(--hour-height); + padding: 4px 8px; + font-size: 11px; + color: var(--color-text-secondary); + text-align: right; + position: relative; + + &::after { + content: ''; + position: absolute; + top: -1px; + right: 0; + width: 5px; + height: 1px; + background: var(--color-hour-line); + } + + &:first-child::after { + display: none; + } +} + +/* Grid container */ +swp-grid-container { + grid-column: 2; + grid-row: 1 / 3; + display: grid; + grid-template-columns: minmax(0, 1fr); + grid-template-rows: subgrid; + overflow: hidden; +} + +/* Viewport/Track for slide animation */ +swp-header-viewport { + display: grid; + grid-template-columns: repeat(var(--grid-columns), minmax(var(--day-column-min-width), 1fr)); + grid-template-rows: auto auto; + min-width: calc(var(--grid-columns) * var(--day-column-min-width)); + overflow-y: scroll; + overflow-x: hidden; + + &::-webkit-scrollbar { background: transparent; } + &::-webkit-scrollbar-thumb { background: transparent; } +} + +swp-content-viewport { + overflow: hidden; + min-height: 0; + width: 100%; +} + +swp-header-track { + display: grid; + grid-template-columns: subgrid; + grid-column: 1 / -1; + grid-row: 1; +} + +swp-content-track { + display: flex; + height: 100%; + + > swp-scrollable-content { + flex: 0 0 100%; + height: 100%; + } +} + +/* Header */ +swp-calendar-header { + display: grid; + grid-template-columns: subgrid; + grid-column: 1 / -1; + grid-auto-rows: auto; + background: var(--color-surface); + + &[data-levels="date"] > swp-day-header { grid-row: 1; } + + &[data-levels="resource date"] { + > swp-resource-header { grid-row: 1; } + > swp-day-header { grid-row: 2; } + } + + &[data-levels="team resource date"] { + > swp-team-header { grid-row: 1; } + > swp-resource-header { grid-row: 2; } + > swp-day-header { grid-row: 3; } + } + + &[data-levels="department resource date"] { + > swp-department-header { grid-row: 1; } + > swp-resource-header { grid-row: 2; } + > swp-day-header { grid-row: 3; } + } +} + +swp-day-header, +swp-resource-header, +swp-team-header, +swp-department-header { + padding: 8px; + text-align: center; + border-right: 1px solid var(--color-border); + border-bottom: 1px solid var(--color-border); + user-select: none; +} + +swp-team-header { + background: var(--color-team-bg); + color: var(--color-team-text); + font-weight: 500; + grid-column: span var(--team-cols, 1); +} + +swp-department-header { + background: var(--color-team-bg); + color: var(--color-team-text); + font-weight: 500; + grid-column: span var(--department-cols, 1); +} + +swp-resource-header { + background: var(--color-background-alt); + font-size: 13px; +} + +swp-day-header { + swp-day-name { + display: block; + font-size: 11px; + color: var(--color-text-secondary); + text-transform: uppercase; + } + + swp-day-date { + display: block; + font-size: 24px; + font-weight: 300; + } + + &[data-hidden="true"] { + display: none; + } +} + +/* Scrollable content */ +swp-scrollable-content { + display: block; + overflow: auto; +} + +swp-time-grid { + display: block; + position: relative; + min-height: calc((var(--day-end-hour) - var(--day-start-hour)) * var(--hour-height)); + min-width: calc(var(--grid-columns) * var(--day-column-min-width)); + + /* Timelinjer */ + &::after { + content: ''; + position: absolute; + inset: 0; + z-index: 2; + background-image: repeating-linear-gradient( + to bottom, + transparent, + transparent calc(var(--hour-height) - 1px), + var(--color-hour-line) calc(var(--hour-height) - 1px), + var(--color-hour-line) var(--hour-height) + ); + pointer-events: none; + } +} + +/* Kvarterlinjer - 3 linjer per time (15, 30, 45 min) */ +swp-grid-lines { + display: block; + position: absolute; + inset: 0; + z-index: 1; + background-image: repeating-linear-gradient( + to bottom, + transparent 0, + transparent calc(var(--hour-height) / 4 - 1px), + var(--color-grid-line-light) calc(var(--hour-height) / 4 - 1px), + var(--color-grid-line-light) calc(var(--hour-height) / 4), + transparent calc(var(--hour-height) / 4), + transparent calc(var(--hour-height) / 2 - 1px), + var(--color-grid-line-light) calc(var(--hour-height) / 2 - 1px), + var(--color-grid-line-light) calc(var(--hour-height) / 2), + transparent calc(var(--hour-height) / 2), + transparent calc(var(--hour-height) * 3 / 4 - 1px), + var(--color-grid-line-light) calc(var(--hour-height) * 3 / 4 - 1px), + var(--color-grid-line-light) calc(var(--hour-height) * 3 / 4), + transparent calc(var(--hour-height) * 3 / 4), + transparent var(--hour-height) + ); +} + +swp-day-columns { + position: absolute; + inset: 0; + display: grid; + grid-template-columns: repeat(var(--grid-columns), minmax(var(--day-column-min-width), 1fr)); + min-width: calc(var(--grid-columns) * var(--day-column-min-width)); +} + +swp-day-column { + position: relative; + border-right: 1px solid var(--color-border); +} + +swp-events-layer { + position: absolute; + inset: 0; + z-index: 10; +} + +/* Unavailable time zones (outside working hours) */ +swp-unavailable-layer { + position: absolute; + inset: 0; + z-index: 5; + pointer-events: none; +} + +swp-unavailable-zone { + position: absolute; + left: 0; + right: 0; + background: var(--color-unavailable, rgba(0, 0, 0, 0.05)); + pointer-events: none; +} + +/* V2 Events - Event styling (from V1 calendar-events-css.css) */ + +/* Event base styles */ +swp-day-columns swp-event { + --b-text: var(--color-text); + + position: absolute; + border-radius: 3px; + overflow: hidden; + cursor: pointer; + transition: background-color 200ms ease, box-shadow 150ms ease, transform 150ms ease; + z-index: 10; + left: 2px; + right: 2px; + font-size: 12px; + padding: 4px 6px; + user-select: none; + + /* Color system using color-mix() */ + background-color: color-mix(in srgb, var(--b-primary) 10%, var(--b-mix)); + color: var(--b-text); + border-left: 4px solid var(--b-primary); + + /* Enable container queries for responsive layout */ + container-type: size; + container-name: event; + + /* CSS Grid layout for time, title, and description */ + display: grid; + grid-template-columns: auto 1fr; + grid-template-rows: auto 1fr; + gap: 2px 4px; + align-items: start; + + /* Dragging state */ + &.dragging { + position: absolute; + z-index: 999999; + left: 2px; + right: 2px; + width: auto; + cursor: grabbing; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + } + + /* Ghost clone (stays in original position during drag) */ + &.drag-ghost { + opacity: 0.3; + pointer-events: none; + } + + /* Hover state */ + &:hover { + background-color: color-mix(in srgb, var(--b-primary) 15%, var(--b-mix)); + } +} + +swp-day-columns swp-event:hover { + z-index: 20; +} + +/* Resize handle - actual draggable element */ +swp-resize-handle { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 15px; + cursor: ns-resize; + z-index: 25; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transition: opacity 150ms ease; +} + +/* Show handle on hover */ +swp-day-columns swp-event:hover swp-resize-handle { + opacity: 1; +} + +/* Handle visual indicator (grip lines) */ +swp-resize-handle::before { + content: ''; + width: 30px; + height: 4px; + background: rgba(255, 255, 255, 0.9); + border-radius: 2px; + box-shadow: + 0 -2px 0 rgba(255, 255, 255, 0.9), + 0 2px 0 rgba(255, 255, 255, 0.9), + 0 0 4px rgba(0, 0, 0, 0.2); +} + +/* Global resizing state */ +.swp--resizing { + user-select: none !important; + cursor: ns-resize !important; +} + +.swp--resizing * { + cursor: ns-resize !important; +} + +swp-day-columns swp-event-time { + grid-column: 1; + grid-row: 1; + font-size: 0.875rem; + font-weight: 500; + white-space: nowrap; +} + +swp-day-columns swp-event-title { + grid-column: 2; + grid-row: 1; + font-size: 0.875rem; + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +swp-day-columns swp-event-description { + grid-column: 1 / -1; + grid-row: 2; + display: block; + font-size: 0.875rem; + opacity: 0.8; + line-height: 1.3; + overflow: hidden; + word-wrap: break-word; + + /* Ensure description fills available height for gradient effect */ + min-height: 100%; + align-self: stretch; + + /* Fade-out effect for long descriptions */ + -webkit-mask-image: linear-gradient(to bottom, black 70%, transparent 100%); + mask-image: linear-gradient(to bottom, black 70%, transparent 100%); +} + +/* Container queries for height-based layout */ + +/* Hide description when event is too short (< 30px) */ +@container event (height < 30px) { + swp-day-columns swp-event-description { + display: none; + } +} + +/* Full description for tall events (>= 100px) */ +@container event (height >= 100px) { + swp-day-columns swp-event-description { + max-height: none; + } +} + +/* Multi-day events */ +swp-multi-day-event { + position: relative; + height: 28px; + margin: 2px 4px; + padding: 0 8px; + border-radius: 4px; + display: flex; + align-items: center; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: all var(--transition-fast); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + user-select: none; + + /* Color system using color-mix() */ + --b-text: var(--color-text); + background-color: color-mix(in srgb, var(--b-primary) 10%, var(--b-mix)); + color: var(--b-text); + border-left: 4px solid var(--b-primary); + + &:hover { + background-color: color-mix(in srgb, var(--b-primary) 15%, var(--b-mix)); + } + + /* Continuation indicators */ + &[data-continues-before="true"] { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + margin-left: 0; + padding-left: 20px; + + &::before { + content: '\25C0'; + position: absolute; + left: 4px; + opacity: 0.6; + font-size: 0.75rem; + } + } + + &[data-continues-after="true"] { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + margin-right: 0; + padding-right: 20px; + + &::after { + content: '\25B6'; + position: absolute; + right: 4px; + opacity: 0.6; + font-size: 0.75rem; + } + } + + &:hover { + transform: translateY(-1px); + box-shadow: var(--shadow-sm); + } +} + +/* All-day events */ +swp-allday-event { + --b-text: var(--color-text); + background-color: color-mix(in srgb, var(--b-primary) 10%, var(--b-mix)); + color: var(--b-text); + border-left: 4px solid var(--b-primary); + cursor: pointer; + transition: background-color 200ms ease; + user-select: none; + + &:hover { + background-color: color-mix(in srgb, var(--b-primary) 15%, var(--b-mix)); + } +} + +/* Event creation preview */ +swp-event-preview { + position: absolute; + left: 8px; + right: 8px; + background: rgba(33, 150, 243, 0.1); + border: 2px dashed var(--color-primary); + border-radius: 4px; + + /* Position via CSS variables */ + top: calc(var(--preview-start) * var(--minute-height)); + height: calc(var(--preview-duration) * var(--minute-height)); +} + +/* Event filtering styles */ +/* When filter is active, all events are dimmed by default */ +swp-events-layer[data-filter-active="true"] swp-event { + opacity: 0.2; + transition: opacity 200ms ease; +} + +/* Events that match the filter stay normal */ +swp-events-layer[data-filter-active="true"] swp-event[data-matches="true"] { + opacity: 1; +} + +/* Event overlap styling */ +/* Event group container for column sharing */ +swp-event-group { + position: absolute; + display: grid; + gap: 2px; + left: 2px; + right: 2px; + z-index: 10; +} + +/* Grid column configurations */ +swp-event-group.cols-2 { + grid-template-columns: 1fr 1fr; +} + +swp-event-group.cols-3 { + grid-template-columns: 1fr 1fr 1fr; +} + +swp-event-group.cols-4 { + grid-template-columns: 1fr 1fr 1fr 1fr; +} + +/* Stack levels using margin-left */ +swp-event-group.stack-level-0 { + margin-left: 0px; +} + +swp-event-group.stack-level-1 { + margin-left: 15px; +} + +swp-event-group.stack-level-2 { + margin-left: 30px; +} + +swp-event-group.stack-level-3 { + margin-left: 45px; +} + +swp-event-group.stack-level-4 { + margin-left: 60px; +} + +/* Shadow for stacked events (level 1+) */ +swp-event[data-stack-link]:not([data-stack-link*='"stackLevel":0']), +swp-event-group[data-stack-link]:not([data-stack-link*='"stackLevel":0']) swp-event { + box-shadow: + 0 -1px 2px rgba(0, 0, 0, 0.1), + 0 1px 2px rgba(0, 0, 0, 0.1); +} + +/* Child events within grid */ +swp-event-group swp-event { + position: relative; + left: 0; + right: 0; +} + +/* All-day event transition for smooth repositioning */ +swp-allday-container swp-event.transitioning { + transition: grid-area 200ms ease-out, grid-row 200ms ease-out, grid-column 200ms ease-out; +} + +/* Color utility classes */ +.is-red { --b-primary: var(--b-color-red); } +.is-pink { --b-primary: var(--b-color-pink); } +.is-magenta { --b-primary: var(--b-color-magenta); } +.is-purple { --b-primary: var(--b-color-purple); } +.is-violet { --b-primary: var(--b-color-violet); } +.is-deep-purple { --b-primary: var(--b-color-deep-purple); } +.is-indigo { --b-primary: var(--b-color-indigo); } +.is-blue { --b-primary: var(--b-color-blue); } +.is-light-blue { --b-primary: var(--b-color-light-blue); } +.is-cyan { --b-primary: var(--b-color-cyan); } +.is-teal { --b-primary: var(--b-color-teal); } +.is-green { --b-primary: var(--b-color-green); } +.is-light-green { --b-primary: var(--b-color-light-green); } +.is-lime { --b-primary: var(--b-color-lime); } +.is-yellow { --b-primary: var(--b-color-yellow); } +.is-amber { --b-primary: var(--b-color-amber); } +.is-orange { --b-primary: var(--b-color-orange); } +.is-deep-orange { --b-primary: var(--b-color-deep-orange); } + +/* Header drawer items */ +swp-header-item { + --b-text: var(--color-text); + + /* Positioneres via style.gridArea */ + height: 22px; + margin: 1px 4px; + padding: 2px 8px; + border-radius: 3px; + font-size: 0.75rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: flex; + align-items: center; + cursor: pointer; + user-select: none; + transition: background-color 200ms ease; + + /* Color system - inverted from swp-event */ + background-color: color-mix(in srgb, var(--b-primary) 15%, var(--b-mix)); + color: var(--b-text); + + &:hover { + background-color: color-mix(in srgb, var(--b-primary) 10%, var(--b-mix)); + } + + /* Dragging state */ + &.dragging { + opacity: 0.7; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + } +} diff --git a/test-package/index.html b/test-package/index.html new file mode 100644 index 0000000..fcc8084 --- /dev/null +++ b/test-package/index.html @@ -0,0 +1,41 @@ + + + + + + Calendar Package Test + + + +

Calendar Package Test

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + diff --git a/test-package/package-lock.json b/test-package/package-lock.json new file mode 100644 index 0000000..c458173 --- /dev/null +++ b/test-package/package-lock.json @@ -0,0 +1,654 @@ +{ + "name": "test-package", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "test-package", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@novadi/core": "^0.6.0", + "calendar": "^0.1.6", + "dayjs": "^1.11.19" + }, + "devDependencies": { + "esbuild": "^0.27.2", + "typescript": "^5.9.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@novadi/core": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@novadi/core/-/core-0.6.0.tgz", + "integrity": "sha512-CU1134Nd7ULMg9OQbID5oP+FLtrMkNiLJ17+dmy4jjmPDcPK/dVzKTFxvJmbBvEfZEc9WtmkmJjqw11ABU7Jxw==", + "license": "MIT", + "dependencies": { + "unplugin": "^2.3.10" + }, + "optionalDependencies": { + "@rollup/rollup-win32-x64-msvc": "^4.52.5" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.56.0.tgz", + "integrity": "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/calendar": { + "version": "0.1.6", + "resolved": "http://npm.jarjarbinks:4873/calendar/-/calendar-0.1.6.tgz", + "integrity": "sha512-dZKOg6gHTAexklxsBGnszTWDi0rkV68XXV9epaHxZP6RlMvys155dpitq6q3aWCGbSw8xKeTF7FTHaz5yJoT6A==", + "dependencies": { + "dayjs": "^1.11.0" + }, + "peerDependencies": { + "@novadi/core": "^0.6.0" + } + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unplugin": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "license": "MIT" + } + } +} diff --git a/test-package/package.json b/test-package/package.json new file mode 100644 index 0000000..c0d1011 --- /dev/null +++ b/test-package/package.json @@ -0,0 +1,21 @@ +{ + "name": "test-package", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "@novadi/core": "^0.6.0", + "calendar": "^0.1.6", + "dayjs": "^1.11.19" + }, + "devDependencies": { + "esbuild": "^0.27.2", + "typescript": "^5.9.3" + } +} diff --git a/test-package/src/index.ts b/test-package/src/index.ts new file mode 100644 index 0000000..43e78c8 --- /dev/null +++ b/test-package/src/index.ts @@ -0,0 +1,139 @@ +import { Container } from '@novadi/core'; +import { + registerCoreServices, + CalendarApp, + IndexedDBContext, + SettingsService, + ViewConfigService, + EventService, + EventBus, + CalendarEvents, + EventPersistenceManager +} from 'calendar'; +import { registerSchedules } from 'calendar/schedules'; + +async function init() { + // Check if database already exists + const databases = await indexedDB.databases(); + const dbExists = databases.some(db => db.name === 'CalendarTestDB'); + + const container = new Container(); + const builder = container.builder(); + registerCoreServices(builder, { + dbConfig: { dbName: 'CalendarTestDB', dbVersion: 4 } + }); + registerSchedules(builder); + const app = builder.build(); + + console.log('Container created'); + + // Initialize IndexedDB + const dbContext = app.resolveType(); + await dbContext.initialize(); + console.log('IndexedDB initialized'); + + const settingsService = app.resolveType(); + const viewConfigService = app.resolveType(); + const eventService = app.resolveType(); + + if (dbExists) { + console.log('Database exists, skipping seed'); + } else { + console.log('Seeding data...'); + await seedData(settingsService, viewConfigService, eventService); + } + + // Initialize and render + const calendarApp = app.resolveType(); + const containerEl = document.querySelector('swp-calendar-container') as HTMLElement; + await calendarApp.init(containerEl); + + const eventBus = app.resolveType(); + eventBus.emit(CalendarEvents.CMD_RENDER, { viewId: 'simple' }); + + console.log('Calendar rendered'); + + // Debug listeners + document.addEventListener('event:drag-end', (e) => { + console.log('event:drag-end:', (e as CustomEvent).detail); + }); + document.addEventListener('event:updated', (e) => { + console.log('event:updated:', (e as CustomEvent).detail); + }); + + const persistenceManager = app.resolveType(); + console.log('EventPersistenceManager resolved:', persistenceManager); +} + +async function seedData( + settingsService: SettingsService, + viewConfigService: ViewConfigService, + eventService: EventService +) { + // Grid settings + await settingsService.save({ + id: 'grid', + dayStartHour: 8, + dayEndHour: 17, + workStartHour: 9, + workEndHour: 16, + hourHeight: 64, + snapInterval: 15, + syncStatus: 'synced' + }); + + // Workweek settings + await settingsService.save({ + id: 'workweek', + presets: { + standard: { id: 'standard', label: 'Standard', workDays: [1, 2, 3, 4, 5], periodDays: 7 } + }, + defaultPreset: 'standard', + firstDayOfWeek: 1, + syncStatus: 'synced' + }); + + // Simple view config + await viewConfigService.save({ + id: 'simple', + groupings: [{ type: 'date', values: [], idProperty: 'date', derivedFrom: 'start' }], + syncStatus: 'synced' + }); + + // Add test events + const today = new Date(); + today.setHours(0, 0, 0, 0); + console.log('Event date:', today.toISOString()); + + const start1 = new Date(today); + start1.setHours(9, 0, 0, 0); + const end1 = new Date(today); + end1.setHours(10, 0, 0, 0); + + await eventService.save({ + id: '1', + title: 'Morgenmøde', + start: start1, + end: end1, + type: 'meeting', + allDay: false, + syncStatus: 'synced' + }); + + const start2 = new Date(today); + start2.setHours(12, 0, 0, 0); + const end2 = new Date(today); + end2.setHours(13, 0, 0, 0); + + await eventService.save({ + id: '2', + title: 'Frokost', + start: start2, + end: end2, + type: 'break', + allDay: false, + syncStatus: 'synced' + }); +} + +init().catch(console.error); diff --git a/test-package/tsconfig.json b/test-package/tsconfig.json new file mode 100644 index 0000000..ebbec2c --- /dev/null +++ b/test-package/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist", + "rootDir": "./src", + "lib": ["ES2024", "DOM", "DOM.Iterable"], + "experimentalDecorators": true, + "emitDecoratorMetadata": true + }, + "include": ["src/**/*"] +} diff --git a/wwwroot/css/dashboard.css b/wwwroot/css/dashboard.css index 81b78bf..b03b55f 100644 --- a/wwwroot/css/dashboard.css +++ b/wwwroot/css/dashboard.css @@ -867,3 +867,291 @@ swp-date-display i { grid-template-columns: 1fr; } } + +/* ========================================== + WAITLIST MINI CARD + ========================================== */ +swp-waitlist-card { + display: flex; + align-items: center; + gap: 12px; + padding: 16px; + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); + cursor: pointer; + transition: all 0.15s ease; +} + +swp-waitlist-card:hover { + border-color: var(--color-teal); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +swp-waitlist-icon { + position: relative; + font-size: 24px; + color: var(--color-text-secondary); +} + +swp-waitlist-badge { + position: absolute; + top: -8px; + right: -8px; + min-width: 20px; + height: 20px; + padding: 0 6px; + background: var(--color-teal); + color: white; + font-size: 11px; + font-weight: 600; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; +} + +swp-waitlist-label { + font-size: 14px; + font-weight: 500; + color: var(--color-text); +} + +/* ========================================== + WAITLIST DRAWER + ========================================== */ +swp-drawer-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.3); + opacity: 0; + visibility: hidden; + transition: all 0.3s ease; + z-index: 999; +} + +swp-drawer-overlay.visible { + opacity: 1; + visibility: visible; +} + +swp-waitlist-drawer { + position: fixed; + top: 0; + right: 0; + width: 420px; + height: 100vh; + background: var(--color-surface); + border-left: 1px solid var(--color-border); + box-shadow: -4px 0 20px rgba(0, 0, 0, 0.1); + transform: translateX(100%); + transition: transform 0.3s ease; + z-index: 1000; + display: flex; + flex-direction: column; +} + +swp-waitlist-drawer.open { + transform: translateX(0); +} + +swp-drawer-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 24px; + border-bottom: 1px solid var(--color-border); +} + +swp-drawer-title { + font-size: 16px; + font-weight: 600; + color: var(--color-text); + display: flex; + align-items: center; + gap: 8px; +} + +swp-drawer-title swp-count { + font-size: 14px; + font-weight: 500; + color: var(--color-text-secondary); +} + +swp-drawer-close { + width: 32px; + height: 32px; + border-radius: 6px; + background: var(--color-background-alt); + border: none; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + color: var(--color-text-secondary); + transition: all 0.15s ease; +} + +swp-drawer-close:hover { + background: var(--color-background-hover); + color: var(--color-text); +} + +swp-drawer-close i { + font-size: 18px; +} + +swp-drawer-body { + flex: 1; + overflow-y: auto; + padding: 16px; +} + +swp-waitlist-list { + display: flex; + flex-direction: column; + gap: 12px; +} + +swp-waitlist-item { + display: flex; + flex-direction: column; + gap: 12px; + padding: 16px; + background: var(--color-background-alt); + border-radius: 8px; + border: 1px solid var(--color-border); +} + +swp-waitlist-customer { + display: flex; + align-items: center; + gap: 12px; +} + +swp-waitlist-customer swp-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + background: var(--color-teal); + color: white; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + font-weight: 600; + flex-shrink: 0; +} + +swp-waitlist-customer-info { + flex: 1; +} + +swp-waitlist-name { + font-size: 14px; + font-weight: 600; + color: var(--color-text); +} + +swp-waitlist-phone { + font-size: 12px; + color: var(--color-text-secondary); +} + +swp-waitlist-service { + font-size: 13px; + font-weight: 500; + color: var(--color-teal); + padding: 6px 10px; + background: color-mix(in srgb, var(--color-teal) 10%, transparent); + border-radius: 4px; + display: inline-block; +} + +swp-waitlist-meta { + display: flex; + flex-direction: column; + gap: 6px; +} + +swp-waitlist-periods { + display: flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; +} + +swp-waitlist-periods swp-label { + font-size: 12px; + color: var(--color-text-secondary); +} + +swp-waitlist-period-tag { + font-size: 11px; + padding: 3px 8px; + background: var(--color-background); + border-radius: 4px; + color: var(--color-text); +} + +swp-waitlist-dates { + display: flex; + align-items: center; + gap: 16px; +} + +swp-waitlist-date { + font-size: 11px; + color: var(--color-text-secondary); + display: flex; + align-items: center; + gap: 4px; +} + +swp-waitlist-date i { + font-size: 12px; +} + +swp-waitlist-date.expires { + color: var(--color-text-secondary); +} + +swp-waitlist-date.expires.soon { + color: var(--color-amber); + font-weight: 500; +} + +swp-waitlist-actions { + display: flex; + gap: 8px; + padding-top: 8px; + border-top: 1px solid var(--color-border); +} + +swp-waitlist-actions swp-btn { + flex: 1; + justify-content: center; + padding: 8px 12px; + font-size: 12px; +} + +swp-waitlist-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 48px 24px; + text-align: center; +} + +swp-waitlist-empty i { + font-size: 48px; + color: var(--color-border); + margin-bottom: 16px; +} + +swp-waitlist-empty span { + font-size: 14px; + color: var(--color-text-secondary); +} diff --git a/wwwroot/js/calendar-min.js b/wwwroot/js/calendar-min.js new file mode 100644 index 0000000..322988f --- /dev/null +++ b/wwwroot/js/calendar-min.js @@ -0,0 +1,26 @@ +var Lt=Object.create;var vt=Object.defineProperty;var $t=Object.getOwnPropertyDescriptor;var Ht=Object.getOwnPropertyNames;var Bt=Object.getPrototypeOf,Wt=Object.prototype.hasOwnProperty;var se=(o,e)=>()=>(e||o((e={exports:{}}).exports,e),e.exports);var Pt=(o,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of Ht(e))!Wt.call(o,r)&&r!==t&&vt(o,r,{get:()=>e[r],enumerable:!(n=$t(e,r))||n.enumerable});return o};var ie=(o,e,t)=>(t=o!=null?Lt(Bt(o)):{},Pt(e||!o||!o.__esModule?vt(t,"default",{value:o,enumerable:!0}):t,o));var Et=se((rt,st)=>{(function(o,e){typeof rt=="object"&&typeof st<"u"?st.exports=e():typeof define=="function"&&define.amd?define(e):(o=typeof globalThis<"u"?globalThis:o||self).dayjs=e()})(rt,function(){"use strict";var o=1e3,e=6e4,t=36e5,n="millisecond",r="second",s="minute",i="hour",a="day",c="week",d="month",g="quarter",w="year",E="date",m="Invalid Date",u=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,D=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,C={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ordinal:function(S){var f=["th","st","nd","rd"],h=S%100;return"["+S+(f[(h-20)%10]||f[h]||f[0])+"]"}},k=function(S,f,h){var y=String(S);return!y||y.length>=f?S:""+Array(f+1-y.length).join(h)+S},l={s:k,z:function(S){var f=-S.utcOffset(),h=Math.abs(f),y=Math.floor(h/60),p=h%60;return(f<=0?"+":"-")+k(y,2,"0")+":"+k(p,2,"0")},m:function S(f,h){if(f.date()1)return S(M[0])}else{var R=f.name;W[R]=f,p=R}return!y&&p&&(O=p),p||!y&&O},x=function(S,f){if(N(S))return S.clone();var h=typeof f=="object"?f:{};return h.date=S,h.args=arguments,new Y(h)},A=l;A.l=$,A.i=N,A.w=function(S,f){return x(S,{locale:f.$L,utc:f.$u,x:f.$x,$offset:f.$offset})};var Y=function(){function S(h){this.$L=$(h.locale,null,!0),this.parse(h),this.$x=this.$x||h.x||{},this[P]=!0}var f=S.prototype;return f.parse=function(h){this.$d=function(y){var p=y.date,T=y.utc;if(p===null)return new Date(NaN);if(A.u(p))return new Date;if(p instanceof Date)return new Date(p);if(typeof p=="string"&&!/Z$/i.test(p)){var M=p.match(u);if(M){var R=M[2]-1||0,L=(M[7]||"0").substring(0,3);return T?new Date(Date.UTC(M[1],R,M[3]||1,M[4]||0,M[5]||0,M[6]||0,L)):new Date(M[1],R,M[3]||1,M[4]||0,M[5]||0,M[6]||0,L)}}return new Date(p)}(h),this.init()},f.init=function(){var h=this.$d;this.$y=h.getFullYear(),this.$M=h.getMonth(),this.$D=h.getDate(),this.$W=h.getDay(),this.$H=h.getHours(),this.$m=h.getMinutes(),this.$s=h.getSeconds(),this.$ms=h.getMilliseconds()},f.$utils=function(){return A},f.isValid=function(){return this.$d.toString()!==m},f.isSame=function(h,y){var p=x(h);return this.startOf(y)<=p&&p<=this.endOf(y)},f.isAfter=function(h,y){return x(h){(function(o,e){typeof it=="object"&&typeof at<"u"?at.exports=e():typeof define=="function"&&define.amd?define(e):(o=typeof globalThis<"u"?globalThis:o||self).dayjs_plugin_utc=e()})(it,function(){"use strict";var o="minute",e=/[+-]\d\d(?::?\d\d)?/g,t=/([+-]|\d\d)/g;return function(n,r,s){var i=r.prototype;s.utc=function(m){var u={date:m,utc:!0,args:arguments};return new r(u)},i.utc=function(m){var u=s(this.toDate(),{locale:this.$L,utc:!0});return m?u.add(this.utcOffset(),o):u},i.local=function(){return s(this.toDate(),{locale:this.$L,utc:!1})};var a=i.parse;i.parse=function(m){m.utc&&(this.$u=!0),this.$utils().u(m.$offset)||(this.$offset=m.$offset),a.call(this,m)};var c=i.init;i.init=function(){if(this.$u){var m=this.$d;this.$y=m.getUTCFullYear(),this.$M=m.getUTCMonth(),this.$D=m.getUTCDate(),this.$W=m.getUTCDay(),this.$H=m.getUTCHours(),this.$m=m.getUTCMinutes(),this.$s=m.getUTCSeconds(),this.$ms=m.getUTCMilliseconds()}else c.call(this)};var d=i.utcOffset;i.utcOffset=function(m,u){var D=this.$utils().u;if(D(m))return this.$u?0:D(this.$offset)?d.call(this):this.$offset;if(typeof m=="string"&&(m=function(O){O===void 0&&(O="");var W=O.match(e);if(!W)return null;var P=(""+W[0]).match(t)||["-",0,0],N=P[0],$=60*+P[1]+ +P[2];return $===0?0:N==="+"?$:-$}(m),m===null))return this;var C=Math.abs(m)<=16?60*m:m;if(C===0)return this.utc(u);var k=this.clone();if(u)return k.$offset=C,k.$u=!1,k;var l=this.$u?this.toDate().getTimezoneOffset():-1*this.utcOffset();return(k=this.local().add(C+l,o)).$offset=C,k.$x.$localOffset=l,k};var g=i.format;i.format=function(m){var u=m||(this.$u?"YYYY-MM-DDTHH:mm:ss[Z]":"");return g.call(this,u)},i.valueOf=function(){var m=this.$utils().u(this.$offset)?0:this.$offset+(this.$x.$localOffset||this.$d.getTimezoneOffset());return this.$d.valueOf()-6e4*m},i.isUTC=function(){return!!this.$u},i.toISOString=function(){return this.toDate().toISOString()},i.toString=function(){return this.toDate().toUTCString()};var w=i.toDate;i.toDate=function(m){return m==="s"&&this.$offset?s(this.format("YYYY-MM-DD HH:mm:ss:SSS")).toDate():w.call(this)};var E=i.diff;i.diff=function(m,u,D){if(m&&this.$u===m.$u)return E.call(this,m,u,D);var C=this.local(),k=s(m).local();return E.call(C,k,u,D)}}})});var St=se((ot,lt)=>{(function(o,e){typeof ot=="object"&&typeof lt<"u"?lt.exports=e():typeof define=="function"&&define.amd?define(e):(o=typeof globalThis<"u"?globalThis:o||self).dayjs_plugin_timezone=e()})(ot,function(){"use strict";var o={year:0,month:1,day:2,hour:3,minute:4,second:5},e={};return function(t,n,r){var s,i=function(g,w,E){E===void 0&&(E={});var m=new Date(g),u=function(D,C){C===void 0&&(C={});var k=C.timeZoneName||"short",l=D+"|"+k,O=e[l];return O||(O=new Intl.DateTimeFormat("en-US",{hour12:!1,timeZone:D,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:k}),e[l]=O),O}(w,E);return u.formatToParts(m)},a=function(g,w){for(var E=i(g,w),m=[],u=0;u=0&&(m[l]=parseInt(k,10))}var O=m[3],W=O===24?0:O,P=m[0]+"-"+m[1]+"-"+m[2]+" "+W+":"+m[4]+":"+m[5]+":000",N=+g;return(r.utc(P).valueOf()-(N-=N%1e3))/6e4},c=n.prototype;c.tz=function(g,w){g===void 0&&(g=s);var E,m=this.utcOffset(),u=this.toDate(),D=u.toLocaleString("en-US",{timeZone:g}),C=Math.round((u-new Date(D))/1e3/60),k=15*-Math.round(u.getTimezoneOffset()/15)-C;if(!Number(k))E=this.utcOffset(0,w);else if(E=r(D,{locale:this.$L}).$set("millisecond",this.$ms).utcOffset(k,!0),w){var l=E.utcOffset();E=E.add(m-l,"minute")}return E.$x.$timezone=g,E},c.offsetName=function(g){var w=this.$x.$timezone||r.tz.guess(),E=i(this.valueOf(),w,{timeZoneName:g}).find(function(m){return m.type.toLowerCase()==="timezonename"});return E&&E.value};var d=c.startOf;c.startOf=function(g,w){if(!this.$x||!this.$x.$timezone)return d.call(this,g,w);var E=r(this.format("YYYY-MM-DD HH:mm:ss:SSS"),{locale:this.$L});return d.call(E,g,w).tz(this.$x.$timezone,!0)},r.tz=function(g,w,E){var m=E&&w,u=E||w||s,D=a(+r(),u);if(typeof g!="string")return r(g).tz(u);var C=function(W,P,N){var $=W-60*P*1e3,x=a($,N);if(P===x)return[$,P];var A=a($-=60*(x-P)*1e3,N);return x===A?[$,x]:[W-60*Math.min(x,A)*1e3,Math.max(x,A)]}(r.utc(g,m).valueOf(),D,u),k=C[0],l=C[1],O=r(k).utcOffset(l);return O.$x.$timezone=u,O},r.tz.guess=function(){return Intl.DateTimeFormat().resolvedOptions().timeZone},r.tz.setDefault=function(g){s=g}}})});var wt=se((ct,dt)=>{(function(o,e){typeof ct=="object"&&typeof dt<"u"?dt.exports=e():typeof define=="function"&&define.amd?define(e):(o=typeof globalThis<"u"?globalThis:o||self).dayjs_plugin_isoWeek=e()})(ct,function(){"use strict";var o="day";return function(e,t,n){var r=function(a){return a.add(4-a.isoWeekday(),o)},s=t.prototype;s.isoWeekYear=function(){return r(this).year()},s.isoWeek=function(a){if(!this.$utils().u(a))return this.add(7*(a-this.isoWeek()),o);var c,d,g,w,E=r(this),m=(c=this.isoWeekYear(),d=this.$u,g=(d?n.utc:n)().year(c).startOf("year"),w=4-g.isoWeekday(),g.isoWeekday()>4&&(w+=7),g.add(w,o));return E.diff(m,"week")+1},s.isoWeekday=function(a){return this.$utils().u(a)?this.day()||7:this.day(this.day()%7?a:a-7)};var i=s.startOf;s.startOf=function(a,c){var d=this.$utils(),g=!!d.u(c)||c;return d.p(a)==="isoweek"?g?this.date(this.date()-(this.isoWeekday()-1)).startOf("day"):this.date(this.date()-1-(this.isoWeekday()-1)+7).endOf("day"):i.bind(this)(a,c)}}})});var Ct=se((ut,ht)=>{(function(o,e){typeof ut=="object"&&typeof ht<"u"?ht.exports=e():typeof define=="function"&&define.amd?define(e):(o=typeof globalThis<"u"?globalThis:o||self).dayjs_plugin_customParseFormat=e()})(ut,function(){"use strict";var o={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},e=/(\[[^[]*\])|([-_:/.,()\s]+)|(A|a|Q|YYYY|YY?|ww?|MM?M?M?|Do|DD?|hh?|HH?|mm?|ss?|S{1,3}|z|ZZ?)/g,t=/\d/,n=/\d\d/,r=/\d\d?/,s=/\d*[^-_:/,()\s\d]+/,i={},a=function(u){return(u=+u)+(u>68?1900:2e3)},c=function(u){return function(D){this[u]=+D}},d=[/[+-]\d\d:?(\d\d)?|Z/,function(u){(this.zone||(this.zone={})).offset=function(D){if(!D||D==="Z")return 0;var C=D.match(/([+-]|\d\d)/g),k=60*C[1]+(+C[2]||0);return k===0?0:C[0]==="+"?-k:k}(u)}],g=function(u){var D=i[u];return D&&(D.indexOf?D:D.s.concat(D.f))},w=function(u,D){var C,k=i.meridiem;if(k){for(var l=1;l<=24;l+=1)if(u.indexOf(k(l,0,D))>-1){C=l>12;break}}else C=u===(D?"pm":"PM");return C},E={A:[s,function(u){this.afternoon=w(u,!1)}],a:[s,function(u){this.afternoon=w(u,!0)}],Q:[t,function(u){this.month=3*(u-1)+1}],S:[t,function(u){this.milliseconds=100*+u}],SS:[n,function(u){this.milliseconds=10*+u}],SSS:[/\d{3}/,function(u){this.milliseconds=+u}],s:[r,c("seconds")],ss:[r,c("seconds")],m:[r,c("minutes")],mm:[r,c("minutes")],H:[r,c("hours")],h:[r,c("hours")],HH:[r,c("hours")],hh:[r,c("hours")],D:[r,c("day")],DD:[n,c("day")],Do:[s,function(u){var D=i.ordinal,C=u.match(/\d+/);if(this.day=C[0],D)for(var k=1;k<=31;k+=1)D(k).replace(/\[|\]/g,"")===u&&(this.day=k)}],w:[r,c("week")],ww:[n,c("week")],M:[r,c("month")],MM:[n,c("month")],MMM:[s,function(u){var D=g("months"),C=(g("monthsShort")||D.map(function(k){return k.slice(0,3)})).indexOf(u)+1;if(C<1)throw new Error;this.month=C%12||C}],MMMM:[s,function(u){var D=g("months").indexOf(u)+1;if(D<1)throw new Error;this.month=D%12||D}],Y:[/[+-]?\d+/,c("year")],YY:[n,function(u){this.year=a(u)}],YYYY:[/\d{4}/,c("year")],Z:d,ZZ:d};function m(u){var D,C;D=u,C=i&&i.formats;for(var k=(u=D.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g,function(x,A,Y){var z=Y&&Y.toUpperCase();return A||C[Y]||o[Y]||C[z].replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,function(S,f,h){return f||h.slice(1)})})).match(e),l=k.length,O=0;O-1)return new Date((p==="X"?1e3:1)*y);var R=m(p)(y),L=R.year,H=R.month,F=R.day,q=R.hours,ee=R.minutes,Z=R.seconds,re=R.milliseconds,j=R.zone,_=R.week,G=new Date,X=F||(L||H?1:G.getDate()),te=L||G.getFullYear(),ve=0;L&&!H||(ve=H>0?H-1:G.getMonth());var ye,Qe=q||0,Ze=ee||0,Xe=Z||0,Ke=re||0;return j?new Date(Date.UTC(te,ve,X,Qe,Ze,Xe,Ke+60*j.offset*1e3)):T?new Date(Date.UTC(te,ve,X,Qe,Ze,Xe,Ke)):(ye=new Date(te,ve,X,Qe,Ze,Xe,Ke),_&&(ye=M(ye).week(_).toDate()),ye)}catch{return new Date("")}}(W,$,P,C),this.init(),z&&z!==!0&&(this.$L=this.locale(z).$L),Y&&W!=this.format($)&&(this.$d=new Date("")),i={}}else if($ instanceof Array)for(var S=$.length,f=1;f<=S;f+=1){N[1]=$[f-1];var h=C.apply(this,N);if(h.isValid()){this.$d=h.$d,this.$L=h.$L,this.init();break}f===S&&(this.$d=new Date(""))}else l.call(this,O)}}})});var Tt=se((gt,mt)=>{(function(o,e){typeof gt=="object"&&typeof mt<"u"?mt.exports=e():typeof define=="function"&&define.amd?define(e):(o=typeof globalThis<"u"?globalThis:o||self).dayjs_plugin_isSameOrAfter=e()})(gt,function(){"use strict";return function(o,e){e.prototype.isSameOrAfter=function(t,n){return this.isSame(t,n)||this.isAfter(t,n)}}})});var kt=se((ft,pt)=>{(function(o,e){typeof ft=="object"&&typeof pt<"u"?pt.exports=e():typeof define=="function"&&define.amd?define(e):(o=typeof globalThis<"u"?globalThis:o||self).dayjs_plugin_isSameOrBefore=e()})(ft,function(){"use strict";return function(o,e){e.prototype.isSameOrBefore=function(t,n){return this.isSame(t,n)||this.isBefore(t,n)}}})});var Nt=0;function ae(o){let e=++Nt;return{symbol:Symbol(o?`Token(${o})`:`Token#${e}`),description:o,toString(){return o?`Token<${o}>`:`Token<#${e}>`}}}var de=class extends Error{constructor(e){super(e),this.name="ContainerError"}},ue=class extends de{constructor(e,t=[]){let n=t.length>0?` + Dependency path: ${t.join(" -> ")}`:"";super(`Token "${e}" is not bound or registered in the container.${n}`),this.name="BindingNotFoundError"}},he=class extends de{constructor(e){super(`Circular dependency detected: ${e.join(" -> ")}`),this.name="CircularDependencyError"}};var yt=new WeakMap;function Ft(o){let e=yt.get(o);if(e)return e;let t=o.toString(),n=t.match(/constructor\s*\(([^)]*)\)/)||t.match(/^[^(]*\(([^)]*)\)/);if(!n||!n[1])return[];let r=n[1].split(",").map(s=>s.trim()).filter(s=>s.length>0).map(s=>{let i=s.split(/[:=]/)[0].trim();return i=i.replace(/^((public|private|protected|readonly)\s+)+/,""),i.includes("{")||i.includes("[")?null:i}).filter(s=>s!==null);return yt.set(o,r),r}function _t(o,e,t){if(!t.map)throw new Error("AutoWire map strategy requires options.map to be defined");let n=Ft(o),r=[];for(let s of n){let i=t.map[s];if(i===void 0){if(t.strict)throw new Error(`Cannot resolve parameter "${s}" on ${o.name}. Not found in autowire map. Add it to the map: .autoWire({ map: { ${s}: ... } })`);r.push(void 0);continue}typeof i=="function"?r.push(i(e)):r.push(e.resolve(i))}return r}function Yt(o,e,t){if(!t.mapResolvers||t.mapResolvers.length===0)return[];let n=[];for(let r=0;r0?Yt(o,e,n):n.map&&Object.keys(n.map).length>0?_t(o,e,n):[]}var le=class{constructor(e,t){this.registrations=t,this.configs=[],this.defaultLifetime="singleton",this.pending=e}as(e){if(e&&typeof e=="object"&&"symbol"in e){let t={token:e,type:this.pending.type,value:this.pending.value,factory:this.pending.factory,constructor:this.pending.constructor,lifetime:this.defaultLifetime};return this.configs.push(t),this.registrations.push(t),this}else{let t={token:null,type:this.pending.type,value:this.pending.value,factory:this.pending.factory,constructor:this.pending.constructor,lifetime:this.defaultLifetime,interfaceType:e};return this.configs.push(t),this.registrations.push(t),this}}asDefaultInterface(e){return this.as("TInterface",e),this.asDefault()}asKeyedInterface(e,t){return this.as("TInterface",t),this.keyed(e)}asImplementedInterfaces(e){if(e.length===0)return this;if(this.configs.length>0){for(let n of this.configs)n.lifetime="singleton",n.additionalTokens=n.additionalTokens||[],n.additionalTokens.push(...e);return this}let t={token:e[0],type:this.pending.type,value:this.pending.value,factory:this.pending.factory,constructor:this.pending.constructor,lifetime:"singleton"};this.configs.push(t),this.registrations.push(t);for(let n=1;ni.resolve(n),{lifetime:t.lifetime}),r.add(s)}build(){let e=this.baseContainer.createChild();this.resolveInterfaceTokens(e);let t=new Set,n=new Map,r=new Map,s=new Map,i=this.identifyNonDefaultTokens();for(let a of this.registrations){if(this.shouldSkipRegistration(a,i,t))continue;let c=this.createBindingToken(a,n,r,s);this.applyRegistration(e,{...a,token:c}),t.add(a.token),this.registerAdditionalInterfaces(e,a,c,t)}return e.__namedRegistrations=n,e.__keyedRegistrations=r,e.__multiRegistrations=s,e}analyzeConstructor(e){let t=e.toString();return{hasDependencies:/constructor\s*\([^)]+\)/.test(t)}}createOptimizedFactory(e,t,n){if(t.lifetime==="singleton"){let r=new t.constructor;e.bindValue(t.token,r)}else if(t.lifetime==="transient"){let r=t.constructor,s=()=>new r;e.fastTransientCache.set(t.token,s),e.bindFactory(t.token,s,n)}else{let r=()=>new t.constructor;e.bindFactory(t.token,r,n)}}createAutoWireFactory(e,t,n){let r=s=>{let i=Je(t.constructor,s,t.autowireOptions);return new t.constructor(...i)};e.bindFactory(t.token,r,n)}createParameterFactory(e,t,n){let r=()=>{let s=Object.values(t.parameterValues);return new t.constructor(...s)};e.bindFactory(t.token,r,n)}applyTypeRegistration(e,t,n){let{hasDependencies:r}=this.analyzeConstructor(t.constructor);if(!r&&!t.autowireOptions&&!t.parameterValues){this.createOptimizedFactory(e,t,n);return}if(t.autowireOptions){this.createAutoWireFactory(e,t,n);return}if(t.parameterValues){this.createParameterFactory(e,t,n);return}if(r){let i=t.constructor.name||"UnnamedClass";throw new Error(`Service "${i}" has constructor dependencies but no autowiring configuration. + +Solutions: + 1. \u2B50 Use the NovaDI transformer (recommended): + - Add "@novadi/core/unplugin" to your build config + - Transformer automatically generates .autoWire() for all dependencies + + 2. Add manual autowiring: + .autoWire({ map: { /* param: resolver */ } }) + + 3. Use a factory function: + .register((c) => new ${i}(...)) + +See docs: https://github.com/janus007/NovaDI#autowire`)}let s=()=>new t.constructor;e.bindFactory(t.token,s,n)}applyRegistration(e,t){let n={lifetime:t.lifetime};switch(t.type){case"instance":e.bindValue(t.token,t.value);break;case"factory":e.bindFactory(t.token,t.factory,n);break;case"type":this.applyTypeRegistration(e,t,n);break}}};function Vt(o){return o&&typeof o.dispose=="function"}var et=class{constructor(){this.resolvingStack=new Set,this.perRequestCache=new Map}isResolving(e){return this.resolvingStack.has(e)}enterResolve(e){this.resolvingStack.add(e)}exitResolve(e){this.resolvingStack.delete(e),this.path=void 0}getPath(){return this.path||(this.path=Array.from(this.resolvingStack).map(e=>e.toString())),[...this.path]}cachePerRequest(e,t){this.perRequestCache.set(e,t)}getPerRequest(e){return this.perRequestCache.get(e)}hasPerRequest(e){return this.perRequestCache.has(e)}reset(){this.resolvingStack.clear(),this.perRequestCache.clear(),this.path=void 0}},tt=class{constructor(){this.pool=[],this.maxSize=10}acquire(){let e=this.pool.pop();return e?(e.reset(),e):new et}release(e){this.pool.lengthnew t)}resolve(e){let t=this.tryGetFromCaches(e);if(t!==void 0)return t;if(this.currentContext)return this.resolveWithContext(e,this.currentContext);let n=o.contextPool.acquire();this.currentContext=n;try{return this.resolveWithContext(e,n)}finally{this.currentContext=void 0,o.contextPool.release(n)}}resolveSingletonUnsafe(e){return this.ultraFastSingletonCache.get(e)??this.singletonCache.get(e)}resolveTransientSimple(e){let t=this.fastTransientCache.get(e);return t?t():this.resolve(e)}resolveBatch(e){let t=!!this.currentContext,n=this.currentContext||o.contextPool.acquire();t||(this.currentContext=n);try{return e.map(s=>{let i=this.tryGetFromCaches(s);return i!==void 0?i:this.resolveWithContext(s,n)})}finally{t||(this.currentContext=void 0,o.contextPool.release(n))}}async resolveAsync(e){if(this.currentContext)return this.resolveAsyncWithContext(e,this.currentContext);let t=o.contextPool.acquire();this.currentContext=t;try{return await this.resolveAsyncWithContext(e,t)}finally{this.currentContext=void 0,o.contextPool.release(t)}}tryGetFromCaches(e){let t=this.ultraFastSingletonCache.get(e);if(t!==void 0)return t;if(this.singletonCache.has(e)){let r=this.singletonCache.get(e);return this.ultraFastSingletonCache.set(e,r),r}let n=this.fastTransientCache.get(e);if(n)return n()}cacheInstance(e,t,n,r){n==="singleton"?(this.singletonCache.set(e,t),this.singletonOrder.push(e),this.ultraFastSingletonCache.set(e,t)):n==="per-request"&&r&&r.cachePerRequest(e,t)}validateAndGetBinding(e,t){if(t.isResolving(e))throw new he([...t.getPath(),e.toString()]);let n=this.getBinding(e);if(!n)throw new ue(e.toString(),t.getPath());return n}instantiateBindingSync(e,t,n){switch(e.type){case"value":return e.value;case"factory":let r=e.factory(this);if(r instanceof Promise)throw new Error(`Async factory detected for ${t.toString()}. Use resolveAsync() instead.`);return r;case"class":let i=(e.dependencies||[]).map(a=>this.resolveWithContext(a,n));return new e.constructor(...i);case"inline-class":return new e.constructor;default:throw new Error(`Unknown binding type: ${e.type}`)}}async instantiateBindingAsync(e,t){switch(e.type){case"value":return e.value;case"factory":return await Promise.resolve(e.factory(this));case"class":let n=e.dependencies||[],r=await Promise.all(n.map(s=>this.resolveAsyncWithContext(s,t)));return new e.constructor(...r);case"inline-class":return new e.constructor;default:throw new Error(`Unknown binding type: ${e.type}`)}}createChild(){return new o(this)}async dispose(){let e=[];for(let t=this.singletonOrder.length-1;t>=0;t--){let n=this.singletonOrder[t],r=this.singletonCache.get(n);if(r&&Vt(r))try{await r.dispose()}catch(s){e.push(s)}}this.singletonCache.clear(),this.singletonOrder.length=0}builder(){return new ge(this)}resolveNamed(e){let t=this.__namedRegistrations;if(!t)throw new Error(`Named service "${e}" not found. No named registrations exist.`);let n=t.get(e);if(!n)throw new Error(`Named service "${e}" not found`);return this.resolve(n.token)}resolveKeyed(e){let t=this.__keyedRegistrations;if(!t)throw new Error("Keyed service not found. No keyed registrations exist.");let n=t.get(e);if(!n){let r=typeof e=="symbol"?e.toString():`"${e}"`;throw new Error(`Keyed service ${r} not found`)}return this.resolve(n.token)}resolveAll(e){let t=this.__multiRegistrations;if(!t)return[];let n=t.get(e);return!n||n.length===0?[]:n.map(r=>this.resolve(r))}getRegistry(){let e=[];return this.bindings.forEach((t,n)=>{e.push({token:n.description||n.symbol.toString(),type:t.type,lifetime:t.lifetime,dependencies:t.dependencies?.map(r=>r.description||r.symbol.toString())})}),e}interfaceToken(e){let t=e||`Interface_${Math.random().toString(36).substr(2,9)}`;if(this.interfaceRegistry.has(t))return this.interfaceRegistry.get(t);if(this.parent)return this.parent.interfaceToken(t);let n=ae(t);return this.interfaceRegistry.set(t,n),n}resolveType(e){let t=e||"",n=this.interfaceTokenCache.get(t);return n||(n=this.interfaceToken(e),this.interfaceTokenCache.set(t,n)),this.resolve(n)}resolveTypeKeyed(e,t){return this.resolveKeyed(e)}resolveTypeAll(e){let t=this.interfaceToken(e);return this.resolveAll(t)}resolveWithContext(e,t){let n=this.validateAndGetBinding(e,t);if(n.lifetime==="per-request"&&t.hasPerRequest(e))return t.getPerRequest(e);if(n.lifetime==="singleton"&&this.singletonCache.has(e))return this.singletonCache.get(e);t.enterResolve(e);try{let r=this.instantiateBindingSync(n,e,t);return this.cacheInstance(e,r,n.lifetime,t),r}finally{t.exitResolve(e)}}async resolveAsyncWithContext(e,t){let n=this.validateAndGetBinding(e,t);if(n.lifetime==="per-request"&&t.hasPerRequest(e))return t.getPerRequest(e);if(n.lifetime==="singleton"&&this.singletonCache.has(e))return this.singletonCache.get(e);t.enterResolve(e);try{let r=await this.instantiateBindingAsync(n,t);return this.cacheInstance(e,r,n.lifetime,t),r}finally{t.exitResolve(e)}}getBinding(e){return this.bindingCache||this.buildBindingCache(),this.bindingCache.get(e)}buildBindingCache(){this.bindingCache=new Map;let e=this;for(;e;)e.bindings.forEach((t,n)=>{this.bindingCache.has(n)||this.bindingCache.set(n,t)}),e=e.parent}invalidateBindingCache(){this.bindingCache=void 0,this.ultraFastSingletonCache.clear()}};ce.contextPool=new tt;var nt=class{constructor(){this.eventLog=[],this.debug=!1,this.listeners=new Set,this.logConfig={calendar:!0,grid:!0,event:!0,scroll:!0,navigation:!0,view:!0,default:!0}}on(e,t,n){return document.addEventListener(e,t,n),this.listeners.add({eventType:e,handler:t,options:n}),()=>this.off(e,t)}once(e,t){return this.on(e,t,{once:!0})}off(e,t){document.removeEventListener(e,t);for(let n of this.listeners)if(n.eventType===e&&n.handler===t){this.listeners.delete(n);break}}emit(e,t={}){if(!e)return!1;let n=new CustomEvent(e,{detail:t??{},bubbles:!0,cancelable:!0});return this.debug&&this.logEventWithGrouping(e,t),this.eventLog.push({type:e,detail:t??{},timestamp:Date.now()}),!document.dispatchEvent(n)}logEventWithGrouping(e,t){let n=this.extractCategory(e);if(!this.logConfig[n])return;let{emoji:r,color:s}=this.getCategoryStyle(n)}extractCategory(e){if(!e)return"unknown";if(e.includes(":"))return e.split(":")[0];let t=e.toLowerCase();return t.includes("grid")||t.includes("rendered")?"grid":t.includes("event")||t.includes("sync")?"event":t.includes("scroll")?"scroll":t.includes("nav")||t.includes("date")?"navigation":t.includes("view")?"view":"default"}getCategoryStyle(e){let t={calendar:{emoji:"\u{1F5D3}\uFE0F",color:"#2196F3"},grid:{emoji:"\u{1F4CA}",color:"#4CAF50"},event:{emoji:"\u{1F4C5}",color:"#FF9800"},scroll:{emoji:"\u{1F4DC}",color:"#9C27B0"},navigation:{emoji:"\u{1F9ED}",color:"#F44336"},view:{emoji:"\u{1F441}\uFE0F",color:"#00BCD4"},default:{emoji:"\u{1F4E2}",color:"#607D8B"}};return t[e]||t.default}setLogConfig(e){this.logConfig={...this.logConfig,...e}}getLogConfig(){return{...this.logConfig}}getEventLog(e){return e?this.eventLog.filter(t=>t.type===e):this.eventLog}setDebug(e){this.debug=e}},b=new nt;var ne={EVENT_HEIGHT:22,EVENT_GAP:2,CONTAINER_PADDING:4,MAX_COLLAPSED_ROWS:4,get SINGLE_ROW_HEIGHT(){return this.EVENT_HEIGHT+this.EVENT_GAP}},me={standard:{id:"standard",workDays:[1,2,3,4,5],totalDays:5,firstWorkDay:1},compressed:{id:"compressed",workDays:[1,2,3,4],totalDays:4,firstWorkDay:1},midweek:{id:"midweek",workDays:[3,4,5],totalDays:3,firstWorkDay:3},weekend:{id:"weekend",workDays:[6,7],totalDays:2,firstWorkDay:6},fullweek:{id:"fullweek",workDays:[1,2,3,4,5,6,7],totalDays:7,firstWorkDay:1}},K=class o{constructor(e,t,n,r,s,i,a=new Date){this.apiEndpoint="/api",this.config=e,this.gridSettings=t,this.dateViewSettings=n,this.timeFormatConfig=r,this.currentWorkWeek=s,this.currentView=i,this.selectedDate=a,o._instance=this}static getInstance(){if(!o._instance)throw new Error("Configuration has not been initialized. Call ConfigManager.load() first.");return o._instance}setSelectedDate(e){this.selectedDate=e}getWorkWeekSettings(){return me[this.currentWorkWeek]||me.standard}};K._instance=null;var I=ie(Et(),1),Mt=ie(Dt(),1),At=ie(St(),1),Rt=ie(wt(),1),bt=ie(Ct(),1),xt=ie(Tt(),1),It=ie(kt(),1);I.default.extend(Mt.default);I.default.extend(At.default);I.default.extend(Rt.default);I.default.extend(bt.default);I.default.extend(xt.default);I.default.extend(It.default);var U=class{constructor(e){this.timezone=e.timeFormatConfig.timezone}toUTC(e){return I.default.tz(e,this.timezone).utc().toISOString()}fromUTC(e){return I.default.utc(e).tz(this.timezone).toDate()}formatTime(e,t=!1){let n=t?"HH:mm:ss":"HH:mm";return(0,I.default)(e).format(n)}formatTimeRange(e,t){return`${this.formatTime(e)} - ${this.formatTime(t)}`}formatTechnicalDateTime(e){return(0,I.default)(e).format("YYYY-MM-DD HH:mm:ss")}formatDate(e){return(0,I.default)(e).format("YYYY-MM-DD")}formatMonthYear(e,t="en-US"){return e.toLocaleDateString(t,{month:"long",year:"numeric"})}formatISODate(e){return this.formatDate(e)}formatTime12(e){return(0,I.default)(e).format("h:mm A")}getDayName(e,t="short",n="da-DK"){return new Intl.DateTimeFormat(n,{weekday:t}).format(e)}formatDateRange(e,t,n={}){let{locale:r="en-US",month:s="short",day:i="numeric"}=n,a=e.getFullYear(),c=t.getFullYear(),d=new Intl.DateTimeFormat(r,{month:s,day:i,year:a!==c?"numeric":void 0});return typeof d.formatRange=="function"?d.formatRange(e,t):`${d.format(e)} - ${d.format(t)}`}timeToMinutes(e){let t=e.split(":").map(Number),n=t[0]||0,r=t[1]||0;return n*60+r}minutesToTime(e){let t=Math.floor(e/60),n=e%60;return(0,I.default)().hour(t).minute(n).format("HH:mm")}formatTimeFromMinutes(e){return this.minutesToTime(e)}getMinutesSinceMidnight(e){let t=(0,I.default)(e);return t.hour()*60+t.minute()}getDurationMinutes(e,t){let n=(0,I.default)(e);return(0,I.default)(t).diff(n,"minute")}getWeekBounds(e){let t=(0,I.default)(e);return{start:t.startOf("week").add(1,"day").toDate(),end:t.endOf("week").add(1,"day").toDate()}}addWeeks(e,t){return(0,I.default)(e).add(t,"week").toDate()}addMonths(e,t){return(0,I.default)(e).add(t,"month").toDate()}getWeekNumber(e){return(0,I.default)(e).isoWeek()}getFullWeekDates(e){let t=[];for(let n=0;n<7;n++)t.push(this.addDays(e,n));return t}getWorkWeekDates(e,t){let n=[],r=this.getWeekBounds(e),s=this.startOfDay(r.start);return t.forEach(i=>{let a=new Date(s),c=i===7?6:i-1;a.setDate(s.getDate()+c),n.push(a)}),n}createDateAtTime(e,t){let n=Math.floor(t/60),r=t%60;return(0,I.default)(e).startOf("day").hour(n).minute(r).toDate()}snapToInterval(e,t){let n=this.getMinutesSinceMidnight(e),r=Math.round(n/t)*t;return this.createDateAtTime(e,r)}isSameDay(e,t){return(0,I.default)(e).isSame(t,"day")}startOfDay(e){return(0,I.default)(e).startOf("day").toDate()}endOfDay(e){return(0,I.default)(e).endOf("day").toDate()}addDays(e,t){return(0,I.default)(e).add(t,"day").toDate()}addMinutes(e,t){return(0,I.default)(e).add(t,"minute").toDate()}parseISO(e){return(0,I.default)(e).toDate()}isValid(e){return(0,I.default)(e).isValid()}differenceInCalendarDays(e,t){let n=(0,I.default)(e).startOf("day"),r=(0,I.default)(t).startOf("day");return n.diff(r,"day")}isValidRange(e,t){return!this.isValid(e)||!this.isValid(t)?!1:e.getTime()<=t.getTime()}isWithinBounds(e){if(!this.isValid(e))return!1;let t=e.getFullYear();return t>=1900&&t<=2100}validateDate(e,t={}){if(!this.isValid(e))return{valid:!1,error:"Invalid date"};if(!this.isWithinBounds(e))return{valid:!1,error:"Date out of bounds (1900-2100)"};let n=new Date;return t.requireFuture&&e<=n?{valid:!1,error:"Date must be in the future"}:t.requirePast&&e>=n?{valid:!1,error:"Date must be in the past"}:t.minDate&&et.maxDate?{valid:!1,error:`Date must be before ${this.formatDate(t.maxDate)}`}:{valid:!0}}};var V=class o{static getDateService(){if(!o.dateService){if(!o.settings)throw new Error("TimeFormatter must be configured before use. Call TimeFormatter.configure() first.");let e={timeFormatConfig:{timezone:o.settings.timezone}};o.dateService=new U(e)}return o.dateService}static configure(e){o.settings=e,o.dateService=null}static convertToLocalTime(e){if(typeof e=="string")return o.getDateService().fromUTC(e);let t=e.toISOString();return o.getDateService().fromUTC(t)}static format24Hour(e){if(!o.settings)throw new Error("TimeFormatter must be configured before use. Call TimeFormatter.configure() first.");let t=o.convertToLocalTime(e);return o.getDateService().formatTime(t,o.settings.showSeconds)}static formatTime(e){return o.format24Hour(e)}static formatTimeRange(e,t){let n=o.convertToLocalTime(e),r=o.convertToLocalTime(t);return o.getDateService().formatTimeRange(n,r)}};V.settings=null;V.dateService=null;var v={INITIALIZED:"core:initialized",READY:"core:ready",DESTROYED:"core:destroyed",VIEW_CHANGED:"view:changed",VIEW_RENDERED:"view:rendered",WORKWEEK_CHANGED:"workweek:changed",NAV_BUTTON_CLICKED:"nav:button-clicked",DATE_CHANGED:"nav:date-changed",NAVIGATION_COMPLETED:"nav:navigation-completed",PERIOD_INFO_UPDATE:"nav:period-info-update",NAVIGATE_TO_EVENT:"nav:navigate-to-event",DATA_LOADING:"data:loading",DATA_LOADED:"data:loaded",DATA_ERROR:"data:error",EVENTS_FILTERED:"data:events-filtered",REMOTE_UPDATE_RECEIVED:"data:remote-update",GRID_RENDERED:"grid:rendered",GRID_CLICKED:"grid:clicked",CELL_SELECTED:"grid:cell-selected",EVENT_CREATED:"event:created",EVENT_UPDATED:"event:updated",EVENT_DELETED:"event:deleted",EVENT_SELECTED:"event:selected",ERROR:"system:error",REFRESH_REQUESTED:"system:refresh",OFFLINE_MODE_CHANGED:"system:offline-mode-changed",SYNC_STARTED:"sync:started",SYNC_COMPLETED:"sync:completed",SYNC_FAILED:"sync:failed",SYNC_RETRY:"sync:retry",FILTER_CHANGED:"filter:changed",EVENTS_RENDERED:"events:rendered"};var fe=class{constructor(e,t){this.eventBus=e,this.config=t,this.setupEventListeners(),this.syncGridCSSVariables(),this.syncWorkweekCSSVariables()}setupEventListeners(){this.eventBus.on(v.WORKWEEK_CHANGED,e=>{let{settings:t}=e.detail;this.syncWorkweekCSSVariables(t)})}syncGridCSSVariables(){let e=this.config.gridSettings;document.documentElement.style.setProperty("--hour-height",`${e.hourHeight}px`),document.documentElement.style.setProperty("--day-start-hour",e.dayStartHour.toString()),document.documentElement.style.setProperty("--day-end-hour",e.dayEndHour.toString()),document.documentElement.style.setProperty("--work-start-hour",e.workStartHour.toString()),document.documentElement.style.setProperty("--work-end-hour",e.workEndHour.toString())}syncWorkweekCSSVariables(e){let t=e||this.config.getWorkWeekSettings();document.documentElement.style.setProperty("--grid-columns",t.totalDays.toString())}static async load(){let e=await fetch("/wwwroot/data/calendar-config.json");if(!e.ok)throw new Error(`Failed to load config: ${e.statusText}`);let t=await e.json(),n={scrollbarWidth:t.scrollbar.width,scrollbarColor:t.scrollbar.color,scrollbarTrackColor:t.scrollbar.trackColor,scrollbarHoverColor:t.scrollbar.hoverColor,scrollbarBorderRadius:t.scrollbar.borderRadius,allowDrag:t.interaction.allowDrag,allowResize:t.interaction.allowResize,allowCreate:t.interaction.allowCreate,apiEndpoint:t.api.endpoint,dateFormat:t.api.dateFormat,timeFormat:t.api.timeFormat,enableSearch:t.features.enableSearch,enableTouch:t.features.enableTouch,defaultEventDuration:t.eventDefaults.defaultEventDuration,minEventDuration:t.gridSettings.snapInterval,maxEventDuration:t.eventDefaults.maxEventDuration},r=new K(n,t.gridSettings,t.dateViewSettings,t.timeFormatConfig,t.currentWorkWeek,t.currentView||"week");return V.configure(r.timeFormatConfig),r}};var Ee=class{constructor(e){this.eventBus=e}parseEventIdFromURL(){try{let t=new URLSearchParams(window.location.search).get("eventId");return t&&t.trim()!==""?t.trim():null}catch(e){return console.warn("URLManager: Failed to parse URL parameters:",e),null}}getAllQueryParams(){try{let e=new URLSearchParams(window.location.search),t={};for(let[n,r]of e.entries())t[n]=r;return t}catch(e){return console.warn("URLManager: Failed to parse URL parameters:",e),{}}}updateURL(e){try{let t=new URL(window.location.href);Object.entries(e).forEach(([n,r])=>{r===null?t.searchParams.delete(n):t.searchParams.set(n,r)}),window.history.replaceState({},"",t.toString())}catch(t){console.warn("URLManager: Failed to update URL:",t)}}hasQueryParams(){return window.location.search.length>0}};var De=class{constructor(e,t,n,r){this.eventBus=e,this.dateService=t,this.config=n,this.repository=r}async loadData(){try{await this.repository.loadEvents()}catch(e){throw console.error("Failed to load event data:",e),e}}async getEvents(e=!1){let t=await this.repository.loadEvents();return e?[...t]:t}async getEventById(e){return(await this.repository.loadEvents()).find(n=>n.id===e)}async getEventForNavigation(e){let t=await this.getEventById(e);if(!t)return null;let n=this.dateService.validateDate(t.start);return n.valid?this.dateService.isValidRange(t.start,t.end)?{event:t,eventDate:t.start}:(console.warn(`EventManager: Invalid date range for event ${e}: start must be before end`),null):(console.warn(`EventManager: Invalid event start date for event ${e}:`,n.error),null)}async navigateToEvent(e){let t=await this.getEventForNavigation(e);if(!t)return console.warn(`EventManager: Event with ID ${e} not found`),!1;let{event:n,eventDate:r}=t;return this.eventBus.emit(v.NAVIGATE_TO_EVENT,{eventId:e,event:n,eventDate:r,eventStartTime:n.start}),!0}async getEventsForPeriod(e,t){return(await this.repository.loadEvents()).filter(r=>r.start<=t&&r.end>=e)}async addEvent(e){let t=await this.repository.createEvent(e,"local");return this.eventBus.emit(v.EVENT_CREATED,{event:t}),t}async updateEvent(e,t){try{let n=await this.repository.updateEvent(e,t,"local");return this.eventBus.emit(v.EVENT_UPDATED,{event:n}),n}catch(n){return console.error(`Failed to update event ${e}:`,n),null}}async deleteEvent(e){try{return await this.repository.deleteEvent(e,"local"),this.eventBus.emit(v.EVENT_DELETED,{eventId:e}),!0}catch(t){return console.error(`Failed to delete event ${e}:`,t),!1}}async handleRemoteUpdate(e){try{await this.repository.updateEvent(e.id,e,"remote"),this.eventBus.emit(v.REMOTE_UPDATE_RECEIVED,{event:e}),this.eventBus.emit(v.EVENT_UPDATED,{event:e})}catch(t){console.error(`Failed to handle remote update for event ${e.id}:`,t)}}};var B=class{static updateColumnBoundsCache(){this.columnBoundsCache=[];let e=document.querySelectorAll("swp-day-column"),t=1;e.forEach(n=>{let r=n.getBoundingClientRect(),s=n.dataset.date;s&&this.columnBoundsCache.push({boundingClientRect:r,element:n,date:s,left:r.left,right:r.right,index:t++})}),this.columnBoundsCache.sort((n,r)=>n.left-r.left)}static getColumnBounds(e){this.columnBoundsCache.length===0&&this.updateColumnBoundsCache();let t=this.columnBoundsCache.find(n=>e.x>=n.left&&e.x<=n.right);return t||null}static getColumnBoundsByDate(e){this.columnBoundsCache.length===0&&this.updateColumnBoundsCache();let t=e.toISOString().split("T")[0];return this.columnBoundsCache.find(r=>r.date===t)||null}static getColumns(){return[...this.columnBoundsCache]}static getHeaderColumns(){let e=[],t=document.querySelectorAll("swp-calendar-header swp-day-header"),n=1;return t.forEach(r=>{let s=r.getBoundingClientRect(),i=r.dataset.date;i&&e.push({boundingClientRect:s,element:r,date:i,left:s.left,right:s.right,index:n++})}),e.sort((r,s)=>r.left-s.left),e}};B.columnBoundsCache=[];var Se=class{constructor(e,t,n,r){this.dragMouseLeaveHeaderListener=null,this.eventBus=e,this.eventManager=t,this.strategy=n,this.dateService=r,this.setupEventListeners()}async renderEvents(e){this.strategy.clearEvents(e.container);let t=await this.eventManager.getEventsForPeriod(e.startDate,e.endDate);if(t.length===0)return;let n=t.filter(r=>!r.allDay);console.log("\u{1F3AF} EventRenderingService: Event filtering",{totalEvents:t.length,timedEvents:n.length,allDayEvents:t.length-n.length}),n.length>0&&this.strategy.renderEvents(n,e.container),this.eventBus.emit(v.EVENTS_RENDERED,{events:t,container:e.container})}setupEventListeners(){this.eventBus.on(v.GRID_RENDERED,e=>{this.handleGridRendered(e)}),this.eventBus.on(v.VIEW_CHANGED,e=>{this.handleViewChanged(e)}),this.setupDragEventListeners()}handleGridRendered(e){let{container:t,startDate:n,endDate:r}=e.detail;!t||!n||!r||this.renderEvents({container:t,startDate:n,endDate:r})}handleViewChanged(e){this.clearEvents()}setupDragEventListeners(){this.setupDragStartListener(),this.setupDragMoveListener(),this.setupDragEndListener(),this.setupDragColumnChangeListener(),this.setupDragMouseLeaveHeaderListener(),this.setupDragMouseEnterColumnListener(),this.setupResizeEndListener(),this.setupNavigationCompletedListener()}setupDragStartListener(){this.eventBus.on("drag:start",e=>{let t=e.detail;t.originalElement.hasAttribute("data-allday")||t.originalElement&&this.strategy.handleDragStart&&t.columnBounds&&this.strategy.handleDragStart(t)})}setupDragMoveListener(){this.eventBus.on("drag:move",e=>{let t=e.detail;t.draggedClone.hasAttribute("data-allday")||this.strategy.handleDragMove&&this.strategy.handleDragMove(t)})}setupDragEndListener(){this.eventBus.on("drag:end",async e=>{let{originalElement:t,draggedClone:n,originalSourceColumn:r,finalPosition:s,target:i}=e.detail,a=s.column,c=s.snappedY,d=n;i==="swp-day-column"&&a&&(t&&n&&this.strategy.handleDragEnd&&this.strategy.handleDragEnd(t,n,a,c),await this.eventManager.updateEvent(d.eventId,{start:d.start,end:d.end,allDay:!1}),await this.reRenderAffectedColumns(r,a))})}setupDragColumnChangeListener(){this.eventBus.on("drag:column-change",e=>{let t=e.detail;t.draggedClone&&t.draggedClone.hasAttribute("data-allday")||this.strategy.handleColumnChange&&this.strategy.handleColumnChange(t)})}setupDragMouseLeaveHeaderListener(){this.dragMouseLeaveHeaderListener=e=>{let{targetDate:t,mousePosition:n,originalElement:r,draggedClone:s}=e.detail;s&&(s.style.display=""),console.log("\u{1F6AA} EventRendererManager: Received drag:mouseleave-header",{targetDate:t,originalElement:r,cloneElement:s})},this.eventBus.on("drag:mouseleave-header",this.dragMouseLeaveHeaderListener)}setupDragMouseEnterColumnListener(){this.eventBus.on("drag:mouseenter-column",e=>{let t=e.detail;t.draggedClone.hasAttribute("data-allday")&&(console.log("\u{1F3AF} EventRendererManager: Received drag:mouseenter-column",{targetColumn:t.targetColumn,snappedY:t.snappedY,calendarEvent:t.calendarEvent}),this.strategy.handleConvertAllDayToTimed&&this.strategy.handleConvertAllDayToTimed(t))})}setupResizeEndListener(){this.eventBus.on("resize:end",async e=>{let{eventId:t,element:n}=e.detail,r=n,s=r.start,i=r.end;await this.eventManager.updateEvent(t,{start:s,end:i}),console.log("\u{1F4DD} EventRendererManager: Updated event after resize",{eventId:t,newStart:s,newEnd:i});let a=B.getColumnBoundsByDate(s);a&&await this.renderSingleColumn(a)})}setupNavigationCompletedListener(){this.eventBus.on(v.NAVIGATION_COMPLETED,()=>{this.strategy.handleNavigationCompleted&&this.strategy.handleNavigationCompleted()})}async reRenderAffectedColumns(e,t){e&&await this.renderSingleColumn(e),t&&t.date!==e?.date&&await this.renderSingleColumn(t)}clearColumnEvents(e){let t=e.querySelectorAll("swp-event"),n=e.querySelectorAll("swp-event-group");t.forEach(r=>r.remove()),n.forEach(r=>r.remove())}async renderSingleColumn(e){let t=this.dateService.parseISO(`${e.date}T00:00:00`),n=this.dateService.parseISO(`${e.date}T23:59:59.999`),s=(await this.eventManager.getEventsForPeriod(t,n)).filter(a=>!a.allDay),i=e.element.querySelector("swp-events-layer");if(!i){console.warn("EventRendererManager: Events layer not found in column");return}this.clearColumnEvents(i),this.strategy.renderSingleColumnEvents&&this.strategy.renderSingleColumnEvents(e,s),console.log("\u{1F504} EventRendererManager: Re-rendered single column",{columnDate:e.date,eventsCount:s.length})}clearEvents(e){this.strategy.clearEvents(e)}refresh(e){this.clearEvents(e)}};var we=class{constructor(e,t){this.container=null,this.currentDate=new Date,this.currentView="week",this.gridRenderer=e,this.dateService=t,this.init()}init(){this.findElements(),this.subscribeToEvents()}getISOWeekStart(e){let t=this.dateService.getWeekBounds(e);return this.dateService.startOfDay(t.start)}getWeekEnd(e){let t=this.dateService.getWeekBounds(e);return this.dateService.endOfDay(t.end)}findElements(){this.container=document.querySelector("swp-calendar-container")}subscribeToEvents(){b.on(v.VIEW_CHANGED,e=>{let t=e.detail;this.currentView=t.currentView,this.render()}),b.on(v.REFRESH_REQUESTED,e=>{this.render()}),b.on(v.WORKWEEK_CHANGED,()=>{this.render()})}async render(){if(!this.container)return;this.gridRenderer.renderGrid(this.container,this.currentDate);let e=this.getPeriodRange(),t=this.getLayoutConfig();b.emit(v.GRID_RENDERED,{container:this.container,currentDate:this.currentDate,startDate:e.startDate,endDate:e.endDate,layoutConfig:t,columnCount:t.columnCount})}getCurrentPeriodLabel(){switch(this.currentView){case"week":case"day":let e=this.getISOWeekStart(this.currentDate),t=this.getWeekEnd(this.currentDate);return this.dateService.formatDateRange(e,t);case"month":return this.dateService.formatMonthYear(this.currentDate);default:let n=this.getISOWeekStart(this.currentDate),r=this.getWeekEnd(this.currentDate);return this.dateService.formatDateRange(n,r)}}navigateNext(){let e;switch(this.currentView){case"week":e=this.dateService.addWeeks(this.currentDate,1);break;case"month":e=this.dateService.addMonths(this.currentDate,1);break;case"day":e=this.dateService.addDays(this.currentDate,1);break;default:e=this.dateService.addWeeks(this.currentDate,1)}this.currentDate=e,b.emit(v.NAVIGATION_COMPLETED,{direction:"next",newDate:e,periodLabel:this.getCurrentPeriodLabel()}),this.render()}navigatePrevious(){let e;switch(this.currentView){case"week":e=this.dateService.addWeeks(this.currentDate,-1);break;case"month":e=this.dateService.addMonths(this.currentDate,-1);break;case"day":e=this.dateService.addDays(this.currentDate,-1);break;default:e=this.dateService.addWeeks(this.currentDate,-1)}this.currentDate=e,b.emit(v.NAVIGATION_COMPLETED,{direction:"previous",newDate:e,periodLabel:this.getCurrentPeriodLabel()}),this.render()}getDisplayDates(){switch(this.currentView){case"week":let e=this.getISOWeekStart(this.currentDate);return this.dateService.getFullWeekDates(e);case"month":return this.getMonthDates(this.currentDate);case"day":return[this.currentDate];default:let t=this.getISOWeekStart(this.currentDate);return this.dateService.getFullWeekDates(t)}}getPeriodRange(){switch(this.currentView){case"week":let e=this.getISOWeekStart(this.currentDate),t=this.getWeekEnd(this.currentDate);return{startDate:e,endDate:t};case"month":return{startDate:this.getMonthStart(this.currentDate),endDate:this.getMonthEnd(this.currentDate)};case"day":return{startDate:this.currentDate,endDate:this.currentDate};default:let n=this.getISOWeekStart(this.currentDate),r=this.getWeekEnd(this.currentDate);return{startDate:n,endDate:r}}}getLayoutConfig(){switch(this.currentView){case"week":return{columnCount:7,type:"week"};case"month":return{columnCount:7,type:"month"};case"day":return{columnCount:1,type:"day"};default:return{columnCount:7,type:"week"}}}getMonthStart(e){let t=e.getFullYear(),n=e.getMonth();return this.dateService.startOfDay(new Date(t,n,1))}getMonthEnd(e){let t=this.dateService.addMonths(e,1),n=this.getMonthStart(t);return this.dateService.endOfDay(this.dateService.addDays(n,-1))}getMonthDates(e){let t=[],n=this.getMonthStart(e),r=this.getMonthEnd(e),s=Math.ceil((r.getTime()-n.getTime())/(1e3*60*60*24))+1;for(let i=0;i{this.syncTimeAxisPosition(),this.setupScrolling()}),b.on("header:height-changed",()=>{this.updateScrollableHeight()}),b.on("header:ready",()=>{this.calendarHeader=document.querySelector("swp-calendar-header"),this.scrollableContent&&this.calendarHeader&&(this.setupHorizontalScrollSynchronization(),this.syncCalendarHeaderPosition()),this.updateScrollableHeight()}),window.addEventListener("resize",()=>{this.updateScrollableHeight()}),b.on("scroll:to-event-time",e=>{let t=e,{eventStartTime:n}=t.detail;n&&this.scrollToEventTime(n)})}setupScrolling(){this.findElements(),this.scrollableContent&&this.calendarContainer&&(this.setupResizeObserver(),this.updateScrollableHeight(),this.setupScrollSynchronization()),this.scrollableContent&&this.calendarHeader&&this.setupHorizontalScrollSynchronization()}findElements(){this.scrollableContent=document.querySelector("swp-scrollable-content"),this.calendarContainer=document.querySelector("swp-calendar-container"),this.timeAxis=document.querySelector("swp-time-axis"),this.calendarHeader=document.querySelector("swp-calendar-header")}scrollTo(e){this.scrollableContent&&(this.scrollableContent.scrollTop=e)}scrollToHour(e){let t=`${e.toString().padStart(2,"0")}:00`,n=this.positionUtils.timeToPixels(t);this.scrollTo(n)}scrollToEventTime(e){try{let t=new Date(e),n=t.getHours(),r=t.getMinutes(),s=n+r/60;this.scrollToHour(s)}catch(t){console.warn("ScrollManager: Failed to scroll to event time:",t)}}setupResizeObserver(){this.calendarContainer&&(this.resizeObserver&&this.resizeObserver.disconnect(),this.resizeObserver=new ResizeObserver(e=>{for(let t of e)this.updateScrollableHeight()}),this.resizeObserver.observe(this.calendarContainer))}updateScrollableHeight(){if(!this.scrollableContent||!this.calendarContainer)return;let e=this.calendarContainer.getBoundingClientRect(),t=document.querySelector("swp-calendar-nav"),n=t?t.getBoundingClientRect().height:0,r=document.querySelector("swp-calendar-header"),s=r?r.getBoundingClientRect().height:80,i=e.height-s,a=e.width-60;i>0&&(this.scrollableContent.style.height=`${i}px`),a>0&&(this.scrollableContent.style.width=`${a}px`)}setupScrollSynchronization(){if(!this.scrollableContent||!this.timeAxis)return;let e=null;this.scrollableContent.addEventListener("scroll",()=>{e&&cancelAnimationFrame(e),e=requestAnimationFrame(()=>{this.syncTimeAxisPosition()})})}syncTimeAxisPosition(){if(!this.scrollableContent||!this.timeAxis)return;let e=this.scrollableContent.scrollTop,t=this.timeAxis.querySelector("swp-time-axis-content");t&&(t.style.transform=`translateY(-${e}px)`,e%100)}setupHorizontalScrollSynchronization(){!this.scrollableContent||!this.calendarHeader||this.scrollableContent.addEventListener("scroll",()=>{this.syncCalendarHeaderPosition()})}syncCalendarHeaderPosition(){if(!this.scrollableContent||!this.calendarHeader)return;let e=this.scrollableContent.scrollLeft;this.calendarHeader.style.transform=`translateX(-${e}px)`,e%100}};var Te=class{constructor(e,t,n,r,s){this.animationQueue=0,this.eventBus=e,this.dateService=r,this.weekInfoRenderer=s,this.gridRenderer=n,this.currentWeek=this.getISOWeekStart(new Date),this.targetWeek=new Date(this.currentWeek),this.init()}init(){this.setupEventListeners()}getISOWeekStart(e){let t=this.dateService.getWeekBounds(e);return this.dateService.startOfDay(t.start)}setupEventListeners(){this.eventBus.on(v.INITIALIZED,()=>{this.updateWeekInfo()}),this.eventBus.on(v.FILTER_CHANGED,e=>{let t=e.detail;this.weekInfoRenderer.applyFilterToPreRenderedGrids(t)}),this.eventBus.on(v.NAV_BUTTON_CLICKED,e=>{let{action:t}=e.detail;switch(t){case"prev":this.navigateToPreviousWeek();break;case"next":this.navigateToNextWeek();break;case"today":this.navigateToToday();break}}),this.eventBus.on(v.DATE_CHANGED,e=>{let n=e.detail.currentDate;if(!n){console.warn("NavigationManager: No date provided in DATE_CHANGED event");return}let r=new Date(n),s=this.dateService.validateDate(r);if(!s.valid){console.warn("NavigationManager: Invalid date received:",s.error);return}this.navigateToDate(r)}),this.eventBus.on(v.NAVIGATE_TO_EVENT,e=>{let t=e,{eventDate:n,eventStartTime:r}=t.detail;if(!n||!r){console.warn("NavigationManager: Invalid event navigation data");return}this.navigateToEventDate(n,r)})}navigateToEventDate(e,t){let n=this.getISOWeekStart(e);this.targetWeek=new Date(n);let r=this.currentWeek.getTime(),s=n.getTime(),i=()=>{this.eventBus.emit("scroll:to-event-time",{eventStartTime:t})};rs?(this.animationQueue++,this.animateTransition("prev",n),this.eventBus.once(v.NAVIGATION_COMPLETED,i)):i()}navigateToPreviousWeek(){this.targetWeek=this.dateService.addWeeks(this.targetWeek,-1);let e=new Date(this.targetWeek);this.animationQueue++,this.animateTransition("prev",e)}navigateToNextWeek(){this.targetWeek=this.dateService.addWeeks(this.targetWeek,1);let e=new Date(this.targetWeek);this.animationQueue++,this.animateTransition("next",e)}navigateToToday(){let e=new Date,t=this.getISOWeekStart(e);this.targetWeek=new Date(t);let n=this.currentWeek.getTime(),r=t.getTime();nr&&(this.animationQueue++,this.animateTransition("prev",t))}navigateToDate(e){let t=this.getISOWeekStart(e);this.targetWeek=new Date(t);let n=this.currentWeek.getTime(),r=t.getTime();nr&&(this.animationQueue++,this.animateTransition("prev",t))}animateTransition(e,t){let n=document.querySelector("swp-calendar-container"),r=document.querySelector("swp-calendar-container swp-grid-container:not([data-prerendered])");if(!n||!r)return;document.documentElement.style.setProperty("--all-day-row-height","0px");let i;console.group("\u{1F527} NavigationManager.refactored"),console.log("Calling GridRenderer instead of NavigationRenderer"),console.log("Target week:",t),i=this.gridRenderer.createNavigationGrid(n,t),console.groupEnd(),i.style.transform="",r.style.transform="";let a=r.animate([{transform:"translateX(0)",opacity:"1"},{transform:e==="next"?"translateX(-100%)":"translateX(100%)",opacity:"0.5"}],{duration:400,easing:"ease-in-out",fill:"forwards"});i.animate([{transform:e==="next"?"translateX(100%)":"translateX(-100%)"},{transform:"translateX(0)"}],{duration:400,easing:"ease-in-out",fill:"forwards"}).addEventListener("finish",()=>{let d=n.querySelectorAll("swp-grid-container");for(let g=0;g{let n=r=>{r.preventDefault();let s=t.getAttribute("data-action");s&&this.isValidAction(s)&&this.handleNavigation(s)};t.addEventListener("click",n),this.buttonListeners.set(t,n)})}handleNavigation(e){this.eventBus.emit(v.NAV_BUTTON_CLICKED,{action:e})}isValidAction(e){return["prev","next","today"].includes(e)}};var Me=class{constructor(e,t){this.buttonListeners=new Map,this.eventBus=e,this.config=t,this.setupButtonListeners(),this.setupEventListeners()}setupButtonListeners(){document.querySelectorAll("swp-view-button[data-view]").forEach(t=>{let n=r=>{r.preventDefault();let s=t.getAttribute("data-view");s&&this.isValidView(s)&&this.changeView(s)};t.addEventListener("click",n),this.buttonListeners.set(t,n)}),this.updateButtonStates()}setupEventListeners(){this.eventBus.on(v.INITIALIZED,()=>{this.initializeView()}),this.eventBus.on(v.DATE_CHANGED,()=>{this.refreshCurrentView()})}changeView(e){if(e===this.config.currentView)return;let t=this.config.currentView;this.config.currentView=e,this.updateButtonStates(),this.eventBus.emit(v.VIEW_CHANGED,{previousView:t,currentView:e})}updateButtonStates(){document.querySelectorAll("swp-view-button[data-view]").forEach(t=>{t.getAttribute("data-view")===this.config.currentView?t.setAttribute("data-active","true"):t.removeAttribute("data-active")})}initializeView(){this.updateButtonStates(),this.emitViewRendered()}emitViewRendered(){this.eventBus.emit(v.VIEW_RENDERED,{view:this.config.currentView})}refreshCurrentView(){this.emitViewRendered()}isValidView(e){return["day","week","month"].includes(e)}};var Ae=class{constructor(e,t,n,r,s,i){this.currentView="week",this.currentDate=new Date,this.isInitialized=!1,this.eventBus=e,this.eventManager=t,this.gridManager=n,this.eventRenderer=r,this.scrollManager=s,this.config=i,this.setupEventListeners()}async initialize(){if(!this.isInitialized)try{await this.eventManager.loadData(),await this.gridManager.render(),this.scrollManager.initialize(),this.setView(this.currentView),this.setCurrentDate(this.currentDate),this.isInitialized=!0,this.eventBus.emit(v.INITIALIZED,{currentDate:this.currentDate,currentView:this.currentView})}catch(e){throw e}}setView(e){if(this.currentView===e)return;let t=this.currentView;this.currentView=e,this.eventBus.emit(v.VIEW_CHANGED,{previousView:t,currentView:e,date:this.currentDate})}setCurrentDate(e){let t=this.currentDate;this.currentDate=new Date(e),this.eventBus.emit(v.DATE_CHANGED,{previousDate:t,currentDate:this.currentDate,view:this.currentView})}setupEventListeners(){this.eventBus.on(v.WORKWEEK_CHANGED,e=>{let t=e;this.handleWorkweekChange()})}calculateCurrentPeriod(){let e=new Date(this.currentDate);switch(this.currentView){case"day":let t=new Date(e);t.setHours(0,0,0,0);let n=new Date(e);return n.setHours(23,59,59,999),{start:t.toISOString(),end:n.toISOString()};case"week":let r=new Date(e),s=r.getDay(),i=s===0?6:s-1;r.setDate(r.getDate()-i),r.setHours(0,0,0,0);let a=new Date(r);return a.setDate(a.getDate()+6),a.setHours(23,59,59,999),{start:r.toISOString(),end:a.toISOString()};case"month":let c=new Date(e.getFullYear(),e.getMonth(),1),d=new Date(e.getFullYear(),e.getMonth()+1,0,23,59,59,999);return{start:c.toISOString(),end:d.toISOString()};default:let g=new Date(e);g.setDate(g.getDate()-3),g.setHours(0,0,0,0);let w=new Date(e);return w.setDate(w.getDate()+3),w.setHours(23,59,59,999),{start:g.toISOString(),end:w.toISOString()}}}handleWorkweekChange(){this.eventBus.emit("workweek:header-update",{currentDate:this.currentDate,currentView:this.currentView})}};var Re=class extends HTMLElement{constructor(){super(),this.config=K.getInstance(),this.dateService=new U(this.config)}get eventId(){return this.dataset.eventId||""}set eventId(e){this.dataset.eventId=e}get start(){return new Date(this.dataset.start||"")}set start(e){this.dataset.start=this.dateService.toUTC(e)}get end(){return new Date(this.dataset.end||"")}set end(e){this.dataset.end=this.dateService.toUTC(e)}get title(){return this.dataset.title||""}set title(e){this.dataset.title=e}get description(){return this.dataset.description||""}set description(e){this.dataset.description=e}get type(){return this.dataset.type||"work"}set type(e){this.dataset.type=e}},Q=class extends Re{static get observedAttributes(){return["data-start","data-end","data-title","data-description","data-type"]}connectedCallback(){this.hasChildNodes()||this.render()}attributeChangedCallback(e,t,n){t!==n&&this.isConnected&&this.updateDisplay()}updatePosition(e,t){this.style.top=`${t+1}px`;let{startMinutes:n,endMinutes:r}=this.calculateTimesFromPosition(t),s=this.dateService.createDateAtTime(e,n),i=this.dateService.createDateAtTime(e,r);if(r>=1440){let a=Math.floor(r/1440);i=this.dateService.addDays(i,a)}this.start=s,this.end=i}updateHeight(e){this.style.height=`${e}px`;let t=this.config.gridSettings,{hourHeight:n,snapInterval:r}=t,s=this.start,i=e/n*60,a=Math.round(i/r)*r,c=this.dateService.addMinutes(s,a);this.end=c}createClone(){let e=this.cloneNode(!0);e.dataset.eventId=`clone-${this.eventId}`,e.style.pointerEvents="none";let t=this.querySelector("swp-event-time");if(t){let n=t.getAttribute("data-duration");n&&(e.dataset.originalDuration=n)}return e.style.height=this.style.height||`${this.getBoundingClientRect().height}px`,e}render(){let e=this.start,t=this.end,n=V.formatTimeRange(e,t),r=(t.getTime()-e.getTime())/(1e3*60);this.innerHTML=` + ${n} + ${this.title} + ${this.description?`${this.description}`:""} + `}updateDisplay(){let e=this.querySelector("swp-event-time"),t=this.querySelector("swp-event-title"),n=this.querySelector("swp-event-description");if(e&&this.dataset.start&&this.dataset.end){let r=new Date(this.dataset.start),s=new Date(this.dataset.end),i=V.formatTimeRange(r,s);e.textContent=i;let a=(s.getTime()-r.getTime())/(1e3*60);e.setAttribute("data-duration",a.toString())}if(t&&this.dataset.title&&(t.textContent=this.dataset.title),this.dataset.description){if(n)n.textContent=this.dataset.description;else if(this.description){let r=document.createElement("swp-event-description");r.textContent=this.description,this.appendChild(r)}}else n&&n.remove()}calculateTimesFromPosition(e){let t=this.config.gridSettings,{hourHeight:n,dayStartHour:r,snapInterval:s}=t,i=parseInt(this.dataset.originalDuration||this.dataset.duration||"60"),a=e/n*60,c=r*60+a,d=Math.round(c/s)*s,g=d+i;return{startMinutes:d,endMinutes:g}}static fromCalendarEvent(e){let t=document.createElement("swp-event"),n=K.getInstance(),r=new U(n);return t.dataset.eventId=e.id,t.dataset.title=e.title,t.dataset.description=e.description||"",t.dataset.start=r.toUTC(e.start),t.dataset.end=r.toUTC(e.end),t.dataset.type=e.type,t.dataset.duration=e.metadata?.duration?.toString()||"60",t}static extractCalendarEventFromElement(e){return{id:e.dataset.eventId||"",title:e.dataset.title||"",description:e.dataset.description||void 0,start:new Date(e.dataset.start||""),end:new Date(e.dataset.end||""),type:e.dataset.type||"work",allDay:!1,syncStatus:"synced",metadata:{duration:e.dataset.duration}}}},oe=class extends Re{connectedCallback(){this.textContent||(this.textContent=this.dataset.title||"Untitled")}createClone(){let e=this.cloneNode(!0);return e.dataset.eventId=`clone-${this.eventId}`,e.style.pointerEvents="none",e.style.opacity="1",e}applyGridPositioning(e,t,n){let r=`${e} / ${t} / ${e+1} / ${n+1}`;this.style.gridArea=r}static fromCalendarEvent(e){let t=document.createElement("swp-allday-event"),n=K.getInstance(),r=new U(n);return t.dataset.eventId=e.id,t.dataset.title=e.title,t.dataset.start=r.toUTC(e.start),t.dataset.end=r.toUTC(e.end),t.dataset.type=e.type,t.dataset.allday="true",t.textContent=e.title,t}};customElements.define("swp-event",Q);customElements.define("swp-allday-event",oe);var be=class{constructor(e,t){this.mouseDownPosition={x:0,y:0},this.currentMousePosition={x:0,y:0},this.mouseOffset={x:0,y:0},this.currentColumn=null,this.previousColumn=null,this.originalSourceColumn=null,this.isDragStarted=!1,this.dragThreshold=5,this.scrollableContent=null,this.scrollDeltaY=0,this.lastScrollTop=0,this.isScrollCompensating=!1,this.dragAnimationId=null,this.targetY=0,this.currentY=0,this.targetColumn=null,this.eventBus=e,this.positionUtils=t,this.init()}init(){document.body.addEventListener("mousemove",this.handleMouseMove.bind(this)),document.body.addEventListener("mousedown",this.handleMouseDown.bind(this)),document.body.addEventListener("mouseup",this.handleMouseUp.bind(this));let e=document.querySelector("swp-calendar-container");e&&(e.addEventListener("mouseleave",()=>{this.originalElement&&this.isDragStarted&&this.cancelDrag()}),e.addEventListener("mouseenter",t=>{let n=t.target;n.closest("swp-calendar-header")?this.handleHeaderMouseEnter(t):n.closest("swp-day-column")&&this.handleColumnMouseEnter(t)},!0),e.addEventListener("mouseleave",t=>{t.target.closest("swp-calendar-header")&&this.handleHeaderMouseLeave(t)},!0)),B.updateColumnBoundsCache(),window.addEventListener("resize",()=>{B.updateColumnBoundsCache()}),this.eventBus.on("navigation:completed",()=>{B.updateColumnBoundsCache()}),this.eventBus.on(v.GRID_RENDERED,t=>{this.handleGridRendered(t)}),this.eventBus.on("edgescroll:started",()=>{this.isScrollCompensating=!0,this.scrollableContent&&(this.lastScrollTop=this.scrollableContent.scrollTop)}),this.eventBus.on("edgescroll:stopped",()=>{this.isScrollCompensating=!1}),this.eventBus.on("drag:mouseenter-header",()=>{this.scrollDeltaY=0,this.lastScrollTop=0}),this.eventBus.on("drag:mouseenter-column",()=>{this.scrollDeltaY=0,this.lastScrollTop=0})}handleGridRendered(e){this.scrollableContent=document.querySelector("swp-scrollable-content"),this.scrollableContent.addEventListener("scroll",this.handleScroll.bind(this),{passive:!0})}handleMouseDown(e){this.cleanupDragState(),B.updateColumnBoundsCache();let t=e.target;if(t.closest("swp-resize-handle"))return;let n=t;for(;n&&n.tagName!=="SWP-GRID-CONTAINER"&&!(n.tagName==="SWP-EVENT"||n.tagName==="SWP-ALLDAY-EVENT");)if(n=n.parentElement,!n)return;if(n){this.originalElement=n;let r=n.getBoundingClientRect();this.mouseOffset={x:e.clientX-r.left,y:e.clientY-r.top},this.mouseDownPosition={x:e.clientX,y:e.clientY}}}handleMouseMove(e){if(e.buttons===1){if(this.currentMousePosition={x:e.clientX,y:e.clientY},!this.isDragStarted&&this.originalElement&&!this.initializeDrag(this.currentMousePosition))return;this.isDragStarted&&this.originalElement&&this.draggedClone&&(this.continueDrag(this.currentMousePosition),this.detectColumnChange(this.currentMousePosition))}}initializeDrag(e){let t=Math.abs(e.x-this.mouseDownPosition.x),n=Math.abs(e.y-this.mouseDownPosition.y);if(Math.sqrt(t*t+n*n)0&&e.forEach(t=>t.remove())}cancelDrag(){if(!this.originalElement||!this.draggedClone)return;let e=this.draggedClone.getBoundingClientRect(),t=this.originalElement.getBoundingClientRect(),n=t.left-e.left,r=t.top-e.top;this.draggedClone.style.transition="transform 300ms ease-out",this.draggedClone.style.transform=`translate(${n}px, ${r}px)`,setTimeout(()=>{this.cleanupAllClones(),this.originalElement&&(this.originalElement.style.opacity="",this.originalElement.style.cursor=""),this.eventBus.emit("drag:cancelled",{originalElement:this.originalElement,reason:"mouse-left-grid"}),this.cleanupDragState(),this.stopDragAnimation()},300)}calculateSnapPosition(e,t){let n=e-this.mouseOffset.y,r=this.positionUtils.getPositionFromCoordinate(n,t);return Math.max(0,r)}animateDrag(){if(!this.isDragStarted||!this.draggedClone||!this.targetColumn){this.dragAnimationId=null;return}let e=this.targetY-this.currentY,t=e*.3;if(Math.abs(e)>.5){this.currentY+=t;let n={originalElement:this.originalElement,draggedClone:this.draggedClone,mousePosition:this.currentMousePosition,snappedY:this.currentY,columnBounds:this.targetColumn,mouseOffset:this.mouseOffset};this.eventBus.emit("drag:move",n),this.dragAnimationId=requestAnimationFrame(()=>this.animateDrag())}else{this.currentY=this.targetY;let n={originalElement:this.originalElement,draggedClone:this.draggedClone,mousePosition:this.currentMousePosition,snappedY:this.currentY,columnBounds:this.targetColumn,mouseOffset:this.mouseOffset};this.eventBus.emit("drag:move",n),this.dragAnimationId=null}}handleScroll(){if(!this.isDragStarted||!this.draggedClone||!this.scrollableContent||!this.isScrollCompensating)return;let e=this.scrollableContent.scrollTop,t=e-this.lastScrollTop;this.scrollDeltaY+=t,this.lastScrollTop=e,this.continueDrag(this.currentMousePosition)}stopDragAnimation(){this.dragAnimationId!==null&&(cancelAnimationFrame(this.dragAnimationId),this.dragAnimationId=null)}cleanupDragState(){this.previousColumn=null,this.originalElement=null,this.draggedClone=null,this.currentColumn=null,this.originalSourceColumn=null,this.isDragStarted=!1,this.scrollDeltaY=0,this.lastScrollTop=0}detectDropTarget(e){let t=this.draggedClone;for(;t&&t!==document.body;){if(t.tagName==="SWP-ALLDAY-CONTAINER")return"swp-day-header";if(t.tagName==="SWP-DAY-COLUMN")return"swp-day-column";t=t.parentElement}return null}handleHeaderMouseEnter(e){if(!this.isDragStarted||!this.draggedClone)return;let t={x:e.clientX,y:e.clientY},n=B.getColumnBounds(t);if(n){let r=Q.extractCalendarEventFromElement(this.draggedClone),s={targetColumn:n,mousePosition:t,originalElement:this.originalElement,draggedClone:this.draggedClone,calendarEvent:r,replaceClone:i=>{this.draggedClone=i,this.dragAnimationId}};this.eventBus.emit("drag:mouseenter-header",s)}}handleColumnMouseEnter(e){if(!this.isDragStarted||!this.draggedClone||!this.draggedClone.hasAttribute("data-allday"))return;let t={x:e.clientX,y:e.clientY},n=B.getColumnBounds(t);if(!n)return;let r=this.calculateSnapPosition(t.y,n),s=Q.extractCalendarEventFromElement(this.draggedClone),i={targetColumn:n,mousePosition:t,snappedY:r,originalElement:this.originalElement,draggedClone:this.draggedClone,calendarEvent:s,replaceClone:a=>{this.draggedClone=a,this.dragAnimationId,this.stopDragAnimation()}};this.eventBus.emit("drag:mouseenter-column",i)}handleHeaderMouseLeave(e){if(!this.isDragStarted||!this.draggedClone||!this.draggedClone.hasAttribute("data-allday"))return;let t={x:e.clientX,y:e.clientY},n=B.getColumnBounds(t);if(!n)return;let r={targetDate:n.date,mousePosition:t,originalElement:this.originalElement,draggedClone:this.draggedClone};this.eventBus.emit("drag:mouseleave-header",r)}};var xe=class{constructor(e){this.weekDates=e,this.tracks=[]}calculateLayout(e){let t=[];this.tracks=[new Array(this.weekDates.length).fill(!1)];let n=e.filter(r=>this.isEventVisible(r));for(let r of n){let s=this.getEventStartDay(r),i=this.getEventEndDay(r);if(s>0&&i>0){let a=this.findAvailableTrack(s-1,i-1);for(let d=s-1;d<=i-1;d++)this.tracks[a][d]=!0;let c={calenderEvent:r,gridArea:`${a+1} / ${s} / ${a+2} / ${i+1}`,startColumn:s,endColumn:i,row:a+1,columnSpan:i-s+1};t.push(c)}}return t}findAvailableTrack(e,t){for(let n=0;n=0?s+1:0}getEventEndDay(e){let t=this.formatDate(e.end),n=this.weekDates[this.weekDates.length-1],r=t>n?n:t,s=this.weekDates.indexOf(r);return s>=0?s+1:0}isEventVisible(e){if(this.weekDates.length===0)return!1;let t=this.formatDate(e.start),n=this.formatDate(e.end),r=this.weekDates[0],s=this.weekDates[this.weekDates.length-1];return!(ns)}formatDate(e){let t=e.getFullYear(),n=String(e.getMonth()+1).padStart(2,"0"),r=String(e.getDate()).padStart(2,"0");return`${t}-${n}-${r}`}};var Ie=class{constructor(e,t,n){this.layoutEngine=null,this.currentAllDayEvents=[],this.currentWeekDates=[],this.isExpanded=!1,this.actualRowCount=0,this.eventManager=e,this.allDayEventRenderer=t,this.dateService=n,document.documentElement.style.setProperty("--single-row-height",`${ne.EVENT_HEIGHT}px`),this.setupEventListeners()}setupEventListeners(){b.on("drag:mouseenter-header",e=>{let t=e.detail;t.draggedClone.hasAttribute("data-allday")||(console.log("\u{1F504} AllDayManager: Received drag:mouseenter-header",{targetDate:t.targetColumn,originalElementId:t.originalElement?.dataset?.eventId,originalElementTag:t.originalElement?.tagName}),this.handleConvertToAllDay(t))}),b.on("drag:mouseleave-header",e=>{let{originalElement:t,cloneElement:n}=e.detail;console.log("\u{1F6AA} AllDayManager: Received drag:mouseleave-header",{originalElementId:t?.dataset?.eventId})}),b.on("drag:start",e=>{let t=e.detail;t.draggedClone?.hasAttribute("data-allday")&&this.allDayEventRenderer.handleDragStart(t)}),b.on("drag:column-change",e=>{let t=e.detail;t.draggedClone?.hasAttribute("data-allday")&&this.handleColumnChange(t)}),b.on("drag:end",e=>{let t=e.detail;if(console.log("\u{1F3AF} AllDayManager: drag:end received",{target:t.target,originalElementTag:t.originalElement?.tagName,hasAllDayAttribute:t.originalElement?.hasAttribute("data-allday"),eventId:t.originalElement?.dataset.eventId}),t.target==="swp-day-header"&&t.originalElement?.hasAttribute("data-allday")){console.log("\u2705 AllDayManager: Handling all-day \u2192 all-day drop"),this.handleDragEnd(t);return}if(t.target==="swp-day-header"&&!t.originalElement?.hasAttribute("data-allday")){console.log("\u{1F504} AllDayManager: Timed \u2192 all-day conversion on drop"),this.handleTimedToAllDayDrop(t);return}if(t.target==="swp-day-column"&&t.originalElement?.hasAttribute("data-allday")){let n=t.originalElement.dataset.eventId;console.log("\u{1F504} AllDayManager: All-day \u2192 timed conversion",{eventId:n}),this.fadeOutAndRemove(t.originalElement);let r=this.currentAllDayEvents.filter(i=>i.id!==n),s=this.calculateAllDayEventsLayout(r,this.currentWeekDates);this.allDayEventRenderer.renderAllDayEventsForPeriod(s),this.checkAndAnimateAllDayHeight()}}),b.on("drag:cancelled",e=>{let{draggedElement:t,reason:n}=e.detail;console.log("\u{1F6AB} AllDayManager: Drag cancelled",{eventId:t?.dataset?.eventId,reason:n})}),b.on("header:ready",async e=>{let t=e.detail,n=new Date(t.headerElements.at(0).date),r=new Date(t.headerElements.at(-1).date),i=(await this.eventManager.getEventsForPeriod(n,r)).filter(c=>c.allDay),a=this.calculateAllDayEventsLayout(i,t.headerElements);this.allDayEventRenderer.renderAllDayEventsForPeriod(a),this.checkAndAnimateAllDayHeight()}),b.on(v.VIEW_CHANGED,e=>{this.allDayEventRenderer.handleViewChanged(e)})}getAllDayContainer(){return document.querySelector("swp-calendar-header swp-allday-container")}getCalendarHeader(){return document.querySelector("swp-calendar-header")}getHeaderSpacer(){return document.querySelector("swp-header-spacer")}getMaxRowFromDOM(){let e=this.getAllDayContainer();if(!e)return 0;let t=0;return e.querySelectorAll("swp-allday-event:not(.max-event-indicator):not([data-removing])").forEach(r=>{let i=parseInt(r.style.gridRow)||1;t=Math.max(t,i)}),t}getGridAreaFromDOM(e){let t=this.getAllDayContainer();return t&&t.querySelector(`[data-event-id="${e}"]`)?.style.gridArea||null}countEventsInColumnFromDOM(e){let t=this.getAllDayContainer();if(!t)return 0;let n=0;return t.querySelectorAll("swp-allday-event:not(.max-event-indicator)").forEach(s=>{let c=s.style.gridColumn.match(/(\d+)\s*\/\s*(\d+)/);if(c){let d=parseInt(c[1]),g=parseInt(c[2])-1;d<=e&&g>=e&&n++}}),n}calculateAllDayHeight(e){let t=document.documentElement,n=e*ne.SINGLE_ROW_HEIGHT,r=t.style.getPropertyValue("--all-day-row-height")||"0px",s=parseInt(r)||0,i=n-s;return{targetHeight:n,currentHeight:s,heightDifference:i}}checkAndAnimateAllDayHeight(){let e=this.getMaxRowFromDOM();console.log("\u{1F4CA} AllDayManager: Height calculation",{maxRows:e,isExpanded:this.isExpanded}),this.actualRowCount=e;let t=e;e>ne.MAX_COLLAPSED_ROWS?(this.updateChevronButton(!0),this.isExpanded?this.clearOverflowIndicators():(t=ne.MAX_COLLAPSED_ROWS,this.updateOverflowIndicators())):(this.updateChevronButton(!1),this.clearOverflowIndicators()),console.log("\u{1F3AC} AllDayManager: Will animate to",{displayRows:t,maxRows:e,willAnimate:t!==this.actualRowCount}),console.log(`\u{1F3AF} AllDayManager: Animating to ${t} rows`),this.animateToRows(t)}animateToRows(e){let{targetHeight:t,currentHeight:n,heightDifference:r}=this.calculateAllDayHeight(e);if(t===n)return;console.log(`\u{1F3AC} All-day height animation: ${n}px \u2192 ${t}px (${Math.ceil(n/ne.SINGLE_ROW_HEIGHT)} \u2192 ${e} rows)`);let s=this.getCalendarHeader(),i=this.getHeaderSpacer(),a=this.getAllDayContainer();if(!s||!a)return;let c=parseFloat(getComputedStyle(s).height),d=c+r,g=[s.animate([{height:`${c}px`},{height:`${d}px`}],{duration:150,easing:"ease-out",fill:"forwards"})];if(i){let E=document.documentElement.style.getPropertyValue("--header-height"),m=parseInt(E),u=m+n,D=m+t;g.push(i.animate([{height:`${u}px`},{height:`${D}px`}],{duration:150,easing:"ease-out"}))}Promise.all(g.map(w=>w.finished)).then(()=>{document.documentElement.style.setProperty("--all-day-row-height",`${t}px`),b.emit("header:height-changed")})}calculateAllDayEventsLayout(e,t){return this.currentAllDayEvents=e,this.currentWeekDates=t,new xe(t.map(r=>r.date)).calculateLayout(e)}handleConvertToAllDay(e){let t=this.getAllDayContainer();if(!t)return;let n=oe.fromCalendarEvent(e.calendarEvent);n.style.gridRow="1",n.style.gridColumn=e.targetColumn.index.toString(),e.draggedClone.remove(),e.replaceClone(n),t.appendChild(n),B.updateColumnBoundsCache(),this.checkAndAnimateAllDayHeight()}handleColumnChange(e){if(!this.getAllDayContainer())return;let n=B.getColumnBounds(e.mousePosition);if(n==null||!e.draggedClone)return;let r=window.getComputedStyle(e.draggedClone),s=parseInt(r.gridColumnStart)||n.index,a=(parseInt(r.gridColumnEnd)||n.index+1)-s,c=n.index,d=c+a;e.draggedClone.style.gridColumn=`${c} / ${d}`}fadeOutAndRemove(e){console.log("\u{1F5D1}\uFE0F AllDayManager: About to remove all-day event",{eventId:e.dataset.eventId,element:e.tagName}),e.setAttribute("data-removing","true"),e.style.transition="opacity 0.3s ease-out",e.style.opacity="0",setTimeout(()=>{e.remove(),console.log("\u2705 AllDayManager: All-day event removed from DOM")},300)}async handleTimedToAllDayDrop(e){if(!e.draggedClone||!e.finalPosition.column)return;let t=e.draggedClone,n=t.eventId.replace("clone-",""),r=e.finalPosition.column.date;console.log("\u{1F504} AllDayManager: Converting timed event to all-day",{eventId:n,targetDate:r});let s=new Date(r);s.setHours(t.start.getHours(),t.start.getMinutes(),0,0);let i=new Date(r);i.setHours(t.end.getHours(),t.end.getMinutes(),0,0),await this.eventManager.updateEvent(n,{start:s,end:i,allDay:!0}),this.fadeOutAndRemove(e.originalElement);let a={id:n,title:t.title,start:s,end:i,type:t.type,allDay:!0,syncStatus:"synced"},c=[...this.currentAllDayEvents,a],d=this.calculateAllDayEventsLayout(c,this.currentWeekDates);this.allDayEventRenderer.renderAllDayEventsForPeriod(d),this.checkAndAnimateAllDayHeight()}async handleDragEnd(e){if(!e.draggedClone||!e.finalPosition.column)return;let t=e.draggedClone,n=t.eventId.replace("clone-",""),r=e.finalPosition.column.date,s=this.dateService.differenceInCalendarDays(t.end,t.start),i=new Date(r);i.setHours(t.start.getHours(),t.start.getMinutes(),0,0);let a=new Date(r);a.setDate(a.getDate()+s),a.setHours(t.end.getHours(),t.end.getMinutes(),0,0),await this.eventManager.updateEvent(n,{start:i,end:a,allDay:!0}),this.fadeOutAndRemove(e.originalElement);let c=this.currentAllDayEvents.map(g=>g.id===n?{...g,start:i,end:a}:g),d=this.calculateAllDayEventsLayout(c,this.currentWeekDates);this.allDayEventRenderer.renderAllDayEventsForPeriod(d),this.checkAndAnimateAllDayHeight()}updateChevronButton(e){let t=this.getHeaderSpacer();if(!t)return;let n=t.querySelector(".allday-chevron");e&&!n?(n=document.createElement("button"),n.className="allday-chevron collapsed",n.innerHTML=` + + + + `,n.onclick=()=>this.toggleExpanded(),t.appendChild(n)):!e&&n?n.remove():n&&(n.classList.toggle("collapsed",!this.isExpanded),n.classList.toggle("expanded",this.isExpanded))}toggleExpanded(){this.isExpanded=!this.isExpanded,this.checkAndAnimateAllDayHeight(),document.querySelectorAll("swp-allday-container swp-allday-event.max-event-overflow-hide, swp-allday-container swp-allday-event.max-event-overflow-show").forEach(t=>{this.isExpanded?(t.classList.remove("max-event-overflow-hide"),t.classList.add("max-event-overflow-show")):(t.classList.remove("max-event-overflow-show"),t.classList.add("max-event-overflow-hide"))})}countEventsInColumn(e){return this.countEventsInColumnFromDOM(e.index)}updateOverflowIndicators(){let e=this.getAllDayContainer();if(!e)return;B.getColumns().forEach(n=>{let s=this.countEventsInColumn(n)-ne.MAX_COLLAPSED_ROWS;if(s>0){let i=e.querySelector(`.max-event-indicator[data-column="${n.index}"]`);if(i)i.innerHTML=`+${s+1} more`;else{let a=document.createElement("swp-allday-event");a.className="max-event-indicator",a.setAttribute("data-column",n.index.toString()),a.style.gridRow=ne.MAX_COLLAPSED_ROWS.toString(),a.style.gridColumn=n.index.toString(),a.innerHTML=`+${s+1} more`,a.onclick=c=>{c.stopPropagation(),this.toggleExpanded()},e.appendChild(a)}}})}clearOverflowIndicators(){let e=this.getAllDayContainer();e&&e.querySelectorAll(".max-event-indicator").forEach(t=>{t.remove()})}};var Oe=class{constructor(e,t){this.config=e,this.positionUtils=t,this.isResizing=!1,this.targetEl=null,this.startY=0,this.startDurationMin=0,this.animationId=null,this.currentHeight=0,this.targetHeight=0,this.pointerCaptured=!1,this.ANIMATION_SPEED=.35,this.Z_INDEX_RESIZING="1000",this.EVENT_REFRESH_THRESHOLD=.5,this.onMouseOver=r=>{let i=r.target.closest("swp-event");if(i&&!this.isResizing&&!i.querySelector(":scope > swp-resize-handle")){let a=this.createResizeHandle();i.appendChild(a)}},this.onPointerDown=r=>{let s=r.target.closest("swp-resize-handle");if(!s)return;let i=s.parentElement;this.startResizing(i,r)},this.onPointerMove=r=>{!this.isResizing||!this.targetEl||this.updateResizeHeight(r.clientY)},this.animate=()=>{if(!this.isResizing||!this.targetEl){this.animationId=null;return}let r=this.targetHeight-this.currentHeight;Math.abs(r)>this.EVENT_REFRESH_THRESHOLD?(this.currentHeight+=r*this.ANIMATION_SPEED,this.targetEl.updateHeight?.(this.currentHeight),this.animationId=requestAnimationFrame(this.animate)):this.finalizeAnimation()},this.onPointerUp=r=>{!this.isResizing||!this.targetEl||(this.cleanupAnimation(),this.snapToGrid(),this.emitResizeEndEvent(),this.cleanupResizing(r))};let n=this.config.gridSettings;this.snapMin=n.snapInterval,this.minDurationMin=this.snapMin}initialize(){this.attachGlobalListeners()}destroy(){this.removeEventListeners()}removeEventListeners(){let e=document.querySelector("swp-calendar-container");e&&e.removeEventListener("mouseover",this.onMouseOver,!0),document.removeEventListener("pointerdown",this.onPointerDown,!0),document.removeEventListener("pointermove",this.onPointerMove,!0),document.removeEventListener("pointerup",this.onPointerUp,!0)}createResizeHandle(){let e=document.createElement("swp-resize-handle");return e.setAttribute("aria-label","Resize event"),e.setAttribute("role","separator"),e}attachGlobalListeners(){let e=document.querySelector("swp-calendar-container");e&&e.addEventListener("mouseover",this.onMouseOver,!0),document.addEventListener("pointerdown",this.onPointerDown,!0),document.addEventListener("pointermove",this.onPointerMove,!0),document.addEventListener("pointerup",this.onPointerUp,!0)}startResizing(e,t){this.targetEl=e,this.isResizing=!0,this.startY=t.clientY;let n=e.offsetHeight;this.startDurationMin=Math.max(this.minDurationMin,Math.round(this.positionUtils.pixelsToMinutes(n))),this.setZIndexForResizing(e),this.capturePointer(t),document.documentElement.classList.add("swp--resizing"),t.preventDefault()}setZIndexForResizing(e){let t=e.closest("swp-event-group")??e;this.prevZ=t.style.zIndex,t.style.zIndex=this.Z_INDEX_RESIZING}capturePointer(e){try{e.target.setPointerCapture?.(e.pointerId),this.pointerCaptured=!0}catch(t){console.warn("Pointer capture failed:",t)}}updateResizeHeight(e){let t=e-this.startY,r=this.positionUtils.minutesToPixels(this.startDurationMin)+t,s=this.positionUtils.minutesToPixels(this.minDurationMin);this.targetHeight=Math.max(s,r),this.animationId==null&&(this.currentHeight=this.targetEl?.offsetHeight,this.animate())}finalizeAnimation(){this.targetEl&&(this.currentHeight=this.targetHeight,this.targetEl.updateHeight?.(this.currentHeight),this.animationId=null)}cleanupAnimation(){this.animationId!=null&&(cancelAnimationFrame(this.animationId),this.animationId=null)}snapToGrid(){if(!this.targetEl)return;let e=this.targetEl.offsetHeight,t=this.positionUtils.minutesToPixels(this.snapMin),n=Math.round(e/t)*t,r=this.positionUtils.minutesToPixels(this.minDurationMin),s=Math.max(r,n)-3;this.targetEl.updateHeight?.(s)}emitResizeEndEvent(){if(!this.targetEl)return;let t={eventId:this.targetEl.dataset.eventId||"",element:this.targetEl,finalHeight:this.targetEl.offsetHeight};b.emit("resize:end",t)}cleanupResizing(e){this.restoreZIndex(),this.releasePointer(e),this.isResizing=!1,this.targetEl=null,document.documentElement.classList.remove("swp--resizing")}restoreZIndex(){if(!this.targetEl||this.prevZ===void 0)return;let e=this.targetEl.closest("swp-event-group")??this.targetEl;e.style.zIndex=this.prevZ,this.prevZ=void 0}releasePointer(e){if(this.pointerCaptured)try{e.target.releasePointerCapture?.(e.pointerId),this.pointerCaptured=!1}catch(t){console.warn("Pointer release failed:",t)}}};var Le=class{constructor(e){this.eventBus=e,this.scrollableContent=null,this.timeGrid=null,this.draggedClone=null,this.scrollRAF=null,this.mouseY=0,this.isDragging=!1,this.isScrolling=!1,this.lastTs=0,this.rect=null,this.initialScrollTop=0,this.scrollListener=null,this.OUTER_ZONE=100,this.INNER_ZONE=50,this.SLOW_SPEED_PXS=140,this.FAST_SPEED_PXS=640,this.init()}init(){setTimeout(()=>{this.scrollableContent=document.querySelector("swp-scrollable-content"),this.timeGrid=document.querySelector("swp-time-grid"),this.scrollableContent&&(this.scrollableContent.style.scrollBehavior="auto",this.scrollListener=this.handleScroll.bind(this),this.scrollableContent.addEventListener("scroll",this.scrollListener,{passive:!0}))},100),document.body.addEventListener("mousemove",e=>{this.isDragging&&(this.mouseY=e.clientY)}),this.subscribeToEvents()}subscribeToEvents(){this.eventBus.on("drag:start",e=>{let t=e.detail;this.draggedClone=t.draggedClone,this.startDrag()}),this.eventBus.on("drag:end",()=>this.stopDrag()),this.eventBus.on("drag:cancelled",()=>this.stopDrag()),this.eventBus.on("drag:mouseenter-header",()=>{console.log("\u{1F504} EdgeScrollManager: Event converting to all-day - stopping scroll"),this.stopDrag()}),this.eventBus.on("drag:mouseenter-column",()=>{this.startDrag()})}startDrag(){console.log("\u{1F3AC} EdgeScrollManager: Starting drag"),this.isDragging=!0,this.isScrolling=!1,this.lastTs=performance.now(),this.scrollableContent&&(this.initialScrollTop=this.scrollableContent.scrollTop),this.scrollRAF===null&&(this.scrollRAF=requestAnimationFrame(e=>this.scrollTick(e)))}stopDrag(){this.isDragging=!1,this.isScrolling&&(this.isScrolling=!1,console.log("\u{1F6D1} EdgeScrollManager: Edge-scroll stopped (drag ended)"),this.eventBus.emit("edgescroll:stopped",{})),this.scrollRAF!==null&&(cancelAnimationFrame(this.scrollRAF),this.scrollRAF=null),this.rect=null,this.lastTs=0,this.initialScrollTop=0}handleScroll(){if(!this.isDragging||!this.scrollableContent)return;let e=this.scrollableContent.scrollTop,t=Math.abs(e-this.initialScrollTop);t>1&&!this.isScrolling&&(this.isScrolling=!0,console.log("\u{1F4BE} EdgeScrollManager: Edge-scroll started (actual scroll detected)",{initialScrollTop:this.initialScrollTop,currentScrollTop:e,scrollDelta:t}),this.eventBus.emit("edgescroll:started",{}))}scrollTick(e){let t=this.lastTs?(e-this.lastTs)/1e3:0;if(this.lastTs=e,!this.scrollableContent){this.stopDrag();return}this.rect||(this.rect=this.scrollableContent.getBoundingClientRect());let n=0;if(this.isDragging){let r=this.mouseY-this.rect.top,s=this.rect.bottom-this.mouseY;r=g&&n>0;w||E?(this.isScrolling&&(this.isScrolling=!1,this.initialScrollTop=this.scrollableContent.scrollTop,console.log("\u{1F6D1} EdgeScrollManager: Edge-scroll stopped (reached boundary)"),this.eventBus.emit("edgescroll:stopped",{})),this.isDragging&&(this.scrollRAF=requestAnimationFrame(m=>this.scrollTick(m)))):(this.scrollableContent.scrollTop+=n*t,this.rect=null,this.scrollRAF=requestAnimationFrame(m=>this.scrollTick(m)))}else this.isScrolling&&(this.isScrolling=!1,this.initialScrollTop=this.scrollableContent.scrollTop,console.log("\u{1F6D1} EdgeScrollManager: Edge-scroll stopped (mouse left edge)"),this.eventBus.emit("edgescroll:stopped",{})),this.isDragging?this.scrollRAF=requestAnimationFrame(r=>this.scrollTick(r)):this.stopDrag()}};var $e=class{constructor(e,t){this.headerRenderer=e,this.config=t,this.handleDragMouseEnterHeader=this.handleDragMouseEnterHeader.bind(this),this.handleDragMouseLeaveHeader=this.handleDragMouseLeaveHeader.bind(this),this.setupNavigationListener()}setupHeaderDragListeners(){console.log("\u{1F3AF} HeaderManager: Setting up drag event listeners"),b.on("drag:mouseenter-header",this.handleDragMouseEnterHeader),b.on("drag:mouseleave-header",this.handleDragMouseLeaveHeader),console.log("\u2705 HeaderManager: Drag event listeners attached")}handleDragMouseEnterHeader(e){let{targetColumn:t,mousePosition:n,originalElement:r,draggedClone:s}=e.detail;console.log("\u{1F3AF} HeaderManager: Received drag:mouseenter-header",{targetDate:t,originalElement:!!r,cloneElement:!!s})}handleDragMouseLeaveHeader(e){let{targetDate:t,mousePosition:n,originalElement:r,draggedClone:s}=e.detail;console.log("\u{1F6AA} HeaderManager: Received drag:mouseleave-header",{targetDate:t,originalElement:!!r,cloneElement:!!s})}setupNavigationListener(){b.on(v.NAVIGATION_COMPLETED,e=>{let{currentDate:t}=e.detail;this.updateHeader(t)}),b.on(v.DATE_CHANGED,e=>{let{currentDate:t}=e.detail;this.updateHeader(t)}),b.on("workweek:header-update",e=>{let{currentDate:t}=e.detail;this.updateHeader(t)})}updateHeader(e){console.log("\u{1F3AF} HeaderManager.updateHeader called",{currentDate:e,rendererType:this.headerRenderer.constructor.name});let t=document.querySelector("swp-calendar-header");if(!t){console.warn("\u274C HeaderManager: No calendar header found!");return}t.innerHTML="";let n={currentWeek:e,config:this.config};this.headerRenderer.render(t,n),this.setupHeaderDragListeners();let r={headerElements:B.getHeaderColumns()};b.emit("header:ready",r)}};var He=class{constructor(e,t){this.buttonListeners=new Map,this.eventBus=e,this.config=t,this.setupButtonListeners()}setupButtonListeners(){document.querySelectorAll("swp-preset-button[data-workweek]").forEach(t=>{let n=r=>{r.preventDefault();let s=t.getAttribute("data-workweek");s&&this.changePreset(s)};t.addEventListener("click",n),this.buttonListeners.set(t,n)}),this.updateButtonStates()}changePreset(e){if(!me[e]){console.warn(`Invalid preset ID "${e}"`);return}if(e===this.config.currentWorkWeek)return;let t=this.config.currentWorkWeek;this.config.currentWorkWeek=e;let n=me[e];this.updateButtonStates(),this.eventBus.emit(v.WORKWEEK_CHANGED,{workWeekId:e,previousWorkWeekId:t,settings:n})}updateButtonStates(){document.querySelectorAll("swp-preset-button[data-workweek]").forEach(t=>{t.getAttribute("data-workweek")===this.config.currentWorkWeek?t.setAttribute("data-active","true"):t.removeAttribute("data-active")})}};var Be=class{constructor(e,t){this.indexedDB=e,this.queue=t}async loadEvents(){return this.indexedDB.isInitialized()||(await this.indexedDB.initialize(),await this.indexedDB.seedIfEmpty()),await this.indexedDB.getAllEvents()}async createEvent(e,t="local"){let n=this.generateEventId(),s={...e,id:n,syncStatus:t==="local"?"pending":"synced"};return await this.indexedDB.saveEvent(s),t==="local"&&await this.queue.enqueue({type:"create",eventId:n,data:s,timestamp:Date.now(),retryCount:0}),s}async updateEvent(e,t,n="local"){let r=await this.indexedDB.getEvent(e);if(!r)throw new Error(`Event with ID ${e} not found`);let i={...r,...t,id:e,syncStatus:n==="local"?"pending":"synced"};return await this.indexedDB.saveEvent(i),n==="local"&&await this.queue.enqueue({type:"update",eventId:e,data:t,timestamp:Date.now(),retryCount:0}),i}async deleteEvent(e,t="local"){if(!await this.indexedDB.getEvent(e))throw new Error(`Event with ID ${e} not found`);t==="local"&&await this.queue.enqueue({type:"delete",eventId:e,data:{},timestamp:Date.now(),retryCount:0}),await this.indexedDB.deleteEvent(e)}generateEventId(){let e=Date.now(),t=Math.random().toString(36).substring(2,9);return`${e}-${t}`}};var We=class{constructor(e){this.apiEndpoint=e.apiEndpoint}async sendCreate(e){throw new Error("ApiEventRepository.sendCreate not implemented yet")}async sendUpdate(e,t){throw new Error("ApiEventRepository.sendUpdate not implemented yet")}async sendDelete(e){throw new Error("ApiEventRepository.sendDelete not implemented yet")}async fetchAll(){throw new Error("ApiEventRepository.fetchAll not implemented yet")}async initializeSignalR(){throw new Error("SignalR not implemented yet")}};var J=class o{constructor(){this.db=null,this.initialized=!1}async initialize(){return new Promise((e,t)=>{let n=indexedDB.open(o.DB_NAME,o.DB_VERSION);n.onerror=()=>{t(new Error(`Failed to open IndexedDB: ${n.error}`))},n.onsuccess=()=>{this.db=n.result,this.initialized=!0,e()},n.onupgradeneeded=r=>{let s=r.target.result;if(!s.objectStoreNames.contains(o.EVENTS_STORE)){let i=s.createObjectStore(o.EVENTS_STORE,{keyPath:"id"});i.createIndex("start","start",{unique:!1}),i.createIndex("end","end",{unique:!1}),i.createIndex("syncStatus","syncStatus",{unique:!1})}s.objectStoreNames.contains(o.QUEUE_STORE)||s.createObjectStore(o.QUEUE_STORE,{keyPath:"id"}).createIndex("timestamp","timestamp",{unique:!1}),s.objectStoreNames.contains(o.SYNC_STATE_STORE)||s.createObjectStore(o.SYNC_STATE_STORE,{keyPath:"key"})}})}isInitialized(){return this.initialized}ensureDB(){if(!this.db)throw new Error("IndexedDB not initialized. Call initialize() first.");return this.db}async getEvent(e){let t=this.ensureDB();return new Promise((n,r)=>{let a=t.transaction([o.EVENTS_STORE],"readonly").objectStore(o.EVENTS_STORE).get(e);a.onsuccess=()=>{let c=a.result;n(c?this.deserializeEvent(c):null)},a.onerror=()=>{r(new Error(`Failed to get event ${e}: ${a.error}`))}})}async getAllEvents(){let e=this.ensureDB();return new Promise((t,n)=>{let i=e.transaction([o.EVENTS_STORE],"readonly").objectStore(o.EVENTS_STORE).getAll();i.onsuccess=()=>{let a=i.result;t(a.map(c=>this.deserializeEvent(c)))},i.onerror=()=>{n(new Error(`Failed to get all events: ${i.error}`))}})}async saveEvent(e){let t=this.ensureDB(),n=this.serializeEvent(e);return new Promise((r,s)=>{let c=t.transaction([o.EVENTS_STORE],"readwrite").objectStore(o.EVENTS_STORE).put(n);c.onsuccess=()=>{r()},c.onerror=()=>{s(new Error(`Failed to save event ${e.id}: ${c.error}`))}})}async deleteEvent(e){let t=this.ensureDB();return new Promise((n,r)=>{let a=t.transaction([o.EVENTS_STORE],"readwrite").objectStore(o.EVENTS_STORE).delete(e);a.onsuccess=()=>{n()},a.onerror=()=>{r(new Error(`Failed to delete event ${e}: ${a.error}`))}})}async addToQueue(e){let t=this.ensureDB(),n={...e,id:`${e.type}-${e.eventId}-${Date.now()}`};return new Promise((r,s)=>{let c=t.transaction([o.QUEUE_STORE],"readwrite").objectStore(o.QUEUE_STORE).put(n);c.onsuccess=()=>{r()},c.onerror=()=>{s(new Error(`Failed to add to queue: ${c.error}`))}})}async getQueue(){let e=this.ensureDB();return new Promise((t,n)=>{let a=e.transaction([o.QUEUE_STORE],"readonly").objectStore(o.QUEUE_STORE).index("timestamp").getAll();a.onsuccess=()=>{t(a.result)},a.onerror=()=>{n(new Error(`Failed to get queue: ${a.error}`))}})}async removeFromQueue(e){let t=this.ensureDB();return new Promise((n,r)=>{let a=t.transaction([o.QUEUE_STORE],"readwrite").objectStore(o.QUEUE_STORE).delete(e);a.onsuccess=()=>{n()},a.onerror=()=>{r(new Error(`Failed to remove from queue: ${a.error}`))}})}async clearQueue(){let e=this.ensureDB();return new Promise((t,n)=>{let i=e.transaction([o.QUEUE_STORE],"readwrite").objectStore(o.QUEUE_STORE).clear();i.onsuccess=()=>{t()},i.onerror=()=>{n(new Error(`Failed to clear queue: ${i.error}`))}})}async setSyncState(e,t){let n=this.ensureDB();return new Promise((r,s)=>{let c=n.transaction([o.SYNC_STATE_STORE],"readwrite").objectStore(o.SYNC_STATE_STORE).put({key:e,value:t});c.onsuccess=()=>{r()},c.onerror=()=>{s(new Error(`Failed to set sync state ${e}: ${c.error}`))}})}async getSyncState(e){let t=this.ensureDB();return new Promise((n,r)=>{let a=t.transaction([o.SYNC_STATE_STORE],"readonly").objectStore(o.SYNC_STATE_STORE).get(e);a.onsuccess=()=>{let c=a.result;n(c?c.value:null)},a.onerror=()=>{r(new Error(`Failed to get sync state ${e}: ${a.error}`))}})}serializeEvent(e){return{...e,start:e.start instanceof Date?e.start.toISOString():e.start,end:e.end instanceof Date?e.end.toISOString():e.end}}deserializeEvent(e){return{...e,start:typeof e.start=="string"?new Date(e.start):e.start,end:typeof e.end=="string"?new Date(e.end):e.end}}close(){this.db&&(this.db.close(),this.db=null)}static async deleteDatabase(){return new Promise((e,t)=>{let n=indexedDB.deleteDatabase(o.DB_NAME);n.onsuccess=()=>{e()},n.onerror=()=>{t(new Error(`Failed to delete database: ${n.error}`))}})}async seedIfEmpty(e="data/mock-events.json"){try{let t=await this.getAllEvents();if(t.length>0){console.log(`IndexedDB already has ${t.length} events - skipping seed`);return}if(console.log("IndexedDB is empty - seeding with mock data"),!navigator.onLine){console.warn("Offline and IndexedDB empty - starting with no events");return}let n=await fetch(e);if(!n.ok)throw new Error(`Failed to fetch mock events: ${n.statusText}`);let r=await n.json();for(let s of r){let i={...s,start:new Date(s.start),end:new Date(s.end),allDay:s.allDay||!1,syncStatus:"synced"};await this.saveEvent(i)}console.log(`Seeded IndexedDB with ${r.length} mock events`)}catch(t){console.error("Failed to seed IndexedDB:",t)}}};J.DB_NAME="CalendarDB";J.DB_VERSION=1;J.EVENTS_STORE="events";J.QUEUE_STORE="operationQueue";J.SYNC_STATE_STORE="syncState";var Pe=class{constructor(e){this.indexedDB=e}async enqueue(e){await this.indexedDB.addToQueue(e)}async peek(){let e=await this.indexedDB.getQueue();return e.length>0?e[0]:null}async getAll(){return await this.indexedDB.getQueue()}async remove(e){await this.indexedDB.removeFromQueue(e)}async dequeue(){let e=await this.peek();return e&&await this.remove(e.id),e}async clear(){await this.indexedDB.clearQueue()}async size(){return(await this.getAll()).length}async isEmpty(){return await this.size()===0}async getOperationsForEvent(e){return(await this.getAll()).filter(n=>n.eventId===e)}async removeOperationsForEvent(e){let t=await this.getOperationsForEvent(e);for(let n of t)await this.remove(n.id)}async incrementRetryCount(e){let n=(await this.getAll()).find(r=>r.id===e);n&&(n.retryCount++,await this.remove(e),await this.enqueue(n))}};var Ne=class{constructor(e,t,n,r){this.isOnline=navigator.onLine,this.isSyncing=!1,this.syncInterval=5e3,this.maxRetries=5,this.intervalId=null,this.eventBus=e,this.queue=t,this.indexedDB=n,this.apiRepository=r,this.setupNetworkListeners(),this.startSync(),console.log("SyncManager initialized and started")}setupNetworkListeners(){window.addEventListener("online",()=>{this.isOnline=!0,this.eventBus.emit(v.OFFLINE_MODE_CHANGED,{isOnline:!0}),console.log("SyncManager: Network online - starting sync"),this.startSync()}),window.addEventListener("offline",()=>{this.isOnline=!1,this.eventBus.emit(v.OFFLINE_MODE_CHANGED,{isOnline:!1}),console.log("SyncManager: Network offline - pausing sync"),this.stopSync()})}startSync(){this.intervalId||(console.log("SyncManager: Starting background sync"),this.processQueue(),this.intervalId=window.setInterval(()=>{this.processQueue()},this.syncInterval))}stopSync(){this.intervalId&&(window.clearInterval(this.intervalId),this.intervalId=null,console.log("SyncManager: Stopped background sync"))}async processQueue(){if(this.isOnline&&!this.isSyncing&&!await this.queue.isEmpty()){this.isSyncing=!0;try{let e=await this.queue.getAll();this.eventBus.emit(v.SYNC_STARTED,{operationCount:e.length});for(let t of e)await this.processOperation(t);this.eventBus.emit(v.SYNC_COMPLETED,{operationCount:e.length})}catch(e){console.error("SyncManager: Queue processing error:",e),this.eventBus.emit(v.SYNC_FAILED,{error:e instanceof Error?e.message:"Unknown error"})}finally{this.isSyncing=!1}}}async processOperation(e){if(e.retryCount>=this.maxRetries){console.error(`SyncManager: Max retries exceeded for operation ${e.id}`,e),await this.queue.remove(e.id),await this.markEventAsError(e.eventId);return}try{switch(e.type){case"create":await this.apiRepository.sendCreate(e.data);break;case"update":await this.apiRepository.sendUpdate(e.eventId,e.data);break;case"delete":await this.apiRepository.sendDelete(e.eventId);break;default:console.error(`SyncManager: Unknown operation type ${e.type}`),await this.queue.remove(e.id);return}await this.queue.remove(e.id),await this.markEventAsSynced(e.eventId),console.log(`SyncManager: Successfully synced operation ${e.id}`)}catch(t){console.error(`SyncManager: Failed to sync operation ${e.id}:`,t),await this.queue.incrementRetryCount(e.id);let n=this.calculateBackoff(e.retryCount+1);this.eventBus.emit(v.SYNC_RETRY,{operationId:e.id,retryCount:e.retryCount+1,nextRetryIn:n})}}async markEventAsSynced(e){try{let t=await this.indexedDB.getEvent(e);t&&(t.syncStatus="synced",await this.indexedDB.saveEvent(t))}catch(t){console.error(`SyncManager: Failed to mark event ${e} as synced:`,t)}}async markEventAsError(e){try{let t=await this.indexedDB.getEvent(e);t&&(t.syncStatus="error",await this.indexedDB.saveEvent(t))}catch(t){console.error(`SyncManager: Failed to mark event ${e} as error:`,t)}}calculateBackoff(e){let n=Math.pow(2,e)*1e3;return Math.min(n,6e4)}async triggerManualSync(){console.log("SyncManager: Manual sync triggered"),await this.processQueue()}getSyncStatus(){return{isOnline:this.isOnline,isSyncing:this.isSyncing,isRunning:this.intervalId!==null}}destroy(){this.stopSync()}};var Fe=class{render(e,t){let{currentWeek:n,config:r}=t,s=document.createElement("swp-allday-container");e.appendChild(s);let i=r.timeFormatConfig.timezone,a=r.timeFormatConfig.locale;this.dateService=new U(r);let c=r.getWorkWeekSettings(),d=this.dateService.getWorkWeekDates(n,c.workDays),g=r.dateViewSettings.weekDays;d.slice(0,g).forEach((E,m)=>{let u=document.createElement("swp-day-header");this.dateService.isSameDay(E,new Date)&&(u.dataset.today="true");let D=this.dateService.getDayName(E,"long",a).toUpperCase();u.innerHTML=` + ${D} + ${E.getDate()} + `,u.dataset.date=this.dateService.formatISODate(E),e.appendChild(u)})}};var _e=class{constructor(e,t){this.dateService=e,this.workHoursManager=t}render(e,t){let{currentWeek:n,config:r}=t,s=r.getWorkWeekSettings(),i=this.dateService.getWorkWeekDates(n,s.workDays),a=r.dateViewSettings;i.slice(0,a.weekDays).forEach(d=>{let g=document.createElement("swp-day-column");g.dataset.date=this.dateService.formatISODate(d),this.applyWorkHoursToColumn(g,d);let w=document.createElement("swp-events-layer");g.appendChild(w),e.appendChild(g)})}applyWorkHoursToColumn(e,t){let n=this.workHoursManager.getWorkHoursForDate(t);if(n==="off")e.dataset.workHours="off";else{let r=this.workHoursManager.calculateNonWorkHoursStyle(n);r&&(e.style.setProperty("--before-work-height",`${r.beforeWorkHeight}px`),e.style.setProperty("--after-work-top",`${r.afterWorkTop}px`))}}};var Ye=class{constructor(e,t,n,r,s){this.draggedClone=null,this.originalEvent=null,this.dateService=e,this.stackManager=t,this.layoutCoordinator=n,this.config=r,this.positionUtils=s}applyDragStyling(e){e.classList.add("dragging"),e.style.removeProperty("margin-left")}handleDragStart(e){if(this.originalEvent=e.originalElement,this.draggedClone=e.draggedClone,this.draggedClone&&e.columnBounds){this.applyDragStyling(this.draggedClone);let t=e.columnBounds.element.querySelector("swp-events-layer");if(t){t.appendChild(this.draggedClone);let n=this.originalEvent.getBoundingClientRect(),r=e.columnBounds.boundingClientRect,s=n.top-r.top;this.draggedClone.style.top=`${s}px`}}this.originalEvent.style.opacity="0.3",this.originalEvent.style.userSelect="none"}handleDragMove(e){let t=e.draggedClone,n=this.dateService.parseISO(e.columnBounds.date);t.updatePosition(n,e.snappedY)}handleColumnChange(e){let t=e.newColumn.element.querySelector("swp-events-layer");if(t&&e.draggedClone.parentElement!==t){t.appendChild(e.draggedClone);let n=parseFloat(e.draggedClone.style.top)||0,r=e.draggedClone,s=this.dateService.parseISO(e.newColumn.date);r.updatePosition(s,n)}}handleConvertAllDayToTimed(e){console.log("\u{1F3AF} DateEventRenderer: Converting all-day to timed event",{eventId:e.calendarEvent.id,targetColumn:e.targetColumn.date,snappedY:e.snappedY});let t=Q.fromCalendarEvent(e.calendarEvent),n=this.calculateEventPosition(e.calendarEvent);t.style.height=`${n.height-3}px`,t.style.left="2px",t.style.right="2px",t.style.width="auto",t.style.pointerEvents="none",this.applyDragStyling(t);let r=e.targetColumn.element.querySelector("swp-events-layer");e.draggedClone.remove(),e.replaceClone(t),r.appendChild(t)}handleDragEnd(e,t,n,r){if(!t||!e){console.warn("Missing draggedClone or originalElement");return}e.tagName==="SWP-EVENT"&&this.fadeOutAndRemove(e);let s=t.dataset.eventId;s&&s.startsWith("clone-")&&(t.dataset.eventId=s.replace("clone-","")),t.classList.remove("dragging"),t.style.pointerEvents="",this.draggedClone=null,this.originalEvent=null;let i=document.querySelector(`swp-event[data-event-id="clone-${s}"]`);i&&i.remove()}handleNavigationCompleted(){}fadeOutAndRemove(e){e.style.transition="opacity 0.3s ease-out",e.style.opacity="0",setTimeout(()=>{e.remove()},300)}renderEvents(e,t){let n=e.filter(s=>!s.allDay);this.getColumns(t).forEach(s=>{let i=this.getEventsForColumn(s,n),a=s.querySelector("swp-events-layer");a&&this.renderColumnEvents(i,a)})}renderSingleColumnEvents(e,t){let n=this.getEventsForColumn(e.element,t),r=e.element.querySelector("swp-events-layer");r&&this.renderColumnEvents(n,r)}renderColumnEvents(e,t){if(e.length===0)return;let n=this.layoutCoordinator.calculateColumnLayout(e);n.gridGroups.forEach(r=>{this.renderGridGroup(r,t)}),n.stackedEvents.forEach(r=>{let s=this.renderEvent(r.event);this.stackManager.applyStackLinkToElement(s,r.stackLink),this.stackManager.applyVisualStyling(s,r.stackLink.stackLevel),t.appendChild(s)})}renderGridGroup(e,t){let n=document.createElement("swp-event-group"),r=e.columns.length;n.classList.add(`cols-${r}`),n.classList.add(`stack-level-${e.stackLevel}`),n.style.top=`${e.position.top}px`;let s={stackLevel:e.stackLevel};this.stackManager.applyStackLinkToElement(n,s),this.stackManager.applyVisualStyling(n,e.stackLevel);let i=e.events[0];e.columns.forEach(a=>{let c=this.renderGridColumn(a,i.start);n.appendChild(c)}),t.appendChild(n)}renderGridColumn(e,t){let n=document.createElement("div");return n.style.position="relative",e.forEach(r=>{let s=this.renderEventInGrid(r,t);n.appendChild(s)}),n}renderEventInGrid(e,t){let n=Q.fromCalendarEvent(e),r=this.calculateEventPosition(e),i=(e.start.getTime()-t.getTime())/(1e3*60),a=this.config.gridSettings,c=i>0?i/60*a.hourHeight:0;return n.style.position="absolute",n.style.top=`${c}px`,n.style.height=`${r.height-3}px`,n.style.left="0",n.style.right="0",n}renderEvent(e){let t=Q.fromCalendarEvent(e),n=this.calculateEventPosition(e);return t.style.position="absolute",t.style.top=`${n.top+1}px`,t.style.height=`${n.height-3}px`,t.style.left="2px",t.style.right="2px",t}calculateEventPosition(e){return this.positionUtils.calculateEventPosition(e.start,e.end)}clearEvents(e){let t="swp-event",n="swp-event-group",r=e?e.querySelectorAll(t):document.querySelectorAll(t),s=e?e.querySelectorAll(n):document.querySelectorAll(n);r.forEach(i=>i.remove()),s.forEach(i=>i.remove())}getColumns(e){let t=e.querySelectorAll("swp-day-column");return Array.from(t)}getEventsForColumn(e,t){let n=e.dataset.date;if(!n)return[];let r=this.dateService.parseISO(`${n}T00:00:00`),s=this.dateService.parseISO(`${n}T23:59:59.999`);return t.filter(a=>a.startr)}};var Ve=class{constructor(){this.container=null,this.originalEvent=null,this.draggedClone=null,this.getContainer()}getContainer(){let e=document.querySelector("swp-calendar-header");return e&&(this.container=e.querySelector("swp-allday-container"),this.container||(this.container=document.createElement("swp-allday-container"),e.appendChild(this.container))),this.container}getAllDayContainer(){return document.querySelector("swp-calendar-header swp-allday-container")}handleDragStart(e){if(this.originalEvent=e.originalElement,this.draggedClone=e.draggedClone,this.draggedClone){let t=this.getAllDayContainer();if(!t)return;this.draggedClone.style.gridColumn=this.originalEvent.style.gridColumn,this.draggedClone.style.gridRow=this.originalEvent.style.gridRow,console.log("handleDragStart:this.draggedClone",this.draggedClone),t.appendChild(this.draggedClone),this.draggedClone.classList.add("dragging"),this.draggedClone.style.zIndex="1000",this.draggedClone.style.cursor="grabbing",this.originalEvent.style.opacity="0.3",this.originalEvent.style.userSelect="none"}}renderAllDayEventWithLayout(e,t){let n=this.getContainer();if(!n)return null;let r=oe.fromCalendarEvent(e);r.applyGridPositioning(t.row,t.startColumn,t.endColumn),r.classList.add("highlight"),n.appendChild(r)}removeAllDayEvent(e){let t=this.getContainer();if(!t)return;let n=t.querySelector(`swp-allday-event[data-event-id="${e}"]`);n&&n.remove()}clearCache(){this.container=null}renderAllDayEventsForPeriod(e){this.clearAllDayEvents(),e.forEach(t=>{this.renderAllDayEventWithLayout(t.calenderEvent,t)})}clearAllDayEvents(){let e=document.querySelector("swp-allday-container");e&&e.querySelectorAll("swp-allday-event:not(.max-event-indicator)").forEach(t=>t.remove())}handleViewChanged(e){this.clearAllDayEvents()}};var ze=class{constructor(e,t,n){this.cachedGridContainer=null,this.cachedTimeAxis=null,this.dateService=t,this.columnRenderer=e,this.config=n}renderGrid(e,t,n="week"){!e||!t||(this.cachedGridContainer=e,e.children.length===0?this.createCompleteGridStructure(e,t,n):this.updateGridContent(e,t,n))}createCompleteGridStructure(e,t,n){let r=document.createDocumentFragment(),s=document.createElement("swp-header-spacer");r.appendChild(s);let i=this.createOptimizedTimeAxis();this.cachedTimeAxis=i,r.appendChild(i);let a=this.createOptimizedGridContainer(t,n);this.cachedGridContainer=a,r.appendChild(a),e.appendChild(r)}createOptimizedTimeAxis(){let e=document.createElement("swp-time-axis"),t=document.createElement("swp-time-axis-content"),n=this.config.gridSettings,r=n.dayStartHour,s=n.dayEndHour,i=document.createDocumentFragment();for(let a=r;a{let t=e,{weekNumber:n,dateRange:r}=t.detail;this.updateWeekInfoInDOM(n,r)})}updateWeekInfoInDOM(e,t){let n=document.querySelector("swp-week-number"),r=document.querySelector("swp-date-range");n&&(n.textContent=`Week ${e}`),r&&(r.textContent=t)}applyFilterToPreRenderedGrids(e){document.querySelectorAll("swp-grid-container").forEach(n=>{n.querySelectorAll("swp-events-layer").forEach(s=>{e.active?(s.setAttribute("data-filter-active","true"),s.querySelectorAll("swp-event").forEach(a=>{let c=a.getAttribute("data-event-id");c&&e.matchingIds.includes(c)?a.setAttribute("data-matches","true"):a.removeAttribute("data-matches")})):(s.removeAttribute("data-filter-active"),s.querySelectorAll("swp-event").forEach(a=>{a.removeAttribute("data-matches")}))})})}};var Ge=class{constructor(e,t){this.dateService=e,this.config=t}minutesToPixels(e){let n=this.config.gridSettings.hourHeight;return e/60*n}pixelsToMinutes(e){let n=this.config.gridSettings.hourHeight;return e/n*60}timeToPixels(e){let t=this.dateService.timeToMinutes(e),r=this.config.gridSettings.dayStartHour*60,s=t-r;return this.minutesToPixels(s)}dateToPixels(e){let t=this.dateService.getMinutesSinceMidnight(e),r=this.config.gridSettings.dayStartHour*60,s=t-r;return this.minutesToPixels(s)}pixelsToTime(e){let t=this.pixelsToMinutes(e),s=this.config.gridSettings.dayStartHour*60+t;return this.dateService.minutesToTime(s)}calculateEventPosition(e,t){let n,r;typeof e=="string"?n=this.timeToPixels(e):n=this.dateToPixels(e),typeof t=="string"?r=this.timeToPixels(t):r=this.dateToPixels(t);let s=Math.max(r-n,this.getMinimumEventHeight()),i=this.pixelsToMinutes(s);return{top:n,height:s,duration:i}}snapToGrid(e){let n=this.config.gridSettings.snapInterval,r=this.minutesToPixels(n);return Math.round(e/r)*r}snapTimeToInterval(e){let t=this.dateService.timeToMinutes(e),r=this.config.gridSettings.snapInterval,s=Math.round(t/r)*r;return this.dateService.minutesToTime(s)}calculateColumnPosition(e,t,n){let r=n/t,s=e*r,i=2,a=r-i;return{left:s+i/2,width:Math.max(a,50)}}eventsOverlap(e,t,n,r){let s=this.calculateEventPosition(e,t),i=this.calculateEventPosition(n,r),a=s.top+s.height,c=i.top+i.height;return!(a<=i.top||c<=s.top)}getPositionFromCoordinate(e,t){let n=e-t.boundingClientRect.top;return this.snapToGrid(n)}isWithinWorkHours(e){let[t]=e.split(":").map(Number),n=this.config.gridSettings;return t>=n.workStartHour&&t=n.dayStartHour&&t{let r=this.dateService.formatISODate(n),s=this.getWorkHoursForDate(n);t.set(r,s)}),t}calculateNonWorkHoursStyle(e){if(e==="off")return null;let t=this.config.gridSettings,n=t.dayStartHour,r=t.hourHeight,s=(e.start-n)*r,i=(e.end-n)*r;return{beforeWorkHeight:Math.max(0,s),afterWorkTop:Math.max(0,i)}}calculateWorkHoursStyle(e){if(e==="off")return null;let t=`${e.start.toString().padStart(2,"0")}:00`,n=`${e.end.toString().padStart(2,"0")}:00`,r=this.positionUtils.calculateEventPosition(t,n);return{top:r.top,height:r.height}}async loadWorkSchedule(e){this.workSchedule=e}getWorkSchedule(){return this.workSchedule}getDayName(e){return["sunday","monday","tuesday","wednesday","thursday","friday","saturday"][e.getDay()]}};var pe=class o{constructor(e){this.config=e}groupEventsByStartTime(e){if(e.length===0)return[];let n=this.config.gridSettings.gridStartThresholdMinutes,r=[...e].sort((i,a)=>i.start.getTime()-a.start.getTime()),s=[];for(let i of r){let a=s.find(c=>c.events.some(d=>{if(Math.abs(i.start.getTime()-d.start.getTime())/6e4<=n)return!0;let w=(d.end.getTime()-i.start.getTime())/(1e3*60);if(w>0&&w<=n)return!0;let E=(i.end.getTime()-d.start.getTime())/(1e3*60);return E>0&&E<=n}));a?a.events.push(i):s.push({events:[i],containerType:"NONE",startTime:i.start})}return s}decideContainerType(e){return e.events.length===1?"NONE":"GRID"}doEventsOverlap(e,t){return e.startt.start}createOptimizedStackLinks(e){let t=new Map;if(e.length===0)return t;let n=[...e].sort((r,s)=>r.start.getTime()-s.start.getTime());for(let r of n){let s=n.filter(a=>a!==r&&this.doEventsOverlap(r,a)),i=0;for(let a of s){let c=t.get(a.id);c&&(i=Math.max(i,c.stackLevel+1))}t.set(r.id,{stackLevel:i})}for(let r of n){let s=t.get(r.id),i=n.filter(d=>d!==r&&this.doEventsOverlap(r,d)),a=i.filter(d=>{let g=t.get(d.id);return g&&g.stackLevel===s.stackLevel-1});a.length>0&&(s.prev=a[0].id);let c=i.filter(d=>{let g=t.get(d.id);return g&&g.stackLevel===s.stackLevel+1});c.length>0&&(s.next=c[0].id)}return t}calculateMarginLeft(e){return e*o.STACK_OFFSET_PX}calculateZIndex(e){return 100+e}serializeStackLink(e){return JSON.stringify(e)}deserializeStackLink(e){try{return JSON.parse(e)}catch{return null}}applyStackLinkToElement(e,t){e.dataset.stackLink=this.serializeStackLink(t)}getStackLinkFromElement(e){let t=e.dataset.stackLink;return t?this.deserializeStackLink(t):null}applyVisualStyling(e,t){e.style.marginLeft=`${this.calculateMarginLeft(t)}px`,e.style.zIndex=`${this.calculateZIndex(t)}`}clearStackLinkFromElement(e){delete e.dataset.stackLink}clearVisualStyling(e){e.style.marginLeft="",e.style.zIndex=""}};pe.STACK_OFFSET_PX=15;var je=class{constructor(e,t,n){this.stackManager=e,this.config=t,this.positionUtils=n}calculateColumnLayout(e){if(e.length===0)return{gridGroups:[],stackedEvents:[]};let t=[],n=[],r=[],s=[...e].sort((i,a)=>i.start.getTime()-a.start.getTime());for(;s.length>0;){let i=s[0],c=this.config.gridSettings.gridStartThresholdMinutes,d=this.expandGridCandidates(i,s,c),g={events:d,containerType:"NONE",startTime:i.start};if(this.stackManager.decideContainerType(g)==="GRID"&&d.length>1){let E=this.calculateGridGroupStackLevelFromRendered(d,r),m=[...d].sort((C,k)=>C.start.getTime()-k.start.getTime())[0],u=this.positionUtils.calculateEventPosition(m.start,m.end),D=this.allocateColumns(d);t.push({events:d,stackLevel:E,position:{top:u.top+1},columns:D}),d.forEach(C=>r.push({event:C,level:E})),s=s.filter(C=>!d.includes(C))}else{let E=this.calculateStackLevelFromRendered(i,r),m=this.positionUtils.calculateEventPosition(i.start,i.end);n.push({event:i,stackLink:{stackLevel:E},position:{top:m.top+1,height:m.height-3}}),r.push({event:i,level:E}),s=s.slice(1)}}return{gridGroups:t,stackedEvents:n}}calculateGridGroupStackLevelFromRendered(e,t){let n=-1;for(let r of e)for(let s of t)this.stackManager.doEventsOverlap(r,s.event)&&(n=Math.max(n,s.level));return n+1}calculateStackLevelFromRendered(e,t){let n=-1;for(let r of t)this.stackManager.doEventsOverlap(e,r.event)&&(n=Math.max(n,r.level));return n+1}detectConflict(e,t,n){if(Math.abs(e.start.getTime()-t.start.getTime())/6e4<=n&&this.stackManager.doEventsOverlap(e,t))return!0;let s=(t.end.getTime()-e.start.getTime())/(1e3*60);if(s>0&&s<=n)return!0;let i=(e.end.getTime()-t.start.getTime())/(1e3*60);return i>0&&i<=n}expandGridCandidates(e,t,n){let r=[e],s=!0;for(;s;){s=!1;for(let i=1;ithis.stackManager.doEventsOverlap(n,a))){s.push(n),r=!0;break}r||t.push([n])}return t}};async function zt(o,e){try{let t=e.parseEventIdFromURL();t&&(console.log(`Deep linking to event ID: ${t}`),setTimeout(async()=>{await o.navigateToEvent(t)||console.warn(`Deep linking failed: Event with ID ${t} not found`)},500))}catch(t){console.warn("Deep linking failed:",t)}}async function Ot(){try{let o=await fe.load(),t=new ce().builder();b.setDebug(!0),t.registerInstance(b).as("IEventBus"),t.registerInstance(o).as("Configuration"),t.registerType(J).as("IndexedDBService"),t.registerType(Pe).as("OperationQueue").autoWire({mapResolvers:[l=>l.resolveType("IndexedDBService")]}),t.registerType(We).as("ApiEventRepository").autoWire({mapResolvers:[l=>l.resolveType("Configuration")]}),t.registerType(Be).as("IEventRepository").autoWire({mapResolvers:[l=>l.resolveType("IndexedDBService"),l=>l.resolveType("OperationQueue")]}),t.registerType(Ne).as("SyncManager").autoWire({mapResolvers:[l=>l.resolveType("IEventBus"),l=>l.resolveType("OperationQueue"),l=>l.resolveType("IndexedDBService"),l=>l.resolveType("ApiEventRepository")]}),t.registerType(Fe).as("IHeaderRenderer"),t.registerType(_e).as("IColumnRenderer").autoWire({mapResolvers:[l=>l.resolveType("DateService"),l=>l.resolveType("WorkHoursManager")]}),t.registerType(Ye).as("IEventRenderer").autoWire({mapResolvers:[l=>l.resolveType("DateService"),l=>l.resolveType("EventStackManager"),l=>l.resolveType("EventLayoutCoordinator"),l=>l.resolveType("Configuration"),l=>l.resolveType("PositionUtils")]}),t.registerType(U).as("DateService").autoWire({mapResolvers:[l=>l.resolveType("Configuration")]}),t.registerType(pe).as("EventStackManager").autoWire({mapResolvers:[l=>l.resolveType("Configuration")]}),t.registerType(je).as("EventLayoutCoordinator").autoWire({mapResolvers:[l=>l.resolveType("EventStackManager"),l=>l.resolveType("Configuration"),l=>l.resolveType("PositionUtils")]}),t.registerType(Ue).as("WorkHoursManager").autoWire({mapResolvers:[l=>l.resolveType("DateService"),l=>l.resolveType("Configuration"),l=>l.resolveType("PositionUtils")]}),t.registerType(Ee).as("URLManager").autoWire({mapResolvers:[l=>l.resolveType("IEventBus")]}),t.registerType(V).as("TimeFormatter"),t.registerType(Ge).as("PositionUtils").autoWire({mapResolvers:[l=>l.resolveType("DateService"),l=>l.resolveType("Configuration")]}),t.registerType(qe).as("WeekInfoRenderer").autoWire({mapResolvers:[l=>l.resolveType("IEventBus"),l=>l.resolveType("EventRenderingService")]}),t.registerType(Ve).as("AllDayEventRenderer"),t.registerType(Se).as("EventRenderingService").autoWire({mapResolvers:[l=>l.resolveType("IEventBus"),l=>l.resolveType("EventManager"),l=>l.resolveType("IEventRenderer"),l=>l.resolveType("DateService")]}),t.registerType(ze).as("GridRenderer").autoWire({mapResolvers:[l=>l.resolveType("IColumnRenderer"),l=>l.resolveType("DateService"),l=>l.resolveType("Configuration")]}),t.registerType(we).as("GridManager").autoWire({mapResolvers:[l=>l.resolveType("GridRenderer"),l=>l.resolveType("DateService")]}),t.registerType(Ce).as("ScrollManager").autoWire({mapResolvers:[l=>l.resolveType("PositionUtils")]}),t.registerType(Te).as("NavigationManager").autoWire({mapResolvers:[l=>l.resolveType("IEventBus"),l=>l.resolveType("EventRenderingService"),l=>l.resolveType("GridRenderer"),l=>l.resolveType("DateService"),l=>l.resolveType("WeekInfoRenderer")]}),t.registerType(ke).as("NavigationButtons").autoWire({mapResolvers:[l=>l.resolveType("IEventBus")]}),t.registerType(Me).as("ViewSelector").autoWire({mapResolvers:[l=>l.resolveType("IEventBus"),l=>l.resolveType("Configuration")]}),t.registerType(be).as("DragDropManager").autoWire({mapResolvers:[l=>l.resolveType("IEventBus"),l=>l.resolveType("PositionUtils")]}),t.registerType(Ie).as("AllDayManager").autoWire({mapResolvers:[l=>l.resolveType("EventManager"),l=>l.resolveType("AllDayEventRenderer"),l=>l.resolveType("DateService")]}),t.registerType(Oe).as("ResizeHandleManager").autoWire({mapResolvers:[l=>l.resolveType("Configuration"),l=>l.resolveType("PositionUtils")]}),t.registerType(Le).as("EdgeScrollManager").autoWire({mapResolvers:[l=>l.resolveType("IEventBus")]}),t.registerType($e).as("HeaderManager").autoWire({mapResolvers:[l=>l.resolveType("IHeaderRenderer"),l=>l.resolveType("Configuration")]}),t.registerType(Ae).as("CalendarManager").autoWire({mapResolvers:[l=>l.resolveType("IEventBus"),l=>l.resolveType("EventManager"),l=>l.resolveType("GridManager"),l=>l.resolveType("EventRenderingService"),l=>l.resolveType("ScrollManager"),l=>l.resolveType("Configuration")]}),t.registerType(He).as("WorkweekPresets").autoWire({mapResolvers:[l=>l.resolveType("IEventBus"),l=>l.resolveType("Configuration")]}),t.registerType(fe).as("ConfigManager").autoWire({mapResolvers:[l=>l.resolveType("IEventBus"),l=>l.resolveType("Configuration")]}),t.registerType(De).as("EventManager").autoWire({mapResolvers:[l=>l.resolveType("IEventBus"),l=>l.resolveType("DateService"),l=>l.resolveType("Configuration"),l=>l.resolveType("IEventRepository")]});let n=t.build(),r=n.resolveType("IEventBus"),s=n.resolveType("CalendarManager"),i=n.resolveType("EventManager"),a=n.resolveType("ResizeHandleManager"),c=n.resolveType("HeaderManager"),d=n.resolveType("DragDropManager"),g=n.resolveType("ViewSelector"),w=n.resolveType("NavigationManager"),E=n.resolveType("NavigationButtons"),m=n.resolveType("EdgeScrollManager"),u=n.resolveType("AllDayManager"),D=n.resolveType("URLManager"),C=n.resolveType("WorkweekPresets"),k=n.resolveType("ConfigManager");await s.initialize?.(),await a.initialize?.(),await zt(i,D),window.calendarDebug={eventBus:b,app:n,calendarManager:s,eventManager:i,workweekPresetsManager:C}}catch(o){throw o}}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{Ot().catch(o=>{console.error("Calendar initialization failed:",o)})}):Ot().catch(o=>{console.error("Calendar initialization failed:",o)}); diff --git a/wwwroot/js/calendar-v2.js b/wwwroot/js/calendar-v2.js new file mode 100644 index 0000000..7287e63 --- /dev/null +++ b/wwwroot/js/calendar-v2.js @@ -0,0 +1,1631 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); + +// node_modules/dayjs/dayjs.min.js +var require_dayjs_min = __commonJS({ + "node_modules/dayjs/dayjs.min.js"(exports, module) { + !function(t, e) { + "object" == typeof exports && "undefined" != typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define(e) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs = e(); + }(exports, function() { + "use strict"; + var t = 1e3, e = 6e4, n = 36e5, r = "millisecond", i = "second", s = "minute", u = "hour", a = "day", o = "week", c = "month", f = "quarter", h = "year", d = "date", l = "Invalid Date", $ = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/, y = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g, M = { name: "en", weekdays: "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), months: "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), ordinal: function(t2) { + var e2 = ["th", "st", "nd", "rd"], n2 = t2 % 100; + return "[" + t2 + (e2[(n2 - 20) % 10] || e2[n2] || e2[0]) + "]"; + } }, m = /* @__PURE__ */ __name(function(t2, e2, n2) { + var r2 = String(t2); + return !r2 || r2.length >= e2 ? t2 : "" + Array(e2 + 1 - r2.length).join(n2) + t2; + }, "m"), v = { s: m, z: function(t2) { + var e2 = -t2.utcOffset(), n2 = Math.abs(e2), r2 = Math.floor(n2 / 60), i2 = n2 % 60; + return (e2 <= 0 ? "+" : "-") + m(r2, 2, "0") + ":" + m(i2, 2, "0"); + }, m: /* @__PURE__ */ __name(function t2(e2, n2) { + if (e2.date() < n2.date()) + return -t2(n2, e2); + var r2 = 12 * (n2.year() - e2.year()) + (n2.month() - e2.month()), i2 = e2.clone().add(r2, c), s2 = n2 - i2 < 0, u2 = e2.clone().add(r2 + (s2 ? -1 : 1), c); + return +(-(r2 + (n2 - i2) / (s2 ? i2 - u2 : u2 - i2)) || 0); + }, "t"), a: function(t2) { + return t2 < 0 ? Math.ceil(t2) || 0 : Math.floor(t2); + }, p: function(t2) { + return { M: c, y: h, w: o, d: a, D: d, h: u, m: s, s: i, ms: r, Q: f }[t2] || String(t2 || "").toLowerCase().replace(/s$/, ""); + }, u: function(t2) { + return void 0 === t2; + } }, g = "en", D = {}; + D[g] = M; + var p = "$isDayjsObject", S = /* @__PURE__ */ __name(function(t2) { + return t2 instanceof _ || !(!t2 || !t2[p]); + }, "S"), w = /* @__PURE__ */ __name(function t2(e2, n2, r2) { + var i2; + if (!e2) + return g; + if ("string" == typeof e2) { + var s2 = e2.toLowerCase(); + D[s2] && (i2 = s2), n2 && (D[s2] = n2, i2 = s2); + var u2 = e2.split("-"); + if (!i2 && u2.length > 1) + return t2(u2[0]); + } else { + var a2 = e2.name; + D[a2] = e2, i2 = a2; + } + return !r2 && i2 && (g = i2), i2 || !r2 && g; + }, "t"), O = /* @__PURE__ */ __name(function(t2, e2) { + if (S(t2)) + return t2.clone(); + var n2 = "object" == typeof e2 ? e2 : {}; + return n2.date = t2, n2.args = arguments, new _(n2); + }, "O"), b = v; + b.l = w, b.i = S, b.w = function(t2, e2) { + return O(t2, { locale: e2.$L, utc: e2.$u, x: e2.$x, $offset: e2.$offset }); + }; + var _ = function() { + function M2(t2) { + this.$L = w(t2.locale, null, true), this.parse(t2), this.$x = this.$x || t2.x || {}, this[p] = true; + } + __name(M2, "M"); + var m2 = M2.prototype; + return m2.parse = function(t2) { + this.$d = function(t3) { + var e2 = t3.date, n2 = t3.utc; + if (null === e2) + return /* @__PURE__ */ new Date(NaN); + if (b.u(e2)) + return /* @__PURE__ */ new Date(); + if (e2 instanceof Date) + return new Date(e2); + if ("string" == typeof e2 && !/Z$/i.test(e2)) { + var r2 = e2.match($); + if (r2) { + var i2 = r2[2] - 1 || 0, s2 = (r2[7] || "0").substring(0, 3); + return n2 ? new Date(Date.UTC(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2)) : new Date(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2); + } + } + return new Date(e2); + }(t2), this.init(); + }, m2.init = function() { + var t2 = this.$d; + this.$y = t2.getFullYear(), this.$M = t2.getMonth(), this.$D = t2.getDate(), this.$W = t2.getDay(), this.$H = t2.getHours(), this.$m = t2.getMinutes(), this.$s = t2.getSeconds(), this.$ms = t2.getMilliseconds(); + }, m2.$utils = function() { + return b; + }, m2.isValid = function() { + return !(this.$d.toString() === l); + }, m2.isSame = function(t2, e2) { + var n2 = O(t2); + return this.startOf(e2) <= n2 && n2 <= this.endOf(e2); + }, m2.isAfter = function(t2, e2) { + return O(t2) < this.startOf(e2); + }, m2.isBefore = function(t2, e2) { + return this.endOf(e2) < O(t2); + }, m2.$g = function(t2, e2, n2) { + return b.u(t2) ? this[e2] : this.set(n2, t2); + }, m2.unix = function() { + return Math.floor(this.valueOf() / 1e3); + }, m2.valueOf = function() { + return this.$d.getTime(); + }, m2.startOf = function(t2, e2) { + var n2 = this, r2 = !!b.u(e2) || e2, f2 = b.p(t2), l2 = /* @__PURE__ */ __name(function(t3, e3) { + var i2 = b.w(n2.$u ? Date.UTC(n2.$y, e3, t3) : new Date(n2.$y, e3, t3), n2); + return r2 ? i2 : i2.endOf(a); + }, "l"), $2 = /* @__PURE__ */ __name(function(t3, e3) { + return b.w(n2.toDate()[t3].apply(n2.toDate("s"), (r2 ? [0, 0, 0, 0] : [23, 59, 59, 999]).slice(e3)), n2); + }, "$"), y2 = this.$W, M3 = this.$M, m3 = this.$D, v2 = "set" + (this.$u ? "UTC" : ""); + switch (f2) { + case h: + return r2 ? l2(1, 0) : l2(31, 11); + case c: + return r2 ? l2(1, M3) : l2(0, M3 + 1); + case o: + var g2 = this.$locale().weekStart || 0, D2 = (y2 < g2 ? y2 + 7 : y2) - g2; + return l2(r2 ? m3 - D2 : m3 + (6 - D2), M3); + case a: + case d: + return $2(v2 + "Hours", 0); + case u: + return $2(v2 + "Minutes", 1); + case s: + return $2(v2 + "Seconds", 2); + case i: + return $2(v2 + "Milliseconds", 3); + default: + return this.clone(); + } + }, m2.endOf = function(t2) { + return this.startOf(t2, false); + }, m2.$set = function(t2, e2) { + var n2, o2 = b.p(t2), f2 = "set" + (this.$u ? "UTC" : ""), l2 = (n2 = {}, n2[a] = f2 + "Date", n2[d] = f2 + "Date", n2[c] = f2 + "Month", n2[h] = f2 + "FullYear", n2[u] = f2 + "Hours", n2[s] = f2 + "Minutes", n2[i] = f2 + "Seconds", n2[r] = f2 + "Milliseconds", n2)[o2], $2 = o2 === a ? this.$D + (e2 - this.$W) : e2; + if (o2 === c || o2 === h) { + var y2 = this.clone().set(d, 1); + y2.$d[l2]($2), y2.init(), this.$d = y2.set(d, Math.min(this.$D, y2.daysInMonth())).$d; + } else + l2 && this.$d[l2]($2); + return this.init(), this; + }, m2.set = function(t2, e2) { + return this.clone().$set(t2, e2); + }, m2.get = function(t2) { + return this[b.p(t2)](); + }, m2.add = function(r2, f2) { + var d2, l2 = this; + r2 = Number(r2); + var $2 = b.p(f2), y2 = /* @__PURE__ */ __name(function(t2) { + var e2 = O(l2); + return b.w(e2.date(e2.date() + Math.round(t2 * r2)), l2); + }, "y"); + if ($2 === c) + return this.set(c, this.$M + r2); + if ($2 === h) + return this.set(h, this.$y + r2); + if ($2 === a) + return y2(1); + if ($2 === o) + return y2(7); + var M3 = (d2 = {}, d2[s] = e, d2[u] = n, d2[i] = t, d2)[$2] || 1, m3 = this.$d.getTime() + r2 * M3; + return b.w(m3, this); + }, m2.subtract = function(t2, e2) { + return this.add(-1 * t2, e2); + }, m2.format = function(t2) { + var e2 = this, n2 = this.$locale(); + if (!this.isValid()) + return n2.invalidDate || l; + var r2 = t2 || "YYYY-MM-DDTHH:mm:ssZ", i2 = b.z(this), s2 = this.$H, u2 = this.$m, a2 = this.$M, o2 = n2.weekdays, c2 = n2.months, f2 = n2.meridiem, h2 = /* @__PURE__ */ __name(function(t3, n3, i3, s3) { + return t3 && (t3[n3] || t3(e2, r2)) || i3[n3].slice(0, s3); + }, "h"), d2 = /* @__PURE__ */ __name(function(t3) { + return b.s(s2 % 12 || 12, t3, "0"); + }, "d"), $2 = f2 || function(t3, e3, n3) { + var r3 = t3 < 12 ? "AM" : "PM"; + return n3 ? r3.toLowerCase() : r3; + }; + return r2.replace(y, function(t3, r3) { + return r3 || function(t4) { + switch (t4) { + case "YY": + return String(e2.$y).slice(-2); + case "YYYY": + return b.s(e2.$y, 4, "0"); + case "M": + return a2 + 1; + case "MM": + return b.s(a2 + 1, 2, "0"); + case "MMM": + return h2(n2.monthsShort, a2, c2, 3); + case "MMMM": + return h2(c2, a2); + case "D": + return e2.$D; + case "DD": + return b.s(e2.$D, 2, "0"); + case "d": + return String(e2.$W); + case "dd": + return h2(n2.weekdaysMin, e2.$W, o2, 2); + case "ddd": + return h2(n2.weekdaysShort, e2.$W, o2, 3); + case "dddd": + return o2[e2.$W]; + case "H": + return String(s2); + case "HH": + return b.s(s2, 2, "0"); + case "h": + return d2(1); + case "hh": + return d2(2); + case "a": + return $2(s2, u2, true); + case "A": + return $2(s2, u2, false); + case "m": + return String(u2); + case "mm": + return b.s(u2, 2, "0"); + case "s": + return String(e2.$s); + case "ss": + return b.s(e2.$s, 2, "0"); + case "SSS": + return b.s(e2.$ms, 3, "0"); + case "Z": + return i2; + } + return null; + }(t3) || i2.replace(":", ""); + }); + }, m2.utcOffset = function() { + return 15 * -Math.round(this.$d.getTimezoneOffset() / 15); + }, m2.diff = function(r2, d2, l2) { + var $2, y2 = this, M3 = b.p(d2), m3 = O(r2), v2 = (m3.utcOffset() - this.utcOffset()) * e, g2 = this - m3, D2 = /* @__PURE__ */ __name(function() { + return b.m(y2, m3); + }, "D"); + switch (M3) { + case h: + $2 = D2() / 12; + break; + case c: + $2 = D2(); + break; + case f: + $2 = D2() / 3; + break; + case o: + $2 = (g2 - v2) / 6048e5; + break; + case a: + $2 = (g2 - v2) / 864e5; + break; + case u: + $2 = g2 / n; + break; + case s: + $2 = g2 / e; + break; + case i: + $2 = g2 / t; + break; + default: + $2 = g2; + } + return l2 ? $2 : b.a($2); + }, m2.daysInMonth = function() { + return this.endOf(c).$D; + }, m2.$locale = function() { + return D[this.$L]; + }, m2.locale = function(t2, e2) { + if (!t2) + return this.$L; + var n2 = this.clone(), r2 = w(t2, e2, true); + return r2 && (n2.$L = r2), n2; + }, m2.clone = function() { + return b.w(this.$d, this); + }, m2.toDate = function() { + return new Date(this.valueOf()); + }, m2.toJSON = function() { + return this.isValid() ? this.toISOString() : null; + }, m2.toISOString = function() { + return this.$d.toISOString(); + }, m2.toString = function() { + return this.$d.toUTCString(); + }, M2; + }(), k = _.prototype; + return O.prototype = k, [["$ms", r], ["$s", i], ["$m", s], ["$H", u], ["$W", a], ["$M", c], ["$y", h], ["$D", d]].forEach(function(t2) { + k[t2[1]] = function(e2) { + return this.$g(e2, t2[0], t2[1]); + }; + }), O.extend = function(t2, e2) { + return t2.$i || (t2(e2, _, O), t2.$i = true), O; + }, O.locale = w, O.isDayjs = S, O.unix = function(t2) { + return O(1e3 * t2); + }, O.en = D[g], O.Ls = D, O.p = {}, O; + }); + } +}); + +// node_modules/dayjs/plugin/utc.js +var require_utc = __commonJS({ + "node_modules/dayjs/plugin/utc.js"(exports, module) { + !function(t, i) { + "object" == typeof exports && "undefined" != typeof module ? module.exports = i() : "function" == typeof define && define.amd ? define(i) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs_plugin_utc = i(); + }(exports, function() { + "use strict"; + var t = "minute", i = /[+-]\d\d(?::?\d\d)?/g, e = /([+-]|\d\d)/g; + return function(s, f, n) { + var u = f.prototype; + n.utc = function(t2) { + var i2 = { date: t2, utc: true, args: arguments }; + return new f(i2); + }, u.utc = function(i2) { + var e2 = n(this.toDate(), { locale: this.$L, utc: true }); + return i2 ? e2.add(this.utcOffset(), t) : e2; + }, u.local = function() { + return n(this.toDate(), { locale: this.$L, utc: false }); + }; + var r = u.parse; + u.parse = function(t2) { + t2.utc && (this.$u = true), this.$utils().u(t2.$offset) || (this.$offset = t2.$offset), r.call(this, t2); + }; + var o = u.init; + u.init = function() { + if (this.$u) { + var t2 = this.$d; + this.$y = t2.getUTCFullYear(), this.$M = t2.getUTCMonth(), this.$D = t2.getUTCDate(), this.$W = t2.getUTCDay(), this.$H = t2.getUTCHours(), this.$m = t2.getUTCMinutes(), this.$s = t2.getUTCSeconds(), this.$ms = t2.getUTCMilliseconds(); + } else + o.call(this); + }; + var a = u.utcOffset; + u.utcOffset = function(s2, f2) { + var n2 = this.$utils().u; + if (n2(s2)) + return this.$u ? 0 : n2(this.$offset) ? a.call(this) : this.$offset; + if ("string" == typeof s2 && (s2 = function(t2) { + void 0 === t2 && (t2 = ""); + var s3 = t2.match(i); + if (!s3) + return null; + var f3 = ("" + s3[0]).match(e) || ["-", 0, 0], n3 = f3[0], u3 = 60 * +f3[1] + +f3[2]; + return 0 === u3 ? 0 : "+" === n3 ? u3 : -u3; + }(s2), null === s2)) + return this; + var u2 = Math.abs(s2) <= 16 ? 60 * s2 : s2; + if (0 === u2) + return this.utc(f2); + var r2 = this.clone(); + if (f2) + return r2.$offset = u2, r2.$u = false, r2; + var o2 = this.$u ? this.toDate().getTimezoneOffset() : -1 * this.utcOffset(); + return (r2 = this.local().add(u2 + o2, t)).$offset = u2, r2.$x.$localOffset = o2, r2; + }; + var h = u.format; + u.format = function(t2) { + var i2 = t2 || (this.$u ? "YYYY-MM-DDTHH:mm:ss[Z]" : ""); + return h.call(this, i2); + }, u.valueOf = function() { + var t2 = this.$utils().u(this.$offset) ? 0 : this.$offset + (this.$x.$localOffset || this.$d.getTimezoneOffset()); + return this.$d.valueOf() - 6e4 * t2; + }, u.isUTC = function() { + return !!this.$u; + }, u.toISOString = function() { + return this.toDate().toISOString(); + }, u.toString = function() { + return this.toDate().toUTCString(); + }; + var l = u.toDate; + u.toDate = function(t2) { + return "s" === t2 && this.$offset ? n(this.format("YYYY-MM-DD HH:mm:ss:SSS")).toDate() : l.call(this); + }; + var c = u.diff; + u.diff = function(t2, i2, e2) { + if (t2 && this.$u === t2.$u) + return c.call(this, t2, i2, e2); + var s2 = this.local(), f2 = n(t2).local(); + return c.call(s2, f2, i2, e2); + }; + }; + }); + } +}); + +// node_modules/dayjs/plugin/timezone.js +var require_timezone = __commonJS({ + "node_modules/dayjs/plugin/timezone.js"(exports, module) { + !function(t, e) { + "object" == typeof exports && "undefined" != typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define(e) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs_plugin_timezone = e(); + }(exports, function() { + "use strict"; + var t = { year: 0, month: 1, day: 2, hour: 3, minute: 4, second: 5 }, e = {}; + return function(n, i, o) { + var r, a = /* @__PURE__ */ __name(function(t2, n2, i2) { + void 0 === i2 && (i2 = {}); + var o2 = new Date(t2), r2 = function(t3, n3) { + void 0 === n3 && (n3 = {}); + var i3 = n3.timeZoneName || "short", o3 = t3 + "|" + i3, r3 = e[o3]; + return r3 || (r3 = new Intl.DateTimeFormat("en-US", { hour12: false, timeZone: t3, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", timeZoneName: i3 }), e[o3] = r3), r3; + }(n2, i2); + return r2.formatToParts(o2); + }, "a"), u = /* @__PURE__ */ __name(function(e2, n2) { + for (var i2 = a(e2, n2), r2 = [], u2 = 0; u2 < i2.length; u2 += 1) { + var f2 = i2[u2], s2 = f2.type, m = f2.value, c = t[s2]; + c >= 0 && (r2[c] = parseInt(m, 10)); + } + var d = r2[3], l = 24 === d ? 0 : d, h = r2[0] + "-" + r2[1] + "-" + r2[2] + " " + l + ":" + r2[4] + ":" + r2[5] + ":000", v = +e2; + return (o.utc(h).valueOf() - (v -= v % 1e3)) / 6e4; + }, "u"), f = i.prototype; + f.tz = function(t2, e2) { + void 0 === t2 && (t2 = r); + var n2, i2 = this.utcOffset(), a2 = this.toDate(), u2 = a2.toLocaleString("en-US", { timeZone: t2 }), f2 = Math.round((a2 - new Date(u2)) / 1e3 / 60), s2 = 15 * -Math.round(a2.getTimezoneOffset() / 15) - f2; + if (!Number(s2)) + n2 = this.utcOffset(0, e2); + else if (n2 = o(u2, { locale: this.$L }).$set("millisecond", this.$ms).utcOffset(s2, true), e2) { + var m = n2.utcOffset(); + n2 = n2.add(i2 - m, "minute"); + } + return n2.$x.$timezone = t2, n2; + }, f.offsetName = function(t2) { + var e2 = this.$x.$timezone || o.tz.guess(), n2 = a(this.valueOf(), e2, { timeZoneName: t2 }).find(function(t3) { + return "timezonename" === t3.type.toLowerCase(); + }); + return n2 && n2.value; + }; + var s = f.startOf; + f.startOf = function(t2, e2) { + if (!this.$x || !this.$x.$timezone) + return s.call(this, t2, e2); + var n2 = o(this.format("YYYY-MM-DD HH:mm:ss:SSS"), { locale: this.$L }); + return s.call(n2, t2, e2).tz(this.$x.$timezone, true); + }, o.tz = function(t2, e2, n2) { + var i2 = n2 && e2, a2 = n2 || e2 || r, f2 = u(+o(), a2); + if ("string" != typeof t2) + return o(t2).tz(a2); + var s2 = function(t3, e3, n3) { + var i3 = t3 - 60 * e3 * 1e3, o2 = u(i3, n3); + if (e3 === o2) + return [i3, e3]; + var r2 = u(i3 -= 60 * (o2 - e3) * 1e3, n3); + return o2 === r2 ? [i3, o2] : [t3 - 60 * Math.min(o2, r2) * 1e3, Math.max(o2, r2)]; + }(o.utc(t2, i2).valueOf(), f2, a2), m = s2[0], c = s2[1], d = o(m).utcOffset(c); + return d.$x.$timezone = a2, d; + }, o.tz.guess = function() { + return Intl.DateTimeFormat().resolvedOptions().timeZone; + }, o.tz.setDefault = function(t2) { + r = t2; + }; + }; + }); + } +}); + +// node_modules/dayjs/plugin/isoWeek.js +var require_isoWeek = __commonJS({ + "node_modules/dayjs/plugin/isoWeek.js"(exports, module) { + !function(e, t) { + "object" == typeof exports && "undefined" != typeof module ? module.exports = t() : "function" == typeof define && define.amd ? define(t) : (e = "undefined" != typeof globalThis ? globalThis : e || self).dayjs_plugin_isoWeek = t(); + }(exports, function() { + "use strict"; + var e = "day"; + return function(t, i, s) { + var a = /* @__PURE__ */ __name(function(t2) { + return t2.add(4 - t2.isoWeekday(), e); + }, "a"), d = i.prototype; + d.isoWeekYear = function() { + return a(this).year(); + }, d.isoWeek = function(t2) { + if (!this.$utils().u(t2)) + return this.add(7 * (t2 - this.isoWeek()), e); + var i2, d2, n2, o, r = a(this), u = (i2 = this.isoWeekYear(), d2 = this.$u, n2 = (d2 ? s.utc : s)().year(i2).startOf("year"), o = 4 - n2.isoWeekday(), n2.isoWeekday() > 4 && (o += 7), n2.add(o, e)); + return r.diff(u, "week") + 1; + }, d.isoWeekday = function(e2) { + return this.$utils().u(e2) ? this.day() || 7 : this.day(this.day() % 7 ? e2 : e2 - 7); + }; + var n = d.startOf; + d.startOf = function(e2, t2) { + var i2 = this.$utils(), s2 = !!i2.u(t2) || t2; + return "isoweek" === i2.p(e2) ? s2 ? this.date(this.date() - (this.isoWeekday() - 1)).startOf("day") : this.date(this.date() - 1 - (this.isoWeekday() - 1) + 7).endOf("day") : n.bind(this)(e2, t2); + }; + }; + }); + } +}); + +// src/v2/core/RenderBuilder.ts +function buildPipeline(renderers) { + return { + async run(context) { + for (const renderer of renderers) { + await renderer.render(context); + } + } + }; +} +__name(buildPipeline, "buildPipeline"); + +// src/v2/core/FilterTemplate.ts +var _FilterTemplate = class _FilterTemplate { + constructor(dateService, entityResolver) { + this.dateService = dateService; + this.entityResolver = entityResolver; + this.fields = []; + } + /** + * Tilføj felt til template + * @param idProperty - Property-navn (bruges på både event og column.dataset) + * @param derivedFrom - Hvis feltet udledes fra anden property (f.eks. date fra start) + */ + addField(idProperty, derivedFrom) { + this.fields.push({ idProperty, derivedFrom }); + return this; + } + /** + * Parse dot-notation string into components + * @example 'resource.teamId' → { entityType: 'resource', property: 'teamId', foreignKey: 'resourceId' } + */ + parseDotNotation(idProperty) { + if (!idProperty.includes(".")) + return null; + const [entityType, property] = idProperty.split("."); + return { + entityType, + property, + foreignKey: entityType + "Id" + // Convention: resource → resourceId + }; + } + /** + * Get dataset key for column lookup + * For dot-notation 'resource.teamId', we look for 'teamId' in dataset + */ + getDatasetKey(idProperty) { + const dotNotation = this.parseDotNotation(idProperty); + if (dotNotation) { + return dotNotation.property; + } + return idProperty; + } + /** + * Byg nøgle fra kolonne + * Læser værdier fra column.dataset[idProperty] + * For dot-notation, uses the property part (resource.teamId → teamId) + */ + buildKeyFromColumn(column) { + return this.fields.map((f) => { + const key = this.getDatasetKey(f.idProperty); + return column.dataset[key] || ""; + }).join(":"); + } + /** + * Byg nøgle fra event + * Læser værdier fra event[idProperty] eller udleder fra derivedFrom + * For dot-notation, resolves via EntityResolver + */ + buildKeyFromEvent(event) { + const eventRecord = event; + return this.fields.map((f) => { + const dotNotation = this.parseDotNotation(f.idProperty); + if (dotNotation) { + return this.resolveDotNotation(eventRecord, dotNotation); + } + if (f.derivedFrom) { + const sourceValue = eventRecord[f.derivedFrom]; + if (sourceValue instanceof Date) { + return this.dateService.getDateKey(sourceValue); + } + return String(sourceValue || ""); + } + return String(eventRecord[f.idProperty] || ""); + }).join(":"); + } + /** + * Resolve dot-notation reference via EntityResolver + */ + resolveDotNotation(eventRecord, dotNotation) { + if (!this.entityResolver) { + console.warn(`FilterTemplate: EntityResolver required for dot-notation '${dotNotation.entityType}.${dotNotation.property}'`); + return ""; + } + const foreignId = eventRecord[dotNotation.foreignKey]; + if (!foreignId) + return ""; + const entity = this.entityResolver.resolve(dotNotation.entityType, String(foreignId)); + if (!entity) + return ""; + return String(entity[dotNotation.property] || ""); + } + /** + * Match event mod kolonne + */ + matches(event, column) { + return this.buildKeyFromEvent(event) === this.buildKeyFromColumn(column); + } +}; +__name(_FilterTemplate, "FilterTemplate"); +var FilterTemplate = _FilterTemplate; + +// src/v2/core/CalendarOrchestrator.ts +var _CalendarOrchestrator = class _CalendarOrchestrator { + constructor(allRenderers, eventRenderer, scheduleRenderer, headerDrawerRenderer, dateService, entityServices) { + this.allRenderers = allRenderers; + this.eventRenderer = eventRenderer; + this.scheduleRenderer = scheduleRenderer; + this.headerDrawerRenderer = headerDrawerRenderer; + this.dateService = dateService; + this.entityServices = entityServices; + } + async render(viewConfig, container) { + const headerContainer = container.querySelector("swp-calendar-header"); + const columnContainer = container.querySelector("swp-day-columns"); + if (!headerContainer || !columnContainer) { + throw new Error("Missing swp-calendar-header or swp-day-columns"); + } + const filter = {}; + for (const grouping of viewConfig.groupings) { + filter[grouping.type] = grouping.values; + } + const filterTemplate = new FilterTemplate(this.dateService); + for (const grouping of viewConfig.groupings) { + if (grouping.idProperty) { + filterTemplate.addField(grouping.idProperty, grouping.derivedFrom); + } + } + const { parentChildMap, childType } = await this.resolveBelongsTo(viewConfig.groupings, filter); + const context = { headerContainer, columnContainer, filter, groupings: viewConfig.groupings, parentChildMap, childType }; + headerContainer.innerHTML = ""; + columnContainer.innerHTML = ""; + const levels = viewConfig.groupings.map((g) => g.type).join(" "); + headerContainer.dataset.levels = levels; + const activeRenderers = this.selectRenderers(viewConfig); + const pipeline = buildPipeline(activeRenderers); + await pipeline.run(context); + await this.scheduleRenderer.render(container, filter); + await this.eventRenderer.render(container, filter, filterTemplate); + await this.headerDrawerRenderer.render(container, filter, filterTemplate); + } + selectRenderers(viewConfig) { + const types = viewConfig.groupings.map((g) => g.type); + return types.map((type) => this.allRenderers.find((r) => r.type === type)).filter((r) => r !== void 0); + } + /** + * Resolve belongsTo relations to build parent-child map + * e.g., belongsTo: 'team.resourceIds' → { team1: ['EMP001', 'EMP002'], team2: [...] } + * Also returns the childType (the grouping type that has belongsTo) + */ + async resolveBelongsTo(groupings, filter) { + const childGrouping = groupings.find((g) => g.belongsTo); + if (!childGrouping?.belongsTo) + return {}; + const [entityType, property] = childGrouping.belongsTo.split("."); + if (!entityType || !property) + return {}; + const parentIds = filter[entityType] || []; + if (parentIds.length === 0) + return {}; + const service = this.entityServices.find( + (s) => s.entityType.toLowerCase() === entityType + ); + if (!service) + return {}; + const allEntities = await service.getAll(); + const entities = allEntities.filter( + (e) => parentIds.includes(e.id) + ); + const map = {}; + for (const entity of entities) { + const entityRecord = entity; + const children = entityRecord[property] || []; + map[entityRecord.id] = children; + } + return { parentChildMap: map, childType: childGrouping.type }; + } +}; +__name(_CalendarOrchestrator, "CalendarOrchestrator"); +var CalendarOrchestrator = _CalendarOrchestrator; + +// src/v2/core/NavigationAnimator.ts +var _NavigationAnimator = class _NavigationAnimator { + constructor(headerTrack, contentTrack) { + this.headerTrack = headerTrack; + this.contentTrack = contentTrack; + } + async slide(direction, renderFn) { + const out = direction === "left" ? "-100%" : "100%"; + const into = direction === "left" ? "100%" : "-100%"; + await this.animateOut(out); + await renderFn(); + await this.animateIn(into); + } + async animateOut(translate) { + await Promise.all([ + this.headerTrack.animate( + [{ transform: "translateX(0)" }, { transform: `translateX(${translate})` }], + { duration: 200, easing: "ease-in" } + ).finished, + this.contentTrack.animate( + [{ transform: "translateX(0)" }, { transform: `translateX(${translate})` }], + { duration: 200, easing: "ease-in" } + ).finished + ]); + } + async animateIn(translate) { + await Promise.all([ + this.headerTrack.animate( + [{ transform: `translateX(${translate})` }, { transform: "translateX(0)" }], + { duration: 200, easing: "ease-out" } + ).finished, + this.contentTrack.animate( + [{ transform: `translateX(${translate})` }, { transform: "translateX(0)" }], + { duration: 200, easing: "ease-out" } + ).finished + ]); + } +}; +__name(_NavigationAnimator, "NavigationAnimator"); +var NavigationAnimator = _NavigationAnimator; + +// src/v2/features/date/DateRenderer.ts +var _DateRenderer = class _DateRenderer { + constructor(dateService) { + this.dateService = dateService; + this.type = "date"; + } + render(context) { + const dates = context.filter["date"] || []; + const resourceIds = context.filter["resource"] || []; + const dateGrouping = context.groupings?.find((g) => g.type === "date"); + const hideHeader = dateGrouping?.hideHeader === true; + const iterations = resourceIds.length || 1; + let columnCount = 0; + for (let r = 0; r < iterations; r++) { + const resourceId = resourceIds[r]; + for (const dateStr of dates) { + const date = this.dateService.parseISO(dateStr); + const segments = { date: dateStr }; + if (resourceId) + segments.resource = resourceId; + const columnKey = this.dateService.buildColumnKey(segments); + const header = document.createElement("swp-day-header"); + header.dataset.date = dateStr; + header.dataset.columnKey = columnKey; + if (resourceId) { + header.dataset.resourceId = resourceId; + } + if (hideHeader) { + header.dataset.hidden = "true"; + } + header.innerHTML = ` + ${this.dateService.getDayName(date, "short")} + ${date.getDate()} + `; + context.headerContainer.appendChild(header); + const column = document.createElement("swp-day-column"); + column.dataset.date = dateStr; + column.dataset.columnKey = columnKey; + if (resourceId) { + column.dataset.resourceId = resourceId; + } + column.innerHTML = ""; + context.columnContainer.appendChild(column); + columnCount++; + } + } + const container = context.columnContainer.closest("swp-calendar-container"); + if (container) { + container.style.setProperty("--grid-columns", String(columnCount)); + } + } +}; +__name(_DateRenderer, "DateRenderer"); +var DateRenderer = _DateRenderer; + +// src/v2/core/DateService.ts +var import_dayjs = __toESM(require_dayjs_min(), 1); +var import_utc = __toESM(require_utc(), 1); +var import_timezone = __toESM(require_timezone(), 1); +var import_isoWeek = __toESM(require_isoWeek(), 1); +import_dayjs.default.extend(import_utc.default); +import_dayjs.default.extend(import_timezone.default); +import_dayjs.default.extend(import_isoWeek.default); +var _DateService = class _DateService { + constructor(config, baseDate) { + this.config = config; + this.timezone = config.timezone; + this.baseDate = baseDate ? (0, import_dayjs.default)(baseDate) : (0, import_dayjs.default)(); + } + /** + * Set a fixed base date (useful for demos with static mock data) + */ + setBaseDate(date) { + this.baseDate = (0, import_dayjs.default)(date); + } + /** + * Get the current base date (either fixed or today) + */ + getBaseDate() { + return this.baseDate.toDate(); + } + parseISO(isoString) { + return (0, import_dayjs.default)(isoString).toDate(); + } + getDayName(date, format = "short") { + return new Intl.DateTimeFormat(this.config.locale, { weekday: format }).format(date); + } + getWeekDates(offset = 0, days = 7) { + const monday = this.baseDate.startOf("week").add(1, "day").add(offset, "week"); + return Array.from( + { length: days }, + (_, i) => monday.add(i, "day").format("YYYY-MM-DD") + ); + } + /** + * Get dates for specific weekdays within a week + * @param offset - Week offset from base date (0 = current week) + * @param workDays - Array of ISO weekday numbers (1=Monday, 7=Sunday) + * @returns Array of date strings in YYYY-MM-DD format + */ + getWorkWeekDates(offset, workDays) { + const monday = this.baseDate.startOf("week").add(1, "day").add(offset, "week"); + return workDays.map((isoDay) => { + const daysFromMonday = isoDay === 7 ? 6 : isoDay - 1; + return monday.add(daysFromMonday, "day").format("YYYY-MM-DD"); + }); + } + // ============================================ + // FORMATTING + // ============================================ + formatTime(date, showSeconds = false) { + const pattern = showSeconds ? "HH:mm:ss" : "HH:mm"; + return (0, import_dayjs.default)(date).format(pattern); + } + formatTimeRange(start, end) { + return `${this.formatTime(start)} - ${this.formatTime(end)}`; + } + formatDate(date) { + return (0, import_dayjs.default)(date).format("YYYY-MM-DD"); + } + getDateKey(date) { + return this.formatDate(date); + } + // ============================================ + // COLUMN KEY + // ============================================ + /** + * Build a uniform columnKey from grouping segments + * Handles any combination of date, resource, team, etc. + * + * @example + * buildColumnKey({ date: '2025-12-09' }) → "2025-12-09" + * buildColumnKey({ date: '2025-12-09', resource: 'EMP001' }) → "2025-12-09:EMP001" + */ + buildColumnKey(segments) { + const date = segments.date; + const others = Object.entries(segments).filter(([k]) => k !== "date").sort(([a], [b]) => a.localeCompare(b)).map(([, v]) => v); + return date ? [date, ...others].join(":") : others.join(":"); + } + /** + * Parse a columnKey back into segments + * Assumes format: "date:resource:..." or just "date" + */ + parseColumnKey(columnKey) { + const parts = columnKey.split(":"); + return { + date: parts[0], + resource: parts[1] + }; + } + /** + * Extract dateKey from columnKey (first segment) + */ + getDateFromColumnKey(columnKey) { + return columnKey.split(":")[0]; + } + // ============================================ + // TIME CALCULATIONS + // ============================================ + timeToMinutes(timeString) { + const parts = timeString.split(":").map(Number); + const hours = parts[0] || 0; + const minutes = parts[1] || 0; + return hours * 60 + minutes; + } + minutesToTime(totalMinutes) { + const hours = Math.floor(totalMinutes / 60); + const minutes = totalMinutes % 60; + return (0, import_dayjs.default)().hour(hours).minute(minutes).format("HH:mm"); + } + getMinutesSinceMidnight(date) { + const d = (0, import_dayjs.default)(date); + return d.hour() * 60 + d.minute(); + } + // ============================================ + // UTC CONVERSIONS + // ============================================ + toUTC(localDate) { + return import_dayjs.default.tz(localDate, this.timezone).utc().toISOString(); + } + fromUTC(utcString) { + return import_dayjs.default.utc(utcString).tz(this.timezone).toDate(); + } + // ============================================ + // DATE CREATION + // ============================================ + createDateAtTime(baseDate, timeString) { + const totalMinutes = this.timeToMinutes(timeString); + const hours = Math.floor(totalMinutes / 60); + const minutes = totalMinutes % 60; + return (0, import_dayjs.default)(baseDate).startOf("day").hour(hours).minute(minutes).toDate(); + } + getISOWeekDay(date) { + return (0, import_dayjs.default)(date).isoWeekday(); + } +}; +__name(_DateService, "DateService"); +var DateService = _DateService; + +// src/v2/utils/PositionUtils.ts +function calculateEventPosition(start, end, config) { + const startMinutes = start.getHours() * 60 + start.getMinutes(); + const endMinutes = end.getHours() * 60 + end.getMinutes(); + const dayStartMinutes = config.dayStartHour * 60; + const minuteHeight = config.hourHeight / 60; + const top = (startMinutes - dayStartMinutes) * minuteHeight; + const height = (endMinutes - startMinutes) * minuteHeight; + return { top, height }; +} +__name(calculateEventPosition, "calculateEventPosition"); +function minutesToPixels(minutes, config) { + return minutes / 60 * config.hourHeight; +} +__name(minutesToPixels, "minutesToPixels"); +function pixelsToMinutes(pixels, config) { + return pixels / config.hourHeight * 60; +} +__name(pixelsToMinutes, "pixelsToMinutes"); +function snapToGrid(pixels, config) { + const snapPixels = minutesToPixels(config.snapInterval, config); + return Math.round(pixels / snapPixels) * snapPixels; +} +__name(snapToGrid, "snapToGrid"); + +// src/v2/constants/CoreEvents.ts +var CoreEvents = { + // Lifecycle events + INITIALIZED: "core:initialized", + READY: "core:ready", + DESTROYED: "core:destroyed", + // View events + VIEW_CHANGED: "view:changed", + VIEW_RENDERED: "view:rendered", + // Navigation events + DATE_CHANGED: "nav:date-changed", + NAVIGATION_COMPLETED: "nav:navigation-completed", + // Data events + DATA_LOADING: "data:loading", + DATA_LOADED: "data:loaded", + DATA_ERROR: "data:error", + // Grid events + GRID_RENDERED: "grid:rendered", + GRID_CLICKED: "grid:clicked", + // Event management + EVENT_CREATED: "event:created", + EVENT_UPDATED: "event:updated", + EVENT_DELETED: "event:deleted", + EVENT_SELECTED: "event:selected", + // Event drag-drop + EVENT_DRAG_START: "event:drag-start", + EVENT_DRAG_MOVE: "event:drag-move", + EVENT_DRAG_END: "event:drag-end", + EVENT_DRAG_CANCEL: "event:drag-cancel", + EVENT_DRAG_COLUMN_CHANGE: "event:drag-column-change", + // Header drag (timed → header conversion) + EVENT_DRAG_ENTER_HEADER: "event:drag-enter-header", + EVENT_DRAG_MOVE_HEADER: "event:drag-move-header", + EVENT_DRAG_LEAVE_HEADER: "event:drag-leave-header", + // Event resize + EVENT_RESIZE_START: "event:resize-start", + EVENT_RESIZE_END: "event:resize-end", + // Edge scroll + EDGE_SCROLL_TICK: "edge-scroll:tick", + EDGE_SCROLL_STARTED: "edge-scroll:started", + EDGE_SCROLL_STOPPED: "edge-scroll:stopped", + // System events + ERROR: "system:error", + // Sync events + SYNC_STARTED: "sync:started", + SYNC_COMPLETED: "sync:completed", + SYNC_FAILED: "sync:failed", + // Entity events - for audit and sync + ENTITY_SAVED: "entity:saved", + ENTITY_DELETED: "entity:deleted", + // Audit events + AUDIT_LOGGED: "audit:logged", + // Rendering events + EVENTS_RENDERED: "events:rendered" +}; + +// src/v2/features/event/EventLayoutEngine.ts +function eventsOverlap(a, b) { + return a.start < b.end && a.end > b.start; +} +__name(eventsOverlap, "eventsOverlap"); +function eventsWithinThreshold(a, b, thresholdMinutes) { + const thresholdMs = thresholdMinutes * 60 * 1e3; + const startToStartDiff = Math.abs(a.start.getTime() - b.start.getTime()); + if (startToStartDiff <= thresholdMs) + return true; + const bStartsBeforeAEnds = a.end.getTime() - b.start.getTime(); + if (bStartsBeforeAEnds > 0 && bStartsBeforeAEnds <= thresholdMs) + return true; + const aStartsBeforeBEnds = b.end.getTime() - a.start.getTime(); + if (aStartsBeforeBEnds > 0 && aStartsBeforeBEnds <= thresholdMs) + return true; + return false; +} +__name(eventsWithinThreshold, "eventsWithinThreshold"); +function findOverlapGroups(events) { + if (events.length === 0) + return []; + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + const used = /* @__PURE__ */ new Set(); + const groups = []; + for (const event of sorted) { + if (used.has(event.id)) + continue; + const group = [event]; + used.add(event.id); + let expanded = true; + while (expanded) { + expanded = false; + for (const candidate of sorted) { + if (used.has(candidate.id)) + continue; + const connects = group.some((member) => eventsOverlap(member, candidate)); + if (connects) { + group.push(candidate); + used.add(candidate.id); + expanded = true; + } + } + } + groups.push(group); + } + return groups; +} +__name(findOverlapGroups, "findOverlapGroups"); +function findGridCandidates(events, thresholdMinutes) { + if (events.length === 0) + return []; + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + const used = /* @__PURE__ */ new Set(); + const groups = []; + for (const event of sorted) { + if (used.has(event.id)) + continue; + const group = [event]; + used.add(event.id); + let expanded = true; + while (expanded) { + expanded = false; + for (const candidate of sorted) { + if (used.has(candidate.id)) + continue; + const connects = group.some( + (member) => eventsWithinThreshold(member, candidate, thresholdMinutes) + ); + if (connects) { + group.push(candidate); + used.add(candidate.id); + expanded = true; + } + } + } + groups.push(group); + } + return groups; +} +__name(findGridCandidates, "findGridCandidates"); +function calculateStackLevels(events) { + const levels = /* @__PURE__ */ new Map(); + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + for (const event of sorted) { + let maxOverlappingLevel = -1; + for (const [id, level] of levels) { + const other = events.find((e) => e.id === id); + if (other && eventsOverlap(event, other)) { + maxOverlappingLevel = Math.max(maxOverlappingLevel, level); + } + } + levels.set(event.id, maxOverlappingLevel + 1); + } + return levels; +} +__name(calculateStackLevels, "calculateStackLevels"); +function allocateColumns(events) { + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + const columns = []; + for (const event of sorted) { + let placed = false; + for (const column of columns) { + const canFit = !column.some((e) => eventsOverlap(event, e)); + if (canFit) { + column.push(event); + placed = true; + break; + } + } + if (!placed) { + columns.push([event]); + } + } + return columns; +} +__name(allocateColumns, "allocateColumns"); +function calculateColumnLayout(events, config) { + const thresholdMinutes = config.gridStartThresholdMinutes ?? 10; + const result = { + grids: [], + stacked: [] + }; + if (events.length === 0) + return result; + const overlapGroups = findOverlapGroups(events); + for (const overlapGroup of overlapGroups) { + if (overlapGroup.length === 1) { + result.stacked.push({ + event: overlapGroup[0], + stackLevel: 0 + }); + continue; + } + const gridSubgroups = findGridCandidates(overlapGroup, thresholdMinutes); + const largestGridCandidate = gridSubgroups.reduce((max, g) => g.length > max.length ? g : max, gridSubgroups[0]); + if (largestGridCandidate.length === overlapGroup.length) { + const columns = allocateColumns(overlapGroup); + const earliest = overlapGroup.reduce((min, e) => e.start < min.start ? e : min, overlapGroup[0]); + const position = calculateEventPosition(earliest.start, earliest.end, config); + result.grids.push({ + events: overlapGroup, + columns, + stackLevel: 0, + position: { top: position.top } + }); + } else { + const levels = calculateStackLevels(overlapGroup); + for (const event of overlapGroup) { + result.stacked.push({ + event, + stackLevel: levels.get(event.id) ?? 0 + }); + } + } + } + return result; +} +__name(calculateColumnLayout, "calculateColumnLayout"); + +// src/v2/features/event/EventRenderer.ts +var _EventRenderer = class _EventRenderer { + constructor(eventService, dateService, gridConfig, eventBus) { + this.eventService = eventService; + this.dateService = dateService; + this.gridConfig = gridConfig; + this.eventBus = eventBus; + this.container = null; + this.setupListeners(); + } + /** + * Setup listeners for drag-drop and update events + */ + setupListeners() { + this.eventBus.on(CoreEvents.EVENT_DRAG_COLUMN_CHANGE, (e) => { + const payload = e.detail; + this.handleColumnChange(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_MOVE, (e) => { + const payload = e.detail; + this.updateDragTimestamp(payload); + }); + this.eventBus.on(CoreEvents.EVENT_UPDATED, (e) => { + const payload = e.detail; + this.handleEventUpdated(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_END, (e) => { + const payload = e.detail; + this.handleDragEnd(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_LEAVE_HEADER, (e) => { + const payload = e.detail; + this.handleDragLeaveHeader(payload); + }); + } + /** + * Handle EVENT_DRAG_END - remove element if dropped in header + */ + handleDragEnd(payload) { + if (payload.target === "header") { + const element = this.container?.querySelector(`swp-content-viewport swp-event[data-event-id="${payload.swpEvent.eventId}"]`); + element?.remove(); + } + } + /** + * Handle header item leaving header - create swp-event in grid + */ + handleDragLeaveHeader(payload) { + if (payload.source !== "header") + return; + if (!payload.targetColumn || !payload.start || !payload.end) + return; + if (payload.element) { + payload.element.classList.add("drag-ghost"); + payload.element.style.opacity = "0.3"; + payload.element.style.pointerEvents = "none"; + } + const event = { + id: payload.eventId, + title: payload.title || "", + description: "", + start: payload.start, + end: payload.end, + type: "customer", + allDay: false, + syncStatus: "pending" + }; + const element = this.createEventElement(event); + let eventsLayer = payload.targetColumn.querySelector("swp-events-layer"); + if (!eventsLayer) { + eventsLayer = document.createElement("swp-events-layer"); + payload.targetColumn.appendChild(eventsLayer); + } + eventsLayer.appendChild(element); + element.classList.add("dragging"); + } + /** + * Handle EVENT_UPDATED - re-render affected columns + */ + async handleEventUpdated(payload) { + if (payload.sourceColumnKey !== payload.targetColumnKey) { + await this.rerenderColumn(payload.sourceColumnKey); + } + await this.rerenderColumn(payload.targetColumnKey); + } + /** + * Re-render a single column with fresh data from IndexedDB + */ + async rerenderColumn(columnKey) { + const column = this.findColumn(columnKey); + if (!column) + return; + const date = column.dataset.date; + const resourceId = column.dataset.resourceId; + if (!date) + return; + const startDate = new Date(date); + const endDate = new Date(date); + endDate.setHours(23, 59, 59, 999); + const events = resourceId ? await this.eventService.getByResourceAndDateRange(resourceId, startDate, endDate) : await this.eventService.getByDateRange(startDate, endDate); + const timedEvents = events.filter( + (event) => !event.allDay && this.dateService.getDateKey(event.start) === date + ); + let eventsLayer = column.querySelector("swp-events-layer"); + if (!eventsLayer) { + eventsLayer = document.createElement("swp-events-layer"); + column.appendChild(eventsLayer); + } + eventsLayer.innerHTML = ""; + const layout = calculateColumnLayout(timedEvents, this.gridConfig); + layout.grids.forEach((grid) => { + const groupEl = this.renderGridGroup(grid); + eventsLayer.appendChild(groupEl); + }); + layout.stacked.forEach((item) => { + const eventEl = this.renderStackedEvent(item.event, item.stackLevel); + eventsLayer.appendChild(eventEl); + }); + } + /** + * Find a column element by columnKey + */ + findColumn(columnKey) { + if (!this.container) + return null; + return this.container.querySelector(`swp-day-column[data-column-key="${columnKey}"]`); + } + /** + * Handle event moving to a new column during drag + */ + handleColumnChange(payload) { + const eventsLayer = payload.newColumn.querySelector("swp-events-layer"); + if (!eventsLayer) + return; + eventsLayer.appendChild(payload.element); + payload.element.style.top = `${payload.currentY}px`; + } + /** + * Update timestamp display during drag (snapped to grid) + */ + updateDragTimestamp(payload) { + const timeEl = payload.element.querySelector("swp-event-time"); + if (!timeEl) + return; + const snappedY = snapToGrid(payload.currentY, this.gridConfig); + const minutesFromGridStart = pixelsToMinutes(snappedY, this.gridConfig); + const startMinutes = this.gridConfig.dayStartHour * 60 + minutesFromGridStart; + const height = parseFloat(payload.element.style.height) || this.gridConfig.hourHeight; + const durationMinutes = pixelsToMinutes(height, this.gridConfig); + const start = this.minutesToDate(startMinutes); + const end = this.minutesToDate(startMinutes + durationMinutes); + timeEl.textContent = this.dateService.formatTimeRange(start, end); + } + /** + * Convert minutes since midnight to a Date object (today) + */ + minutesToDate(minutes) { + const date = /* @__PURE__ */ new Date(); + date.setHours(Math.floor(minutes / 60) % 24, minutes % 60, 0, 0); + return date; + } + /** + * Render events for visible dates into day columns + * @param container - Calendar container element + * @param filter - Filter with 'date' and optionally 'resource' arrays + * @param filterTemplate - Template for matching events to columns + */ + async render(container, filter, filterTemplate) { + this.container = container; + const visibleDates = filter["date"] || []; + if (visibleDates.length === 0) + return; + const startDate = new Date(visibleDates[0]); + const endDate = new Date(visibleDates[visibleDates.length - 1]); + endDate.setHours(23, 59, 59, 999); + const events = await this.eventService.getByDateRange(startDate, endDate); + const dayColumns = container.querySelector("swp-day-columns"); + if (!dayColumns) + return; + const columns = dayColumns.querySelectorAll("swp-day-column"); + columns.forEach((column) => { + const columnEl = column; + const columnEvents = events.filter((event) => filterTemplate.matches(event, columnEl)); + let eventsLayer = column.querySelector("swp-events-layer"); + if (!eventsLayer) { + eventsLayer = document.createElement("swp-events-layer"); + column.appendChild(eventsLayer); + } + eventsLayer.innerHTML = ""; + const timedEvents = columnEvents.filter((event) => !event.allDay); + const layout = calculateColumnLayout(timedEvents, this.gridConfig); + layout.grids.forEach((grid) => { + const groupEl = this.renderGridGroup(grid); + eventsLayer.appendChild(groupEl); + }); + layout.stacked.forEach((item) => { + const eventEl = this.renderStackedEvent(item.event, item.stackLevel); + eventsLayer.appendChild(eventEl); + }); + }); + } + /** + * Create a single event element + * + * CLEAN approach: + * - Only data-id for lookup + * - Visible content in innerHTML only + */ + createEventElement(event) { + const element = document.createElement("swp-event"); + element.dataset.eventId = event.id; + if (event.resourceId) { + element.dataset.resourceId = event.resourceId; + } + const position = calculateEventPosition(event.start, event.end, this.gridConfig); + element.style.top = `${position.top}px`; + element.style.height = `${position.height}px`; + const colorClass = this.getColorClass(event); + if (colorClass) { + element.classList.add(colorClass); + } + element.innerHTML = ` + ${this.dateService.formatTimeRange(event.start, event.end)} + ${this.escapeHtml(event.title)} + ${event.description ? `${this.escapeHtml(event.description)}` : ""} + `; + return element; + } + /** + * Get color class based on metadata.color or event type + */ + getColorClass(event) { + if (event.metadata?.color) { + return `is-${event.metadata.color}`; + } + const typeColors = { + "customer": "is-blue", + "vacation": "is-green", + "break": "is-amber", + "meeting": "is-purple", + "blocked": "is-red" + }; + return typeColors[event.type] || "is-blue"; + } + /** + * Escape HTML to prevent XSS + */ + escapeHtml(text) { + const div = document.createElement("div"); + div.textContent = text; + return div.innerHTML; + } + /** + * Render a GRID group with side-by-side columns + * Used when multiple events start at the same time + */ + renderGridGroup(layout) { + const group = document.createElement("swp-event-group"); + group.classList.add(`cols-${layout.columns.length}`); + group.style.top = `${layout.position.top}px`; + if (layout.stackLevel > 0) { + group.style.marginLeft = `${layout.stackLevel * 15}px`; + group.style.zIndex = `${100 + layout.stackLevel}`; + } + let maxBottom = 0; + for (const event of layout.events) { + const pos = calculateEventPosition(event.start, event.end, this.gridConfig); + const eventBottom = pos.top + pos.height; + if (eventBottom > maxBottom) + maxBottom = eventBottom; + } + const groupHeight = maxBottom - layout.position.top; + group.style.height = `${groupHeight}px`; + layout.columns.forEach((columnEvents) => { + const wrapper = document.createElement("div"); + wrapper.style.position = "relative"; + columnEvents.forEach((event) => { + const eventEl = this.createEventElement(event); + const pos = calculateEventPosition(event.start, event.end, this.gridConfig); + eventEl.style.top = `${pos.top - layout.position.top}px`; + eventEl.style.position = "absolute"; + eventEl.style.left = "0"; + eventEl.style.right = "0"; + wrapper.appendChild(eventEl); + }); + group.appendChild(wrapper); + }); + return group; + } + /** + * Render a STACKED event with margin-left offset + * Used for overlapping events that don't start at the same time + */ + renderStackedEvent(event, stackLevel) { + const element = this.createEventElement(event); + element.dataset.stackLink = JSON.stringify({ stackLevel }); + if (stackLevel > 0) { + element.style.marginLeft = `${stackLevel * 15}px`; + element.style.zIndex = `${100 + stackLevel}`; + } + return element; + } +}; +__name(_EventRenderer, "EventRenderer"); +var EventRenderer = _EventRenderer; + +// src/v2/core/BaseGroupingRenderer.ts +var _BaseGroupingRenderer = class _BaseGroupingRenderer { + /** + * Main render method - handles common logic + */ + async render(context) { + const allowedIds = context.filter[this.type] || []; + if (allowedIds.length === 0) + return; + const entities = await this.getEntities(allowedIds); + const dateCount = context.filter["date"]?.length || 1; + const childIds = context.childType ? context.filter[context.childType] || [] : []; + for (const entity of entities) { + const entityChildIds = context.parentChildMap?.[entity.id] || []; + const childCount = entityChildIds.filter((id) => childIds.includes(id)).length; + const colspan = childCount * dateCount; + const header = document.createElement(this.config.elementTag); + header.dataset[this.config.idAttribute] = entity.id; + header.style.setProperty(this.config.colspanVar, String(colspan)); + this.renderHeader(entity, header, context); + context.headerContainer.appendChild(header); + } + } + /** + * Override this method for custom header rendering + * Default: just sets textContent to display name + */ + renderHeader(entity, header, _context) { + header.textContent = this.getDisplayName(entity); + } + /** + * Helper to render a single entity header. + * Can be used by subclasses that override render() but want consistent header creation. + */ + createHeader(entity, context) { + const header = document.createElement(this.config.elementTag); + header.dataset[this.config.idAttribute] = entity.id; + this.renderHeader(entity, header, context); + return header; + } +}; +__name(_BaseGroupingRenderer, "BaseGroupingRenderer"); +var BaseGroupingRenderer = _BaseGroupingRenderer; + +// src/v2/features/resource/ResourceRenderer.ts +var _ResourceRenderer = class _ResourceRenderer extends BaseGroupingRenderer { + constructor(resourceService) { + super(); + this.resourceService = resourceService; + this.type = "resource"; + this.config = { + elementTag: "swp-resource-header", + idAttribute: "resourceId", + colspanVar: "--resource-cols" + }; + } + getEntities(ids) { + return this.resourceService.getByIds(ids); + } + getDisplayName(entity) { + return entity.displayName; + } + /** + * Override render to handle: + * 1. Special ordering when parentChildMap exists (resources grouped by parent) + * 2. Different colspan calculation (just dateCount, not childCount * dateCount) + */ + async render(context) { + const resourceIds = context.filter["resource"] || []; + const dateCount = context.filter["date"]?.length || 1; + let orderedResourceIds; + if (context.parentChildMap) { + orderedResourceIds = []; + for (const childIds of Object.values(context.parentChildMap)) { + for (const childId of childIds) { + if (resourceIds.includes(childId)) { + orderedResourceIds.push(childId); + } + } + } + } else { + orderedResourceIds = resourceIds; + } + const resources = await this.getEntities(orderedResourceIds); + const resourceMap = new Map(resources.map((r) => [r.id, r])); + for (const resourceId of orderedResourceIds) { + const resource = resourceMap.get(resourceId); + if (!resource) + continue; + const header = this.createHeader(resource, context); + header.style.gridColumn = `span ${dateCount}`; + context.headerContainer.appendChild(header); + } + } +}; +__name(_ResourceRenderer, "ResourceRenderer"); +var ResourceRenderer = _ResourceRenderer; + +// src/v2/features/team/TeamRenderer.ts +var _TeamRenderer = class _TeamRenderer extends BaseGroupingRenderer { + constructor(teamService) { + super(); + this.teamService = teamService; + this.type = "team"; + this.config = { + elementTag: "swp-team-header", + idAttribute: "teamId", + colspanVar: "--team-cols" + }; + } + getEntities(ids) { + return this.teamService.getByIds(ids); + } + getDisplayName(entity) { + return entity.name; + } +}; +__name(_TeamRenderer, "TeamRenderer"); +var TeamRenderer = _TeamRenderer; + +// src/v2/features/timeaxis/TimeAxisRenderer.ts +var _TimeAxisRenderer = class _TimeAxisRenderer { + render(container, startHour = 6, endHour = 20) { + container.innerHTML = ""; + for (let hour = startHour; hour <= endHour; hour++) { + const marker = document.createElement("swp-hour-marker"); + marker.textContent = `${hour.toString().padStart(2, "0")}:00`; + container.appendChild(marker); + } + } +}; +__name(_TimeAxisRenderer, "TimeAxisRenderer"); +var TimeAxisRenderer = _TimeAxisRenderer; +export { + CalendarOrchestrator, + DateRenderer, + DateService, + EventRenderer, + NavigationAnimator, + ResourceRenderer, + TeamRenderer, + TimeAxisRenderer, + buildPipeline +}; +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vbm9kZV9tb2R1bGVzL2RheWpzL2RheWpzLm1pbi5qcyIsICIuLi8uLi9ub2RlX21vZHVsZXMvZGF5anMvcGx1Z2luL3V0Yy5qcyIsICIuLi8uLi9ub2RlX21vZHVsZXMvZGF5anMvcGx1Z2luL3RpbWV6b25lLmpzIiwgIi4uLy4uL25vZGVfbW9kdWxlcy9kYXlqcy9wbHVnaW4vaXNvV2Vlay5qcyIsICIuLi8uLi9zcmMvdjIvY29yZS9SZW5kZXJCdWlsZGVyLnRzIiwgIi4uLy4uL3NyYy92Mi9jb3JlL0ZpbHRlclRlbXBsYXRlLnRzIiwgIi4uLy4uL3NyYy92Mi9jb3JlL0NhbGVuZGFyT3JjaGVzdHJhdG9yLnRzIiwgIi4uLy4uL3NyYy92Mi9jb3JlL05hdmlnYXRpb25BbmltYXRvci50cyIsICIuLi8uLi9zcmMvdjIvZmVhdHVyZXMvZGF0ZS9EYXRlUmVuZGVyZXIudHMiLCAiLi4vLi4vc3JjL3YyL2NvcmUvRGF0ZVNlcnZpY2UudHMiLCAiLi4vLi4vc3JjL3YyL3V0aWxzL1Bvc2l0aW9uVXRpbHMudHMiLCAiLi4vLi4vc3JjL3YyL2NvbnN0YW50cy9Db3JlRXZlbnRzLnRzIiwgIi4uLy4uL3NyYy92Mi9mZWF0dXJlcy9ldmVudC9FdmVudExheW91dEVuZ2luZS50cyIsICIuLi8uLi9zcmMvdjIvZmVhdHVyZXMvZXZlbnQvRXZlbnRSZW5kZXJlci50cyIsICIuLi8uLi9zcmMvdjIvY29yZS9CYXNlR3JvdXBpbmdSZW5kZXJlci50cyIsICIuLi8uLi9zcmMvdjIvZmVhdHVyZXMvcmVzb3VyY2UvUmVzb3VyY2VSZW5kZXJlci50cyIsICIuLi8uLi9zcmMvdjIvZmVhdHVyZXMvdGVhbS9UZWFtUmVuZGVyZXIudHMiLCAiLi4vLi4vc3JjL3YyL2ZlYXR1cmVzL3RpbWVheGlzL1RpbWVBeGlzUmVuZGVyZXIudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbIiFmdW5jdGlvbih0LGUpe1wib2JqZWN0XCI9PXR5cGVvZiBleHBvcnRzJiZcInVuZGVmaW5lZFwiIT10eXBlb2YgbW9kdWxlP21vZHVsZS5leHBvcnRzPWUoKTpcImZ1bmN0aW9uXCI9PXR5cGVvZiBkZWZpbmUmJmRlZmluZS5hbWQ/ZGVmaW5lKGUpOih0PVwidW5kZWZpbmVkXCIhPXR5cGVvZiBnbG9iYWxUaGlzP2dsb2JhbFRoaXM6dHx8c2VsZikuZGF5anM9ZSgpfSh0aGlzLChmdW5jdGlvbigpe1widXNlIHN0cmljdFwiO3ZhciB0PTFlMyxlPTZlNCxuPTM2ZTUscj1cIm1pbGxpc2Vjb25kXCIsaT1cInNlY29uZFwiLHM9XCJtaW51dGVcIix1PVwiaG91clwiLGE9XCJkYXlcIixvPVwid2Vla1wiLGM9XCJtb250aFwiLGY9XCJxdWFydGVyXCIsaD1cInllYXJcIixkPVwiZGF0ZVwiLGw9XCJJbnZhbGlkIERhdGVcIiwkPS9eKFxcZHs0fSlbLS9dPyhcXGR7MSwyfSk/Wy0vXT8oXFxkezAsMn0pW1R0XFxzXSooXFxkezEsMn0pPzo/KFxcZHsxLDJ9KT86PyhcXGR7MSwyfSk/Wy46XT8oXFxkKyk/JC8seT0vXFxbKFteXFxdXSspXXxZezEsNH18TXsxLDR9fER7MSwyfXxkezEsNH18SHsxLDJ9fGh7MSwyfXxhfEF8bXsxLDJ9fHN7MSwyfXxaezEsMn18U1NTL2csTT17bmFtZTpcImVuXCIsd2Vla2RheXM6XCJTdW5kYXlfTW9uZGF5X1R1ZXNkYXlfV2VkbmVzZGF5X1RodXJzZGF5X0ZyaWRheV9TYXR1cmRheVwiLnNwbGl0KFwiX1wiKSxtb250aHM6XCJKYW51YXJ5X0ZlYnJ1YXJ5X01hcmNoX0FwcmlsX01heV9KdW5lX0p1bHlfQXVndXN0X1NlcHRlbWJlcl9PY3RvYmVyX05vdmVtYmVyX0RlY2VtYmVyXCIuc3BsaXQoXCJfXCIpLG9yZGluYWw6ZnVuY3Rpb24odCl7dmFyIGU9W1widGhcIixcInN0XCIsXCJuZFwiLFwicmRcIl0sbj10JTEwMDtyZXR1cm5cIltcIit0KyhlWyhuLTIwKSUxMF18fGVbbl18fGVbMF0pK1wiXVwifX0sbT1mdW5jdGlvbih0LGUsbil7dmFyIHI9U3RyaW5nKHQpO3JldHVybiFyfHxyLmxlbmd0aD49ZT90OlwiXCIrQXJyYXkoZSsxLXIubGVuZ3RoKS5qb2luKG4pK3R9LHY9e3M6bSx6OmZ1bmN0aW9uKHQpe3ZhciBlPS10LnV0Y09mZnNldCgpLG49TWF0aC5hYnMoZSkscj1NYXRoLmZsb29yKG4vNjApLGk9biU2MDtyZXR1cm4oZTw9MD9cIitcIjpcIi1cIikrbShyLDIsXCIwXCIpK1wiOlwiK20oaSwyLFwiMFwiKX0sbTpmdW5jdGlvbiB0KGUsbil7aWYoZS5kYXRlKCk8bi5kYXRlKCkpcmV0dXJuLXQobixlKTt2YXIgcj0xMioobi55ZWFyKCktZS55ZWFyKCkpKyhuLm1vbnRoKCktZS5tb250aCgpKSxpPWUuY2xvbmUoKS5hZGQocixjKSxzPW4taTwwLHU9ZS5jbG9uZSgpLmFkZChyKyhzPy0xOjEpLGMpO3JldHVybisoLShyKyhuLWkpLyhzP2ktdTp1LWkpKXx8MCl9LGE6ZnVuY3Rpb24odCl7cmV0dXJuIHQ8MD9NYXRoLmNlaWwodCl8fDA6TWF0aC5mbG9vcih0KX0scDpmdW5jdGlvbih0KXtyZXR1cm57TTpjLHk6aCx3Om8sZDphLEQ6ZCxoOnUsbTpzLHM6aSxtczpyLFE6Zn1bdF18fFN0cmluZyh0fHxcIlwiKS50b0xvd2VyQ2FzZSgpLnJlcGxhY2UoL3MkLyxcIlwiKX0sdTpmdW5jdGlvbih0KXtyZXR1cm4gdm9pZCAwPT09dH19LGc9XCJlblwiLEQ9e307RFtnXT1NO3ZhciBwPVwiJGlzRGF5anNPYmplY3RcIixTPWZ1bmN0aW9uKHQpe3JldHVybiB0IGluc3RhbmNlb2YgX3x8ISghdHx8IXRbcF0pfSx3PWZ1bmN0aW9uIHQoZSxuLHIpe3ZhciBpO2lmKCFlKXJldHVybiBnO2lmKFwic3RyaW5nXCI9PXR5cGVvZiBlKXt2YXIgcz1lLnRvTG93ZXJDYXNlKCk7RFtzXSYmKGk9cyksbiYmKERbc109bixpPXMpO3ZhciB1PWUuc3BsaXQoXCItXCIpO2lmKCFpJiZ1Lmxlbmd0aD4xKXJldHVybiB0KHVbMF0pfWVsc2V7dmFyIGE9ZS5uYW1lO0RbYV09ZSxpPWF9cmV0dXJuIXImJmkmJihnPWkpLGl8fCFyJiZnfSxPPWZ1bmN0aW9uKHQsZSl7aWYoUyh0KSlyZXR1cm4gdC5jbG9uZSgpO3ZhciBuPVwib2JqZWN0XCI9PXR5cGVvZiBlP2U6e307cmV0dXJuIG4uZGF0ZT10LG4uYXJncz1hcmd1bWVudHMsbmV3IF8obil9LGI9djtiLmw9dyxiLmk9UyxiLnc9ZnVuY3Rpb24odCxlKXtyZXR1cm4gTyh0LHtsb2NhbGU6ZS4kTCx1dGM6ZS4kdSx4OmUuJHgsJG9mZnNldDplLiRvZmZzZXR9KX07dmFyIF89ZnVuY3Rpb24oKXtmdW5jdGlvbiBNKHQpe3RoaXMuJEw9dyh0LmxvY2FsZSxudWxsLCEwKSx0aGlzLnBhcnNlKHQpLHRoaXMuJHg9dGhpcy4keHx8dC54fHx7fSx0aGlzW3BdPSEwfXZhciBtPU0ucHJvdG90eXBlO3JldHVybiBtLnBhcnNlPWZ1bmN0aW9uKHQpe3RoaXMuJGQ9ZnVuY3Rpb24odCl7dmFyIGU9dC5kYXRlLG49dC51dGM7aWYobnVsbD09PWUpcmV0dXJuIG5ldyBEYXRlKE5hTik7aWYoYi51KGUpKXJldHVybiBuZXcgRGF0ZTtpZihlIGluc3RhbmNlb2YgRGF0ZSlyZXR1cm4gbmV3IERhdGUoZSk7aWYoXCJzdHJpbmdcIj09dHlwZW9mIGUmJiEvWiQvaS50ZXN0KGUpKXt2YXIgcj1lLm1hdGNoKCQpO2lmKHIpe3ZhciBpPXJbMl0tMXx8MCxzPShyWzddfHxcIjBcIikuc3Vic3RyaW5nKDAsMyk7cmV0dXJuIG4/bmV3IERhdGUoRGF0ZS5VVEMoclsxXSxpLHJbM118fDEscls0XXx8MCxyWzVdfHwwLHJbNl18fDAscykpOm5ldyBEYXRlKHJbMV0saSxyWzNdfHwxLHJbNF18fDAscls1XXx8MCxyWzZdfHwwLHMpfX1yZXR1cm4gbmV3IERhdGUoZSl9KHQpLHRoaXMuaW5pdCgpfSxtLmluaXQ9ZnVuY3Rpb24oKXt2YXIgdD10aGlzLiRkO3RoaXMuJHk9dC5nZXRGdWxsWWVhcigpLHRoaXMuJE09dC5nZXRNb250aCgpLHRoaXMuJEQ9dC5nZXREYXRlKCksdGhpcy4kVz10LmdldERheSgpLHRoaXMuJEg9dC5nZXRIb3VycygpLHRoaXMuJG09dC5nZXRNaW51dGVzKCksdGhpcy4kcz10LmdldFNlY29uZHMoKSx0aGlzLiRtcz10LmdldE1pbGxpc2Vjb25kcygpfSxtLiR1dGlscz1mdW5jdGlvbigpe3JldHVybiBifSxtLmlzVmFsaWQ9ZnVuY3Rpb24oKXtyZXR1cm4hKHRoaXMuJGQudG9TdHJpbmcoKT09PWwpfSxtLmlzU2FtZT1mdW5jdGlvbih0LGUpe3ZhciBuPU8odCk7cmV0dXJuIHRoaXMuc3RhcnRPZihlKTw9biYmbjw9dGhpcy5lbmRPZihlKX0sbS5pc0FmdGVyPWZ1bmN0aW9uKHQsZSl7cmV0dXJuIE8odCk8dGhpcy5zdGFydE9mKGUpfSxtLmlzQmVmb3JlPWZ1bmN0aW9uKHQsZSl7cmV0dXJuIHRoaXMuZW5kT2YoZSk8Tyh0KX0sbS4kZz1mdW5jdGlvbih0LGUsbil7cmV0dXJuIGIudSh0KT90aGlzW2VdOnRoaXMuc2V0KG4sdCl9LG0udW5peD1mdW5jdGlvbigpe3JldHVybiBNYXRoLmZsb29yKHRoaXMudmFsdWVPZigpLzFlMyl9LG0udmFsdWVPZj1mdW5jdGlvbigpe3JldHVybiB0aGlzLiRkLmdldFRpbWUoKX0sbS5zdGFydE9mPWZ1bmN0aW9uKHQsZSl7dmFyIG49dGhpcyxyPSEhYi51KGUpfHxlLGY9Yi5wKHQpLGw9ZnVuY3Rpb24odCxlKXt2YXIgaT1iLncobi4kdT9EYXRlLlVUQyhuLiR5LGUsdCk6bmV3IERhdGUobi4keSxlLHQpLG4pO3JldHVybiByP2k6aS5lbmRPZihhKX0sJD1mdW5jdGlvbih0LGUpe3JldHVybiBiLncobi50b0RhdGUoKVt0XS5hcHBseShuLnRvRGF0ZShcInNcIiksKHI/WzAsMCwwLDBdOlsyMyw1OSw1OSw5OTldKS5zbGljZShlKSksbil9LHk9dGhpcy4kVyxNPXRoaXMuJE0sbT10aGlzLiRELHY9XCJzZXRcIisodGhpcy4kdT9cIlVUQ1wiOlwiXCIpO3N3aXRjaChmKXtjYXNlIGg6cmV0dXJuIHI/bCgxLDApOmwoMzEsMTEpO2Nhc2UgYzpyZXR1cm4gcj9sKDEsTSk6bCgwLE0rMSk7Y2FzZSBvOnZhciBnPXRoaXMuJGxvY2FsZSgpLndlZWtTdGFydHx8MCxEPSh5PGc/eSs3OnkpLWc7cmV0dXJuIGwocj9tLUQ6bSsoNi1EKSxNKTtjYXNlIGE6Y2FzZSBkOnJldHVybiAkKHYrXCJIb3Vyc1wiLDApO2Nhc2UgdTpyZXR1cm4gJCh2K1wiTWludXRlc1wiLDEpO2Nhc2UgczpyZXR1cm4gJCh2K1wiU2Vjb25kc1wiLDIpO2Nhc2UgaTpyZXR1cm4gJCh2K1wiTWlsbGlzZWNvbmRzXCIsMyk7ZGVmYXVsdDpyZXR1cm4gdGhpcy5jbG9uZSgpfX0sbS5lbmRPZj1mdW5jdGlvbih0KXtyZXR1cm4gdGhpcy5zdGFydE9mKHQsITEpfSxtLiRzZXQ9ZnVuY3Rpb24odCxlKXt2YXIgbixvPWIucCh0KSxmPVwic2V0XCIrKHRoaXMuJHU/XCJVVENcIjpcIlwiKSxsPShuPXt9LG5bYV09ZitcIkRhdGVcIixuW2RdPWYrXCJEYXRlXCIsbltjXT1mK1wiTW9udGhcIixuW2hdPWYrXCJGdWxsWWVhclwiLG5bdV09ZitcIkhvdXJzXCIsbltzXT1mK1wiTWludXRlc1wiLG5baV09ZitcIlNlY29uZHNcIixuW3JdPWYrXCJNaWxsaXNlY29uZHNcIixuKVtvXSwkPW89PT1hP3RoaXMuJEQrKGUtdGhpcy4kVyk6ZTtpZihvPT09Y3x8bz09PWgpe3ZhciB5PXRoaXMuY2xvbmUoKS5zZXQoZCwxKTt5LiRkW2xdKCQpLHkuaW5pdCgpLHRoaXMuJGQ9eS5zZXQoZCxNYXRoLm1pbih0aGlzLiRELHkuZGF5c0luTW9udGgoKSkpLiRkfWVsc2UgbCYmdGhpcy4kZFtsXSgkKTtyZXR1cm4gdGhpcy5pbml0KCksdGhpc30sbS5zZXQ9ZnVuY3Rpb24odCxlKXtyZXR1cm4gdGhpcy5jbG9uZSgpLiRzZXQodCxlKX0sbS5nZXQ9ZnVuY3Rpb24odCl7cmV0dXJuIHRoaXNbYi5wKHQpXSgpfSxtLmFkZD1mdW5jdGlvbihyLGYpe3ZhciBkLGw9dGhpcztyPU51bWJlcihyKTt2YXIgJD1iLnAoZikseT1mdW5jdGlvbih0KXt2YXIgZT1PKGwpO3JldHVybiBiLncoZS5kYXRlKGUuZGF0ZSgpK01hdGgucm91bmQodCpyKSksbCl9O2lmKCQ9PT1jKXJldHVybiB0aGlzLnNldChjLHRoaXMuJE0rcik7aWYoJD09PWgpcmV0dXJuIHRoaXMuc2V0KGgsdGhpcy4keStyKTtpZigkPT09YSlyZXR1cm4geSgxKTtpZigkPT09bylyZXR1cm4geSg3KTt2YXIgTT0oZD17fSxkW3NdPWUsZFt1XT1uLGRbaV09dCxkKVskXXx8MSxtPXRoaXMuJGQuZ2V0VGltZSgpK3IqTTtyZXR1cm4gYi53KG0sdGhpcyl9LG0uc3VidHJhY3Q9ZnVuY3Rpb24odCxlKXtyZXR1cm4gdGhpcy5hZGQoLTEqdCxlKX0sbS5mb3JtYXQ9ZnVuY3Rpb24odCl7dmFyIGU9dGhpcyxuPXRoaXMuJGxvY2FsZSgpO2lmKCF0aGlzLmlzVmFsaWQoKSlyZXR1cm4gbi5pbnZhbGlkRGF0ZXx8bDt2YXIgcj10fHxcIllZWVktTU0tRERUSEg6bW06c3NaXCIsaT1iLnoodGhpcykscz10aGlzLiRILHU9dGhpcy4kbSxhPXRoaXMuJE0sbz1uLndlZWtkYXlzLGM9bi5tb250aHMsZj1uLm1lcmlkaWVtLGg9ZnVuY3Rpb24odCxuLGkscyl7cmV0dXJuIHQmJih0W25dfHx0KGUscikpfHxpW25dLnNsaWNlKDAscyl9LGQ9ZnVuY3Rpb24odCl7cmV0dXJuIGIucyhzJTEyfHwxMix0LFwiMFwiKX0sJD1mfHxmdW5jdGlvbih0LGUsbil7dmFyIHI9dDwxMj9cIkFNXCI6XCJQTVwiO3JldHVybiBuP3IudG9Mb3dlckNhc2UoKTpyfTtyZXR1cm4gci5yZXBsYWNlKHksKGZ1bmN0aW9uKHQscil7cmV0dXJuIHJ8fGZ1bmN0aW9uKHQpe3N3aXRjaCh0KXtjYXNlXCJZWVwiOnJldHVybiBTdHJpbmcoZS4keSkuc2xpY2UoLTIpO2Nhc2VcIllZWVlcIjpyZXR1cm4gYi5zKGUuJHksNCxcIjBcIik7Y2FzZVwiTVwiOnJldHVybiBhKzE7Y2FzZVwiTU1cIjpyZXR1cm4gYi5zKGErMSwyLFwiMFwiKTtjYXNlXCJNTU1cIjpyZXR1cm4gaChuLm1vbnRoc1Nob3J0LGEsYywzKTtjYXNlXCJNTU1NXCI6cmV0dXJuIGgoYyxhKTtjYXNlXCJEXCI6cmV0dXJuIGUuJEQ7Y2FzZVwiRERcIjpyZXR1cm4gYi5zKGUuJEQsMixcIjBcIik7Y2FzZVwiZFwiOnJldHVybiBTdHJpbmcoZS4kVyk7Y2FzZVwiZGRcIjpyZXR1cm4gaChuLndlZWtkYXlzTWluLGUuJFcsbywyKTtjYXNlXCJkZGRcIjpyZXR1cm4gaChuLndlZWtkYXlzU2hvcnQsZS4kVyxvLDMpO2Nhc2VcImRkZGRcIjpyZXR1cm4gb1tlLiRXXTtjYXNlXCJIXCI6cmV0dXJuIFN0cmluZyhzKTtjYXNlXCJISFwiOnJldHVybiBiLnMocywyLFwiMFwiKTtjYXNlXCJoXCI6cmV0dXJuIGQoMSk7Y2FzZVwiaGhcIjpyZXR1cm4gZCgyKTtjYXNlXCJhXCI6cmV0dXJuICQocyx1LCEwKTtjYXNlXCJBXCI6cmV0dXJuICQocyx1LCExKTtjYXNlXCJtXCI6cmV0dXJuIFN0cmluZyh1KTtjYXNlXCJtbVwiOnJldHVybiBiLnModSwyLFwiMFwiKTtjYXNlXCJzXCI6cmV0dXJuIFN0cmluZyhlLiRzKTtjYXNlXCJzc1wiOnJldHVybiBiLnMoZS4kcywyLFwiMFwiKTtjYXNlXCJTU1NcIjpyZXR1cm4gYi5zKGUuJG1zLDMsXCIwXCIpO2Nhc2VcIlpcIjpyZXR1cm4gaX1yZXR1cm4gbnVsbH0odCl8fGkucmVwbGFjZShcIjpcIixcIlwiKX0pKX0sbS51dGNPZmZzZXQ9ZnVuY3Rpb24oKXtyZXR1cm4gMTUqLU1hdGgucm91bmQodGhpcy4kZC5nZXRUaW1lem9uZU9mZnNldCgpLzE1KX0sbS5kaWZmPWZ1bmN0aW9uKHIsZCxsKXt2YXIgJCx5PXRoaXMsTT1iLnAoZCksbT1PKHIpLHY9KG0udXRjT2Zmc2V0KCktdGhpcy51dGNPZmZzZXQoKSkqZSxnPXRoaXMtbSxEPWZ1bmN0aW9uKCl7cmV0dXJuIGIubSh5LG0pfTtzd2l0Y2goTSl7Y2FzZSBoOiQ9RCgpLzEyO2JyZWFrO2Nhc2UgYzokPUQoKTticmVhaztjYXNlIGY6JD1EKCkvMzticmVhaztjYXNlIG86JD0oZy12KS82MDQ4ZTU7YnJlYWs7Y2FzZSBhOiQ9KGctdikvODY0ZTU7YnJlYWs7Y2FzZSB1OiQ9Zy9uO2JyZWFrO2Nhc2UgczokPWcvZTticmVhaztjYXNlIGk6JD1nL3Q7YnJlYWs7ZGVmYXVsdDokPWd9cmV0dXJuIGw/JDpiLmEoJCl9LG0uZGF5c0luTW9udGg9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5lbmRPZihjKS4kRH0sbS4kbG9jYWxlPWZ1bmN0aW9uKCl7cmV0dXJuIERbdGhpcy4kTF19LG0ubG9jYWxlPWZ1bmN0aW9uKHQsZSl7aWYoIXQpcmV0dXJuIHRoaXMuJEw7dmFyIG49dGhpcy5jbG9uZSgpLHI9dyh0LGUsITApO3JldHVybiByJiYobi4kTD1yKSxufSxtLmNsb25lPWZ1bmN0aW9uKCl7cmV0dXJuIGIudyh0aGlzLiRkLHRoaXMpfSxtLnRvRGF0ZT1mdW5jdGlvbigpe3JldHVybiBuZXcgRGF0ZSh0aGlzLnZhbHVlT2YoKSl9LG0udG9KU09OPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuaXNWYWxpZCgpP3RoaXMudG9JU09TdHJpbmcoKTpudWxsfSxtLnRvSVNPU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuJGQudG9JU09TdHJpbmcoKX0sbS50b1N0cmluZz1mdW5jdGlvbigpe3JldHVybiB0aGlzLiRkLnRvVVRDU3RyaW5nKCl9LE19KCksaz1fLnByb3RvdHlwZTtyZXR1cm4gTy5wcm90b3R5cGU9ayxbW1wiJG1zXCIscl0sW1wiJHNcIixpXSxbXCIkbVwiLHNdLFtcIiRIXCIsdV0sW1wiJFdcIixhXSxbXCIkTVwiLGNdLFtcIiR5XCIsaF0sW1wiJERcIixkXV0uZm9yRWFjaCgoZnVuY3Rpb24odCl7a1t0WzFdXT1mdW5jdGlvbihlKXtyZXR1cm4gdGhpcy4kZyhlLHRbMF0sdFsxXSl9fSkpLE8uZXh0ZW5kPWZ1bmN0aW9uKHQsZSl7cmV0dXJuIHQuJGl8fCh0KGUsXyxPKSx0LiRpPSEwKSxPfSxPLmxvY2FsZT13LE8uaXNEYXlqcz1TLE8udW5peD1mdW5jdGlvbih0KXtyZXR1cm4gTygxZTMqdCl9LE8uZW49RFtnXSxPLkxzPUQsTy5wPXt9LE99KSk7IiwgIiFmdW5jdGlvbih0LGkpe1wib2JqZWN0XCI9PXR5cGVvZiBleHBvcnRzJiZcInVuZGVmaW5lZFwiIT10eXBlb2YgbW9kdWxlP21vZHVsZS5leHBvcnRzPWkoKTpcImZ1bmN0aW9uXCI9PXR5cGVvZiBkZWZpbmUmJmRlZmluZS5hbWQ/ZGVmaW5lKGkpOih0PVwidW5kZWZpbmVkXCIhPXR5cGVvZiBnbG9iYWxUaGlzP2dsb2JhbFRoaXM6dHx8c2VsZikuZGF5anNfcGx1Z2luX3V0Yz1pKCl9KHRoaXMsKGZ1bmN0aW9uKCl7XCJ1c2Ugc3RyaWN0XCI7dmFyIHQ9XCJtaW51dGVcIixpPS9bKy1dXFxkXFxkKD86Oj9cXGRcXGQpPy9nLGU9LyhbKy1dfFxcZFxcZCkvZztyZXR1cm4gZnVuY3Rpb24ocyxmLG4pe3ZhciB1PWYucHJvdG90eXBlO24udXRjPWZ1bmN0aW9uKHQpe3ZhciBpPXtkYXRlOnQsdXRjOiEwLGFyZ3M6YXJndW1lbnRzfTtyZXR1cm4gbmV3IGYoaSl9LHUudXRjPWZ1bmN0aW9uKGkpe3ZhciBlPW4odGhpcy50b0RhdGUoKSx7bG9jYWxlOnRoaXMuJEwsdXRjOiEwfSk7cmV0dXJuIGk/ZS5hZGQodGhpcy51dGNPZmZzZXQoKSx0KTplfSx1LmxvY2FsPWZ1bmN0aW9uKCl7cmV0dXJuIG4odGhpcy50b0RhdGUoKSx7bG9jYWxlOnRoaXMuJEwsdXRjOiExfSl9O3ZhciByPXUucGFyc2U7dS5wYXJzZT1mdW5jdGlvbih0KXt0LnV0YyYmKHRoaXMuJHU9ITApLHRoaXMuJHV0aWxzKCkudSh0LiRvZmZzZXQpfHwodGhpcy4kb2Zmc2V0PXQuJG9mZnNldCksci5jYWxsKHRoaXMsdCl9O3ZhciBvPXUuaW5pdDt1LmluaXQ9ZnVuY3Rpb24oKXtpZih0aGlzLiR1KXt2YXIgdD10aGlzLiRkO3RoaXMuJHk9dC5nZXRVVENGdWxsWWVhcigpLHRoaXMuJE09dC5nZXRVVENNb250aCgpLHRoaXMuJEQ9dC5nZXRVVENEYXRlKCksdGhpcy4kVz10LmdldFVUQ0RheSgpLHRoaXMuJEg9dC5nZXRVVENIb3VycygpLHRoaXMuJG09dC5nZXRVVENNaW51dGVzKCksdGhpcy4kcz10LmdldFVUQ1NlY29uZHMoKSx0aGlzLiRtcz10LmdldFVUQ01pbGxpc2Vjb25kcygpfWVsc2Ugby5jYWxsKHRoaXMpfTt2YXIgYT11LnV0Y09mZnNldDt1LnV0Y09mZnNldD1mdW5jdGlvbihzLGYpe3ZhciBuPXRoaXMuJHV0aWxzKCkudTtpZihuKHMpKXJldHVybiB0aGlzLiR1PzA6bih0aGlzLiRvZmZzZXQpP2EuY2FsbCh0aGlzKTp0aGlzLiRvZmZzZXQ7aWYoXCJzdHJpbmdcIj09dHlwZW9mIHMmJihzPWZ1bmN0aW9uKHQpe3ZvaWQgMD09PXQmJih0PVwiXCIpO3ZhciBzPXQubWF0Y2goaSk7aWYoIXMpcmV0dXJuIG51bGw7dmFyIGY9KFwiXCIrc1swXSkubWF0Y2goZSl8fFtcIi1cIiwwLDBdLG49ZlswXSx1PTYwKitmWzFdKyArZlsyXTtyZXR1cm4gMD09PXU/MDpcIitcIj09PW4/dTotdX0ocyksbnVsbD09PXMpKXJldHVybiB0aGlzO3ZhciB1PU1hdGguYWJzKHMpPD0xNj82MCpzOnM7aWYoMD09PXUpcmV0dXJuIHRoaXMudXRjKGYpO3ZhciByPXRoaXMuY2xvbmUoKTtpZihmKXJldHVybiByLiRvZmZzZXQ9dSxyLiR1PSExLHI7dmFyIG89dGhpcy4kdT90aGlzLnRvRGF0ZSgpLmdldFRpbWV6b25lT2Zmc2V0KCk6LTEqdGhpcy51dGNPZmZzZXQoKTtyZXR1cm4ocj10aGlzLmxvY2FsKCkuYWRkKHUrbyx0KSkuJG9mZnNldD11LHIuJHguJGxvY2FsT2Zmc2V0PW8scn07dmFyIGg9dS5mb3JtYXQ7dS5mb3JtYXQ9ZnVuY3Rpb24odCl7dmFyIGk9dHx8KHRoaXMuJHU/XCJZWVlZLU1NLUREVEhIOm1tOnNzW1pdXCI6XCJcIik7cmV0dXJuIGguY2FsbCh0aGlzLGkpfSx1LnZhbHVlT2Y9ZnVuY3Rpb24oKXt2YXIgdD10aGlzLiR1dGlscygpLnUodGhpcy4kb2Zmc2V0KT8wOnRoaXMuJG9mZnNldCsodGhpcy4keC4kbG9jYWxPZmZzZXR8fHRoaXMuJGQuZ2V0VGltZXpvbmVPZmZzZXQoKSk7cmV0dXJuIHRoaXMuJGQudmFsdWVPZigpLTZlNCp0fSx1LmlzVVRDPWZ1bmN0aW9uKCl7cmV0dXJuISF0aGlzLiR1fSx1LnRvSVNPU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMudG9EYXRlKCkudG9JU09TdHJpbmcoKX0sdS50b1N0cmluZz1mdW5jdGlvbigpe3JldHVybiB0aGlzLnRvRGF0ZSgpLnRvVVRDU3RyaW5nKCl9O3ZhciBsPXUudG9EYXRlO3UudG9EYXRlPWZ1bmN0aW9uKHQpe3JldHVyblwic1wiPT09dCYmdGhpcy4kb2Zmc2V0P24odGhpcy5mb3JtYXQoXCJZWVlZLU1NLUREIEhIOm1tOnNzOlNTU1wiKSkudG9EYXRlKCk6bC5jYWxsKHRoaXMpfTt2YXIgYz11LmRpZmY7dS5kaWZmPWZ1bmN0aW9uKHQsaSxlKXtpZih0JiZ0aGlzLiR1PT09dC4kdSlyZXR1cm4gYy5jYWxsKHRoaXMsdCxpLGUpO3ZhciBzPXRoaXMubG9jYWwoKSxmPW4odCkubG9jYWwoKTtyZXR1cm4gYy5jYWxsKHMsZixpLGUpfX19KSk7IiwgIiFmdW5jdGlvbih0LGUpe1wib2JqZWN0XCI9PXR5cGVvZiBleHBvcnRzJiZcInVuZGVmaW5lZFwiIT10eXBlb2YgbW9kdWxlP21vZHVsZS5leHBvcnRzPWUoKTpcImZ1bmN0aW9uXCI9PXR5cGVvZiBkZWZpbmUmJmRlZmluZS5hbWQ/ZGVmaW5lKGUpOih0PVwidW5kZWZpbmVkXCIhPXR5cGVvZiBnbG9iYWxUaGlzP2dsb2JhbFRoaXM6dHx8c2VsZikuZGF5anNfcGx1Z2luX3RpbWV6b25lPWUoKX0odGhpcywoZnVuY3Rpb24oKXtcInVzZSBzdHJpY3RcIjt2YXIgdD17eWVhcjowLG1vbnRoOjEsZGF5OjIsaG91cjozLG1pbnV0ZTo0LHNlY29uZDo1fSxlPXt9O3JldHVybiBmdW5jdGlvbihuLGksbyl7dmFyIHIsYT1mdW5jdGlvbih0LG4saSl7dm9pZCAwPT09aSYmKGk9e30pO3ZhciBvPW5ldyBEYXRlKHQpLHI9ZnVuY3Rpb24odCxuKXt2b2lkIDA9PT1uJiYobj17fSk7dmFyIGk9bi50aW1lWm9uZU5hbWV8fFwic2hvcnRcIixvPXQrXCJ8XCIraSxyPWVbb107cmV0dXJuIHJ8fChyPW5ldyBJbnRsLkRhdGVUaW1lRm9ybWF0KFwiZW4tVVNcIix7aG91cjEyOiExLHRpbWVab25lOnQseWVhcjpcIm51bWVyaWNcIixtb250aDpcIjItZGlnaXRcIixkYXk6XCIyLWRpZ2l0XCIsaG91cjpcIjItZGlnaXRcIixtaW51dGU6XCIyLWRpZ2l0XCIsc2Vjb25kOlwiMi1kaWdpdFwiLHRpbWVab25lTmFtZTppfSksZVtvXT1yKSxyfShuLGkpO3JldHVybiByLmZvcm1hdFRvUGFydHMobyl9LHU9ZnVuY3Rpb24oZSxuKXtmb3IodmFyIGk9YShlLG4pLHI9W10sdT0wO3U8aS5sZW5ndGg7dSs9MSl7dmFyIGY9aVt1XSxzPWYudHlwZSxtPWYudmFsdWUsYz10W3NdO2M+PTAmJihyW2NdPXBhcnNlSW50KG0sMTApKX12YXIgZD1yWzNdLGw9MjQ9PT1kPzA6ZCxoPXJbMF0rXCItXCIrclsxXStcIi1cIityWzJdK1wiIFwiK2wrXCI6XCIrcls0XStcIjpcIityWzVdK1wiOjAwMFwiLHY9K2U7cmV0dXJuKG8udXRjKGgpLnZhbHVlT2YoKS0odi09diUxZTMpKS82ZTR9LGY9aS5wcm90b3R5cGU7Zi50ej1mdW5jdGlvbih0LGUpe3ZvaWQgMD09PXQmJih0PXIpO3ZhciBuLGk9dGhpcy51dGNPZmZzZXQoKSxhPXRoaXMudG9EYXRlKCksdT1hLnRvTG9jYWxlU3RyaW5nKFwiZW4tVVNcIix7dGltZVpvbmU6dH0pLGY9TWF0aC5yb3VuZCgoYS1uZXcgRGF0ZSh1KSkvMWUzLzYwKSxzPTE1Ki1NYXRoLnJvdW5kKGEuZ2V0VGltZXpvbmVPZmZzZXQoKS8xNSktZjtpZighTnVtYmVyKHMpKW49dGhpcy51dGNPZmZzZXQoMCxlKTtlbHNlIGlmKG49byh1LHtsb2NhbGU6dGhpcy4kTH0pLiRzZXQoXCJtaWxsaXNlY29uZFwiLHRoaXMuJG1zKS51dGNPZmZzZXQocywhMCksZSl7dmFyIG09bi51dGNPZmZzZXQoKTtuPW4uYWRkKGktbSxcIm1pbnV0ZVwiKX1yZXR1cm4gbi4keC4kdGltZXpvbmU9dCxufSxmLm9mZnNldE5hbWU9ZnVuY3Rpb24odCl7dmFyIGU9dGhpcy4keC4kdGltZXpvbmV8fG8udHouZ3Vlc3MoKSxuPWEodGhpcy52YWx1ZU9mKCksZSx7dGltZVpvbmVOYW1lOnR9KS5maW5kKChmdW5jdGlvbih0KXtyZXR1cm5cInRpbWV6b25lbmFtZVwiPT09dC50eXBlLnRvTG93ZXJDYXNlKCl9KSk7cmV0dXJuIG4mJm4udmFsdWV9O3ZhciBzPWYuc3RhcnRPZjtmLnN0YXJ0T2Y9ZnVuY3Rpb24odCxlKXtpZighdGhpcy4keHx8IXRoaXMuJHguJHRpbWV6b25lKXJldHVybiBzLmNhbGwodGhpcyx0LGUpO3ZhciBuPW8odGhpcy5mb3JtYXQoXCJZWVlZLU1NLUREIEhIOm1tOnNzOlNTU1wiKSx7bG9jYWxlOnRoaXMuJEx9KTtyZXR1cm4gcy5jYWxsKG4sdCxlKS50eih0aGlzLiR4LiR0aW1lem9uZSwhMCl9LG8udHo9ZnVuY3Rpb24odCxlLG4pe3ZhciBpPW4mJmUsYT1ufHxlfHxyLGY9dSgrbygpLGEpO2lmKFwic3RyaW5nXCIhPXR5cGVvZiB0KXJldHVybiBvKHQpLnR6KGEpO3ZhciBzPWZ1bmN0aW9uKHQsZSxuKXt2YXIgaT10LTYwKmUqMWUzLG89dShpLG4pO2lmKGU9PT1vKXJldHVybltpLGVdO3ZhciByPXUoaS09NjAqKG8tZSkqMWUzLG4pO3JldHVybiBvPT09cj9baSxvXTpbdC02MCpNYXRoLm1pbihvLHIpKjFlMyxNYXRoLm1heChvLHIpXX0oby51dGModCxpKS52YWx1ZU9mKCksZixhKSxtPXNbMF0sYz1zWzFdLGQ9byhtKS51dGNPZmZzZXQoYyk7cmV0dXJuIGQuJHguJHRpbWV6b25lPWEsZH0sby50ei5ndWVzcz1mdW5jdGlvbigpe3JldHVybiBJbnRsLkRhdGVUaW1lRm9ybWF0KCkucmVzb2x2ZWRPcHRpb25zKCkudGltZVpvbmV9LG8udHouc2V0RGVmYXVsdD1mdW5jdGlvbih0KXtyPXR9fX0pKTsiLCAiIWZ1bmN0aW9uKGUsdCl7XCJvYmplY3RcIj09dHlwZW9mIGV4cG9ydHMmJlwidW5kZWZpbmVkXCIhPXR5cGVvZiBtb2R1bGU/bW9kdWxlLmV4cG9ydHM9dCgpOlwiZnVuY3Rpb25cIj09dHlwZW9mIGRlZmluZSYmZGVmaW5lLmFtZD9kZWZpbmUodCk6KGU9XCJ1bmRlZmluZWRcIiE9dHlwZW9mIGdsb2JhbFRoaXM/Z2xvYmFsVGhpczplfHxzZWxmKS5kYXlqc19wbHVnaW5faXNvV2Vlaz10KCl9KHRoaXMsKGZ1bmN0aW9uKCl7XCJ1c2Ugc3RyaWN0XCI7dmFyIGU9XCJkYXlcIjtyZXR1cm4gZnVuY3Rpb24odCxpLHMpe3ZhciBhPWZ1bmN0aW9uKHQpe3JldHVybiB0LmFkZCg0LXQuaXNvV2Vla2RheSgpLGUpfSxkPWkucHJvdG90eXBlO2QuaXNvV2Vla1llYXI9ZnVuY3Rpb24oKXtyZXR1cm4gYSh0aGlzKS55ZWFyKCl9LGQuaXNvV2Vlaz1mdW5jdGlvbih0KXtpZighdGhpcy4kdXRpbHMoKS51KHQpKXJldHVybiB0aGlzLmFkZCg3Kih0LXRoaXMuaXNvV2VlaygpKSxlKTt2YXIgaSxkLG4sbyxyPWEodGhpcyksdT0oaT10aGlzLmlzb1dlZWtZZWFyKCksZD10aGlzLiR1LG49KGQ/cy51dGM6cykoKS55ZWFyKGkpLnN0YXJ0T2YoXCJ5ZWFyXCIpLG89NC1uLmlzb1dlZWtkYXkoKSxuLmlzb1dlZWtkYXkoKT40JiYobys9Nyksbi5hZGQobyxlKSk7cmV0dXJuIHIuZGlmZih1LFwid2Vla1wiKSsxfSxkLmlzb1dlZWtkYXk9ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuJHV0aWxzKCkudShlKT90aGlzLmRheSgpfHw3OnRoaXMuZGF5KHRoaXMuZGF5KCklNz9lOmUtNyl9O3ZhciBuPWQuc3RhcnRPZjtkLnN0YXJ0T2Y9ZnVuY3Rpb24oZSx0KXt2YXIgaT10aGlzLiR1dGlscygpLHM9ISFpLnUodCl8fHQ7cmV0dXJuXCJpc293ZWVrXCI9PT1pLnAoZSk/cz90aGlzLmRhdGUodGhpcy5kYXRlKCktKHRoaXMuaXNvV2Vla2RheSgpLTEpKS5zdGFydE9mKFwiZGF5XCIpOnRoaXMuZGF0ZSh0aGlzLmRhdGUoKS0xLSh0aGlzLmlzb1dlZWtkYXkoKS0xKSs3KS5lbmRPZihcImRheVwiKTpuLmJpbmQodGhpcykoZSx0KX19fSkpOyIsICJpbXBvcnQgeyBJUmVuZGVyZXIsIElSZW5kZXJDb250ZXh0IH0gZnJvbSAnLi9JR3JvdXBpbmdSZW5kZXJlcic7XG5cbmV4cG9ydCBpbnRlcmZhY2UgUGlwZWxpbmUge1xuICBydW4oY29udGV4dDogSVJlbmRlckNvbnRleHQpOiBQcm9taXNlPHZvaWQ+O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gYnVpbGRQaXBlbGluZShyZW5kZXJlcnM6IElSZW5kZXJlcltdKTogUGlwZWxpbmUge1xuICByZXR1cm4ge1xuICAgIGFzeW5jIHJ1bihjb250ZXh0OiBJUmVuZGVyQ29udGV4dCkge1xuICAgICAgZm9yIChjb25zdCByZW5kZXJlciBvZiByZW5kZXJlcnMpIHtcbiAgICAgICAgYXdhaXQgcmVuZGVyZXIucmVuZGVyKGNvbnRleHQpO1xuICAgICAgfVxuICAgIH1cbiAgfTtcbn1cbiIsICJpbXBvcnQgeyBJQ2FsZW5kYXJFdmVudCB9IGZyb20gJy4uL3R5cGVzL0NhbGVuZGFyVHlwZXMnO1xyXG5pbXBvcnQgeyBEYXRlU2VydmljZSB9IGZyb20gJy4vRGF0ZVNlcnZpY2UnO1xyXG5pbXBvcnQgeyBJRW50aXR5UmVzb2x2ZXIgfSBmcm9tICcuL0lFbnRpdHlSZXNvbHZlcic7XHJcblxyXG4vKipcclxuICogRmllbGQgZGVmaW5pdGlvbiBmb3IgRmlsdGVyVGVtcGxhdGVcclxuICovXHJcbmludGVyZmFjZSBJRmlsdGVyRmllbGQge1xyXG4gIGlkUHJvcGVydHk6IHN0cmluZztcclxuICBkZXJpdmVkRnJvbT86IHN0cmluZztcclxufVxyXG5cclxuLyoqXHJcbiAqIFBhcnNlZCBkb3Qtbm90YXRpb24gcmVmZXJlbmNlXHJcbiAqL1xyXG5pbnRlcmZhY2UgSURvdE5vdGF0aW9uIHtcclxuICBlbnRpdHlUeXBlOiBzdHJpbmc7ICAgLy8gZS5nLiwgJ3Jlc291cmNlJ1xyXG4gIHByb3BlcnR5OiBzdHJpbmc7ICAgICAvLyBlLmcuLCAndGVhbUlkJ1xyXG4gIGZvcmVpZ25LZXk6IHN0cmluZzsgICAvLyBlLmcuLCAncmVzb3VyY2VJZCdcclxufVxyXG5cclxuLyoqXHJcbiAqIEZpbHRlclRlbXBsYXRlIC0gQnlnZ2VyIG5cdTAwRjhnbGVyIHRpbCBldmVudC1rb2xvbm5lIG1hdGNoaW5nXHJcbiAqXHJcbiAqIFZpZXdDb25maWcgZGVmaW5lcmVyIGh2aWxrZSBmZWx0ZXIgKGlkUHJvcGVydGllcykgZGVyIGluZGdcdTAwRTVyIGkga29sb25uZW5zIG5cdTAwRjhnbGUuXHJcbiAqIFNhbW1lIHRlbXBsYXRlIGJydWdlcyB0aWwgYXQgYnlnZ2Ugblx1MDBGOGdsZSBmb3IgYlx1MDBFNWRlIGtvbG9ubmUgb2cgZXZlbnQuXHJcbiAqXHJcbiAqIFN1cHBvcnRzIGRvdC1ub3RhdGlvbiBmb3IgaGllcmFyY2hpY2FsIHJlbGF0aW9uczpcclxuICogLSAncmVzb3VyY2UudGVhbUlkJyBcdTIxOTIgbG9va3MgdXAgZXZlbnQucmVzb3VyY2VJZCBcdTIxOTIgcmVzb3VyY2UgZW50aXR5IFx1MjE5MiB0ZWFtSWRcclxuICpcclxuICogUHJpbmNpcDogS29sb25uZW5zIG5cdTAwRjhnbGUtdGVtcGxhdGUgYmVzdGVtbWVyIGh2YWQgZGVyIG1hdGNoZXMgcFx1MDBFNS5cclxuICpcclxuICogQHNlZSBkb2NzL2ZpbHRlci10ZW1wbGF0ZS5tZFxyXG4gKi9cclxuZXhwb3J0IGNsYXNzIEZpbHRlclRlbXBsYXRlIHtcclxuICBwcml2YXRlIGZpZWxkczogSUZpbHRlckZpZWxkW10gPSBbXTtcclxuXHJcbiAgY29uc3RydWN0b3IoXHJcbiAgICBwcml2YXRlIGRhdGVTZXJ2aWNlOiBEYXRlU2VydmljZSxcclxuICAgIHByaXZhdGUgZW50aXR5UmVzb2x2ZXI/OiBJRW50aXR5UmVzb2x2ZXJcclxuICApIHt9XHJcblxyXG4gIC8qKlxyXG4gICAqIFRpbGZcdTAwRjhqIGZlbHQgdGlsIHRlbXBsYXRlXHJcbiAgICogQHBhcmFtIGlkUHJvcGVydHkgLSBQcm9wZXJ0eS1uYXZuIChicnVnZXMgcFx1MDBFNSBiXHUwMEU1ZGUgZXZlbnQgb2cgY29sdW1uLmRhdGFzZXQpXHJcbiAgICogQHBhcmFtIGRlcml2ZWRGcm9tIC0gSHZpcyBmZWx0ZXQgdWRsZWRlcyBmcmEgYW5kZW4gcHJvcGVydHkgKGYuZWtzLiBkYXRlIGZyYSBzdGFydClcclxuICAgKi9cclxuICBhZGRGaWVsZChpZFByb3BlcnR5OiBzdHJpbmcsIGRlcml2ZWRGcm9tPzogc3RyaW5nKTogdGhpcyB7XHJcbiAgICB0aGlzLmZpZWxkcy5wdXNoKHsgaWRQcm9wZXJ0eSwgZGVyaXZlZEZyb20gfSk7XHJcbiAgICByZXR1cm4gdGhpcztcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIFBhcnNlIGRvdC1ub3RhdGlvbiBzdHJpbmcgaW50byBjb21wb25lbnRzXHJcbiAgICogQGV4YW1wbGUgJ3Jlc291cmNlLnRlYW1JZCcgXHUyMTkyIHsgZW50aXR5VHlwZTogJ3Jlc291cmNlJywgcHJvcGVydHk6ICd0ZWFtSWQnLCBmb3JlaWduS2V5OiAncmVzb3VyY2VJZCcgfVxyXG4gICAqL1xyXG4gIHByaXZhdGUgcGFyc2VEb3ROb3RhdGlvbihpZFByb3BlcnR5OiBzdHJpbmcpOiBJRG90Tm90YXRpb24gfCBudWxsIHtcclxuICAgIGlmICghaWRQcm9wZXJ0eS5pbmNsdWRlcygnLicpKSByZXR1cm4gbnVsbDtcclxuICAgIGNvbnN0IFtlbnRpdHlUeXBlLCBwcm9wZXJ0eV0gPSBpZFByb3BlcnR5LnNwbGl0KCcuJyk7XHJcbiAgICByZXR1cm4ge1xyXG4gICAgICBlbnRpdHlUeXBlLFxyXG4gICAgICBwcm9wZXJ0eSxcclxuICAgICAgZm9yZWlnbktleTogZW50aXR5VHlwZSArICdJZCcgLy8gQ29udmVudGlvbjogcmVzb3VyY2UgXHUyMTkyIHJlc291cmNlSWRcclxuICAgIH07XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBHZXQgZGF0YXNldCBrZXkgZm9yIGNvbHVtbiBsb29rdXBcclxuICAgKiBGb3IgZG90LW5vdGF0aW9uICdyZXNvdXJjZS50ZWFtSWQnLCB3ZSBsb29rIGZvciAndGVhbUlkJyBpbiBkYXRhc2V0XHJcbiAgICovXHJcbiAgcHJpdmF0ZSBnZXREYXRhc2V0S2V5KGlkUHJvcGVydHk6IHN0cmluZyk6IHN0cmluZyB7XHJcbiAgICBjb25zdCBkb3ROb3RhdGlvbiA9IHRoaXMucGFyc2VEb3ROb3RhdGlvbihpZFByb3BlcnR5KTtcclxuICAgIGlmIChkb3ROb3RhdGlvbikge1xyXG4gICAgICByZXR1cm4gZG90Tm90YXRpb24ucHJvcGVydHk7IC8vICd0ZWFtSWQnXHJcbiAgICB9XHJcbiAgICByZXR1cm4gaWRQcm9wZXJ0eTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIEJ5ZyBuXHUwMEY4Z2xlIGZyYSBrb2xvbm5lXHJcbiAgICogTFx1MDBFNnNlciB2XHUwMEU2cmRpZXIgZnJhIGNvbHVtbi5kYXRhc2V0W2lkUHJvcGVydHldXHJcbiAgICogRm9yIGRvdC1ub3RhdGlvbiwgdXNlcyB0aGUgcHJvcGVydHkgcGFydCAocmVzb3VyY2UudGVhbUlkIFx1MjE5MiB0ZWFtSWQpXHJcbiAgICovXHJcbiAgYnVpbGRLZXlGcm9tQ29sdW1uKGNvbHVtbjogSFRNTEVsZW1lbnQpOiBzdHJpbmcge1xyXG4gICAgcmV0dXJuIHRoaXMuZmllbGRzXHJcbiAgICAgIC5tYXAoZiA9PiB7XHJcbiAgICAgICAgY29uc3Qga2V5ID0gdGhpcy5nZXREYXRhc2V0S2V5KGYuaWRQcm9wZXJ0eSk7XHJcbiAgICAgICAgcmV0dXJuIGNvbHVtbi5kYXRhc2V0W2tleV0gfHwgJyc7XHJcbiAgICAgIH0pXHJcbiAgICAgIC5qb2luKCc6Jyk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBCeWcgblx1MDBGOGdsZSBmcmEgZXZlbnRcclxuICAgKiBMXHUwMEU2c2VyIHZcdTAwRTZyZGllciBmcmEgZXZlbnRbaWRQcm9wZXJ0eV0gZWxsZXIgdWRsZWRlciBmcmEgZGVyaXZlZEZyb21cclxuICAgKiBGb3IgZG90LW5vdGF0aW9uLCByZXNvbHZlcyB2aWEgRW50aXR5UmVzb2x2ZXJcclxuICAgKi9cclxuICBidWlsZEtleUZyb21FdmVudChldmVudDogSUNhbGVuZGFyRXZlbnQpOiBzdHJpbmcge1xyXG4gICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1leHBsaWNpdC1hbnlcclxuICAgIGNvbnN0IGV2ZW50UmVjb3JkID0gZXZlbnQgYXMgYW55O1xyXG4gICAgcmV0dXJuIHRoaXMuZmllbGRzXHJcbiAgICAgIC5tYXAoZiA9PiB7XHJcbiAgICAgICAgLy8gQ2hlY2sgZm9yIGRvdC1ub3RhdGlvbiAoZS5nLiwgJ3Jlc291cmNlLnRlYW1JZCcpXHJcbiAgICAgICAgY29uc3QgZG90Tm90YXRpb24gPSB0aGlzLnBhcnNlRG90Tm90YXRpb24oZi5pZFByb3BlcnR5KTtcclxuICAgICAgICBpZiAoZG90Tm90YXRpb24pIHtcclxuICAgICAgICAgIHJldHVybiB0aGlzLnJlc29sdmVEb3ROb3RhdGlvbihldmVudFJlY29yZCwgZG90Tm90YXRpb24pO1xyXG4gICAgICAgIH1cclxuXHJcbiAgICAgICAgaWYgKGYuZGVyaXZlZEZyb20pIHtcclxuICAgICAgICAgIC8vIFVkbGVkIHZcdTAwRTZyZGkgKGYuZWtzLiBkYXRlIGZyYSBzdGFydClcclxuICAgICAgICAgIGNvbnN0IHNvdXJjZVZhbHVlID0gZXZlbnRSZWNvcmRbZi5kZXJpdmVkRnJvbV07XHJcbiAgICAgICAgICBpZiAoc291cmNlVmFsdWUgaW5zdGFuY2VvZiBEYXRlKSB7XHJcbiAgICAgICAgICAgIHJldHVybiB0aGlzLmRhdGVTZXJ2aWNlLmdldERhdGVLZXkoc291cmNlVmFsdWUpO1xyXG4gICAgICAgICAgfVxyXG4gICAgICAgICAgcmV0dXJuIFN0cmluZyhzb3VyY2VWYWx1ZSB8fCAnJyk7XHJcbiAgICAgICAgfVxyXG4gICAgICAgIHJldHVybiBTdHJpbmcoZXZlbnRSZWNvcmRbZi5pZFByb3BlcnR5XSB8fCAnJyk7XHJcbiAgICAgIH0pXHJcbiAgICAgIC5qb2luKCc6Jyk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBSZXNvbHZlIGRvdC1ub3RhdGlvbiByZWZlcmVuY2UgdmlhIEVudGl0eVJlc29sdmVyXHJcbiAgICovXHJcbiAgcHJpdmF0ZSByZXNvbHZlRG90Tm90YXRpb24oZXZlbnRSZWNvcmQ6IFJlY29yZDxzdHJpbmcsIHVua25vd24+LCBkb3ROb3RhdGlvbjogSURvdE5vdGF0aW9uKTogc3RyaW5nIHtcclxuICAgIGlmICghdGhpcy5lbnRpdHlSZXNvbHZlcikge1xyXG4gICAgICBjb25zb2xlLndhcm4oYEZpbHRlclRlbXBsYXRlOiBFbnRpdHlSZXNvbHZlciByZXF1aXJlZCBmb3IgZG90LW5vdGF0aW9uICcke2RvdE5vdGF0aW9uLmVudGl0eVR5cGV9LiR7ZG90Tm90YXRpb24ucHJvcGVydHl9J2ApO1xyXG4gICAgICByZXR1cm4gJyc7XHJcbiAgICB9XHJcblxyXG4gICAgLy8gR2V0IGZvcmVpZ24ga2V5IHZhbHVlIGZyb20gZXZlbnQgKGUuZy4sIHJlc291cmNlSWQpXHJcbiAgICBjb25zdCBmb3JlaWduSWQgPSBldmVudFJlY29yZFtkb3ROb3RhdGlvbi5mb3JlaWduS2V5XTtcclxuICAgIGlmICghZm9yZWlnbklkKSByZXR1cm4gJyc7XHJcblxyXG4gICAgLy8gUmVzb2x2ZSBlbnRpdHlcclxuICAgIGNvbnN0IGVudGl0eSA9IHRoaXMuZW50aXR5UmVzb2x2ZXIucmVzb2x2ZShkb3ROb3RhdGlvbi5lbnRpdHlUeXBlLCBTdHJpbmcoZm9yZWlnbklkKSk7XHJcbiAgICBpZiAoIWVudGl0eSkgcmV0dXJuICcnO1xyXG5cclxuICAgIC8vIFJldHVybiBwcm9wZXJ0eSB2YWx1ZSBmcm9tIGVudGl0eVxyXG4gICAgcmV0dXJuIFN0cmluZyhlbnRpdHlbZG90Tm90YXRpb24ucHJvcGVydHldIHx8ICcnKTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIE1hdGNoIGV2ZW50IG1vZCBrb2xvbm5lXHJcbiAgICovXHJcbiAgbWF0Y2hlcyhldmVudDogSUNhbGVuZGFyRXZlbnQsIGNvbHVtbjogSFRNTEVsZW1lbnQpOiBib29sZWFuIHtcclxuICAgIHJldHVybiB0aGlzLmJ1aWxkS2V5RnJvbUV2ZW50KGV2ZW50KSA9PT0gdGhpcy5idWlsZEtleUZyb21Db2x1bW4oY29sdW1uKTtcclxuICB9XHJcbn1cclxuIiwgImltcG9ydCB7IElSZW5kZXJlciwgSVJlbmRlckNvbnRleHQgfSBmcm9tICcuL0lHcm91cGluZ1JlbmRlcmVyJztcclxuaW1wb3J0IHsgYnVpbGRQaXBlbGluZSB9IGZyb20gJy4vUmVuZGVyQnVpbGRlcic7XHJcbmltcG9ydCB7IEV2ZW50UmVuZGVyZXIgfSBmcm9tICcuLi9mZWF0dXJlcy9ldmVudC9FdmVudFJlbmRlcmVyJztcclxuaW1wb3J0IHsgU2NoZWR1bGVSZW5kZXJlciB9IGZyb20gJy4uL2ZlYXR1cmVzL3NjaGVkdWxlL1NjaGVkdWxlUmVuZGVyZXInO1xyXG5pbXBvcnQgeyBIZWFkZXJEcmF3ZXJSZW5kZXJlciB9IGZyb20gJy4uL2ZlYXR1cmVzL2hlYWRlcmRyYXdlci9IZWFkZXJEcmF3ZXJSZW5kZXJlcic7XHJcbmltcG9ydCB7IFZpZXdDb25maWcsIEdyb3VwaW5nQ29uZmlnIH0gZnJvbSAnLi9WaWV3Q29uZmlnJztcclxuaW1wb3J0IHsgRmlsdGVyVGVtcGxhdGUgfSBmcm9tICcuL0ZpbHRlclRlbXBsYXRlJztcclxuaW1wb3J0IHsgRGF0ZVNlcnZpY2UgfSBmcm9tICcuL0RhdGVTZXJ2aWNlJztcclxuaW1wb3J0IHsgSUVudGl0eVNlcnZpY2UgfSBmcm9tICcuLi9zdG9yYWdlL0lFbnRpdHlTZXJ2aWNlJztcclxuaW1wb3J0IHsgSVN5bmMgfSBmcm9tICcuLi90eXBlcy9DYWxlbmRhclR5cGVzJztcclxuXHJcbmV4cG9ydCBjbGFzcyBDYWxlbmRhck9yY2hlc3RyYXRvciB7XHJcbiAgY29uc3RydWN0b3IoXHJcbiAgICBwcml2YXRlIGFsbFJlbmRlcmVyczogSVJlbmRlcmVyW10sXHJcbiAgICBwcml2YXRlIGV2ZW50UmVuZGVyZXI6IEV2ZW50UmVuZGVyZXIsXHJcbiAgICBwcml2YXRlIHNjaGVkdWxlUmVuZGVyZXI6IFNjaGVkdWxlUmVuZGVyZXIsXHJcbiAgICBwcml2YXRlIGhlYWRlckRyYXdlclJlbmRlcmVyOiBIZWFkZXJEcmF3ZXJSZW5kZXJlcixcclxuICAgIHByaXZhdGUgZGF0ZVNlcnZpY2U6IERhdGVTZXJ2aWNlLFxyXG4gICAgcHJpdmF0ZSBlbnRpdHlTZXJ2aWNlczogSUVudGl0eVNlcnZpY2U8SVN5bmM+W11cclxuICApIHt9XHJcblxyXG4gIGFzeW5jIHJlbmRlcih2aWV3Q29uZmlnOiBWaWV3Q29uZmlnLCBjb250YWluZXI6IEhUTUxFbGVtZW50KTogUHJvbWlzZTx2b2lkPiB7XHJcbiAgICBjb25zdCBoZWFkZXJDb250YWluZXIgPSBjb250YWluZXIucXVlcnlTZWxlY3Rvcignc3dwLWNhbGVuZGFyLWhlYWRlcicpIGFzIEhUTUxFbGVtZW50O1xyXG4gICAgY29uc3QgY29sdW1uQ29udGFpbmVyID0gY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3IoJ3N3cC1kYXktY29sdW1ucycpIGFzIEhUTUxFbGVtZW50O1xyXG4gICAgaWYgKCFoZWFkZXJDb250YWluZXIgfHwgIWNvbHVtbkNvbnRhaW5lcikge1xyXG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ01pc3Npbmcgc3dwLWNhbGVuZGFyLWhlYWRlciBvciBzd3AtZGF5LWNvbHVtbnMnKTtcclxuICAgIH1cclxuXHJcbiAgICAvLyBCeWcgZmlsdGVyIGZyYSB2aWV3Q29uZmlnXHJcbiAgICBjb25zdCBmaWx0ZXI6IFJlY29yZDxzdHJpbmcsIHN0cmluZ1tdPiA9IHt9O1xyXG4gICAgZm9yIChjb25zdCBncm91cGluZyBvZiB2aWV3Q29uZmlnLmdyb3VwaW5ncykge1xyXG4gICAgICBmaWx0ZXJbZ3JvdXBpbmcudHlwZV0gPSBncm91cGluZy52YWx1ZXM7XHJcbiAgICB9XHJcblxyXG4gICAgLy8gQnlnIEZpbHRlclRlbXBsYXRlIGZyYSB2aWV3Q29uZmlnIGdyb3VwaW5ncyAoa3VuIGRlIG1lZCBpZFByb3BlcnR5KVxyXG4gICAgY29uc3QgZmlsdGVyVGVtcGxhdGUgPSBuZXcgRmlsdGVyVGVtcGxhdGUodGhpcy5kYXRlU2VydmljZSk7XHJcbiAgICBmb3IgKGNvbnN0IGdyb3VwaW5nIG9mIHZpZXdDb25maWcuZ3JvdXBpbmdzKSB7XHJcbiAgICAgIGlmIChncm91cGluZy5pZFByb3BlcnR5KSB7XHJcbiAgICAgICAgZmlsdGVyVGVtcGxhdGUuYWRkRmllbGQoZ3JvdXBpbmcuaWRQcm9wZXJ0eSwgZ3JvdXBpbmcuZGVyaXZlZEZyb20pO1xyXG4gICAgICB9XHJcbiAgICB9XHJcblxyXG4gICAgLy8gUmVzb2x2ZSBiZWxvbmdzVG8gcmVsYXRpb25zIChlLmcuLCB0ZWFtLnJlc291cmNlSWRzKVxyXG4gICAgY29uc3QgeyBwYXJlbnRDaGlsZE1hcCwgY2hpbGRUeXBlIH0gPSBhd2FpdCB0aGlzLnJlc29sdmVCZWxvbmdzVG8odmlld0NvbmZpZy5ncm91cGluZ3MsIGZpbHRlcik7XHJcblxyXG4gICAgY29uc3QgY29udGV4dDogSVJlbmRlckNvbnRleHQgPSB7IGhlYWRlckNvbnRhaW5lciwgY29sdW1uQ29udGFpbmVyLCBmaWx0ZXIsIGdyb3VwaW5nczogdmlld0NvbmZpZy5ncm91cGluZ3MsIHBhcmVudENoaWxkTWFwLCBjaGlsZFR5cGUgfTtcclxuXHJcbiAgICAvLyBDbGVhclxyXG4gICAgaGVhZGVyQ29udGFpbmVyLmlubmVySFRNTCA9ICcnO1xyXG4gICAgY29sdW1uQ29udGFpbmVyLmlubmVySFRNTCA9ICcnO1xyXG5cclxuICAgIC8vIFNcdTAwRTZ0IGRhdGEtbGV2ZWxzIGF0dHJpYnV0IGZvciBDU1MgZ3JpZC1yb3cgc3R5bGluZ1xyXG4gICAgY29uc3QgbGV2ZWxzID0gdmlld0NvbmZpZy5ncm91cGluZ3MubWFwKGcgPT4gZy50eXBlKS5qb2luKCcgJyk7XHJcbiAgICBoZWFkZXJDb250YWluZXIuZGF0YXNldC5sZXZlbHMgPSBsZXZlbHM7XHJcblxyXG4gICAgLy8gVlx1MDBFNmxnIHJlbmRlcmVycyBiYXNlcmV0IHBcdTAwRTUgZ3JvdXBpbmdzIHR5cGVzXHJcbiAgICBjb25zdCBhY3RpdmVSZW5kZXJlcnMgPSB0aGlzLnNlbGVjdFJlbmRlcmVycyh2aWV3Q29uZmlnKTtcclxuXHJcbiAgICAvLyBCeWcgb2cga1x1MDBGOHIgcGlwZWxpbmVcclxuICAgIGNvbnN0IHBpcGVsaW5lID0gYnVpbGRQaXBlbGluZShhY3RpdmVSZW5kZXJlcnMpO1xyXG4gICAgYXdhaXQgcGlwZWxpbmUucnVuKGNvbnRleHQpO1xyXG5cclxuICAgIC8vIFJlbmRlciBzY2hlZHVsZSB1bmF2YWlsYWJsZSB6b25lcyAoZlx1MDBGOHIgZXZlbnRzKVxyXG4gICAgYXdhaXQgdGhpcy5zY2hlZHVsZVJlbmRlcmVyLnJlbmRlcihjb250YWluZXIsIGZpbHRlcik7XHJcblxyXG4gICAgLy8gUmVuZGVyIHRpbWVkIGV2ZW50cyBpbiBncmlkIChtZWQgZmlsdGVyVGVtcGxhdGUgdGlsIG1hdGNoaW5nKVxyXG4gICAgYXdhaXQgdGhpcy5ldmVudFJlbmRlcmVyLnJlbmRlcihjb250YWluZXIsIGZpbHRlciwgZmlsdGVyVGVtcGxhdGUpO1xyXG5cclxuICAgIC8vIFJlbmRlciBhbGxEYXkgZXZlbnRzIGluIGhlYWRlciBkcmF3ZXIgKG1lZCBmaWx0ZXJUZW1wbGF0ZSB0aWwgbWF0Y2hpbmcpXHJcbiAgICBhd2FpdCB0aGlzLmhlYWRlckRyYXdlclJlbmRlcmVyLnJlbmRlcihjb250YWluZXIsIGZpbHRlciwgZmlsdGVyVGVtcGxhdGUpO1xyXG4gIH1cclxuXHJcbiAgcHJpdmF0ZSBzZWxlY3RSZW5kZXJlcnModmlld0NvbmZpZzogVmlld0NvbmZpZyk6IElSZW5kZXJlcltdIHtcclxuICAgIGNvbnN0IHR5cGVzID0gdmlld0NvbmZpZy5ncm91cGluZ3MubWFwKGcgPT4gZy50eXBlKTtcclxuICAgIC8vIFNvcnRcdTAwRTlyIHJlbmRlcmVycyBpIHNhbW1lIHJcdTAwRTZra2VmXHUwMEY4bGdlIHNvbSB2aWV3Q29uZmlnLmdyb3VwaW5nc1xyXG4gICAgcmV0dXJuIHR5cGVzXHJcbiAgICAgIC5tYXAodHlwZSA9PiB0aGlzLmFsbFJlbmRlcmVycy5maW5kKHIgPT4gci50eXBlID09PSB0eXBlKSlcclxuICAgICAgLmZpbHRlcigocik6IHIgaXMgSVJlbmRlcmVyID0+IHIgIT09IHVuZGVmaW5lZCk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBSZXNvbHZlIGJlbG9uZ3NUbyByZWxhdGlvbnMgdG8gYnVpbGQgcGFyZW50LWNoaWxkIG1hcFxyXG4gICAqIGUuZy4sIGJlbG9uZ3NUbzogJ3RlYW0ucmVzb3VyY2VJZHMnIFx1MjE5MiB7IHRlYW0xOiBbJ0VNUDAwMScsICdFTVAwMDInXSwgdGVhbTI6IFsuLi5dIH1cclxuICAgKiBBbHNvIHJldHVybnMgdGhlIGNoaWxkVHlwZSAodGhlIGdyb3VwaW5nIHR5cGUgdGhhdCBoYXMgYmVsb25nc1RvKVxyXG4gICAqL1xyXG4gIHByaXZhdGUgYXN5bmMgcmVzb2x2ZUJlbG9uZ3NUbyhcclxuICAgIGdyb3VwaW5nczogR3JvdXBpbmdDb25maWdbXSxcclxuICAgIGZpbHRlcjogUmVjb3JkPHN0cmluZywgc3RyaW5nW10+XHJcbiAgKTogUHJvbWlzZTx7IHBhcmVudENoaWxkTWFwPzogUmVjb3JkPHN0cmluZywgc3RyaW5nW10+OyBjaGlsZFR5cGU/OiBzdHJpbmcgfT4ge1xyXG4gICAgLy8gRmluZCBncm91cGluZyB3aXRoIGJlbG9uZ3NUb1xyXG4gICAgY29uc3QgY2hpbGRHcm91cGluZyA9IGdyb3VwaW5ncy5maW5kKGcgPT4gZy5iZWxvbmdzVG8pO1xyXG4gICAgaWYgKCFjaGlsZEdyb3VwaW5nPy5iZWxvbmdzVG8pIHJldHVybiB7fTtcclxuXHJcbiAgICAvLyBQYXJzZSBiZWxvbmdzVG86ICd0ZWFtLnJlc291cmNlSWRzJ1xyXG4gICAgY29uc3QgW2VudGl0eVR5cGUsIHByb3BlcnR5XSA9IGNoaWxkR3JvdXBpbmcuYmVsb25nc1RvLnNwbGl0KCcuJyk7XHJcbiAgICBpZiAoIWVudGl0eVR5cGUgfHwgIXByb3BlcnR5KSByZXR1cm4ge307XHJcblxyXG4gICAgLy8gR2V0IHBhcmVudCBJRHMgZnJvbSBmaWx0ZXJcclxuICAgIGNvbnN0IHBhcmVudElkcyA9IGZpbHRlcltlbnRpdHlUeXBlXSB8fCBbXTtcclxuICAgIGlmIChwYXJlbnRJZHMubGVuZ3RoID09PSAwKSByZXR1cm4ge307XHJcblxyXG4gICAgLy8gRmluZCBzZXJ2aWNlIGR5bmFtaXNrIGJhc2VyZXQgcFx1MDBFNSBlbnRpdHlUeXBlIChpbmdlbiBoYXJkY29kZWQgdHlwZSBjaGVjaylcclxuICAgIGNvbnN0IHNlcnZpY2UgPSB0aGlzLmVudGl0eVNlcnZpY2VzLmZpbmQocyA9PlxyXG4gICAgICBzLmVudGl0eVR5cGUudG9Mb3dlckNhc2UoKSA9PT0gZW50aXR5VHlwZVxyXG4gICAgKTtcclxuICAgIGlmICghc2VydmljZSkgcmV0dXJuIHt9O1xyXG5cclxuICAgIC8vIEhlbnQgYWxsZSBlbnRpdGllcyBvZyBmaWx0cmVyIHBcdTAwRTUgcGFyZW50SWRzXHJcbiAgICBjb25zdCBhbGxFbnRpdGllcyA9IGF3YWl0IHNlcnZpY2UuZ2V0QWxsKCk7XHJcbiAgICBjb25zdCBlbnRpdGllcyA9IGFsbEVudGl0aWVzLmZpbHRlcihlID0+XHJcbiAgICAgIHBhcmVudElkcy5pbmNsdWRlcygoZSBhcyB1bmtub3duIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+KS5pZCBhcyBzdHJpbmcpXHJcbiAgICApO1xyXG5cclxuICAgIC8vIEJ5ZyBwYXJlbnQtY2hpbGQgbWFwXHJcbiAgICBjb25zdCBtYXA6IFJlY29yZDxzdHJpbmcsIHN0cmluZ1tdPiA9IHt9O1xyXG4gICAgZm9yIChjb25zdCBlbnRpdHkgb2YgZW50aXRpZXMpIHtcclxuICAgICAgY29uc3QgZW50aXR5UmVjb3JkID0gZW50aXR5IGFzIHVua25vd24gYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj47XHJcbiAgICAgIGNvbnN0IGNoaWxkcmVuID0gKGVudGl0eVJlY29yZFtwcm9wZXJ0eV0gYXMgc3RyaW5nW10pIHx8IFtdO1xyXG4gICAgICBtYXBbZW50aXR5UmVjb3JkLmlkIGFzIHN0cmluZ10gPSBjaGlsZHJlbjtcclxuICAgIH1cclxuXHJcbiAgICByZXR1cm4geyBwYXJlbnRDaGlsZE1hcDogbWFwLCBjaGlsZFR5cGU6IGNoaWxkR3JvdXBpbmcudHlwZSB9O1xyXG4gIH1cclxufVxyXG4iLCAiZXhwb3J0IGNsYXNzIE5hdmlnYXRpb25BbmltYXRvciB7XHJcbiAgY29uc3RydWN0b3IoXHJcbiAgICBwcml2YXRlIGhlYWRlclRyYWNrOiBIVE1MRWxlbWVudCxcclxuICAgIHByaXZhdGUgY29udGVudFRyYWNrOiBIVE1MRWxlbWVudFxyXG4gICkge31cclxuXHJcbiAgYXN5bmMgc2xpZGUoZGlyZWN0aW9uOiAnbGVmdCcgfCAncmlnaHQnLCByZW5kZXJGbjogKCkgPT4gUHJvbWlzZTx2b2lkPik6IFByb21pc2U8dm9pZD4ge1xyXG4gICAgY29uc3Qgb3V0ID0gZGlyZWN0aW9uID09PSAnbGVmdCcgPyAnLTEwMCUnIDogJzEwMCUnO1xyXG4gICAgY29uc3QgaW50byA9IGRpcmVjdGlvbiA9PT0gJ2xlZnQnID8gJzEwMCUnIDogJy0xMDAlJztcclxuXHJcbiAgICBhd2FpdCB0aGlzLmFuaW1hdGVPdXQob3V0KTtcclxuICAgIGF3YWl0IHJlbmRlckZuKCk7XHJcbiAgICBhd2FpdCB0aGlzLmFuaW1hdGVJbihpbnRvKTtcclxuICB9XHJcblxyXG4gIHByaXZhdGUgYXN5bmMgYW5pbWF0ZU91dCh0cmFuc2xhdGU6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xyXG4gICAgYXdhaXQgUHJvbWlzZS5hbGwoW1xyXG4gICAgICB0aGlzLmhlYWRlclRyYWNrLmFuaW1hdGUoXHJcbiAgICAgICAgW3sgdHJhbnNmb3JtOiAndHJhbnNsYXRlWCgwKScgfSwgeyB0cmFuc2Zvcm06IGB0cmFuc2xhdGVYKCR7dHJhbnNsYXRlfSlgIH1dLFxyXG4gICAgICAgIHsgZHVyYXRpb246IDIwMCwgZWFzaW5nOiAnZWFzZS1pbicgfVxyXG4gICAgICApLmZpbmlzaGVkLFxyXG4gICAgICB0aGlzLmNvbnRlbnRUcmFjay5hbmltYXRlKFxyXG4gICAgICAgIFt7IHRyYW5zZm9ybTogJ3RyYW5zbGF0ZVgoMCknIH0sIHsgdHJhbnNmb3JtOiBgdHJhbnNsYXRlWCgke3RyYW5zbGF0ZX0pYCB9XSxcclxuICAgICAgICB7IGR1cmF0aW9uOiAyMDAsIGVhc2luZzogJ2Vhc2UtaW4nIH1cclxuICAgICAgKS5maW5pc2hlZFxyXG4gICAgXSk7XHJcbiAgfVxyXG5cclxuICBwcml2YXRlIGFzeW5jIGFuaW1hdGVJbih0cmFuc2xhdGU6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xyXG4gICAgYXdhaXQgUHJvbWlzZS5hbGwoW1xyXG4gICAgICB0aGlzLmhlYWRlclRyYWNrLmFuaW1hdGUoXHJcbiAgICAgICAgW3sgdHJhbnNmb3JtOiBgdHJhbnNsYXRlWCgke3RyYW5zbGF0ZX0pYCB9LCB7IHRyYW5zZm9ybTogJ3RyYW5zbGF0ZVgoMCknIH1dLFxyXG4gICAgICAgIHsgZHVyYXRpb246IDIwMCwgZWFzaW5nOiAnZWFzZS1vdXQnIH1cclxuICAgICAgKS5maW5pc2hlZCxcclxuICAgICAgdGhpcy5jb250ZW50VHJhY2suYW5pbWF0ZShcclxuICAgICAgICBbeyB0cmFuc2Zvcm06IGB0cmFuc2xhdGVYKCR7dHJhbnNsYXRlfSlgIH0sIHsgdHJhbnNmb3JtOiAndHJhbnNsYXRlWCgwKScgfV0sXHJcbiAgICAgICAgeyBkdXJhdGlvbjogMjAwLCBlYXNpbmc6ICdlYXNlLW91dCcgfVxyXG4gICAgICApLmZpbmlzaGVkXHJcbiAgICBdKTtcclxuICB9XHJcbn1cclxuIiwgImltcG9ydCB7IElSZW5kZXJlciwgSVJlbmRlckNvbnRleHQgfSBmcm9tICcuLi8uLi9jb3JlL0lHcm91cGluZ1JlbmRlcmVyJztcclxuaW1wb3J0IHsgRGF0ZVNlcnZpY2UgfSBmcm9tICcuLi8uLi9jb3JlL0RhdGVTZXJ2aWNlJztcclxuXHJcbmV4cG9ydCBjbGFzcyBEYXRlUmVuZGVyZXIgaW1wbGVtZW50cyBJUmVuZGVyZXIge1xyXG4gIHJlYWRvbmx5IHR5cGUgPSAnZGF0ZSc7XHJcblxyXG4gIGNvbnN0cnVjdG9yKHByaXZhdGUgZGF0ZVNlcnZpY2U6IERhdGVTZXJ2aWNlKSB7fVxyXG5cclxuICByZW5kZXIoY29udGV4dDogSVJlbmRlckNvbnRleHQpOiB2b2lkIHtcclxuICAgIGNvbnN0IGRhdGVzID0gY29udGV4dC5maWx0ZXJbJ2RhdGUnXSB8fCBbXTtcclxuICAgIGNvbnN0IHJlc291cmNlSWRzID0gY29udGV4dC5maWx0ZXJbJ3Jlc291cmNlJ10gfHwgW107XHJcblxyXG4gICAgLy8gQ2hlY2sgaWYgZGF0ZSBoZWFkZXJzIHNob3VsZCBiZSBoaWRkZW4gKGUuZy4sIGluIGRheSB2aWV3KVxyXG4gICAgY29uc3QgZGF0ZUdyb3VwaW5nID0gY29udGV4dC5ncm91cGluZ3M/LmZpbmQoZyA9PiBnLnR5cGUgPT09ICdkYXRlJyk7XHJcbiAgICBjb25zdCBoaWRlSGVhZGVyID0gZGF0ZUdyb3VwaW5nPy5oaWRlSGVhZGVyID09PSB0cnVlO1xyXG5cclxuICAgIC8vIFJlbmRlciBkYXRlcyBmb3IgSFZFUiByZXNvdXJjZSAoZWxsZXIgMSBnYW5nIGh2aXMgaW5nZW4gcmVzb3VyY2VzKVxyXG4gICAgY29uc3QgaXRlcmF0aW9ucyA9IHJlc291cmNlSWRzLmxlbmd0aCB8fCAxO1xyXG4gICAgbGV0IGNvbHVtbkNvdW50ID0gMDtcclxuXHJcbiAgICBmb3IgKGxldCByID0gMDsgciA8IGl0ZXJhdGlvbnM7IHIrKykge1xyXG4gICAgICBjb25zdCByZXNvdXJjZUlkID0gcmVzb3VyY2VJZHNbcl07IC8vIHVuZGVmaW5lZCBodmlzIGluZ2VuIHJlc291cmNlc1xyXG5cclxuICAgICAgZm9yIChjb25zdCBkYXRlU3RyIG9mIGRhdGVzKSB7XHJcbiAgICAgICAgY29uc3QgZGF0ZSA9IHRoaXMuZGF0ZVNlcnZpY2UucGFyc2VJU08oZGF0ZVN0cik7XHJcblxyXG4gICAgICAgIC8vIEJ1aWxkIGNvbHVtbktleSBmb3IgdW5pZm9ybSBpZGVudGlmaWNhdGlvblxyXG4gICAgICAgIGNvbnN0IHNlZ21lbnRzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0geyBkYXRlOiBkYXRlU3RyIH07XHJcbiAgICAgICAgaWYgKHJlc291cmNlSWQpIHNlZ21lbnRzLnJlc291cmNlID0gcmVzb3VyY2VJZDtcclxuICAgICAgICBjb25zdCBjb2x1bW5LZXkgPSB0aGlzLmRhdGVTZXJ2aWNlLmJ1aWxkQ29sdW1uS2V5KHNlZ21lbnRzKTtcclxuXHJcbiAgICAgICAgLy8gSGVhZGVyXHJcbiAgICAgICAgY29uc3QgaGVhZGVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3dwLWRheS1oZWFkZXInKTtcclxuICAgICAgICBoZWFkZXIuZGF0YXNldC5kYXRlID0gZGF0ZVN0cjtcclxuICAgICAgICBoZWFkZXIuZGF0YXNldC5jb2x1bW5LZXkgPSBjb2x1bW5LZXk7XHJcbiAgICAgICAgaWYgKHJlc291cmNlSWQpIHtcclxuICAgICAgICAgIGhlYWRlci5kYXRhc2V0LnJlc291cmNlSWQgPSByZXNvdXJjZUlkO1xyXG4gICAgICAgIH1cclxuICAgICAgICBpZiAoaGlkZUhlYWRlcikge1xyXG4gICAgICAgICAgaGVhZGVyLmRhdGFzZXQuaGlkZGVuID0gJ3RydWUnO1xyXG4gICAgICAgIH1cclxuICAgICAgICBoZWFkZXIuaW5uZXJIVE1MID0gYFxyXG4gICAgICAgICAgPHN3cC1kYXktbmFtZT4ke3RoaXMuZGF0ZVNlcnZpY2UuZ2V0RGF5TmFtZShkYXRlLCAnc2hvcnQnKX08L3N3cC1kYXktbmFtZT5cclxuICAgICAgICAgIDxzd3AtZGF5LWRhdGU+JHtkYXRlLmdldERhdGUoKX08L3N3cC1kYXktZGF0ZT5cclxuICAgICAgICBgO1xyXG4gICAgICAgIGNvbnRleHQuaGVhZGVyQ29udGFpbmVyLmFwcGVuZENoaWxkKGhlYWRlcik7XHJcblxyXG4gICAgICAgIC8vIENvbHVtblxyXG4gICAgICAgIGNvbnN0IGNvbHVtbiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3N3cC1kYXktY29sdW1uJyk7XHJcbiAgICAgICAgY29sdW1uLmRhdGFzZXQuZGF0ZSA9IGRhdGVTdHI7XHJcbiAgICAgICAgY29sdW1uLmRhdGFzZXQuY29sdW1uS2V5ID0gY29sdW1uS2V5O1xyXG4gICAgICAgIGlmIChyZXNvdXJjZUlkKSB7XHJcbiAgICAgICAgICBjb2x1bW4uZGF0YXNldC5yZXNvdXJjZUlkID0gcmVzb3VyY2VJZDtcclxuICAgICAgICB9XHJcbiAgICAgICAgY29sdW1uLmlubmVySFRNTCA9ICc8c3dwLWV2ZW50cy1sYXllcj48L3N3cC1ldmVudHMtbGF5ZXI+JztcclxuICAgICAgICBjb250ZXh0LmNvbHVtbkNvbnRhaW5lci5hcHBlbmRDaGlsZChjb2x1bW4pO1xyXG5cclxuICAgICAgICBjb2x1bW5Db3VudCsrO1xyXG4gICAgICB9XHJcbiAgICB9XHJcblxyXG4gICAgLy8gU2V0IGdyaWQgY29sdW1ucyBvbiBjb250YWluZXJcclxuICAgIGNvbnN0IGNvbnRhaW5lciA9IGNvbnRleHQuY29sdW1uQ29udGFpbmVyLmNsb3Nlc3QoJ3N3cC1jYWxlbmRhci1jb250YWluZXInKTtcclxuICAgIGlmIChjb250YWluZXIpIHtcclxuICAgICAgKGNvbnRhaW5lciBhcyBIVE1MRWxlbWVudCkuc3R5bGUuc2V0UHJvcGVydHkoJy0tZ3JpZC1jb2x1bW5zJywgU3RyaW5nKGNvbHVtbkNvdW50KSk7XHJcbiAgICB9XHJcbiAgfVxyXG59XHJcbiIsICJpbXBvcnQgZGF5anMgZnJvbSAnZGF5anMnO1xyXG5pbXBvcnQgdXRjIGZyb20gJ2RheWpzL3BsdWdpbi91dGMnO1xyXG5pbXBvcnQgdGltZXpvbmUgZnJvbSAnZGF5anMvcGx1Z2luL3RpbWV6b25lJztcclxuaW1wb3J0IGlzb1dlZWsgZnJvbSAnZGF5anMvcGx1Z2luL2lzb1dlZWsnO1xyXG5pbXBvcnQgeyBJVGltZUZvcm1hdENvbmZpZyB9IGZyb20gJy4vSVRpbWVGb3JtYXRDb25maWcnO1xyXG5cclxuLy8gRW5hYmxlIGRheWpzIHBsdWdpbnNcclxuZGF5anMuZXh0ZW5kKHV0Yyk7XHJcbmRheWpzLmV4dGVuZCh0aW1lem9uZSk7XHJcbmRheWpzLmV4dGVuZChpc29XZWVrKTtcclxuXHJcbmV4cG9ydCBjbGFzcyBEYXRlU2VydmljZSB7XHJcbiAgcHJpdmF0ZSB0aW1lem9uZTogc3RyaW5nO1xyXG4gIHByaXZhdGUgYmFzZURhdGU6IGRheWpzLkRheWpzO1xyXG5cclxuICBjb25zdHJ1Y3Rvcihwcml2YXRlIGNvbmZpZzogSVRpbWVGb3JtYXRDb25maWcsIGJhc2VEYXRlPzogRGF0ZSkge1xyXG4gICAgdGhpcy50aW1lem9uZSA9IGNvbmZpZy50aW1lem9uZTtcclxuICAgIC8vIEFsbG93IHNldHRpbmcgYSBmaXhlZCBiYXNlIGRhdGUgZm9yIGRlbW8vdGVzdGluZyBwdXJwb3Nlc1xyXG4gICAgdGhpcy5iYXNlRGF0ZSA9IGJhc2VEYXRlID8gZGF5anMoYmFzZURhdGUpIDogZGF5anMoKTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIFNldCBhIGZpeGVkIGJhc2UgZGF0ZSAodXNlZnVsIGZvciBkZW1vcyB3aXRoIHN0YXRpYyBtb2NrIGRhdGEpXHJcbiAgICovXHJcbiAgc2V0QmFzZURhdGUoZGF0ZTogRGF0ZSk6IHZvaWQge1xyXG4gICAgdGhpcy5iYXNlRGF0ZSA9IGRheWpzKGRhdGUpO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogR2V0IHRoZSBjdXJyZW50IGJhc2UgZGF0ZSAoZWl0aGVyIGZpeGVkIG9yIHRvZGF5KVxyXG4gICAqL1xyXG4gIGdldEJhc2VEYXRlKCk6IERhdGUge1xyXG4gICAgcmV0dXJuIHRoaXMuYmFzZURhdGUudG9EYXRlKCk7XHJcbiAgfVxyXG5cclxuICBwYXJzZUlTTyhpc29TdHJpbmc6IHN0cmluZyk6IERhdGUge1xyXG4gICAgcmV0dXJuIGRheWpzKGlzb1N0cmluZykudG9EYXRlKCk7XHJcbiAgfVxyXG5cclxuICBnZXREYXlOYW1lKGRhdGU6IERhdGUsIGZvcm1hdDogJ3Nob3J0JyB8ICdsb25nJyA9ICdzaG9ydCcpOiBzdHJpbmcge1xyXG4gICAgcmV0dXJuIG5ldyBJbnRsLkRhdGVUaW1lRm9ybWF0KHRoaXMuY29uZmlnLmxvY2FsZSwgeyB3ZWVrZGF5OiBmb3JtYXQgfSkuZm9ybWF0KGRhdGUpO1xyXG4gIH1cclxuXHJcbiAgZ2V0V2Vla0RhdGVzKG9mZnNldCA9IDAsIGRheXMgPSA3KTogc3RyaW5nW10ge1xyXG4gICAgY29uc3QgbW9uZGF5ID0gdGhpcy5iYXNlRGF0ZS5zdGFydE9mKCd3ZWVrJykuYWRkKDEsICdkYXknKS5hZGQob2Zmc2V0LCAnd2VlaycpO1xyXG4gICAgcmV0dXJuIEFycmF5LmZyb20oeyBsZW5ndGg6IGRheXMgfSwgKF8sIGkpID0+XHJcbiAgICAgIG1vbmRheS5hZGQoaSwgJ2RheScpLmZvcm1hdCgnWVlZWS1NTS1ERCcpXHJcbiAgICApO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogR2V0IGRhdGVzIGZvciBzcGVjaWZpYyB3ZWVrZGF5cyB3aXRoaW4gYSB3ZWVrXHJcbiAgICogQHBhcmFtIG9mZnNldCAtIFdlZWsgb2Zmc2V0IGZyb20gYmFzZSBkYXRlICgwID0gY3VycmVudCB3ZWVrKVxyXG4gICAqIEBwYXJhbSB3b3JrRGF5cyAtIEFycmF5IG9mIElTTyB3ZWVrZGF5IG51bWJlcnMgKDE9TW9uZGF5LCA3PVN1bmRheSlcclxuICAgKiBAcmV0dXJucyBBcnJheSBvZiBkYXRlIHN0cmluZ3MgaW4gWVlZWS1NTS1ERCBmb3JtYXRcclxuICAgKi9cclxuICBnZXRXb3JrV2Vla0RhdGVzKG9mZnNldDogbnVtYmVyLCB3b3JrRGF5czogbnVtYmVyW10pOiBzdHJpbmdbXSB7XHJcbiAgICBjb25zdCBtb25kYXkgPSB0aGlzLmJhc2VEYXRlLnN0YXJ0T2YoJ3dlZWsnKS5hZGQoMSwgJ2RheScpLmFkZChvZmZzZXQsICd3ZWVrJyk7XHJcbiAgICByZXR1cm4gd29ya0RheXMubWFwKGlzb0RheSA9PiB7XHJcbiAgICAgIC8vIElTTzogMT1Nb25kYXksIDc9U3VuZGF5IFx1MjE5MiBkYXlzIGZyb20gTW9uZGF5OiAwLTZcclxuICAgICAgY29uc3QgZGF5c0Zyb21Nb25kYXkgPSBpc29EYXkgPT09IDcgPyA2IDogaXNvRGF5IC0gMTtcclxuICAgICAgcmV0dXJuIG1vbmRheS5hZGQoZGF5c0Zyb21Nb25kYXksICdkYXknKS5mb3JtYXQoJ1lZWVktTU0tREQnKTtcclxuICAgIH0pO1xyXG4gIH1cclxuXHJcbiAgLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cclxuICAvLyBGT1JNQVRUSU5HXHJcbiAgLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cclxuXHJcbiAgZm9ybWF0VGltZShkYXRlOiBEYXRlLCBzaG93U2Vjb25kcyA9IGZhbHNlKTogc3RyaW5nIHtcclxuICAgIGNvbnN0IHBhdHRlcm4gPSBzaG93U2Vjb25kcyA/ICdISDptbTpzcycgOiAnSEg6bW0nO1xyXG4gICAgcmV0dXJuIGRheWpzKGRhdGUpLmZvcm1hdChwYXR0ZXJuKTtcclxuICB9XHJcblxyXG4gIGZvcm1hdFRpbWVSYW5nZShzdGFydDogRGF0ZSwgZW5kOiBEYXRlKTogc3RyaW5nIHtcclxuICAgIHJldHVybiBgJHt0aGlzLmZvcm1hdFRpbWUoc3RhcnQpfSAtICR7dGhpcy5mb3JtYXRUaW1lKGVuZCl9YDtcclxuICB9XHJcblxyXG4gIGZvcm1hdERhdGUoZGF0ZTogRGF0ZSk6IHN0cmluZyB7XHJcbiAgICByZXR1cm4gZGF5anMoZGF0ZSkuZm9ybWF0KCdZWVlZLU1NLUREJyk7XHJcbiAgfVxyXG5cclxuICBnZXREYXRlS2V5KGRhdGU6IERhdGUpOiBzdHJpbmcge1xyXG4gICAgcmV0dXJuIHRoaXMuZm9ybWF0RGF0ZShkYXRlKTtcclxuICB9XHJcblxyXG4gIC8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XHJcbiAgLy8gQ09MVU1OIEtFWVxyXG4gIC8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XHJcblxyXG4gIC8qKlxyXG4gICAqIEJ1aWxkIGEgdW5pZm9ybSBjb2x1bW5LZXkgZnJvbSBncm91cGluZyBzZWdtZW50c1xyXG4gICAqIEhhbmRsZXMgYW55IGNvbWJpbmF0aW9uIG9mIGRhdGUsIHJlc291cmNlLCB0ZWFtLCBldGMuXHJcbiAgICpcclxuICAgKiBAZXhhbXBsZVxyXG4gICAqIGJ1aWxkQ29sdW1uS2V5KHsgZGF0ZTogJzIwMjUtMTItMDknIH0pIFx1MjE5MiBcIjIwMjUtMTItMDlcIlxyXG4gICAqIGJ1aWxkQ29sdW1uS2V5KHsgZGF0ZTogJzIwMjUtMTItMDknLCByZXNvdXJjZTogJ0VNUDAwMScgfSkgXHUyMTkyIFwiMjAyNS0xMi0wOTpFTVAwMDFcIlxyXG4gICAqL1xyXG4gIGJ1aWxkQ29sdW1uS2V5KHNlZ21lbnRzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+KTogc3RyaW5nIHtcclxuICAgIC8vIEFsd2F5cyBwdXQgZGF0ZSBmaXJzdCBpZiBwcmVzZW50LCB0aGVuIG90aGVyIHNlZ21lbnRzIGFscGhhYmV0aWNhbGx5XHJcbiAgICBjb25zdCBkYXRlID0gc2VnbWVudHMuZGF0ZTtcclxuICAgIGNvbnN0IG90aGVycyA9IE9iamVjdC5lbnRyaWVzKHNlZ21lbnRzKVxyXG4gICAgICAuZmlsdGVyKChba10pID0+IGsgIT09ICdkYXRlJylcclxuICAgICAgLnNvcnQoKFthXSwgW2JdKSA9PiBhLmxvY2FsZUNvbXBhcmUoYikpXHJcbiAgICAgIC5tYXAoKFssIHZdKSA9PiB2KTtcclxuXHJcbiAgICByZXR1cm4gZGF0ZSA/IFtkYXRlLCAuLi5vdGhlcnNdLmpvaW4oJzonKSA6IG90aGVycy5qb2luKCc6Jyk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBQYXJzZSBhIGNvbHVtbktleSBiYWNrIGludG8gc2VnbWVudHNcclxuICAgKiBBc3N1bWVzIGZvcm1hdDogXCJkYXRlOnJlc291cmNlOi4uLlwiIG9yIGp1c3QgXCJkYXRlXCJcclxuICAgKi9cclxuICBwYXJzZUNvbHVtbktleShjb2x1bW5LZXk6IHN0cmluZyk6IHsgZGF0ZTogc3RyaW5nOyByZXNvdXJjZT86IHN0cmluZyB9IHtcclxuICAgIGNvbnN0IHBhcnRzID0gY29sdW1uS2V5LnNwbGl0KCc6Jyk7XHJcbiAgICByZXR1cm4ge1xyXG4gICAgICBkYXRlOiBwYXJ0c1swXSxcclxuICAgICAgcmVzb3VyY2U6IHBhcnRzWzFdXHJcbiAgICB9O1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogRXh0cmFjdCBkYXRlS2V5IGZyb20gY29sdW1uS2V5IChmaXJzdCBzZWdtZW50KVxyXG4gICAqL1xyXG4gIGdldERhdGVGcm9tQ29sdW1uS2V5KGNvbHVtbktleTogc3RyaW5nKTogc3RyaW5nIHtcclxuICAgIHJldHVybiBjb2x1bW5LZXkuc3BsaXQoJzonKVswXTtcclxuICB9XHJcblxyXG4gIC8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XHJcbiAgLy8gVElNRSBDQUxDVUxBVElPTlNcclxuICAvLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxyXG5cclxuICB0aW1lVG9NaW51dGVzKHRpbWVTdHJpbmc6IHN0cmluZyk6IG51bWJlciB7XHJcbiAgICBjb25zdCBwYXJ0cyA9IHRpbWVTdHJpbmcuc3BsaXQoJzonKS5tYXAoTnVtYmVyKTtcclxuICAgIGNvbnN0IGhvdXJzID0gcGFydHNbMF0gfHwgMDtcclxuICAgIGNvbnN0IG1pbnV0ZXMgPSBwYXJ0c1sxXSB8fCAwO1xyXG4gICAgcmV0dXJuIGhvdXJzICogNjAgKyBtaW51dGVzO1xyXG4gIH1cclxuXHJcbiAgbWludXRlc1RvVGltZSh0b3RhbE1pbnV0ZXM6IG51bWJlcik6IHN0cmluZyB7XHJcbiAgICBjb25zdCBob3VycyA9IE1hdGguZmxvb3IodG90YWxNaW51dGVzIC8gNjApO1xyXG4gICAgY29uc3QgbWludXRlcyA9IHRvdGFsTWludXRlcyAlIDYwO1xyXG4gICAgcmV0dXJuIGRheWpzKCkuaG91cihob3VycykubWludXRlKG1pbnV0ZXMpLmZvcm1hdCgnSEg6bW0nKTtcclxuICB9XHJcblxyXG4gIGdldE1pbnV0ZXNTaW5jZU1pZG5pZ2h0KGRhdGU6IERhdGUpOiBudW1iZXIge1xyXG4gICAgY29uc3QgZCA9IGRheWpzKGRhdGUpO1xyXG4gICAgcmV0dXJuIGQuaG91cigpICogNjAgKyBkLm1pbnV0ZSgpO1xyXG4gIH1cclxuXHJcbiAgLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cclxuICAvLyBVVEMgQ09OVkVSU0lPTlNcclxuICAvLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxyXG5cclxuICB0b1VUQyhsb2NhbERhdGU6IERhdGUpOiBzdHJpbmcge1xyXG4gICAgcmV0dXJuIGRheWpzLnR6KGxvY2FsRGF0ZSwgdGhpcy50aW1lem9uZSkudXRjKCkudG9JU09TdHJpbmcoKTtcclxuICB9XHJcblxyXG4gIGZyb21VVEModXRjU3RyaW5nOiBzdHJpbmcpOiBEYXRlIHtcclxuICAgIHJldHVybiBkYXlqcy51dGModXRjU3RyaW5nKS50eih0aGlzLnRpbWV6b25lKS50b0RhdGUoKTtcclxuICB9XHJcblxyXG4gIC8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XHJcbiAgLy8gREFURSBDUkVBVElPTlxyXG4gIC8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XHJcblxyXG4gIGNyZWF0ZURhdGVBdFRpbWUoYmFzZURhdGU6IERhdGUgfCBzdHJpbmcsIHRpbWVTdHJpbmc6IHN0cmluZyk6IERhdGUge1xyXG4gICAgY29uc3QgdG90YWxNaW51dGVzID0gdGhpcy50aW1lVG9NaW51dGVzKHRpbWVTdHJpbmcpO1xyXG4gICAgY29uc3QgaG91cnMgPSBNYXRoLmZsb29yKHRvdGFsTWludXRlcyAvIDYwKTtcclxuICAgIGNvbnN0IG1pbnV0ZXMgPSB0b3RhbE1pbnV0ZXMgJSA2MDtcclxuICAgIHJldHVybiBkYXlqcyhiYXNlRGF0ZSkuc3RhcnRPZignZGF5JykuaG91cihob3VycykubWludXRlKG1pbnV0ZXMpLnRvRGF0ZSgpO1xyXG4gIH1cclxuXHJcbiAgZ2V0SVNPV2Vla0RheShkYXRlOiBEYXRlIHwgc3RyaW5nKTogbnVtYmVyIHtcclxuICAgIHJldHVybiBkYXlqcyhkYXRlKS5pc29XZWVrZGF5KCk7ICAvLyAxPU1vbmRheSwgNz1TdW5kYXlcclxuICB9XHJcbn1cclxuIiwgIi8qKlxuICogUG9zaXRpb25VdGlscyAtIFBpeGVsL3Bvc2l0aW9uIGNhbGN1bGF0aW9ucyBmb3IgY2FsZW5kYXIgZ3JpZFxuICpcbiAqIFJFU1BPTlNJQklMSVRZOiBDb252ZXJ0IGJldHdlZW4gdGltZSBhbmQgcGl4ZWwgcG9zaXRpb25zXG4gKiBOT1RFOiBEYXRlIGZvcm1hdHRpbmcgYmVsb25ncyBpbiBEYXRlU2VydmljZSwgbm90IGhlcmVcbiAqL1xuXG5pbXBvcnQgeyBJR3JpZENvbmZpZyB9IGZyb20gJy4uL2NvcmUvSUdyaWRDb25maWcnO1xuXG5leHBvcnQgaW50ZXJmYWNlIEV2ZW50UG9zaXRpb24ge1xuICB0b3A6IG51bWJlcjsgICAgLy8gcGl4ZWxzIGZyb20gZGF5IHN0YXJ0XG4gIGhlaWdodDogbnVtYmVyOyAvLyBwaXhlbHNcbn1cblxuLyoqXG4gKiBDYWxjdWxhdGUgcGl4ZWwgcG9zaXRpb24gZm9yIGFuIGV2ZW50IGJhc2VkIG9uIGl0cyB0aW1lc1xuICovXG5leHBvcnQgZnVuY3Rpb24gY2FsY3VsYXRlRXZlbnRQb3NpdGlvbihcbiAgc3RhcnQ6IERhdGUsXG4gIGVuZDogRGF0ZSxcbiAgY29uZmlnOiBJR3JpZENvbmZpZ1xuKTogRXZlbnRQb3NpdGlvbiB7XG4gIGNvbnN0IHN0YXJ0TWludXRlcyA9IHN0YXJ0LmdldEhvdXJzKCkgKiA2MCArIHN0YXJ0LmdldE1pbnV0ZXMoKTtcbiAgY29uc3QgZW5kTWludXRlcyA9IGVuZC5nZXRIb3VycygpICogNjAgKyBlbmQuZ2V0TWludXRlcygpO1xuXG4gIGNvbnN0IGRheVN0YXJ0TWludXRlcyA9IGNvbmZpZy5kYXlTdGFydEhvdXIgKiA2MDtcbiAgY29uc3QgbWludXRlSGVpZ2h0ID0gY29uZmlnLmhvdXJIZWlnaHQgLyA2MDtcblxuICBjb25zdCB0b3AgPSAoc3RhcnRNaW51dGVzIC0gZGF5U3RhcnRNaW51dGVzKSAqIG1pbnV0ZUhlaWdodDtcbiAgY29uc3QgaGVpZ2h0ID0gKGVuZE1pbnV0ZXMgLSBzdGFydE1pbnV0ZXMpICogbWludXRlSGVpZ2h0O1xuXG4gIHJldHVybiB7IHRvcCwgaGVpZ2h0IH07XG59XG5cbi8qKlxuICogQ29udmVydCBtaW51dGVzIHRvIHBpeGVsc1xuICovXG5leHBvcnQgZnVuY3Rpb24gbWludXRlc1RvUGl4ZWxzKG1pbnV0ZXM6IG51bWJlciwgY29uZmlnOiBJR3JpZENvbmZpZyk6IG51bWJlciB7XG4gIHJldHVybiAobWludXRlcyAvIDYwKSAqIGNvbmZpZy5ob3VySGVpZ2h0O1xufVxuXG4vKipcbiAqIENvbnZlcnQgcGl4ZWxzIHRvIG1pbnV0ZXNcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHBpeGVsc1RvTWludXRlcyhwaXhlbHM6IG51bWJlciwgY29uZmlnOiBJR3JpZENvbmZpZyk6IG51bWJlciB7XG4gIHJldHVybiAocGl4ZWxzIC8gY29uZmlnLmhvdXJIZWlnaHQpICogNjA7XG59XG5cbi8qKlxuICogU25hcCBwaXhlbCBwb3NpdGlvbiB0byBncmlkIGludGVydmFsXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzbmFwVG9HcmlkKHBpeGVsczogbnVtYmVyLCBjb25maWc6IElHcmlkQ29uZmlnKTogbnVtYmVyIHtcbiAgY29uc3Qgc25hcFBpeGVscyA9IG1pbnV0ZXNUb1BpeGVscyhjb25maWcuc25hcEludGVydmFsLCBjb25maWcpO1xuICByZXR1cm4gTWF0aC5yb3VuZChwaXhlbHMgLyBzbmFwUGl4ZWxzKSAqIHNuYXBQaXhlbHM7XG59XG4iLCAiLyoqXG4gKiBDb3JlRXZlbnRzIC0gQ29uc29saWRhdGVkIGVzc2VudGlhbCBldmVudHMgZm9yIHRoZSBjYWxlbmRhciBWMlxuICovXG5leHBvcnQgY29uc3QgQ29yZUV2ZW50cyA9IHtcbiAgLy8gTGlmZWN5Y2xlIGV2ZW50c1xuICBJTklUSUFMSVpFRDogJ2NvcmU6aW5pdGlhbGl6ZWQnLFxuICBSRUFEWTogJ2NvcmU6cmVhZHknLFxuICBERVNUUk9ZRUQ6ICdjb3JlOmRlc3Ryb3llZCcsXG5cbiAgLy8gVmlldyBldmVudHNcbiAgVklFV19DSEFOR0VEOiAndmlldzpjaGFuZ2VkJyxcbiAgVklFV19SRU5ERVJFRDogJ3ZpZXc6cmVuZGVyZWQnLFxuXG4gIC8vIE5hdmlnYXRpb24gZXZlbnRzXG4gIERBVEVfQ0hBTkdFRDogJ25hdjpkYXRlLWNoYW5nZWQnLFxuICBOQVZJR0FUSU9OX0NPTVBMRVRFRDogJ25hdjpuYXZpZ2F0aW9uLWNvbXBsZXRlZCcsXG5cbiAgLy8gRGF0YSBldmVudHNcbiAgREFUQV9MT0FESU5HOiAnZGF0YTpsb2FkaW5nJyxcbiAgREFUQV9MT0FERUQ6ICdkYXRhOmxvYWRlZCcsXG4gIERBVEFfRVJST1I6ICdkYXRhOmVycm9yJyxcblxuICAvLyBHcmlkIGV2ZW50c1xuICBHUklEX1JFTkRFUkVEOiAnZ3JpZDpyZW5kZXJlZCcsXG4gIEdSSURfQ0xJQ0tFRDogJ2dyaWQ6Y2xpY2tlZCcsXG5cbiAgLy8gRXZlbnQgbWFuYWdlbWVudFxuICBFVkVOVF9DUkVBVEVEOiAnZXZlbnQ6Y3JlYXRlZCcsXG4gIEVWRU5UX1VQREFURUQ6ICdldmVudDp1cGRhdGVkJyxcbiAgRVZFTlRfREVMRVRFRDogJ2V2ZW50OmRlbGV0ZWQnLFxuICBFVkVOVF9TRUxFQ1RFRDogJ2V2ZW50OnNlbGVjdGVkJyxcblxuICAvLyBFdmVudCBkcmFnLWRyb3BcbiAgRVZFTlRfRFJBR19TVEFSVDogJ2V2ZW50OmRyYWctc3RhcnQnLFxuICBFVkVOVF9EUkFHX01PVkU6ICdldmVudDpkcmFnLW1vdmUnLFxuICBFVkVOVF9EUkFHX0VORDogJ2V2ZW50OmRyYWctZW5kJyxcbiAgRVZFTlRfRFJBR19DQU5DRUw6ICdldmVudDpkcmFnLWNhbmNlbCcsXG4gIEVWRU5UX0RSQUdfQ09MVU1OX0NIQU5HRTogJ2V2ZW50OmRyYWctY29sdW1uLWNoYW5nZScsXG5cbiAgLy8gSGVhZGVyIGRyYWcgKHRpbWVkIFx1MjE5MiBoZWFkZXIgY29udmVyc2lvbilcbiAgRVZFTlRfRFJBR19FTlRFUl9IRUFERVI6ICdldmVudDpkcmFnLWVudGVyLWhlYWRlcicsXG4gIEVWRU5UX0RSQUdfTU9WRV9IRUFERVI6ICdldmVudDpkcmFnLW1vdmUtaGVhZGVyJyxcbiAgRVZFTlRfRFJBR19MRUFWRV9IRUFERVI6ICdldmVudDpkcmFnLWxlYXZlLWhlYWRlcicsXG5cbiAgLy8gRXZlbnQgcmVzaXplXG4gIEVWRU5UX1JFU0laRV9TVEFSVDogJ2V2ZW50OnJlc2l6ZS1zdGFydCcsXG4gIEVWRU5UX1JFU0laRV9FTkQ6ICdldmVudDpyZXNpemUtZW5kJyxcblxuICAvLyBFZGdlIHNjcm9sbFxuICBFREdFX1NDUk9MTF9USUNLOiAnZWRnZS1zY3JvbGw6dGljaycsXG4gIEVER0VfU0NST0xMX1NUQVJURUQ6ICdlZGdlLXNjcm9sbDpzdGFydGVkJyxcbiAgRURHRV9TQ1JPTExfU1RPUFBFRDogJ2VkZ2Utc2Nyb2xsOnN0b3BwZWQnLFxuXG4gIC8vIFN5c3RlbSBldmVudHNcbiAgRVJST1I6ICdzeXN0ZW06ZXJyb3InLFxuXG4gIC8vIFN5bmMgZXZlbnRzXG4gIFNZTkNfU1RBUlRFRDogJ3N5bmM6c3RhcnRlZCcsXG4gIFNZTkNfQ09NUExFVEVEOiAnc3luYzpjb21wbGV0ZWQnLFxuICBTWU5DX0ZBSUxFRDogJ3N5bmM6ZmFpbGVkJyxcblxuICAvLyBFbnRpdHkgZXZlbnRzIC0gZm9yIGF1ZGl0IGFuZCBzeW5jXG4gIEVOVElUWV9TQVZFRDogJ2VudGl0eTpzYXZlZCcsXG4gIEVOVElUWV9ERUxFVEVEOiAnZW50aXR5OmRlbGV0ZWQnLFxuXG4gIC8vIEF1ZGl0IGV2ZW50c1xuICBBVURJVF9MT0dHRUQ6ICdhdWRpdDpsb2dnZWQnLFxuXG4gIC8vIFJlbmRlcmluZyBldmVudHNcbiAgRVZFTlRTX1JFTkRFUkVEOiAnZXZlbnRzOnJlbmRlcmVkJ1xufSBhcyBjb25zdDtcbiIsICIvKipcclxuICogRXZlbnRMYXlvdXRFbmdpbmUgLSBTaW1wbGlmaWVkIHN0YWNraW5nL2dyb3VwaW5nIGFsZ29yaXRobSBmb3IgVjJcclxuICpcclxuICogU3VwcG9ydHMgdHdvIGxheW91dCBtb2RlczpcclxuICogLSBHUklEOiBFdmVudHMgc3RhcnRpbmcgYXQgc2FtZSB0aW1lIHJlbmRlcmVkIHNpZGUtYnktc2lkZVxyXG4gKiAtIFNUQUNLSU5HOiBPdmVybGFwcGluZyBldmVudHMgd2l0aCBtYXJnaW4tbGVmdCBvZmZzZXQgKDE1cHggcGVyIGxldmVsKVxyXG4gKlxyXG4gKiBTaW1wbGlmaWVkIGZyb20gVjE6IE5vIHByZXYvbmV4dCBjaGFpbnMsIHNpbmdsZS1wYXNzIGdyZWVkeSBhbGdvcml0aG1cclxuICovXHJcblxyXG5pbXBvcnQgeyBJQ2FsZW5kYXJFdmVudCB9IGZyb20gJy4uLy4uL3R5cGVzL0NhbGVuZGFyVHlwZXMnO1xyXG5pbXBvcnQgeyBJR3JpZENvbmZpZyB9IGZyb20gJy4uLy4uL2NvcmUvSUdyaWRDb25maWcnO1xyXG5pbXBvcnQgeyBjYWxjdWxhdGVFdmVudFBvc2l0aW9uIH0gZnJvbSAnLi4vLi4vdXRpbHMvUG9zaXRpb25VdGlscyc7XHJcbmltcG9ydCB7IElDb2x1bW5MYXlvdXQsIElHcmlkR3JvdXBMYXlvdXQsIElTdGFja2VkRXZlbnRMYXlvdXQgfSBmcm9tICcuL0V2ZW50TGF5b3V0VHlwZXMnO1xyXG5cclxuLyoqXHJcbiAqIENoZWNrIGlmIHR3byBldmVudHMgb3ZlcmxhcCAoc3RyaWN0IC0gdG91Y2hpbmcgYXQgYm91bmRhcnkgPSBOT1Qgb3ZlcmxhcHBpbmcpXHJcbiAqIFRoaXMgbWF0Y2hlcyBTY2VuYXJpbyA4OiBlbmQ9PT1zdGFydCBpcyBOT1Qgb3ZlcmxhcFxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIGV2ZW50c092ZXJsYXAoYTogSUNhbGVuZGFyRXZlbnQsIGI6IElDYWxlbmRhckV2ZW50KTogYm9vbGVhbiB7XHJcbiAgcmV0dXJuIGEuc3RhcnQgPCBiLmVuZCAmJiBhLmVuZCA+IGIuc3RhcnQ7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBDaGVjayBpZiB0d28gZXZlbnRzIGFyZSB3aXRoaW4gdGhyZXNob2xkIGZvciBncmlkIGdyb3VwaW5nLlxyXG4gKiBUaGlzIGluY2x1ZGVzOlxyXG4gKiAxLiBTdGFydC10by1zdGFydDogRXZlbnRzIHN0YXJ0IHdpdGhpbiB0aHJlc2hvbGQgb2YgZWFjaCBvdGhlclxyXG4gKiAyLiBFbmQtdG8tc3RhcnQ6IE9uZSBldmVudCBzdGFydHMgd2l0aGluIHRocmVzaG9sZCBiZWZvcmUgYW5vdGhlciBlbmRzXHJcbiAqL1xyXG5mdW5jdGlvbiBldmVudHNXaXRoaW5UaHJlc2hvbGQoYTogSUNhbGVuZGFyRXZlbnQsIGI6IElDYWxlbmRhckV2ZW50LCB0aHJlc2hvbGRNaW51dGVzOiBudW1iZXIpOiBib29sZWFuIHtcclxuICBjb25zdCB0aHJlc2hvbGRNcyA9IHRocmVzaG9sZE1pbnV0ZXMgKiA2MCAqIDEwMDA7XHJcblxyXG4gIC8vIFN0YXJ0LXRvLXN0YXJ0OiBib3RoIGV2ZW50cyBzdGFydCB3aXRoaW4gdGhyZXNob2xkXHJcbiAgY29uc3Qgc3RhcnRUb1N0YXJ0RGlmZiA9IE1hdGguYWJzKGEuc3RhcnQuZ2V0VGltZSgpIC0gYi5zdGFydC5nZXRUaW1lKCkpO1xyXG4gIGlmIChzdGFydFRvU3RhcnREaWZmIDw9IHRocmVzaG9sZE1zKSByZXR1cm4gdHJ1ZTtcclxuXHJcbiAgLy8gRW5kLXRvLXN0YXJ0OiBvbmUgZXZlbnQgc3RhcnRzIHdpdGhpbiB0aHJlc2hvbGQgYmVmb3JlIHRoZSBvdGhlciBlbmRzXHJcbiAgLy8gQiBzdGFydHMgd2l0aGluIHRocmVzaG9sZCBiZWZvcmUgQSBlbmRzXHJcbiAgY29uc3QgYlN0YXJ0c0JlZm9yZUFFbmRzID0gYS5lbmQuZ2V0VGltZSgpIC0gYi5zdGFydC5nZXRUaW1lKCk7XHJcbiAgaWYgKGJTdGFydHNCZWZvcmVBRW5kcyA+IDAgJiYgYlN0YXJ0c0JlZm9yZUFFbmRzIDw9IHRocmVzaG9sZE1zKSByZXR1cm4gdHJ1ZTtcclxuXHJcbiAgLy8gQSBzdGFydHMgd2l0aGluIHRocmVzaG9sZCBiZWZvcmUgQiBlbmRzXHJcbiAgY29uc3QgYVN0YXJ0c0JlZm9yZUJFbmRzID0gYi5lbmQuZ2V0VGltZSgpIC0gYS5zdGFydC5nZXRUaW1lKCk7XHJcbiAgaWYgKGFTdGFydHNCZWZvcmVCRW5kcyA+IDAgJiYgYVN0YXJ0c0JlZm9yZUJFbmRzIDw9IHRocmVzaG9sZE1zKSByZXR1cm4gdHJ1ZTtcclxuXHJcbiAgcmV0dXJuIGZhbHNlO1xyXG59XHJcblxyXG4vKipcclxuICogQ2hlY2sgaWYgYWxsIGV2ZW50cyBpbiBhIGdyb3VwIHN0YXJ0IHdpdGhpbiB0aHJlc2hvbGQgb2YgZWFjaCBvdGhlclxyXG4gKi9cclxuZnVuY3Rpb24gYWxsU3RhcnRXaXRoaW5UaHJlc2hvbGQoZXZlbnRzOiBJQ2FsZW5kYXJFdmVudFtdLCB0aHJlc2hvbGRNaW51dGVzOiBudW1iZXIpOiBib29sZWFuIHtcclxuICBpZiAoZXZlbnRzLmxlbmd0aCA8PSAxKSByZXR1cm4gdHJ1ZTtcclxuXHJcbiAgLy8gRmluZCBlYXJsaWVzdCBhbmQgbGF0ZXN0IHN0YXJ0IHRpbWVzXHJcbiAgbGV0IGVhcmxpZXN0ID0gZXZlbnRzWzBdLnN0YXJ0LmdldFRpbWUoKTtcclxuICBsZXQgbGF0ZXN0ID0gZXZlbnRzWzBdLnN0YXJ0LmdldFRpbWUoKTtcclxuXHJcbiAgZm9yIChjb25zdCBldmVudCBvZiBldmVudHMpIHtcclxuICAgIGNvbnN0IHRpbWUgPSBldmVudC5zdGFydC5nZXRUaW1lKCk7XHJcbiAgICBpZiAodGltZSA8IGVhcmxpZXN0KSBlYXJsaWVzdCA9IHRpbWU7XHJcbiAgICBpZiAodGltZSA+IGxhdGVzdCkgbGF0ZXN0ID0gdGltZTtcclxuICB9XHJcblxyXG4gIGNvbnN0IGRpZmZNaW51dGVzID0gKGxhdGVzdCAtIGVhcmxpZXN0KSAvICgxMDAwICogNjApO1xyXG4gIHJldHVybiBkaWZmTWludXRlcyA8PSB0aHJlc2hvbGRNaW51dGVzO1xyXG59XHJcblxyXG4vKipcclxuICogRmluZCBncm91cHMgb2Ygb3ZlcmxhcHBpbmcgZXZlbnRzIChjb25uZWN0ZWQgYnkgb3ZlcmxhcCBjaGFpbilcclxuICogRXZlbnRzIGFyZSBncm91cGVkIGlmIHRoZXkgb3ZlcmxhcCB3aXRoIGFueSBldmVudCBpbiB0aGUgZ3JvdXBcclxuICovXHJcbmZ1bmN0aW9uIGZpbmRPdmVybGFwR3JvdXBzKGV2ZW50czogSUNhbGVuZGFyRXZlbnRbXSk6IElDYWxlbmRhckV2ZW50W11bXSB7XHJcbiAgaWYgKGV2ZW50cy5sZW5ndGggPT09IDApIHJldHVybiBbXTtcclxuXHJcbiAgY29uc3Qgc29ydGVkID0gWy4uLmV2ZW50c10uc29ydCgoYSwgYikgPT4gYS5zdGFydC5nZXRUaW1lKCkgLSBiLnN0YXJ0LmdldFRpbWUoKSk7XHJcbiAgY29uc3QgdXNlZCA9IG5ldyBTZXQ8c3RyaW5nPigpO1xyXG4gIGNvbnN0IGdyb3VwczogSUNhbGVuZGFyRXZlbnRbXVtdID0gW107XHJcblxyXG4gIGZvciAoY29uc3QgZXZlbnQgb2Ygc29ydGVkKSB7XHJcbiAgICBpZiAodXNlZC5oYXMoZXZlbnQuaWQpKSBjb250aW51ZTtcclxuXHJcbiAgICAvLyBTdGFydCBhIG5ldyBncm91cCB3aXRoIHRoaXMgZXZlbnRcclxuICAgIGNvbnN0IGdyb3VwOiBJQ2FsZW5kYXJFdmVudFtdID0gW2V2ZW50XTtcclxuICAgIHVzZWQuYWRkKGV2ZW50LmlkKTtcclxuXHJcbiAgICAvLyBFeHBhbmQgZ3JvdXAgYnkgZmluZGluZyBhbGwgY29ubmVjdGVkIGV2ZW50cyAodmlhIG92ZXJsYXApXHJcbiAgICBsZXQgZXhwYW5kZWQgPSB0cnVlO1xyXG4gICAgd2hpbGUgKGV4cGFuZGVkKSB7XHJcbiAgICAgIGV4cGFuZGVkID0gZmFsc2U7XHJcbiAgICAgIGZvciAoY29uc3QgY2FuZGlkYXRlIG9mIHNvcnRlZCkge1xyXG4gICAgICAgIGlmICh1c2VkLmhhcyhjYW5kaWRhdGUuaWQpKSBjb250aW51ZTtcclxuXHJcbiAgICAgICAgLy8gQ2hlY2sgaWYgY2FuZGlkYXRlIG92ZXJsYXBzIHdpdGggYW55IGV2ZW50IGluIGdyb3VwXHJcbiAgICAgICAgY29uc3QgY29ubmVjdHMgPSBncm91cC5zb21lKG1lbWJlciA9PiBldmVudHNPdmVybGFwKG1lbWJlciwgY2FuZGlkYXRlKSk7XHJcblxyXG4gICAgICAgIGlmIChjb25uZWN0cykge1xyXG4gICAgICAgICAgZ3JvdXAucHVzaChjYW5kaWRhdGUpO1xyXG4gICAgICAgICAgdXNlZC5hZGQoY2FuZGlkYXRlLmlkKTtcclxuICAgICAgICAgIGV4cGFuZGVkID0gdHJ1ZTtcclxuICAgICAgICB9XHJcbiAgICAgIH1cclxuICAgIH1cclxuXHJcbiAgICBncm91cHMucHVzaChncm91cCk7XHJcbiAgfVxyXG5cclxuICByZXR1cm4gZ3JvdXBzO1xyXG59XHJcblxyXG4vKipcclxuICogRmluZCBncmlkIGNhbmRpZGF0ZXMgd2l0aGluIGEgZ3JvdXAgLSBldmVudHMgY29ubmVjdGVkIHZpYSB0aHJlc2hvbGQgY2hhaW5cclxuICogVXNlcyBWMSBsb2dpYzogZXZlbnRzIGFyZSBjb25uZWN0ZWQgaWYgd2l0aGluIHRocmVzaG9sZCAobm8gb3ZlcmxhcCByZXF1aXJlbWVudClcclxuICovXHJcbmZ1bmN0aW9uIGZpbmRHcmlkQ2FuZGlkYXRlcyhcclxuICBldmVudHM6IElDYWxlbmRhckV2ZW50W10sXHJcbiAgdGhyZXNob2xkTWludXRlczogbnVtYmVyXHJcbik6IElDYWxlbmRhckV2ZW50W11bXSB7XHJcbiAgaWYgKGV2ZW50cy5sZW5ndGggPT09IDApIHJldHVybiBbXTtcclxuXHJcbiAgY29uc3Qgc29ydGVkID0gWy4uLmV2ZW50c10uc29ydCgoYSwgYikgPT4gYS5zdGFydC5nZXRUaW1lKCkgLSBiLnN0YXJ0LmdldFRpbWUoKSk7XHJcbiAgY29uc3QgdXNlZCA9IG5ldyBTZXQ8c3RyaW5nPigpO1xyXG4gIGNvbnN0IGdyb3VwczogSUNhbGVuZGFyRXZlbnRbXVtdID0gW107XHJcblxyXG4gIGZvciAoY29uc3QgZXZlbnQgb2Ygc29ydGVkKSB7XHJcbiAgICBpZiAodXNlZC5oYXMoZXZlbnQuaWQpKSBjb250aW51ZTtcclxuXHJcbiAgICBjb25zdCBncm91cDogSUNhbGVuZGFyRXZlbnRbXSA9IFtldmVudF07XHJcbiAgICB1c2VkLmFkZChldmVudC5pZCk7XHJcblxyXG4gICAgLy8gRXhwYW5kIGJ5IHRocmVzaG9sZCBjaGFpbiAoVjEgbG9naWM6IG5vIG92ZXJsYXAgcmVxdWlyZW1lbnQsIGp1c3QgdGhyZXNob2xkKVxyXG4gICAgbGV0IGV4cGFuZGVkID0gdHJ1ZTtcclxuICAgIHdoaWxlIChleHBhbmRlZCkge1xyXG4gICAgICBleHBhbmRlZCA9IGZhbHNlO1xyXG4gICAgICBmb3IgKGNvbnN0IGNhbmRpZGF0ZSBvZiBzb3J0ZWQpIHtcclxuICAgICAgICBpZiAodXNlZC5oYXMoY2FuZGlkYXRlLmlkKSkgY29udGludWU7XHJcblxyXG4gICAgICAgIGNvbnN0IGNvbm5lY3RzID0gZ3JvdXAuc29tZShtZW1iZXIgPT5cclxuICAgICAgICAgIGV2ZW50c1dpdGhpblRocmVzaG9sZChtZW1iZXIsIGNhbmRpZGF0ZSwgdGhyZXNob2xkTWludXRlcylcclxuICAgICAgICApO1xyXG5cclxuICAgICAgICBpZiAoY29ubmVjdHMpIHtcclxuICAgICAgICAgIGdyb3VwLnB1c2goY2FuZGlkYXRlKTtcclxuICAgICAgICAgIHVzZWQuYWRkKGNhbmRpZGF0ZS5pZCk7XHJcbiAgICAgICAgICBleHBhbmRlZCA9IHRydWU7XHJcbiAgICAgICAgfVxyXG4gICAgICB9XHJcbiAgICB9XHJcblxyXG4gICAgZ3JvdXBzLnB1c2goZ3JvdXApO1xyXG4gIH1cclxuXHJcbiAgcmV0dXJuIGdyb3VwcztcclxufVxyXG5cclxuLyoqXHJcbiAqIENhbGN1bGF0ZSBzdGFjayBsZXZlbHMgZm9yIG92ZXJsYXBwaW5nIGV2ZW50cyB1c2luZyBncmVlZHkgYWxnb3JpdGhtXHJcbiAqIEZvciBlYWNoIGV2ZW50OiBsZXZlbCA9IG1heChvdmVybGFwcGluZyBhbHJlYWR5LXByb2Nlc3NlZCBldmVudHMpICsgMVxyXG4gKi9cclxuZnVuY3Rpb24gY2FsY3VsYXRlU3RhY2tMZXZlbHMoZXZlbnRzOiBJQ2FsZW5kYXJFdmVudFtdKTogTWFwPHN0cmluZywgbnVtYmVyPiB7XHJcbiAgY29uc3QgbGV2ZWxzID0gbmV3IE1hcDxzdHJpbmcsIG51bWJlcj4oKTtcclxuICBjb25zdCBzb3J0ZWQgPSBbLi4uZXZlbnRzXS5zb3J0KChhLCBiKSA9PiBhLnN0YXJ0LmdldFRpbWUoKSAtIGIuc3RhcnQuZ2V0VGltZSgpKTtcclxuXHJcbiAgZm9yIChjb25zdCBldmVudCBvZiBzb3J0ZWQpIHtcclxuICAgIGxldCBtYXhPdmVybGFwcGluZ0xldmVsID0gLTE7XHJcblxyXG4gICAgLy8gRmluZCBtYXggbGV2ZWwgYW1vbmcgb3ZlcmxhcHBpbmcgZXZlbnRzIGFscmVhZHkgcHJvY2Vzc2VkXHJcbiAgICBmb3IgKGNvbnN0IFtpZCwgbGV2ZWxdIG9mIGxldmVscykge1xyXG4gICAgICBjb25zdCBvdGhlciA9IGV2ZW50cy5maW5kKGUgPT4gZS5pZCA9PT0gaWQpO1xyXG4gICAgICBpZiAob3RoZXIgJiYgZXZlbnRzT3ZlcmxhcChldmVudCwgb3RoZXIpKSB7XHJcbiAgICAgICAgbWF4T3ZlcmxhcHBpbmdMZXZlbCA9IE1hdGgubWF4KG1heE92ZXJsYXBwaW5nTGV2ZWwsIGxldmVsKTtcclxuICAgICAgfVxyXG4gICAgfVxyXG5cclxuICAgIGxldmVscy5zZXQoZXZlbnQuaWQsIG1heE92ZXJsYXBwaW5nTGV2ZWwgKyAxKTtcclxuICB9XHJcblxyXG4gIHJldHVybiBsZXZlbHM7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBBbGxvY2F0ZSBldmVudHMgdG8gY29sdW1ucyBmb3IgR1JJRCBsYXlvdXQgdXNpbmcgZ3JlZWR5IGFsZ29yaXRobVxyXG4gKiBOb24tb3ZlcmxhcHBpbmcgZXZlbnRzIGNhbiBzaGFyZSBhIGNvbHVtbiB0byBtaW5pbWl6ZSB0b3RhbCBjb2x1bW5zXHJcbiAqL1xyXG5mdW5jdGlvbiBhbGxvY2F0ZUNvbHVtbnMoZXZlbnRzOiBJQ2FsZW5kYXJFdmVudFtdKTogSUNhbGVuZGFyRXZlbnRbXVtdIHtcclxuICBjb25zdCBzb3J0ZWQgPSBbLi4uZXZlbnRzXS5zb3J0KChhLCBiKSA9PiBhLnN0YXJ0LmdldFRpbWUoKSAtIGIuc3RhcnQuZ2V0VGltZSgpKTtcclxuICBjb25zdCBjb2x1bW5zOiBJQ2FsZW5kYXJFdmVudFtdW10gPSBbXTtcclxuXHJcbiAgZm9yIChjb25zdCBldmVudCBvZiBzb3J0ZWQpIHtcclxuICAgIC8vIEZpbmQgZmlyc3QgY29sdW1uIHdoZXJlIGV2ZW50IGRvZXNuJ3Qgb3ZlcmxhcCB3aXRoIGV4aXN0aW5nIGV2ZW50c1xyXG4gICAgbGV0IHBsYWNlZCA9IGZhbHNlO1xyXG4gICAgZm9yIChjb25zdCBjb2x1bW4gb2YgY29sdW1ucykge1xyXG4gICAgICBjb25zdCBjYW5GaXQgPSAhY29sdW1uLnNvbWUoZSA9PiBldmVudHNPdmVybGFwKGV2ZW50LCBlKSk7XHJcbiAgICAgIGlmIChjYW5GaXQpIHtcclxuICAgICAgICBjb2x1bW4ucHVzaChldmVudCk7XHJcbiAgICAgICAgcGxhY2VkID0gdHJ1ZTtcclxuICAgICAgICBicmVhaztcclxuICAgICAgfVxyXG4gICAgfVxyXG5cclxuICAgIC8vIE5vIHN1aXRhYmxlIGNvbHVtbiBmb3VuZCwgY3JlYXRlIG5ldyBvbmVcclxuICAgIGlmICghcGxhY2VkKSB7XHJcbiAgICAgIGNvbHVtbnMucHVzaChbZXZlbnRdKTtcclxuICAgIH1cclxuICB9XHJcblxyXG4gIHJldHVybiBjb2x1bW5zO1xyXG59XHJcblxyXG4vKipcclxuICogTWFpbiBlbnRyeSBwb2ludDogQ2FsY3VsYXRlIGNvbXBsZXRlIGxheW91dCBmb3IgYSBjb2x1bW4ncyBldmVudHNcclxuICpcclxuICogQWxnb3JpdGhtOlxyXG4gKiAxLiBGaW5kIG92ZXJsYXAgZ3JvdXBzIChldmVudHMgY29ubmVjdGVkIGJ5IG92ZXJsYXAgY2hhaW4pXHJcbiAqIDIuIEZvciBlYWNoIG92ZXJsYXAgZ3JvdXAsIGZpbmQgZ3JpZCBjYW5kaWRhdGVzIChldmVudHMgd2l0aGluIHRocmVzaG9sZCBjaGFpbilcclxuICogMy4gSWYgYWxsIGV2ZW50cyBpbiBvdmVybGFwIGdyb3VwIGZvcm0gYSBzaW5nbGUgZ3JpZCBjYW5kaWRhdGUgXHUyMTkyIEdSSUQgbW9kZVxyXG4gKiA0LiBPdGhlcndpc2UgXHUyMTkyIFNUQUNLSU5HIG1vZGUgd2l0aCBjYWxjdWxhdGVkIGxldmVsc1xyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIGNhbGN1bGF0ZUNvbHVtbkxheW91dChcclxuICBldmVudHM6IElDYWxlbmRhckV2ZW50W10sXHJcbiAgY29uZmlnOiBJR3JpZENvbmZpZ1xyXG4pOiBJQ29sdW1uTGF5b3V0IHtcclxuICBjb25zdCB0aHJlc2hvbGRNaW51dGVzID0gY29uZmlnLmdyaWRTdGFydFRocmVzaG9sZE1pbnV0ZXMgPz8gMTA7XHJcblxyXG4gIGNvbnN0IHJlc3VsdDogSUNvbHVtbkxheW91dCA9IHtcclxuICAgIGdyaWRzOiBbXSxcclxuICAgIHN0YWNrZWQ6IFtdXHJcbiAgfTtcclxuXHJcbiAgaWYgKGV2ZW50cy5sZW5ndGggPT09IDApIHJldHVybiByZXN1bHQ7XHJcblxyXG4gIC8vIEZpbmQgYWxsIG92ZXJsYXBwaW5nIGV2ZW50IGdyb3Vwc1xyXG4gIGNvbnN0IG92ZXJsYXBHcm91cHMgPSBmaW5kT3ZlcmxhcEdyb3VwcyhldmVudHMpO1xyXG5cclxuICBmb3IgKGNvbnN0IG92ZXJsYXBHcm91cCBvZiBvdmVybGFwR3JvdXBzKSB7XHJcbiAgICBpZiAob3ZlcmxhcEdyb3VwLmxlbmd0aCA9PT0gMSkge1xyXG4gICAgICAvLyBTaW5nbGUgZXZlbnQgLSBubyBncm91cGluZyBuZWVkZWRcclxuICAgICAgcmVzdWx0LnN0YWNrZWQucHVzaCh7XHJcbiAgICAgICAgZXZlbnQ6IG92ZXJsYXBHcm91cFswXSxcclxuICAgICAgICBzdGFja0xldmVsOiAwXHJcbiAgICAgIH0pO1xyXG4gICAgICBjb250aW51ZTtcclxuICAgIH1cclxuXHJcbiAgICAvLyBXaXRoaW4gdGhpcyBvdmVybGFwIGdyb3VwLCBmaW5kIGdyaWQgY2FuZGlkYXRlcyAodGhyZXNob2xkLWNvbm5lY3RlZCBzdWJncm91cHMpXHJcbiAgICBjb25zdCBncmlkU3ViZ3JvdXBzID0gZmluZEdyaWRDYW5kaWRhdGVzKG92ZXJsYXBHcm91cCwgdGhyZXNob2xkTWludXRlcyk7XHJcblxyXG4gICAgLy8gQ2hlY2sgaWYgdGhlIEVOVElSRSBvdmVybGFwIGdyb3VwIGZvcm1zIGEgc2luZ2xlIGdyaWQgY2FuZGlkYXRlXHJcbiAgICAvLyBUaGlzIGhhcHBlbnMgd2hlbiBhbGwgZXZlbnRzIGFyZSBjb25uZWN0ZWQgdmlhIHRocmVzaG9sZCBjaGFpblxyXG4gICAgY29uc3QgbGFyZ2VzdEdyaWRDYW5kaWRhdGUgPSBncmlkU3ViZ3JvdXBzLnJlZHVjZSgobWF4LCBnKSA9PlxyXG4gICAgICBnLmxlbmd0aCA+IG1heC5sZW5ndGggPyBnIDogbWF4LCBncmlkU3ViZ3JvdXBzWzBdKTtcclxuXHJcbiAgICBpZiAobGFyZ2VzdEdyaWRDYW5kaWRhdGUubGVuZ3RoID09PSBvdmVybGFwR3JvdXAubGVuZ3RoKSB7XHJcbiAgICAgIC8vIEFsbCBldmVudHMgaW4gb3ZlcmxhcCBncm91cCBhcmUgY29ubmVjdGVkIHZpYSB0aHJlc2hvbGQgY2hhaW4gXHUyMTkyIEdSSUQgbW9kZVxyXG4gICAgICBjb25zdCBjb2x1bW5zID0gYWxsb2NhdGVDb2x1bW5zKG92ZXJsYXBHcm91cCk7XHJcbiAgICAgIGNvbnN0IGVhcmxpZXN0ID0gb3ZlcmxhcEdyb3VwLnJlZHVjZSgobWluLCBlKSA9PlxyXG4gICAgICAgIGUuc3RhcnQgPCBtaW4uc3RhcnQgPyBlIDogbWluLCBvdmVybGFwR3JvdXBbMF0pO1xyXG4gICAgICBjb25zdCBwb3NpdGlvbiA9IGNhbGN1bGF0ZUV2ZW50UG9zaXRpb24oZWFybGllc3Quc3RhcnQsIGVhcmxpZXN0LmVuZCwgY29uZmlnKTtcclxuXHJcbiAgICAgIHJlc3VsdC5ncmlkcy5wdXNoKHtcclxuICAgICAgICBldmVudHM6IG92ZXJsYXBHcm91cCxcclxuICAgICAgICBjb2x1bW5zLFxyXG4gICAgICAgIHN0YWNrTGV2ZWw6IDAsXHJcbiAgICAgICAgcG9zaXRpb246IHsgdG9wOiBwb3NpdGlvbi50b3AgfVxyXG4gICAgICB9KTtcclxuICAgIH0gZWxzZSB7XHJcbiAgICAgIC8vIE5vdCBhbGwgZXZlbnRzIGNvbm5lY3RlZCB2aWEgdGhyZXNob2xkIFx1MjE5MiBTVEFDS0lORyBtb2RlXHJcbiAgICAgIGNvbnN0IGxldmVscyA9IGNhbGN1bGF0ZVN0YWNrTGV2ZWxzKG92ZXJsYXBHcm91cCk7XHJcbiAgICAgIGZvciAoY29uc3QgZXZlbnQgb2Ygb3ZlcmxhcEdyb3VwKSB7XHJcbiAgICAgICAgcmVzdWx0LnN0YWNrZWQucHVzaCh7XHJcbiAgICAgICAgICBldmVudCxcclxuICAgICAgICAgIHN0YWNrTGV2ZWw6IGxldmVscy5nZXQoZXZlbnQuaWQpID8/IDBcclxuICAgICAgICB9KTtcclxuICAgICAgfVxyXG4gICAgfVxyXG4gIH1cclxuXHJcbiAgcmV0dXJuIHJlc3VsdDtcclxufVxyXG4iLCAiaW1wb3J0IHsgSUNhbGVuZGFyRXZlbnQsIElFdmVudEJ1cywgSUV2ZW50VXBkYXRlZFBheWxvYWQgfSBmcm9tICcuLi8uLi90eXBlcy9DYWxlbmRhclR5cGVzJztcclxuaW1wb3J0IHsgRXZlbnRTZXJ2aWNlIH0gZnJvbSAnLi4vLi4vc3RvcmFnZS9ldmVudHMvRXZlbnRTZXJ2aWNlJztcclxuaW1wb3J0IHsgRGF0ZVNlcnZpY2UgfSBmcm9tICcuLi8uLi9jb3JlL0RhdGVTZXJ2aWNlJztcclxuaW1wb3J0IHsgSUdyaWRDb25maWcgfSBmcm9tICcuLi8uLi9jb3JlL0lHcmlkQ29uZmlnJztcclxuaW1wb3J0IHsgY2FsY3VsYXRlRXZlbnRQb3NpdGlvbiwgc25hcFRvR3JpZCwgcGl4ZWxzVG9NaW51dGVzIH0gZnJvbSAnLi4vLi4vdXRpbHMvUG9zaXRpb25VdGlscyc7XHJcbmltcG9ydCB7IENvcmVFdmVudHMgfSBmcm9tICcuLi8uLi9jb25zdGFudHMvQ29yZUV2ZW50cyc7XHJcbmltcG9ydCB7IElEcmFnQ29sdW1uQ2hhbmdlUGF5bG9hZCwgSURyYWdNb3ZlUGF5bG9hZCwgSURyYWdFbmRQYXlsb2FkLCBJRHJhZ0xlYXZlSGVhZGVyUGF5bG9hZCB9IGZyb20gJy4uLy4uL3R5cGVzL0RyYWdUeXBlcyc7XHJcbmltcG9ydCB7IGNhbGN1bGF0ZUNvbHVtbkxheW91dCB9IGZyb20gJy4vRXZlbnRMYXlvdXRFbmdpbmUnO1xyXG5pbXBvcnQgeyBJR3JpZEdyb3VwTGF5b3V0IH0gZnJvbSAnLi9FdmVudExheW91dFR5cGVzJztcclxuaW1wb3J0IHsgRmlsdGVyVGVtcGxhdGUgfSBmcm9tICcuLi8uLi9jb3JlL0ZpbHRlclRlbXBsYXRlJztcclxuXHJcbi8qKlxyXG4gKiBFdmVudFJlbmRlcmVyIC0gUmVuZGVycyBjYWxlbmRhciBldmVudHMgdG8gdGhlIERPTVxyXG4gKlxyXG4gKiBDTEVBTiBhcHByb2FjaDpcclxuICogLSBPbmx5IGRhdGEtaWQgYXR0cmlidXRlIG9uIGV2ZW50IGVsZW1lbnRcclxuICogLSBpbm5lckhUTUwgY29udGFpbnMgb25seSB2aXNpYmxlIGNvbnRlbnRcclxuICogLSBFdmVudCBkYXRhIHJldHJpZXZlZCB2aWEgRXZlbnRTZXJ2aWNlIHdoZW4gbmVlZGVkXHJcbiAqL1xyXG5leHBvcnQgY2xhc3MgRXZlbnRSZW5kZXJlciB7XHJcbiAgcHJpdmF0ZSBjb250YWluZXI6IEhUTUxFbGVtZW50IHwgbnVsbCA9IG51bGw7XHJcblxyXG4gIGNvbnN0cnVjdG9yKFxyXG4gICAgcHJpdmF0ZSBldmVudFNlcnZpY2U6IEV2ZW50U2VydmljZSxcclxuICAgIHByaXZhdGUgZGF0ZVNlcnZpY2U6IERhdGVTZXJ2aWNlLFxyXG4gICAgcHJpdmF0ZSBncmlkQ29uZmlnOiBJR3JpZENvbmZpZyxcclxuICAgIHByaXZhdGUgZXZlbnRCdXM6IElFdmVudEJ1c1xyXG4gICkge1xyXG4gICAgdGhpcy5zZXR1cExpc3RlbmVycygpO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogU2V0dXAgbGlzdGVuZXJzIGZvciBkcmFnLWRyb3AgYW5kIHVwZGF0ZSBldmVudHNcclxuICAgKi9cclxuICBwcml2YXRlIHNldHVwTGlzdGVuZXJzKCk6IHZvaWQge1xyXG4gICAgdGhpcy5ldmVudEJ1cy5vbihDb3JlRXZlbnRzLkVWRU5UX0RSQUdfQ09MVU1OX0NIQU5HRSwgKGUpID0+IHtcclxuICAgICAgY29uc3QgcGF5bG9hZCA9IChlIGFzIEN1c3RvbUV2ZW50PElEcmFnQ29sdW1uQ2hhbmdlUGF5bG9hZD4pLmRldGFpbDtcclxuICAgICAgdGhpcy5oYW5kbGVDb2x1bW5DaGFuZ2UocGF5bG9hZCk7XHJcbiAgICB9KTtcclxuXHJcbiAgICB0aGlzLmV2ZW50QnVzLm9uKENvcmVFdmVudHMuRVZFTlRfRFJBR19NT1ZFLCAoZSkgPT4ge1xyXG4gICAgICBjb25zdCBwYXlsb2FkID0gKGUgYXMgQ3VzdG9tRXZlbnQ8SURyYWdNb3ZlUGF5bG9hZD4pLmRldGFpbDtcclxuICAgICAgdGhpcy51cGRhdGVEcmFnVGltZXN0YW1wKHBheWxvYWQpO1xyXG4gICAgfSk7XHJcblxyXG4gICAgdGhpcy5ldmVudEJ1cy5vbihDb3JlRXZlbnRzLkVWRU5UX1VQREFURUQsIChlKSA9PiB7XHJcbiAgICAgIGNvbnN0IHBheWxvYWQgPSAoZSBhcyBDdXN0b21FdmVudDxJRXZlbnRVcGRhdGVkUGF5bG9hZD4pLmRldGFpbDtcclxuICAgICAgdGhpcy5oYW5kbGVFdmVudFVwZGF0ZWQocGF5bG9hZCk7XHJcbiAgICB9KTtcclxuXHJcbiAgICB0aGlzLmV2ZW50QnVzLm9uKENvcmVFdmVudHMuRVZFTlRfRFJBR19FTkQsIChlKSA9PiB7XHJcbiAgICAgIGNvbnN0IHBheWxvYWQgPSAoZSBhcyBDdXN0b21FdmVudDxJRHJhZ0VuZFBheWxvYWQ+KS5kZXRhaWw7XHJcbiAgICAgIHRoaXMuaGFuZGxlRHJhZ0VuZChwYXlsb2FkKTtcclxuICAgIH0pO1xyXG5cclxuICAgIHRoaXMuZXZlbnRCdXMub24oQ29yZUV2ZW50cy5FVkVOVF9EUkFHX0xFQVZFX0hFQURFUiwgKGUpID0+IHtcclxuICAgICAgY29uc3QgcGF5bG9hZCA9IChlIGFzIEN1c3RvbUV2ZW50PElEcmFnTGVhdmVIZWFkZXJQYXlsb2FkPikuZGV0YWlsO1xyXG4gICAgICB0aGlzLmhhbmRsZURyYWdMZWF2ZUhlYWRlcihwYXlsb2FkKTtcclxuICAgIH0pO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogSGFuZGxlIEVWRU5UX0RSQUdfRU5EIC0gcmVtb3ZlIGVsZW1lbnQgaWYgZHJvcHBlZCBpbiBoZWFkZXJcclxuICAgKi9cclxuICBwcml2YXRlIGhhbmRsZURyYWdFbmQocGF5bG9hZDogSURyYWdFbmRQYXlsb2FkKTogdm9pZCB7XHJcbiAgICBpZiAocGF5bG9hZC50YXJnZXQgPT09ICdoZWFkZXInKSB7XHJcbiAgICAgIC8vIEV2ZW50IHdhcyBkcm9wcGVkIGluIGhlYWRlciBkcmF3ZXIgLSByZW1vdmUgZnJvbSBncmlkXHJcbiAgICAgIGNvbnN0IGVsZW1lbnQgPSB0aGlzLmNvbnRhaW5lcj8ucXVlcnlTZWxlY3Rvcihgc3dwLWNvbnRlbnQtdmlld3BvcnQgc3dwLWV2ZW50W2RhdGEtZXZlbnQtaWQ9XCIke3BheWxvYWQuc3dwRXZlbnQuZXZlbnRJZH1cIl1gKTtcclxuICAgICAgZWxlbWVudD8ucmVtb3ZlKCk7XHJcbiAgICB9XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBIYW5kbGUgaGVhZGVyIGl0ZW0gbGVhdmluZyBoZWFkZXIgLSBjcmVhdGUgc3dwLWV2ZW50IGluIGdyaWRcclxuICAgKi9cclxuICBwcml2YXRlIGhhbmRsZURyYWdMZWF2ZUhlYWRlcihwYXlsb2FkOiBJRHJhZ0xlYXZlSGVhZGVyUGF5bG9hZCk6IHZvaWQge1xyXG4gICAgLy8gT25seSBoYW5kbGUgd2hlbiBzb3VyY2UgaXMgaGVhZGVyIChoZWFkZXIgaXRlbSBkcmFnZ2VkIHRvIGdyaWQpXHJcbiAgICBpZiAocGF5bG9hZC5zb3VyY2UgIT09ICdoZWFkZXInKSByZXR1cm47XHJcbiAgICBpZiAoIXBheWxvYWQudGFyZ2V0Q29sdW1uIHx8ICFwYXlsb2FkLnN0YXJ0IHx8ICFwYXlsb2FkLmVuZCkgcmV0dXJuO1xyXG5cclxuICAgIC8vIFR1cm4gaGVhZGVyIGl0ZW0gaW50byBnaG9zdCAoc3RheXMgdmlzaWJsZSBidXQgZmFkZWQpXHJcbiAgICBpZiAocGF5bG9hZC5lbGVtZW50KSB7XHJcbiAgICAgIHBheWxvYWQuZWxlbWVudC5jbGFzc0xpc3QuYWRkKCdkcmFnLWdob3N0Jyk7XHJcbiAgICAgIHBheWxvYWQuZWxlbWVudC5zdHlsZS5vcGFjaXR5ID0gJzAuMyc7XHJcbiAgICAgIHBheWxvYWQuZWxlbWVudC5zdHlsZS5wb2ludGVyRXZlbnRzID0gJ25vbmUnO1xyXG4gICAgfVxyXG5cclxuICAgIC8vIENyZWF0ZSBldmVudCBvYmplY3QgZnJvbSBoZWFkZXIgaXRlbSBkYXRhXHJcbiAgICBjb25zdCBldmVudDogSUNhbGVuZGFyRXZlbnQgPSB7XHJcbiAgICAgIGlkOiBwYXlsb2FkLmV2ZW50SWQsXHJcbiAgICAgIHRpdGxlOiBwYXlsb2FkLnRpdGxlIHx8ICcnLFxyXG4gICAgICBkZXNjcmlwdGlvbjogJycsXHJcbiAgICAgIHN0YXJ0OiBwYXlsb2FkLnN0YXJ0LFxyXG4gICAgICBlbmQ6IHBheWxvYWQuZW5kLFxyXG4gICAgICB0eXBlOiAnY3VzdG9tZXInLFxyXG4gICAgICBhbGxEYXk6IGZhbHNlLFxyXG4gICAgICBzeW5jU3RhdHVzOiAncGVuZGluZydcclxuICAgIH07XHJcblxyXG4gICAgLy8gQ3JlYXRlIHN3cC1ldmVudCBlbGVtZW50IHVzaW5nIGV4aXN0aW5nIG1ldGhvZFxyXG4gICAgY29uc3QgZWxlbWVudCA9IHRoaXMuY3JlYXRlRXZlbnRFbGVtZW50KGV2ZW50KTtcclxuXHJcbiAgICAvLyBBZGQgdG8gdGFyZ2V0IGNvbHVtblxyXG4gICAgbGV0IGV2ZW50c0xheWVyID0gcGF5bG9hZC50YXJnZXRDb2x1bW4ucXVlcnlTZWxlY3Rvcignc3dwLWV2ZW50cy1sYXllcicpO1xyXG4gICAgaWYgKCFldmVudHNMYXllcikge1xyXG4gICAgICBldmVudHNMYXllciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3N3cC1ldmVudHMtbGF5ZXInKTtcclxuICAgICAgcGF5bG9hZC50YXJnZXRDb2x1bW4uYXBwZW5kQ2hpbGQoZXZlbnRzTGF5ZXIpO1xyXG4gICAgfVxyXG4gICAgZXZlbnRzTGF5ZXIuYXBwZW5kQ2hpbGQoZWxlbWVudCk7XHJcblxyXG4gICAgLy8gTWFyayBhcyBkcmFnZ2luZyBzbyBEcmFnRHJvcE1hbmFnZXIgY2FuIGNvbnRpbnVlIHdpdGggaXRcclxuICAgIGVsZW1lbnQuY2xhc3NMaXN0LmFkZCgnZHJhZ2dpbmcnKTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIEhhbmRsZSBFVkVOVF9VUERBVEVEIC0gcmUtcmVuZGVyIGFmZmVjdGVkIGNvbHVtbnNcclxuICAgKi9cclxuICBwcml2YXRlIGFzeW5jIGhhbmRsZUV2ZW50VXBkYXRlZChwYXlsb2FkOiBJRXZlbnRVcGRhdGVkUGF5bG9hZCk6IFByb21pc2U8dm9pZD4ge1xyXG4gICAgLy8gUmUtcmVuZGVyIHNvdXJjZSBjb2x1bW4gKGlmIGRpZmZlcmVudCBmcm9tIHRhcmdldClcclxuICAgIGlmIChwYXlsb2FkLnNvdXJjZUNvbHVtbktleSAhPT0gcGF5bG9hZC50YXJnZXRDb2x1bW5LZXkpIHtcclxuICAgICAgYXdhaXQgdGhpcy5yZXJlbmRlckNvbHVtbihwYXlsb2FkLnNvdXJjZUNvbHVtbktleSk7XHJcbiAgICB9XHJcblxyXG4gICAgLy8gUmUtcmVuZGVyIHRhcmdldCBjb2x1bW5cclxuICAgIGF3YWl0IHRoaXMucmVyZW5kZXJDb2x1bW4ocGF5bG9hZC50YXJnZXRDb2x1bW5LZXkpO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogUmUtcmVuZGVyIGEgc2luZ2xlIGNvbHVtbiB3aXRoIGZyZXNoIGRhdGEgZnJvbSBJbmRleGVkREJcclxuICAgKi9cclxuICBwcml2YXRlIGFzeW5jIHJlcmVuZGVyQ29sdW1uKGNvbHVtbktleTogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XHJcbiAgICBjb25zdCBjb2x1bW4gPSB0aGlzLmZpbmRDb2x1bW4oY29sdW1uS2V5KTtcclxuICAgIGlmICghY29sdW1uKSByZXR1cm47XHJcblxyXG4gICAgLy8gUmVhZCBkYXRlIGFuZCByZXNvdXJjZUlkIGRpcmVjdGx5IGZyb20gY29sdW1uIGF0dHJpYnV0ZXMgKGNvbHVtbktleSBpcyBvcGFxdWUpXHJcbiAgICBjb25zdCBkYXRlID0gY29sdW1uLmRhdGFzZXQuZGF0ZTtcclxuICAgIGNvbnN0IHJlc291cmNlSWQgPSBjb2x1bW4uZGF0YXNldC5yZXNvdXJjZUlkO1xyXG5cclxuICAgIGlmICghZGF0ZSkgcmV0dXJuO1xyXG5cclxuICAgIC8vIEdldCBkYXRlIHJhbmdlIGZvciB0aGlzIGRheVxyXG4gICAgY29uc3Qgc3RhcnREYXRlID0gbmV3IERhdGUoZGF0ZSk7XHJcbiAgICBjb25zdCBlbmREYXRlID0gbmV3IERhdGUoZGF0ZSk7XHJcbiAgICBlbmREYXRlLnNldEhvdXJzKDIzLCA1OSwgNTksIDk5OSk7XHJcblxyXG4gICAgLy8gRmV0Y2ggZXZlbnRzIGZyb20gSW5kZXhlZERCXHJcbiAgICBjb25zdCBldmVudHMgPSByZXNvdXJjZUlkXHJcbiAgICAgID8gYXdhaXQgdGhpcy5ldmVudFNlcnZpY2UuZ2V0QnlSZXNvdXJjZUFuZERhdGVSYW5nZShyZXNvdXJjZUlkLCBzdGFydERhdGUsIGVuZERhdGUpXHJcbiAgICAgIDogYXdhaXQgdGhpcy5ldmVudFNlcnZpY2UuZ2V0QnlEYXRlUmFuZ2Uoc3RhcnREYXRlLCBlbmREYXRlKTtcclxuXHJcbiAgICAvLyBGaWx0ZXIgdG8gdGltZWQgZXZlbnRzIGFuZCBtYXRjaCBkYXRlIGV4YWN0bHlcclxuICAgIGNvbnN0IHRpbWVkRXZlbnRzID0gZXZlbnRzLmZpbHRlcihldmVudCA9PlxyXG4gICAgICAhZXZlbnQuYWxsRGF5ICYmIHRoaXMuZGF0ZVNlcnZpY2UuZ2V0RGF0ZUtleShldmVudC5zdGFydCkgPT09IGRhdGVcclxuICAgICk7XHJcblxyXG4gICAgLy8gR2V0IG9yIGNyZWF0ZSBldmVudHMgbGF5ZXJcclxuICAgIGxldCBldmVudHNMYXllciA9IGNvbHVtbi5xdWVyeVNlbGVjdG9yKCdzd3AtZXZlbnRzLWxheWVyJyk7XHJcbiAgICBpZiAoIWV2ZW50c0xheWVyKSB7XHJcbiAgICAgIGV2ZW50c0xheWVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3dwLWV2ZW50cy1sYXllcicpO1xyXG4gICAgICBjb2x1bW4uYXBwZW5kQ2hpbGQoZXZlbnRzTGF5ZXIpO1xyXG4gICAgfVxyXG5cclxuICAgIC8vIENsZWFyIGV4aXN0aW5nIGV2ZW50c1xyXG4gICAgZXZlbnRzTGF5ZXIuaW5uZXJIVE1MID0gJyc7XHJcblxyXG4gICAgLy8gQ2FsY3VsYXRlIGxheW91dCB3aXRoIHN0YWNraW5nL2dyb3VwaW5nXHJcbiAgICBjb25zdCBsYXlvdXQgPSBjYWxjdWxhdGVDb2x1bW5MYXlvdXQodGltZWRFdmVudHMsIHRoaXMuZ3JpZENvbmZpZyk7XHJcblxyXG4gICAgLy8gUmVuZGVyIEdSSUQgZ3JvdXBzXHJcbiAgICBsYXlvdXQuZ3JpZHMuZm9yRWFjaChncmlkID0+IHtcclxuICAgICAgY29uc3QgZ3JvdXBFbCA9IHRoaXMucmVuZGVyR3JpZEdyb3VwKGdyaWQpO1xyXG4gICAgICBldmVudHNMYXllciEuYXBwZW5kQ2hpbGQoZ3JvdXBFbCk7XHJcbiAgICB9KTtcclxuXHJcbiAgICAvLyBSZW5kZXIgU1RBQ0tFRCBldmVudHNcclxuICAgIGxheW91dC5zdGFja2VkLmZvckVhY2goaXRlbSA9PiB7XHJcbiAgICAgIGNvbnN0IGV2ZW50RWwgPSB0aGlzLnJlbmRlclN0YWNrZWRFdmVudChpdGVtLmV2ZW50LCBpdGVtLnN0YWNrTGV2ZWwpO1xyXG4gICAgICBldmVudHNMYXllciEuYXBwZW5kQ2hpbGQoZXZlbnRFbCk7XHJcbiAgICB9KTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIEZpbmQgYSBjb2x1bW4gZWxlbWVudCBieSBjb2x1bW5LZXlcclxuICAgKi9cclxuICBwcml2YXRlIGZpbmRDb2x1bW4oY29sdW1uS2V5OiBzdHJpbmcpOiBIVE1MRWxlbWVudCB8IG51bGwge1xyXG4gICAgaWYgKCF0aGlzLmNvbnRhaW5lcikgcmV0dXJuIG51bGw7XHJcbiAgICByZXR1cm4gdGhpcy5jb250YWluZXIucXVlcnlTZWxlY3Rvcihgc3dwLWRheS1jb2x1bW5bZGF0YS1jb2x1bW4ta2V5PVwiJHtjb2x1bW5LZXl9XCJdYCkgYXMgSFRNTEVsZW1lbnQ7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBIYW5kbGUgZXZlbnQgbW92aW5nIHRvIGEgbmV3IGNvbHVtbiBkdXJpbmcgZHJhZ1xyXG4gICAqL1xyXG4gIHByaXZhdGUgaGFuZGxlQ29sdW1uQ2hhbmdlKHBheWxvYWQ6IElEcmFnQ29sdW1uQ2hhbmdlUGF5bG9hZCk6IHZvaWQge1xyXG4gICAgY29uc3QgZXZlbnRzTGF5ZXIgPSBwYXlsb2FkLm5ld0NvbHVtbi5xdWVyeVNlbGVjdG9yKCdzd3AtZXZlbnRzLWxheWVyJyk7XHJcbiAgICBpZiAoIWV2ZW50c0xheWVyKSByZXR1cm47XHJcblxyXG4gICAgLy8gTW92ZSBlbGVtZW50IHRvIG5ldyBjb2x1bW5cclxuICAgIGV2ZW50c0xheWVyLmFwcGVuZENoaWxkKHBheWxvYWQuZWxlbWVudCk7XHJcblxyXG4gICAgLy8gUHJlc2VydmUgWSBwb3NpdGlvblxyXG4gICAgcGF5bG9hZC5lbGVtZW50LnN0eWxlLnRvcCA9IGAke3BheWxvYWQuY3VycmVudFl9cHhgO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogVXBkYXRlIHRpbWVzdGFtcCBkaXNwbGF5IGR1cmluZyBkcmFnIChzbmFwcGVkIHRvIGdyaWQpXHJcbiAgICovXHJcbiAgcHJpdmF0ZSB1cGRhdGVEcmFnVGltZXN0YW1wKHBheWxvYWQ6IElEcmFnTW92ZVBheWxvYWQpOiB2b2lkIHtcclxuICAgIGNvbnN0IHRpbWVFbCA9IHBheWxvYWQuZWxlbWVudC5xdWVyeVNlbGVjdG9yKCdzd3AtZXZlbnQtdGltZScpO1xyXG4gICAgaWYgKCF0aW1lRWwpIHJldHVybjtcclxuXHJcbiAgICAvLyBTbmFwIHBvc2l0aW9uIHRvIGdyaWQgaW50ZXJ2YWxcclxuICAgIGNvbnN0IHNuYXBwZWRZID0gc25hcFRvR3JpZChwYXlsb2FkLmN1cnJlbnRZLCB0aGlzLmdyaWRDb25maWcpO1xyXG5cclxuICAgIC8vIENhbGN1bGF0ZSBuZXcgc3RhcnQgdGltZVxyXG4gICAgY29uc3QgbWludXRlc0Zyb21HcmlkU3RhcnQgPSBwaXhlbHNUb01pbnV0ZXMoc25hcHBlZFksIHRoaXMuZ3JpZENvbmZpZyk7XHJcbiAgICBjb25zdCBzdGFydE1pbnV0ZXMgPSAodGhpcy5ncmlkQ29uZmlnLmRheVN0YXJ0SG91ciAqIDYwKSArIG1pbnV0ZXNGcm9tR3JpZFN0YXJ0O1xyXG5cclxuICAgIC8vIEtlZXAgb3JpZ2luYWwgZHVyYXRpb24gKGZyb20gZWxlbWVudCBoZWlnaHQpXHJcbiAgICBjb25zdCBoZWlnaHQgPSBwYXJzZUZsb2F0KHBheWxvYWQuZWxlbWVudC5zdHlsZS5oZWlnaHQpIHx8IHRoaXMuZ3JpZENvbmZpZy5ob3VySGVpZ2h0O1xyXG4gICAgY29uc3QgZHVyYXRpb25NaW51dGVzID0gcGl4ZWxzVG9NaW51dGVzKGhlaWdodCwgdGhpcy5ncmlkQ29uZmlnKTtcclxuXHJcbiAgICAvLyBDcmVhdGUgRGF0ZSBvYmplY3RzIGZvciBjb25zaXN0ZW50IGZvcm1hdHRpbmcgdmlhIERhdGVTZXJ2aWNlXHJcbiAgICBjb25zdCBzdGFydCA9IHRoaXMubWludXRlc1RvRGF0ZShzdGFydE1pbnV0ZXMpO1xyXG4gICAgY29uc3QgZW5kID0gdGhpcy5taW51dGVzVG9EYXRlKHN0YXJ0TWludXRlcyArIGR1cmF0aW9uTWludXRlcyk7XHJcblxyXG4gICAgdGltZUVsLnRleHRDb250ZW50ID0gdGhpcy5kYXRlU2VydmljZS5mb3JtYXRUaW1lUmFuZ2Uoc3RhcnQsIGVuZCk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBDb252ZXJ0IG1pbnV0ZXMgc2luY2UgbWlkbmlnaHQgdG8gYSBEYXRlIG9iamVjdCAodG9kYXkpXHJcbiAgICovXHJcbiAgcHJpdmF0ZSBtaW51dGVzVG9EYXRlKG1pbnV0ZXM6IG51bWJlcik6IERhdGUge1xyXG4gICAgY29uc3QgZGF0ZSA9IG5ldyBEYXRlKCk7XHJcbiAgICBkYXRlLnNldEhvdXJzKE1hdGguZmxvb3IobWludXRlcyAvIDYwKSAlIDI0LCBtaW51dGVzICUgNjAsIDAsIDApO1xyXG4gICAgcmV0dXJuIGRhdGU7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBSZW5kZXIgZXZlbnRzIGZvciB2aXNpYmxlIGRhdGVzIGludG8gZGF5IGNvbHVtbnNcclxuICAgKiBAcGFyYW0gY29udGFpbmVyIC0gQ2FsZW5kYXIgY29udGFpbmVyIGVsZW1lbnRcclxuICAgKiBAcGFyYW0gZmlsdGVyIC0gRmlsdGVyIHdpdGggJ2RhdGUnIGFuZCBvcHRpb25hbGx5ICdyZXNvdXJjZScgYXJyYXlzXHJcbiAgICogQHBhcmFtIGZpbHRlclRlbXBsYXRlIC0gVGVtcGxhdGUgZm9yIG1hdGNoaW5nIGV2ZW50cyB0byBjb2x1bW5zXHJcbiAgICovXHJcbiAgYXN5bmMgcmVuZGVyKGNvbnRhaW5lcjogSFRNTEVsZW1lbnQsIGZpbHRlcjogUmVjb3JkPHN0cmluZywgc3RyaW5nW10+LCBmaWx0ZXJUZW1wbGF0ZTogRmlsdGVyVGVtcGxhdGUpOiBQcm9taXNlPHZvaWQ+IHtcclxuICAgIC8vIFN0b3JlIGNvbnRhaW5lciByZWZlcmVuY2UgZm9yIGxhdGVyIHJlLXJlbmRlcnNcclxuICAgIHRoaXMuY29udGFpbmVyID0gY29udGFpbmVyO1xyXG5cclxuICAgIGNvbnN0IHZpc2libGVEYXRlcyA9IGZpbHRlclsnZGF0ZSddIHx8IFtdO1xyXG5cclxuICAgIGlmICh2aXNpYmxlRGF0ZXMubGVuZ3RoID09PSAwKSByZXR1cm47XHJcblxyXG4gICAgLy8gR2V0IGRhdGUgcmFuZ2UgZm9yIHF1ZXJ5XHJcbiAgICBjb25zdCBzdGFydERhdGUgPSBuZXcgRGF0ZSh2aXNpYmxlRGF0ZXNbMF0pO1xyXG4gICAgY29uc3QgZW5kRGF0ZSA9IG5ldyBEYXRlKHZpc2libGVEYXRlc1t2aXNpYmxlRGF0ZXMubGVuZ3RoIC0gMV0pO1xyXG4gICAgZW5kRGF0ZS5zZXRIb3VycygyMywgNTksIDU5LCA5OTkpO1xyXG5cclxuICAgIC8vIEZldGNoIGV2ZW50cyBmcm9tIEluZGV4ZWREQlxyXG4gICAgY29uc3QgZXZlbnRzID0gYXdhaXQgdGhpcy5ldmVudFNlcnZpY2UuZ2V0QnlEYXRlUmFuZ2Uoc3RhcnREYXRlLCBlbmREYXRlKTtcclxuXHJcbiAgICAvLyBGaW5kIGRheSBjb2x1bW5zXHJcbiAgICBjb25zdCBkYXlDb2x1bW5zID0gY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3IoJ3N3cC1kYXktY29sdW1ucycpO1xyXG4gICAgaWYgKCFkYXlDb2x1bW5zKSByZXR1cm47XHJcblxyXG4gICAgY29uc3QgY29sdW1ucyA9IGRheUNvbHVtbnMucXVlcnlTZWxlY3RvckFsbCgnc3dwLWRheS1jb2x1bW4nKTtcclxuXHJcbiAgICAvLyBSZW5kZXIgZXZlbnRzIGludG8gZWFjaCBjb2x1bW4gYmFzZWQgb24gRmlsdGVyVGVtcGxhdGUgbWF0Y2hpbmdcclxuICAgIGNvbHVtbnMuZm9yRWFjaChjb2x1bW4gPT4ge1xyXG4gICAgICBjb25zdCBjb2x1bW5FbCA9IGNvbHVtbiBhcyBIVE1MRWxlbWVudDtcclxuXHJcbiAgICAgIC8vIFVzZSBGaWx0ZXJUZW1wbGF0ZSBmb3IgbWF0Y2hpbmcgLSBvbmx5IGZpZWxkcyBpbiB0ZW1wbGF0ZSBhcmUgY2hlY2tlZFxyXG4gICAgICBjb25zdCBjb2x1bW5FdmVudHMgPSBldmVudHMuZmlsdGVyKGV2ZW50ID0+IGZpbHRlclRlbXBsYXRlLm1hdGNoZXMoZXZlbnQsIGNvbHVtbkVsKSk7XHJcblxyXG4gICAgICAvLyBHZXQgb3IgY3JlYXRlIGV2ZW50cyBsYXllclxyXG4gICAgICBsZXQgZXZlbnRzTGF5ZXIgPSBjb2x1bW4ucXVlcnlTZWxlY3Rvcignc3dwLWV2ZW50cy1sYXllcicpO1xyXG4gICAgICBpZiAoIWV2ZW50c0xheWVyKSB7XHJcbiAgICAgICAgZXZlbnRzTGF5ZXIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzd3AtZXZlbnRzLWxheWVyJyk7XHJcbiAgICAgICAgY29sdW1uLmFwcGVuZENoaWxkKGV2ZW50c0xheWVyKTtcclxuICAgICAgfVxyXG5cclxuICAgICAgLy8gQ2xlYXIgZXhpc3RpbmcgZXZlbnRzXHJcbiAgICAgIGV2ZW50c0xheWVyLmlubmVySFRNTCA9ICcnO1xyXG5cclxuICAgICAgLy8gRmlsdGVyIHRvIHRpbWVkIGV2ZW50cyBvbmx5XHJcbiAgICAgIGNvbnN0IHRpbWVkRXZlbnRzID0gY29sdW1uRXZlbnRzLmZpbHRlcihldmVudCA9PiAhZXZlbnQuYWxsRGF5KTtcclxuXHJcbiAgICAgIC8vIENhbGN1bGF0ZSBsYXlvdXQgd2l0aCBzdGFja2luZy9ncm91cGluZ1xyXG4gICAgICBjb25zdCBsYXlvdXQgPSBjYWxjdWxhdGVDb2x1bW5MYXlvdXQodGltZWRFdmVudHMsIHRoaXMuZ3JpZENvbmZpZyk7XHJcblxyXG4gICAgICAvLyBSZW5kZXIgR1JJRCBncm91cHMgKHNpbXVsdGFuZW91cyBldmVudHMgc2lkZS1ieS1zaWRlKVxyXG4gICAgICBsYXlvdXQuZ3JpZHMuZm9yRWFjaChncmlkID0+IHtcclxuICAgICAgICBjb25zdCBncm91cEVsID0gdGhpcy5yZW5kZXJHcmlkR3JvdXAoZ3JpZCk7XHJcbiAgICAgICAgZXZlbnRzTGF5ZXIhLmFwcGVuZENoaWxkKGdyb3VwRWwpO1xyXG4gICAgICB9KTtcclxuXHJcbiAgICAgIC8vIFJlbmRlciBTVEFDS0VEIGV2ZW50cyAob3ZlcmxhcHBpbmcgd2l0aCBtYXJnaW4gb2Zmc2V0KVxyXG4gICAgICBsYXlvdXQuc3RhY2tlZC5mb3JFYWNoKGl0ZW0gPT4ge1xyXG4gICAgICAgIGNvbnN0IGV2ZW50RWwgPSB0aGlzLnJlbmRlclN0YWNrZWRFdmVudChpdGVtLmV2ZW50LCBpdGVtLnN0YWNrTGV2ZWwpO1xyXG4gICAgICAgIGV2ZW50c0xheWVyIS5hcHBlbmRDaGlsZChldmVudEVsKTtcclxuICAgICAgfSk7XHJcbiAgICB9KTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIENyZWF0ZSBhIHNpbmdsZSBldmVudCBlbGVtZW50XHJcbiAgICpcclxuICAgKiBDTEVBTiBhcHByb2FjaDpcclxuICAgKiAtIE9ubHkgZGF0YS1pZCBmb3IgbG9va3VwXHJcbiAgICogLSBWaXNpYmxlIGNvbnRlbnQgaW4gaW5uZXJIVE1MIG9ubHlcclxuICAgKi9cclxuICBwcml2YXRlIGNyZWF0ZUV2ZW50RWxlbWVudChldmVudDogSUNhbGVuZGFyRXZlbnQpOiBIVE1MRWxlbWVudCB7XHJcbiAgICBjb25zdCBlbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3dwLWV2ZW50Jyk7XHJcblxyXG4gICAgLy8gRGF0YSBhdHRyaWJ1dGVzIGZvciBTd3BFdmVudCBjb21wYXRpYmlsaXR5XHJcbiAgICBlbGVtZW50LmRhdGFzZXQuZXZlbnRJZCA9IGV2ZW50LmlkO1xyXG4gICAgaWYgKGV2ZW50LnJlc291cmNlSWQpIHtcclxuICAgICAgZWxlbWVudC5kYXRhc2V0LnJlc291cmNlSWQgPSBldmVudC5yZXNvdXJjZUlkO1xyXG4gICAgfVxyXG5cclxuICAgIC8vIENhbGN1bGF0ZSBwb3NpdGlvblxyXG4gICAgY29uc3QgcG9zaXRpb24gPSBjYWxjdWxhdGVFdmVudFBvc2l0aW9uKGV2ZW50LnN0YXJ0LCBldmVudC5lbmQsIHRoaXMuZ3JpZENvbmZpZyk7XHJcbiAgICBlbGVtZW50LnN0eWxlLnRvcCA9IGAke3Bvc2l0aW9uLnRvcH1weGA7XHJcbiAgICBlbGVtZW50LnN0eWxlLmhlaWdodCA9IGAke3Bvc2l0aW9uLmhlaWdodH1weGA7XHJcblxyXG4gICAgLy8gQ29sb3IgY2xhc3MgYmFzZWQgb24gZXZlbnQgdHlwZVxyXG4gICAgY29uc3QgY29sb3JDbGFzcyA9IHRoaXMuZ2V0Q29sb3JDbGFzcyhldmVudCk7XHJcbiAgICBpZiAoY29sb3JDbGFzcykge1xyXG4gICAgICBlbGVtZW50LmNsYXNzTGlzdC5hZGQoY29sb3JDbGFzcyk7XHJcbiAgICB9XHJcblxyXG4gICAgLy8gVmlzaWJsZSBjb250ZW50IG9ubHlcclxuICAgIGVsZW1lbnQuaW5uZXJIVE1MID0gYFxyXG4gICAgICA8c3dwLWV2ZW50LXRpbWU+JHt0aGlzLmRhdGVTZXJ2aWNlLmZvcm1hdFRpbWVSYW5nZShldmVudC5zdGFydCwgZXZlbnQuZW5kKX08L3N3cC1ldmVudC10aW1lPlxyXG4gICAgICA8c3dwLWV2ZW50LXRpdGxlPiR7dGhpcy5lc2NhcGVIdG1sKGV2ZW50LnRpdGxlKX08L3N3cC1ldmVudC10aXRsZT5cclxuICAgICAgJHtldmVudC5kZXNjcmlwdGlvbiA/IGA8c3dwLWV2ZW50LWRlc2NyaXB0aW9uPiR7dGhpcy5lc2NhcGVIdG1sKGV2ZW50LmRlc2NyaXB0aW9uKX08L3N3cC1ldmVudC1kZXNjcmlwdGlvbj5gIDogJyd9XHJcbiAgICBgO1xyXG5cclxuICAgIHJldHVybiBlbGVtZW50O1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogR2V0IGNvbG9yIGNsYXNzIGJhc2VkIG9uIG1ldGFkYXRhLmNvbG9yIG9yIGV2ZW50IHR5cGVcclxuICAgKi9cclxuICBwcml2YXRlIGdldENvbG9yQ2xhc3MoZXZlbnQ6IElDYWxlbmRhckV2ZW50KTogc3RyaW5nIHtcclxuICAgIC8vIENoZWNrIG1ldGFkYXRhLmNvbG9yIGZpcnN0XHJcbiAgICBpZiAoZXZlbnQubWV0YWRhdGE/LmNvbG9yKSB7XHJcbiAgICAgIHJldHVybiBgaXMtJHtldmVudC5tZXRhZGF0YS5jb2xvcn1gO1xyXG4gICAgfVxyXG5cclxuICAgIC8vIEZhbGxiYWNrIHRvIHR5cGUtYmFzZWQgY29sb3JcclxuICAgIGNvbnN0IHR5cGVDb2xvcnM6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4gPSB7XHJcbiAgICAgICdjdXN0b21lcic6ICdpcy1ibHVlJyxcclxuICAgICAgJ3ZhY2F0aW9uJzogJ2lzLWdyZWVuJyxcclxuICAgICAgJ2JyZWFrJzogJ2lzLWFtYmVyJyxcclxuICAgICAgJ21lZXRpbmcnOiAnaXMtcHVycGxlJyxcclxuICAgICAgJ2Jsb2NrZWQnOiAnaXMtcmVkJ1xyXG4gICAgfTtcclxuICAgIHJldHVybiB0eXBlQ29sb3JzW2V2ZW50LnR5cGVdIHx8ICdpcy1ibHVlJztcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIEVzY2FwZSBIVE1MIHRvIHByZXZlbnQgWFNTXHJcbiAgICovXHJcbiAgcHJpdmF0ZSBlc2NhcGVIdG1sKHRleHQ6IHN0cmluZyk6IHN0cmluZyB7XHJcbiAgICBjb25zdCBkaXYgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdkaXYnKTtcclxuICAgIGRpdi50ZXh0Q29udGVudCA9IHRleHQ7XHJcbiAgICByZXR1cm4gZGl2LmlubmVySFRNTDtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIFJlbmRlciBhIEdSSUQgZ3JvdXAgd2l0aCBzaWRlLWJ5LXNpZGUgY29sdW1uc1xyXG4gICAqIFVzZWQgd2hlbiBtdWx0aXBsZSBldmVudHMgc3RhcnQgYXQgdGhlIHNhbWUgdGltZVxyXG4gICAqL1xyXG4gIHByaXZhdGUgcmVuZGVyR3JpZEdyb3VwKGxheW91dDogSUdyaWRHcm91cExheW91dCk6IEhUTUxFbGVtZW50IHtcclxuICAgIGNvbnN0IGdyb3VwID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3dwLWV2ZW50LWdyb3VwJyk7XHJcbiAgICBncm91cC5jbGFzc0xpc3QuYWRkKGBjb2xzLSR7bGF5b3V0LmNvbHVtbnMubGVuZ3RofWApO1xyXG4gICAgZ3JvdXAuc3R5bGUudG9wID0gYCR7bGF5b3V0LnBvc2l0aW9uLnRvcH1weGA7XHJcblxyXG4gICAgLy8gU3RhY2sgbGV2ZWwgc3R5bGluZyBmb3IgZW50aXJlIGdyb3VwIChpZiBuZXN0ZWQgaW4gYW5vdGhlciBldmVudClcclxuICAgIGlmIChsYXlvdXQuc3RhY2tMZXZlbCA+IDApIHtcclxuICAgICAgZ3JvdXAuc3R5bGUubWFyZ2luTGVmdCA9IGAke2xheW91dC5zdGFja0xldmVsICogMTV9cHhgO1xyXG4gICAgICBncm91cC5zdHlsZS56SW5kZXggPSBgJHsxMDAgKyBsYXlvdXQuc3RhY2tMZXZlbH1gO1xyXG4gICAgfVxyXG5cclxuICAgIC8vIENhbGN1bGF0ZSB0aGUgaGVpZ2h0IG5lZWRlZCBmb3IgdGhlIGdyb3VwICh0YWxsZXN0IGV2ZW50KVxyXG4gICAgbGV0IG1heEJvdHRvbSA9IDA7XHJcbiAgICBmb3IgKGNvbnN0IGV2ZW50IG9mIGxheW91dC5ldmVudHMpIHtcclxuICAgICAgY29uc3QgcG9zID0gY2FsY3VsYXRlRXZlbnRQb3NpdGlvbihldmVudC5zdGFydCwgZXZlbnQuZW5kLCB0aGlzLmdyaWRDb25maWcpO1xyXG4gICAgICBjb25zdCBldmVudEJvdHRvbSA9IHBvcy50b3AgKyBwb3MuaGVpZ2h0O1xyXG4gICAgICBpZiAoZXZlbnRCb3R0b20gPiBtYXhCb3R0b20pIG1heEJvdHRvbSA9IGV2ZW50Qm90dG9tO1xyXG4gICAgfVxyXG4gICAgY29uc3QgZ3JvdXBIZWlnaHQgPSBtYXhCb3R0b20gLSBsYXlvdXQucG9zaXRpb24udG9wO1xyXG4gICAgZ3JvdXAuc3R5bGUuaGVpZ2h0ID0gYCR7Z3JvdXBIZWlnaHR9cHhgO1xyXG5cclxuICAgIC8vIENyZWF0ZSB3cmFwcGVyIGRpdiBmb3IgZWFjaCBjb2x1bW5cclxuICAgIGxheW91dC5jb2x1bW5zLmZvckVhY2goY29sdW1uRXZlbnRzID0+IHtcclxuICAgICAgY29uc3Qgd3JhcHBlciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2RpdicpO1xyXG4gICAgICB3cmFwcGVyLnN0eWxlLnBvc2l0aW9uID0gJ3JlbGF0aXZlJztcclxuXHJcbiAgICAgIGNvbHVtbkV2ZW50cy5mb3JFYWNoKGV2ZW50ID0+IHtcclxuICAgICAgICBjb25zdCBldmVudEVsID0gdGhpcy5jcmVhdGVFdmVudEVsZW1lbnQoZXZlbnQpO1xyXG4gICAgICAgIC8vIFBvc2l0aW9uIHJlbGF0aXZlIHRvIGdyb3VwIHRvcFxyXG4gICAgICAgIGNvbnN0IHBvcyA9IGNhbGN1bGF0ZUV2ZW50UG9zaXRpb24oZXZlbnQuc3RhcnQsIGV2ZW50LmVuZCwgdGhpcy5ncmlkQ29uZmlnKTtcclxuICAgICAgICBldmVudEVsLnN0eWxlLnRvcCA9IGAke3Bvcy50b3AgLSBsYXlvdXQucG9zaXRpb24udG9wfXB4YDtcclxuICAgICAgICBldmVudEVsLnN0eWxlLnBvc2l0aW9uID0gJ2Fic29sdXRlJztcclxuICAgICAgICBldmVudEVsLnN0eWxlLmxlZnQgPSAnMCc7XHJcbiAgICAgICAgZXZlbnRFbC5zdHlsZS5yaWdodCA9ICcwJztcclxuICAgICAgICB3cmFwcGVyLmFwcGVuZENoaWxkKGV2ZW50RWwpO1xyXG4gICAgICB9KTtcclxuXHJcbiAgICAgIGdyb3VwLmFwcGVuZENoaWxkKHdyYXBwZXIpO1xyXG4gICAgfSk7XHJcblxyXG4gICAgcmV0dXJuIGdyb3VwO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogUmVuZGVyIGEgU1RBQ0tFRCBldmVudCB3aXRoIG1hcmdpbi1sZWZ0IG9mZnNldFxyXG4gICAqIFVzZWQgZm9yIG92ZXJsYXBwaW5nIGV2ZW50cyB0aGF0IGRvbid0IHN0YXJ0IGF0IHRoZSBzYW1lIHRpbWVcclxuICAgKi9cclxuICBwcml2YXRlIHJlbmRlclN0YWNrZWRFdmVudChldmVudDogSUNhbGVuZGFyRXZlbnQsIHN0YWNrTGV2ZWw6IG51bWJlcik6IEhUTUxFbGVtZW50IHtcclxuICAgIGNvbnN0IGVsZW1lbnQgPSB0aGlzLmNyZWF0ZUV2ZW50RWxlbWVudChldmVudCk7XHJcblxyXG4gICAgLy8gQWRkIHN0YWNrIG1ldGFkYXRhIGZvciBkcmFnLWRyb3AgYW5kIG90aGVyIGZlYXR1cmVzXHJcbiAgICBlbGVtZW50LmRhdGFzZXQuc3RhY2tMaW5rID0gSlNPTi5zdHJpbmdpZnkoeyBzdGFja0xldmVsIH0pO1xyXG5cclxuICAgIC8vIFZpc3VhbCBzdHlsaW5nIGJhc2VkIG9uIHN0YWNrIGxldmVsXHJcbiAgICBpZiAoc3RhY2tMZXZlbCA+IDApIHtcclxuICAgICAgZWxlbWVudC5zdHlsZS5tYXJnaW5MZWZ0ID0gYCR7c3RhY2tMZXZlbCAqIDE1fXB4YDtcclxuICAgICAgZWxlbWVudC5zdHlsZS56SW5kZXggPSBgJHsxMDAgKyBzdGFja0xldmVsfWA7XHJcbiAgICB9XHJcblxyXG4gICAgcmV0dXJuIGVsZW1lbnQ7XHJcbiAgfVxyXG59XHJcbiIsICJpbXBvcnQgeyBJUmVuZGVyZXIsIElSZW5kZXJDb250ZXh0IH0gZnJvbSAnLi9JR3JvdXBpbmdSZW5kZXJlcic7XHJcblxyXG4vKipcclxuICogRW50aXR5IG11c3QgaGF2ZSBpZFxyXG4gKi9cclxuZXhwb3J0IGludGVyZmFjZSBJR3JvdXBpbmdFbnRpdHkge1xyXG4gIGlkOiBzdHJpbmc7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBDb25maWd1cmF0aW9uIGZvciBhIGdyb3VwaW5nIHJlbmRlcmVyXHJcbiAqL1xyXG5leHBvcnQgaW50ZXJmYWNlIElHcm91cGluZ1JlbmRlcmVyQ29uZmlnIHtcclxuICBlbGVtZW50VGFnOiBzdHJpbmc7ICAgICAgLy8gZS5nLiwgJ3N3cC10ZWFtLWhlYWRlcidcclxuICBpZEF0dHJpYnV0ZTogc3RyaW5nOyAgICAgLy8gZS5nLiwgJ3RlYW1JZCcgLT4gZGF0YS10ZWFtLWlkXHJcbiAgY29sc3BhblZhcjogc3RyaW5nOyAgICAgIC8vIGUuZy4sICctLXRlYW0tY29scydcclxufVxyXG5cclxuLyoqXHJcbiAqIEFic3RyYWN0IGJhc2UgY2xhc3MgZm9yIGdyb3VwaW5nIHJlbmRlcmVyc1xyXG4gKlxyXG4gKiBIYW5kbGVzOlxyXG4gKiAtIEZldGNoaW5nIGVudGl0aWVzIGJ5IElEc1xyXG4gKiAtIENhbGN1bGF0aW5nIGNvbHNwYW4gZnJvbSBwYXJlbnRDaGlsZE1hcFxyXG4gKiAtIENyZWF0aW5nIGhlYWRlciBlbGVtZW50c1xyXG4gKiAtIEFwcGVuZGluZyB0byBjb250YWluZXJcclxuICpcclxuICogU3ViY2xhc3NlcyBvdmVycmlkZTpcclxuICogLSByZW5kZXJIZWFkZXIoKSBmb3IgY3VzdG9tIGNvbnRlbnRcclxuICogLSBnZXREaXNwbGF5TmFtZSgpIGZvciBlbnRpdHkgZGlzcGxheSB0ZXh0XHJcbiAqL1xyXG5leHBvcnQgYWJzdHJhY3QgY2xhc3MgQmFzZUdyb3VwaW5nUmVuZGVyZXI8VCBleHRlbmRzIElHcm91cGluZ0VudGl0eT4gaW1wbGVtZW50cyBJUmVuZGVyZXIge1xyXG4gIGFic3RyYWN0IHJlYWRvbmx5IHR5cGU6IHN0cmluZztcclxuICBwcm90ZWN0ZWQgYWJzdHJhY3QgcmVhZG9ubHkgY29uZmlnOiBJR3JvdXBpbmdSZW5kZXJlckNvbmZpZztcclxuXHJcbiAgLyoqXHJcbiAgICogRmV0Y2ggZW50aXRpZXMgZnJvbSBzZXJ2aWNlXHJcbiAgICovXHJcbiAgcHJvdGVjdGVkIGFic3RyYWN0IGdldEVudGl0aWVzKGlkczogc3RyaW5nW10pOiBQcm9taXNlPFRbXT47XHJcblxyXG4gIC8qKlxyXG4gICAqIEdldCBkaXNwbGF5IG5hbWUgZm9yIGVudGl0eVxyXG4gICAqL1xyXG4gIHByb3RlY3RlZCBhYnN0cmFjdCBnZXREaXNwbGF5TmFtZShlbnRpdHk6IFQpOiBzdHJpbmc7XHJcblxyXG4gIC8qKlxyXG4gICAqIE1haW4gcmVuZGVyIG1ldGhvZCAtIGhhbmRsZXMgY29tbW9uIGxvZ2ljXHJcbiAgICovXHJcbiAgYXN5bmMgcmVuZGVyKGNvbnRleHQ6IElSZW5kZXJDb250ZXh0KTogUHJvbWlzZTx2b2lkPiB7XHJcbiAgICBjb25zdCBhbGxvd2VkSWRzID0gY29udGV4dC5maWx0ZXJbdGhpcy50eXBlXSB8fCBbXTtcclxuICAgIGlmIChhbGxvd2VkSWRzLmxlbmd0aCA9PT0gMCkgcmV0dXJuO1xyXG5cclxuICAgIGNvbnN0IGVudGl0aWVzID0gYXdhaXQgdGhpcy5nZXRFbnRpdGllcyhhbGxvd2VkSWRzKTtcclxuICAgIGNvbnN0IGRhdGVDb3VudCA9IGNvbnRleHQuZmlsdGVyWydkYXRlJ10/Lmxlbmd0aCB8fCAxO1xyXG4gICAgY29uc3QgY2hpbGRJZHMgPSBjb250ZXh0LmNoaWxkVHlwZSA/IGNvbnRleHQuZmlsdGVyW2NvbnRleHQuY2hpbGRUeXBlXSB8fCBbXSA6IFtdO1xyXG5cclxuICAgIGZvciAoY29uc3QgZW50aXR5IG9mIGVudGl0aWVzKSB7XHJcbiAgICAgIGNvbnN0IGVudGl0eUNoaWxkSWRzID0gY29udGV4dC5wYXJlbnRDaGlsZE1hcD8uW2VudGl0eS5pZF0gfHwgW107XHJcbiAgICAgIGNvbnN0IGNoaWxkQ291bnQgPSBlbnRpdHlDaGlsZElkcy5maWx0ZXIoaWQgPT4gY2hpbGRJZHMuaW5jbHVkZXMoaWQpKS5sZW5ndGg7XHJcbiAgICAgIGNvbnN0IGNvbHNwYW4gPSBjaGlsZENvdW50ICogZGF0ZUNvdW50O1xyXG5cclxuICAgICAgY29uc3QgaGVhZGVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCh0aGlzLmNvbmZpZy5lbGVtZW50VGFnKTtcclxuICAgICAgaGVhZGVyLmRhdGFzZXRbdGhpcy5jb25maWcuaWRBdHRyaWJ1dGVdID0gZW50aXR5LmlkO1xyXG4gICAgICBoZWFkZXIuc3R5bGUuc2V0UHJvcGVydHkodGhpcy5jb25maWcuY29sc3BhblZhciwgU3RyaW5nKGNvbHNwYW4pKTtcclxuXHJcbiAgICAgIC8vIEFsbG93IHN1YmNsYXNzIHRvIGN1c3RvbWl6ZSBoZWFkZXIgY29udGVudFxyXG4gICAgICB0aGlzLnJlbmRlckhlYWRlcihlbnRpdHksIGhlYWRlciwgY29udGV4dCk7XHJcblxyXG4gICAgICBjb250ZXh0LmhlYWRlckNvbnRhaW5lci5hcHBlbmRDaGlsZChoZWFkZXIpO1xyXG4gICAgfVxyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogT3ZlcnJpZGUgdGhpcyBtZXRob2QgZm9yIGN1c3RvbSBoZWFkZXIgcmVuZGVyaW5nXHJcbiAgICogRGVmYXVsdDoganVzdCBzZXRzIHRleHRDb250ZW50IHRvIGRpc3BsYXkgbmFtZVxyXG4gICAqL1xyXG4gIHByb3RlY3RlZCByZW5kZXJIZWFkZXIoZW50aXR5OiBULCBoZWFkZXI6IEhUTUxFbGVtZW50LCBfY29udGV4dDogSVJlbmRlckNvbnRleHQpOiB2b2lkIHtcclxuICAgIGhlYWRlci50ZXh0Q29udGVudCA9IHRoaXMuZ2V0RGlzcGxheU5hbWUoZW50aXR5KTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIEhlbHBlciB0byByZW5kZXIgYSBzaW5nbGUgZW50aXR5IGhlYWRlci5cclxuICAgKiBDYW4gYmUgdXNlZCBieSBzdWJjbGFzc2VzIHRoYXQgb3ZlcnJpZGUgcmVuZGVyKCkgYnV0IHdhbnQgY29uc2lzdGVudCBoZWFkZXIgY3JlYXRpb24uXHJcbiAgICovXHJcbiAgcHJvdGVjdGVkIGNyZWF0ZUhlYWRlcihlbnRpdHk6IFQsIGNvbnRleHQ6IElSZW5kZXJDb250ZXh0KTogSFRNTEVsZW1lbnQge1xyXG4gICAgY29uc3QgaGVhZGVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCh0aGlzLmNvbmZpZy5lbGVtZW50VGFnKTtcclxuICAgIGhlYWRlci5kYXRhc2V0W3RoaXMuY29uZmlnLmlkQXR0cmlidXRlXSA9IGVudGl0eS5pZDtcclxuICAgIHRoaXMucmVuZGVySGVhZGVyKGVudGl0eSwgaGVhZGVyLCBjb250ZXh0KTtcclxuICAgIHJldHVybiBoZWFkZXI7XHJcbiAgfVxyXG59XHJcbiIsICJpbXBvcnQgeyBJUmVuZGVyQ29udGV4dCB9IGZyb20gJy4uLy4uL2NvcmUvSUdyb3VwaW5nUmVuZGVyZXInO1xyXG5pbXBvcnQgeyBCYXNlR3JvdXBpbmdSZW5kZXJlciwgSUdyb3VwaW5nUmVuZGVyZXJDb25maWcgfSBmcm9tICcuLi8uLi9jb3JlL0Jhc2VHcm91cGluZ1JlbmRlcmVyJztcclxuaW1wb3J0IHsgUmVzb3VyY2VTZXJ2aWNlIH0gZnJvbSAnLi4vLi4vc3RvcmFnZS9yZXNvdXJjZXMvUmVzb3VyY2VTZXJ2aWNlJztcclxuaW1wb3J0IHsgSVJlc291cmNlIH0gZnJvbSAnLi4vLi4vdHlwZXMvQ2FsZW5kYXJUeXBlcyc7XHJcblxyXG5leHBvcnQgY2xhc3MgUmVzb3VyY2VSZW5kZXJlciBleHRlbmRzIEJhc2VHcm91cGluZ1JlbmRlcmVyPElSZXNvdXJjZT4ge1xyXG4gIHJlYWRvbmx5IHR5cGUgPSAncmVzb3VyY2UnO1xyXG5cclxuICBwcm90ZWN0ZWQgcmVhZG9ubHkgY29uZmlnOiBJR3JvdXBpbmdSZW5kZXJlckNvbmZpZyA9IHtcclxuICAgIGVsZW1lbnRUYWc6ICdzd3AtcmVzb3VyY2UtaGVhZGVyJyxcclxuICAgIGlkQXR0cmlidXRlOiAncmVzb3VyY2VJZCcsXHJcbiAgICBjb2xzcGFuVmFyOiAnLS1yZXNvdXJjZS1jb2xzJ1xyXG4gIH07XHJcblxyXG4gIGNvbnN0cnVjdG9yKHByaXZhdGUgcmVzb3VyY2VTZXJ2aWNlOiBSZXNvdXJjZVNlcnZpY2UpIHtcclxuICAgIHN1cGVyKCk7XHJcbiAgfVxyXG5cclxuICBwcm90ZWN0ZWQgZ2V0RW50aXRpZXMoaWRzOiBzdHJpbmdbXSk6IFByb21pc2U8SVJlc291cmNlW10+IHtcclxuICAgIHJldHVybiB0aGlzLnJlc291cmNlU2VydmljZS5nZXRCeUlkcyhpZHMpO1xyXG4gIH1cclxuXHJcbiAgcHJvdGVjdGVkIGdldERpc3BsYXlOYW1lKGVudGl0eTogSVJlc291cmNlKTogc3RyaW5nIHtcclxuICAgIHJldHVybiBlbnRpdHkuZGlzcGxheU5hbWU7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBPdmVycmlkZSByZW5kZXIgdG8gaGFuZGxlOlxyXG4gICAqIDEuIFNwZWNpYWwgb3JkZXJpbmcgd2hlbiBwYXJlbnRDaGlsZE1hcCBleGlzdHMgKHJlc291cmNlcyBncm91cGVkIGJ5IHBhcmVudClcclxuICAgKiAyLiBEaWZmZXJlbnQgY29sc3BhbiBjYWxjdWxhdGlvbiAoanVzdCBkYXRlQ291bnQsIG5vdCBjaGlsZENvdW50ICogZGF0ZUNvdW50KVxyXG4gICAqL1xyXG4gIGFzeW5jIHJlbmRlcihjb250ZXh0OiBJUmVuZGVyQ29udGV4dCk6IFByb21pc2U8dm9pZD4ge1xyXG4gICAgY29uc3QgcmVzb3VyY2VJZHMgPSBjb250ZXh0LmZpbHRlclsncmVzb3VyY2UnXSB8fCBbXTtcclxuICAgIGNvbnN0IGRhdGVDb3VudCA9IGNvbnRleHQuZmlsdGVyWydkYXRlJ10/Lmxlbmd0aCB8fCAxO1xyXG5cclxuICAgIC8vIERldGVybWluZSByZW5kZXIgb3JkZXIgYmFzZWQgb24gcGFyZW50Q2hpbGRNYXBcclxuICAgIC8vIElmIHBhcmVudENoaWxkTWFwIGV4aXN0cywgcmVuZGVyIHJlc291cmNlcyBncm91cGVkIGJ5IHBhcmVudCAoZS5nLiwgdGVhbSlcclxuICAgIC8vIE90aGVyd2lzZSwgcmVuZGVyIGluIGZpbHRlciBvcmRlclxyXG4gICAgbGV0IG9yZGVyZWRSZXNvdXJjZUlkczogc3RyaW5nW107XHJcblxyXG4gICAgaWYgKGNvbnRleHQucGFyZW50Q2hpbGRNYXApIHtcclxuICAgICAgLy8gUmVuZGVyIHJlc291cmNlcyBpbiBwYXJlbnQtY2hpbGQgb3JkZXJcclxuICAgICAgb3JkZXJlZFJlc291cmNlSWRzID0gW107XHJcbiAgICAgIGZvciAoY29uc3QgY2hpbGRJZHMgb2YgT2JqZWN0LnZhbHVlcyhjb250ZXh0LnBhcmVudENoaWxkTWFwKSkge1xyXG4gICAgICAgIGZvciAoY29uc3QgY2hpbGRJZCBvZiBjaGlsZElkcykge1xyXG4gICAgICAgICAgaWYgKHJlc291cmNlSWRzLmluY2x1ZGVzKGNoaWxkSWQpKSB7XHJcbiAgICAgICAgICAgIG9yZGVyZWRSZXNvdXJjZUlkcy5wdXNoKGNoaWxkSWQpO1xyXG4gICAgICAgICAgfVxyXG4gICAgICAgIH1cclxuICAgICAgfVxyXG4gICAgfSBlbHNlIHtcclxuICAgICAgb3JkZXJlZFJlc291cmNlSWRzID0gcmVzb3VyY2VJZHM7XHJcbiAgICB9XHJcblxyXG4gICAgY29uc3QgcmVzb3VyY2VzID0gYXdhaXQgdGhpcy5nZXRFbnRpdGllcyhvcmRlcmVkUmVzb3VyY2VJZHMpO1xyXG5cclxuICAgIC8vIENyZWF0ZSBhIG1hcCBmb3IgcXVpY2sgbG9va3VwIHRvIHByZXNlcnZlIG9yZGVyXHJcbiAgICBjb25zdCByZXNvdXJjZU1hcCA9IG5ldyBNYXAocmVzb3VyY2VzLm1hcChyID0+IFtyLmlkLCByXSkpO1xyXG5cclxuICAgIGZvciAoY29uc3QgcmVzb3VyY2VJZCBvZiBvcmRlcmVkUmVzb3VyY2VJZHMpIHtcclxuICAgICAgY29uc3QgcmVzb3VyY2UgPSByZXNvdXJjZU1hcC5nZXQocmVzb3VyY2VJZCk7XHJcbiAgICAgIGlmICghcmVzb3VyY2UpIGNvbnRpbnVlO1xyXG5cclxuICAgICAgY29uc3QgaGVhZGVyID0gdGhpcy5jcmVhdGVIZWFkZXIocmVzb3VyY2UsIGNvbnRleHQpO1xyXG4gICAgICBoZWFkZXIuc3R5bGUuZ3JpZENvbHVtbiA9IGBzcGFuICR7ZGF0ZUNvdW50fWA7XHJcbiAgICAgIGNvbnRleHQuaGVhZGVyQ29udGFpbmVyLmFwcGVuZENoaWxkKGhlYWRlcik7XHJcbiAgICB9XHJcbiAgfVxyXG59XHJcbiIsICJpbXBvcnQgeyBCYXNlR3JvdXBpbmdSZW5kZXJlciwgSUdyb3VwaW5nUmVuZGVyZXJDb25maWcgfSBmcm9tICcuLi8uLi9jb3JlL0Jhc2VHcm91cGluZ1JlbmRlcmVyJztcclxuaW1wb3J0IHsgVGVhbVNlcnZpY2UgfSBmcm9tICcuLi8uLi9zdG9yYWdlL3RlYW1zL1RlYW1TZXJ2aWNlJztcclxuaW1wb3J0IHsgSVRlYW0gfSBmcm9tICcuLi8uLi90eXBlcy9DYWxlbmRhclR5cGVzJztcclxuXHJcbmV4cG9ydCBjbGFzcyBUZWFtUmVuZGVyZXIgZXh0ZW5kcyBCYXNlR3JvdXBpbmdSZW5kZXJlcjxJVGVhbT4ge1xyXG4gIHJlYWRvbmx5IHR5cGUgPSAndGVhbSc7XHJcblxyXG4gIHByb3RlY3RlZCByZWFkb25seSBjb25maWc6IElHcm91cGluZ1JlbmRlcmVyQ29uZmlnID0ge1xyXG4gICAgZWxlbWVudFRhZzogJ3N3cC10ZWFtLWhlYWRlcicsXHJcbiAgICBpZEF0dHJpYnV0ZTogJ3RlYW1JZCcsXHJcbiAgICBjb2xzcGFuVmFyOiAnLS10ZWFtLWNvbHMnXHJcbiAgfTtcclxuXHJcbiAgY29uc3RydWN0b3IocHJpdmF0ZSB0ZWFtU2VydmljZTogVGVhbVNlcnZpY2UpIHtcclxuICAgIHN1cGVyKCk7XHJcbiAgfVxyXG5cclxuICBwcm90ZWN0ZWQgZ2V0RW50aXRpZXMoaWRzOiBzdHJpbmdbXSk6IFByb21pc2U8SVRlYW1bXT4ge1xyXG4gICAgcmV0dXJuIHRoaXMudGVhbVNlcnZpY2UuZ2V0QnlJZHMoaWRzKTtcclxuICB9XHJcblxyXG4gIHByb3RlY3RlZCBnZXREaXNwbGF5TmFtZShlbnRpdHk6IElUZWFtKTogc3RyaW5nIHtcclxuICAgIHJldHVybiBlbnRpdHkubmFtZTtcclxuICB9XHJcbn1cclxuIiwgImV4cG9ydCBjbGFzcyBUaW1lQXhpc1JlbmRlcmVyIHtcclxuICByZW5kZXIoY29udGFpbmVyOiBIVE1MRWxlbWVudCwgc3RhcnRIb3VyID0gNiwgZW5kSG91ciA9IDIwKTogdm9pZCB7XHJcbiAgICBjb250YWluZXIuaW5uZXJIVE1MID0gJyc7XHJcbiAgICBmb3IgKGxldCBob3VyID0gc3RhcnRIb3VyOyBob3VyIDw9IGVuZEhvdXI7IGhvdXIrKykge1xyXG4gICAgICBjb25zdCBtYXJrZXIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzd3AtaG91ci1tYXJrZXInKTtcclxuICAgICAgbWFya2VyLnRleHRDb250ZW50ID0gYCR7aG91ci50b1N0cmluZygpLnBhZFN0YXJ0KDIsICcwJyl9OjAwYDtcclxuICAgICAgY29udGFpbmVyLmFwcGVuZENoaWxkKG1hcmtlcik7XHJcbiAgICB9XHJcbiAgfVxyXG59XHJcbiJdLAogICJtYXBwaW5ncyI6ICI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQTtBQUFBO0FBQUEsS0FBQyxTQUFTLEdBQUUsR0FBRTtBQUFDLGtCQUFVLE9BQU8sV0FBUyxlQUFhLE9BQU8sU0FBTyxPQUFPLFVBQVEsRUFBRSxJQUFFLGNBQVksT0FBTyxVQUFRLE9BQU8sTUFBSSxPQUFPLENBQUMsS0FBRyxJQUFFLGVBQWEsT0FBTyxhQUFXLGFBQVcsS0FBRyxNQUFNLFFBQU0sRUFBRTtBQUFBLElBQUMsRUFBRSxTQUFNLFdBQVU7QUFBQztBQUFhLFVBQUksSUFBRSxLQUFJLElBQUUsS0FBSSxJQUFFLE1BQUssSUFBRSxlQUFjLElBQUUsVUFBUyxJQUFFLFVBQVMsSUFBRSxRQUFPLElBQUUsT0FBTSxJQUFFLFFBQU8sSUFBRSxTQUFRLElBQUUsV0FBVSxJQUFFLFFBQU8sSUFBRSxRQUFPLElBQUUsZ0JBQWUsSUFBRSw4RkFBNkYsSUFBRSx1RkFBc0YsSUFBRSxFQUFDLE1BQUssTUFBSyxVQUFTLDJEQUEyRCxNQUFNLEdBQUcsR0FBRSxRQUFPLHdGQUF3RixNQUFNLEdBQUcsR0FBRSxTQUFRLFNBQVNBLElBQUU7QUFBQyxZQUFJQyxLQUFFLENBQUMsTUFBSyxNQUFLLE1BQUssSUFBSSxHQUFFQyxLQUFFRixLQUFFO0FBQUksZUFBTSxNQUFJQSxNQUFHQyxJQUFHQyxLQUFFLE1BQUksRUFBRSxLQUFHRCxHQUFFQyxFQUFDLEtBQUdELEdBQUUsQ0FBQyxLQUFHO0FBQUEsTUFBRyxFQUFDLEdBQUUsSUFBRSxnQ0FBU0QsSUFBRUMsSUFBRUMsSUFBRTtBQUFDLFlBQUlDLEtBQUUsT0FBT0gsRUFBQztBQUFFLGVBQU0sQ0FBQ0csTUFBR0EsR0FBRSxVQUFRRixLQUFFRCxLQUFFLEtBQUcsTUFBTUMsS0FBRSxJQUFFRSxHQUFFLE1BQU0sRUFBRSxLQUFLRCxFQUFDLElBQUVGO0FBQUEsTUFBQyxHQUF4RixNQUEwRixJQUFFLEVBQUMsR0FBRSxHQUFFLEdBQUUsU0FBU0EsSUFBRTtBQUFDLFlBQUlDLEtBQUUsQ0FBQ0QsR0FBRSxVQUFVLEdBQUVFLEtBQUUsS0FBSyxJQUFJRCxFQUFDLEdBQUVFLEtBQUUsS0FBSyxNQUFNRCxLQUFFLEVBQUUsR0FBRUUsS0FBRUYsS0FBRTtBQUFHLGdCQUFPRCxNQUFHLElBQUUsTUFBSSxPQUFLLEVBQUVFLElBQUUsR0FBRSxHQUFHLElBQUUsTUFBSSxFQUFFQyxJQUFFLEdBQUUsR0FBRztBQUFBLE1BQUMsR0FBRSxHQUFFLGdDQUFTSixHQUFFQyxJQUFFQyxJQUFFO0FBQUMsWUFBR0QsR0FBRSxLQUFLLElBQUVDLEdBQUUsS0FBSztBQUFFLGlCQUFNLENBQUNGLEdBQUVFLElBQUVELEVBQUM7QUFBRSxZQUFJRSxLQUFFLE1BQUlELEdBQUUsS0FBSyxJQUFFRCxHQUFFLEtBQUssTUFBSUMsR0FBRSxNQUFNLElBQUVELEdBQUUsTUFBTSxJQUFHRyxLQUFFSCxHQUFFLE1BQU0sRUFBRSxJQUFJRSxJQUFFLENBQUMsR0FBRUUsS0FBRUgsS0FBRUUsS0FBRSxHQUFFRSxLQUFFTCxHQUFFLE1BQU0sRUFBRSxJQUFJRSxNQUFHRSxLQUFFLEtBQUcsSUFBRyxDQUFDO0FBQUUsZUFBTSxFQUFFLEVBQUVGLE1BQUdELEtBQUVFLE9BQUlDLEtBQUVELEtBQUVFLEtBQUVBLEtBQUVGLFFBQUs7QUFBQSxNQUFFLEdBQW5NLE1BQXFNLEdBQUUsU0FBU0osSUFBRTtBQUFDLGVBQU9BLEtBQUUsSUFBRSxLQUFLLEtBQUtBLEVBQUMsS0FBRyxJQUFFLEtBQUssTUFBTUEsRUFBQztBQUFBLE1BQUMsR0FBRSxHQUFFLFNBQVNBLElBQUU7QUFBQyxlQUFNLEVBQUMsR0FBRSxHQUFFLEdBQUUsR0FBRSxHQUFFLEdBQUUsR0FBRSxHQUFFLEdBQUUsR0FBRSxHQUFFLEdBQUUsR0FBRSxHQUFFLEdBQUUsR0FBRSxJQUFHLEdBQUUsR0FBRSxFQUFDLEVBQUVBLEVBQUMsS0FBRyxPQUFPQSxNQUFHLEVBQUUsRUFBRSxZQUFZLEVBQUUsUUFBUSxNQUFLLEVBQUU7QUFBQSxNQUFDLEdBQUUsR0FBRSxTQUFTQSxJQUFFO0FBQUMsZUFBTyxXQUFTQTtBQUFBLE1BQUMsRUFBQyxHQUFFLElBQUUsTUFBSyxJQUFFLENBQUM7QUFBRSxRQUFFLENBQUMsSUFBRTtBQUFFLFVBQUksSUFBRSxrQkFBaUIsSUFBRSxnQ0FBU0EsSUFBRTtBQUFDLGVBQU9BLGNBQWEsS0FBRyxFQUFFLENBQUNBLE1BQUcsQ0FBQ0EsR0FBRSxDQUFDO0FBQUEsTUFBRSxHQUEvQyxNQUFpRCxJQUFFLGdDQUFTQSxHQUFFQyxJQUFFQyxJQUFFQyxJQUFFO0FBQUMsWUFBSUM7QUFBRSxZQUFHLENBQUNIO0FBQUUsaUJBQU87QUFBRSxZQUFHLFlBQVUsT0FBT0EsSUFBRTtBQUFDLGNBQUlJLEtBQUVKLEdBQUUsWUFBWTtBQUFFLFlBQUVJLEVBQUMsTUFBSUQsS0FBRUMsS0FBR0gsT0FBSSxFQUFFRyxFQUFDLElBQUVILElBQUVFLEtBQUVDO0FBQUcsY0FBSUMsS0FBRUwsR0FBRSxNQUFNLEdBQUc7QUFBRSxjQUFHLENBQUNHLE1BQUdFLEdBQUUsU0FBTztBQUFFLG1CQUFPTixHQUFFTSxHQUFFLENBQUMsQ0FBQztBQUFBLFFBQUMsT0FBSztBQUFDLGNBQUlDLEtBQUVOLEdBQUU7QUFBSyxZQUFFTSxFQUFDLElBQUVOLElBQUVHLEtBQUVHO0FBQUEsUUFBQztBQUFDLGVBQU0sQ0FBQ0osTUFBR0MsT0FBSSxJQUFFQSxLQUFHQSxNQUFHLENBQUNELE1BQUc7QUFBQSxNQUFDLEdBQTVOLE1BQThOLElBQUUsZ0NBQVNILElBQUVDLElBQUU7QUFBQyxZQUFHLEVBQUVELEVBQUM7QUFBRSxpQkFBT0EsR0FBRSxNQUFNO0FBQUUsWUFBSUUsS0FBRSxZQUFVLE9BQU9ELEtBQUVBLEtBQUUsQ0FBQztBQUFFLGVBQU9DLEdBQUUsT0FBS0YsSUFBRUUsR0FBRSxPQUFLLFdBQVUsSUFBSSxFQUFFQSxFQUFDO0FBQUEsTUFBQyxHQUE5RyxNQUFnSCxJQUFFO0FBQUUsUUFBRSxJQUFFLEdBQUUsRUFBRSxJQUFFLEdBQUUsRUFBRSxJQUFFLFNBQVNGLElBQUVDLElBQUU7QUFBQyxlQUFPLEVBQUVELElBQUUsRUFBQyxRQUFPQyxHQUFFLElBQUcsS0FBSUEsR0FBRSxJQUFHLEdBQUVBLEdBQUUsSUFBRyxTQUFRQSxHQUFFLFFBQU8sQ0FBQztBQUFBLE1BQUM7QUFBRSxVQUFJLElBQUUsV0FBVTtBQUFDLGlCQUFTTyxHQUFFUixJQUFFO0FBQUMsZUFBSyxLQUFHLEVBQUVBLEdBQUUsUUFBTyxNQUFLLElBQUUsR0FBRSxLQUFLLE1BQU1BLEVBQUMsR0FBRSxLQUFLLEtBQUcsS0FBSyxNQUFJQSxHQUFFLEtBQUcsQ0FBQyxHQUFFLEtBQUssQ0FBQyxJQUFFO0FBQUEsUUFBRTtBQUFsRixlQUFBUSxJQUFBO0FBQW1GLFlBQUlDLEtBQUVELEdBQUU7QUFBVSxlQUFPQyxHQUFFLFFBQU0sU0FBU1QsSUFBRTtBQUFDLGVBQUssS0FBRyxTQUFTQSxJQUFFO0FBQUMsZ0JBQUlDLEtBQUVELEdBQUUsTUFBS0UsS0FBRUYsR0FBRTtBQUFJLGdCQUFHLFNBQU9DO0FBQUUscUJBQU8sb0JBQUksS0FBSyxHQUFHO0FBQUUsZ0JBQUcsRUFBRSxFQUFFQSxFQUFDO0FBQUUscUJBQU8sb0JBQUk7QUFBSyxnQkFBR0EsY0FBYTtBQUFLLHFCQUFPLElBQUksS0FBS0EsRUFBQztBQUFFLGdCQUFHLFlBQVUsT0FBT0EsTUFBRyxDQUFDLE1BQU0sS0FBS0EsRUFBQyxHQUFFO0FBQUMsa0JBQUlFLEtBQUVGLEdBQUUsTUFBTSxDQUFDO0FBQUUsa0JBQUdFLElBQUU7QUFBQyxvQkFBSUMsS0FBRUQsR0FBRSxDQUFDLElBQUUsS0FBRyxHQUFFRSxNQUFHRixHQUFFLENBQUMsS0FBRyxLQUFLLFVBQVUsR0FBRSxDQUFDO0FBQUUsdUJBQU9ELEtBQUUsSUFBSSxLQUFLLEtBQUssSUFBSUMsR0FBRSxDQUFDLEdBQUVDLElBQUVELEdBQUUsQ0FBQyxLQUFHLEdBQUVBLEdBQUUsQ0FBQyxLQUFHLEdBQUVBLEdBQUUsQ0FBQyxLQUFHLEdBQUVBLEdBQUUsQ0FBQyxLQUFHLEdBQUVFLEVBQUMsQ0FBQyxJQUFFLElBQUksS0FBS0YsR0FBRSxDQUFDLEdBQUVDLElBQUVELEdBQUUsQ0FBQyxLQUFHLEdBQUVBLEdBQUUsQ0FBQyxLQUFHLEdBQUVBLEdBQUUsQ0FBQyxLQUFHLEdBQUVBLEdBQUUsQ0FBQyxLQUFHLEdBQUVFLEVBQUM7QUFBQSxjQUFDO0FBQUEsWUFBQztBQUFDLG1CQUFPLElBQUksS0FBS0osRUFBQztBQUFBLFVBQUMsRUFBRUQsRUFBQyxHQUFFLEtBQUssS0FBSztBQUFBLFFBQUMsR0FBRVMsR0FBRSxPQUFLLFdBQVU7QUFBQyxjQUFJVCxLQUFFLEtBQUs7QUFBRyxlQUFLLEtBQUdBLEdBQUUsWUFBWSxHQUFFLEtBQUssS0FBR0EsR0FBRSxTQUFTLEdBQUUsS0FBSyxLQUFHQSxHQUFFLFFBQVEsR0FBRSxLQUFLLEtBQUdBLEdBQUUsT0FBTyxHQUFFLEtBQUssS0FBR0EsR0FBRSxTQUFTLEdBQUUsS0FBSyxLQUFHQSxHQUFFLFdBQVcsR0FBRSxLQUFLLEtBQUdBLEdBQUUsV0FBVyxHQUFFLEtBQUssTUFBSUEsR0FBRSxnQkFBZ0I7QUFBQSxRQUFDLEdBQUVTLEdBQUUsU0FBTyxXQUFVO0FBQUMsaUJBQU87QUFBQSxRQUFDLEdBQUVBLEdBQUUsVUFBUSxXQUFVO0FBQUMsaUJBQU0sRUFBRSxLQUFLLEdBQUcsU0FBUyxNQUFJO0FBQUEsUUFBRSxHQUFFQSxHQUFFLFNBQU8sU0FBU1QsSUFBRUMsSUFBRTtBQUFDLGNBQUlDLEtBQUUsRUFBRUYsRUFBQztBQUFFLGlCQUFPLEtBQUssUUFBUUMsRUFBQyxLQUFHQyxNQUFHQSxNQUFHLEtBQUssTUFBTUQsRUFBQztBQUFBLFFBQUMsR0FBRVEsR0FBRSxVQUFRLFNBQVNULElBQUVDLElBQUU7QUFBQyxpQkFBTyxFQUFFRCxFQUFDLElBQUUsS0FBSyxRQUFRQyxFQUFDO0FBQUEsUUFBQyxHQUFFUSxHQUFFLFdBQVMsU0FBU1QsSUFBRUMsSUFBRTtBQUFDLGlCQUFPLEtBQUssTUFBTUEsRUFBQyxJQUFFLEVBQUVELEVBQUM7QUFBQSxRQUFDLEdBQUVTLEdBQUUsS0FBRyxTQUFTVCxJQUFFQyxJQUFFQyxJQUFFO0FBQUMsaUJBQU8sRUFBRSxFQUFFRixFQUFDLElBQUUsS0FBS0MsRUFBQyxJQUFFLEtBQUssSUFBSUMsSUFBRUYsRUFBQztBQUFBLFFBQUMsR0FBRVMsR0FBRSxPQUFLLFdBQVU7QUFBQyxpQkFBTyxLQUFLLE1BQU0sS0FBSyxRQUFRLElBQUUsR0FBRztBQUFBLFFBQUMsR0FBRUEsR0FBRSxVQUFRLFdBQVU7QUFBQyxpQkFBTyxLQUFLLEdBQUcsUUFBUTtBQUFBLFFBQUMsR0FBRUEsR0FBRSxVQUFRLFNBQVNULElBQUVDLElBQUU7QUFBQyxjQUFJQyxLQUFFLE1BQUtDLEtBQUUsQ0FBQyxDQUFDLEVBQUUsRUFBRUYsRUFBQyxLQUFHQSxJQUFFUyxLQUFFLEVBQUUsRUFBRVYsRUFBQyxHQUFFVyxLQUFFLGdDQUFTWCxJQUFFQyxJQUFFO0FBQUMsZ0JBQUlHLEtBQUUsRUFBRSxFQUFFRixHQUFFLEtBQUcsS0FBSyxJQUFJQSxHQUFFLElBQUdELElBQUVELEVBQUMsSUFBRSxJQUFJLEtBQUtFLEdBQUUsSUFBR0QsSUFBRUQsRUFBQyxHQUFFRSxFQUFDO0FBQUUsbUJBQU9DLEtBQUVDLEtBQUVBLEdBQUUsTUFBTSxDQUFDO0FBQUEsVUFBQyxHQUEzRixNQUE2RlEsS0FBRSxnQ0FBU1osSUFBRUMsSUFBRTtBQUFDLG1CQUFPLEVBQUUsRUFBRUMsR0FBRSxPQUFPLEVBQUVGLEVBQUMsRUFBRSxNQUFNRSxHQUFFLE9BQU8sR0FBRyxJQUFHQyxLQUFFLENBQUMsR0FBRSxHQUFFLEdBQUUsQ0FBQyxJQUFFLENBQUMsSUFBRyxJQUFHLElBQUcsR0FBRyxHQUFHLE1BQU1GLEVBQUMsQ0FBQyxHQUFFQyxFQUFDO0FBQUEsVUFBQyxHQUFwRyxNQUFzR1csS0FBRSxLQUFLLElBQUdMLEtBQUUsS0FBSyxJQUFHQyxLQUFFLEtBQUssSUFBR0ssS0FBRSxTQUFPLEtBQUssS0FBRyxRQUFNO0FBQUksa0JBQU9KLElBQUU7QUFBQSxZQUFDLEtBQUs7QUFBRSxxQkFBT1AsS0FBRVEsR0FBRSxHQUFFLENBQUMsSUFBRUEsR0FBRSxJQUFHLEVBQUU7QUFBQSxZQUFFLEtBQUs7QUFBRSxxQkFBT1IsS0FBRVEsR0FBRSxHQUFFSCxFQUFDLElBQUVHLEdBQUUsR0FBRUgsS0FBRSxDQUFDO0FBQUEsWUFBRSxLQUFLO0FBQUUsa0JBQUlPLEtBQUUsS0FBSyxRQUFRLEVBQUUsYUFBVyxHQUFFQyxNQUFHSCxLQUFFRSxLQUFFRixLQUFFLElBQUVBLE1BQUdFO0FBQUUscUJBQU9KLEdBQUVSLEtBQUVNLEtBQUVPLEtBQUVQLE1BQUcsSUFBRU8sS0FBR1IsRUFBQztBQUFBLFlBQUUsS0FBSztBQUFBLFlBQUUsS0FBSztBQUFFLHFCQUFPSSxHQUFFRSxLQUFFLFNBQVEsQ0FBQztBQUFBLFlBQUUsS0FBSztBQUFFLHFCQUFPRixHQUFFRSxLQUFFLFdBQVUsQ0FBQztBQUFBLFlBQUUsS0FBSztBQUFFLHFCQUFPRixHQUFFRSxLQUFFLFdBQVUsQ0FBQztBQUFBLFlBQUUsS0FBSztBQUFFLHFCQUFPRixHQUFFRSxLQUFFLGdCQUFlLENBQUM7QUFBQSxZQUFFO0FBQVEscUJBQU8sS0FBSyxNQUFNO0FBQUEsVUFBQztBQUFBLFFBQUMsR0FBRUwsR0FBRSxRQUFNLFNBQVNULElBQUU7QUFBQyxpQkFBTyxLQUFLLFFBQVFBLElBQUUsS0FBRTtBQUFBLFFBQUMsR0FBRVMsR0FBRSxPQUFLLFNBQVNULElBQUVDLElBQUU7QUFBQyxjQUFJQyxJQUFFZSxLQUFFLEVBQUUsRUFBRWpCLEVBQUMsR0FBRVUsS0FBRSxTQUFPLEtBQUssS0FBRyxRQUFNLEtBQUlDLE1BQUdULEtBQUUsQ0FBQyxHQUFFQSxHQUFFLENBQUMsSUFBRVEsS0FBRSxRQUFPUixHQUFFLENBQUMsSUFBRVEsS0FBRSxRQUFPUixHQUFFLENBQUMsSUFBRVEsS0FBRSxTQUFRUixHQUFFLENBQUMsSUFBRVEsS0FBRSxZQUFXUixHQUFFLENBQUMsSUFBRVEsS0FBRSxTQUFRUixHQUFFLENBQUMsSUFBRVEsS0FBRSxXQUFVUixHQUFFLENBQUMsSUFBRVEsS0FBRSxXQUFVUixHQUFFLENBQUMsSUFBRVEsS0FBRSxnQkFBZVIsSUFBR2UsRUFBQyxHQUFFTCxLQUFFSyxPQUFJLElBQUUsS0FBSyxNQUFJaEIsS0FBRSxLQUFLLE1BQUlBO0FBQUUsY0FBR2dCLE9BQUksS0FBR0EsT0FBSSxHQUFFO0FBQUMsZ0JBQUlKLEtBQUUsS0FBSyxNQUFNLEVBQUUsSUFBSSxHQUFFLENBQUM7QUFBRSxZQUFBQSxHQUFFLEdBQUdGLEVBQUMsRUFBRUMsRUFBQyxHQUFFQyxHQUFFLEtBQUssR0FBRSxLQUFLLEtBQUdBLEdBQUUsSUFBSSxHQUFFLEtBQUssSUFBSSxLQUFLLElBQUdBLEdBQUUsWUFBWSxDQUFDLENBQUMsRUFBRTtBQUFBLFVBQUU7QUFBTSxZQUFBRixNQUFHLEtBQUssR0FBR0EsRUFBQyxFQUFFQyxFQUFDO0FBQUUsaUJBQU8sS0FBSyxLQUFLLEdBQUU7QUFBQSxRQUFJLEdBQUVILEdBQUUsTUFBSSxTQUFTVCxJQUFFQyxJQUFFO0FBQUMsaUJBQU8sS0FBSyxNQUFNLEVBQUUsS0FBS0QsSUFBRUMsRUFBQztBQUFBLFFBQUMsR0FBRVEsR0FBRSxNQUFJLFNBQVNULElBQUU7QUFBQyxpQkFBTyxLQUFLLEVBQUUsRUFBRUEsRUFBQyxDQUFDLEVBQUU7QUFBQSxRQUFDLEdBQUVTLEdBQUUsTUFBSSxTQUFTTixJQUFFTyxJQUFFO0FBQUMsY0FBSVEsSUFBRVAsS0FBRTtBQUFLLFVBQUFSLEtBQUUsT0FBT0EsRUFBQztBQUFFLGNBQUlTLEtBQUUsRUFBRSxFQUFFRixFQUFDLEdBQUVHLEtBQUUsZ0NBQVNiLElBQUU7QUFBQyxnQkFBSUMsS0FBRSxFQUFFVSxFQUFDO0FBQUUsbUJBQU8sRUFBRSxFQUFFVixHQUFFLEtBQUtBLEdBQUUsS0FBSyxJQUFFLEtBQUssTUFBTUQsS0FBRUcsRUFBQyxDQUFDLEdBQUVRLEVBQUM7QUFBQSxVQUFDLEdBQXJFO0FBQXVFLGNBQUdDLE9BQUk7QUFBRSxtQkFBTyxLQUFLLElBQUksR0FBRSxLQUFLLEtBQUdULEVBQUM7QUFBRSxjQUFHUyxPQUFJO0FBQUUsbUJBQU8sS0FBSyxJQUFJLEdBQUUsS0FBSyxLQUFHVCxFQUFDO0FBQUUsY0FBR1MsT0FBSTtBQUFFLG1CQUFPQyxHQUFFLENBQUM7QUFBRSxjQUFHRCxPQUFJO0FBQUUsbUJBQU9DLEdBQUUsQ0FBQztBQUFFLGNBQUlMLE1BQUdVLEtBQUUsQ0FBQyxHQUFFQSxHQUFFLENBQUMsSUFBRSxHQUFFQSxHQUFFLENBQUMsSUFBRSxHQUFFQSxHQUFFLENBQUMsSUFBRSxHQUFFQSxJQUFHTixFQUFDLEtBQUcsR0FBRUgsS0FBRSxLQUFLLEdBQUcsUUFBUSxJQUFFTixLQUFFSztBQUFFLGlCQUFPLEVBQUUsRUFBRUMsSUFBRSxJQUFJO0FBQUEsUUFBQyxHQUFFQSxHQUFFLFdBQVMsU0FBU1QsSUFBRUMsSUFBRTtBQUFDLGlCQUFPLEtBQUssSUFBSSxLQUFHRCxJQUFFQyxFQUFDO0FBQUEsUUFBQyxHQUFFUSxHQUFFLFNBQU8sU0FBU1QsSUFBRTtBQUFDLGNBQUlDLEtBQUUsTUFBS0MsS0FBRSxLQUFLLFFBQVE7QUFBRSxjQUFHLENBQUMsS0FBSyxRQUFRO0FBQUUsbUJBQU9BLEdBQUUsZUFBYTtBQUFFLGNBQUlDLEtBQUVILE1BQUcsd0JBQXVCSSxLQUFFLEVBQUUsRUFBRSxJQUFJLEdBQUVDLEtBQUUsS0FBSyxJQUFHQyxLQUFFLEtBQUssSUFBR0MsS0FBRSxLQUFLLElBQUdVLEtBQUVmLEdBQUUsVUFBU2lCLEtBQUVqQixHQUFFLFFBQU9RLEtBQUVSLEdBQUUsVUFBU2tCLEtBQUUsZ0NBQVNwQixJQUFFRSxJQUFFRSxJQUFFQyxJQUFFO0FBQUMsbUJBQU9MLE9BQUlBLEdBQUVFLEVBQUMsS0FBR0YsR0FBRUMsSUFBRUUsRUFBQyxNQUFJQyxHQUFFRixFQUFDLEVBQUUsTUFBTSxHQUFFRyxFQUFDO0FBQUEsVUFBQyxHQUEzRCxNQUE2RGEsS0FBRSxnQ0FBU2xCLElBQUU7QUFBQyxtQkFBTyxFQUFFLEVBQUVLLEtBQUUsTUFBSSxJQUFHTCxJQUFFLEdBQUc7QUFBQSxVQUFDLEdBQXRDLE1BQXdDWSxLQUFFRixNQUFHLFNBQVNWLElBQUVDLElBQUVDLElBQUU7QUFBQyxnQkFBSUMsS0FBRUgsS0FBRSxLQUFHLE9BQUs7QUFBSyxtQkFBT0UsS0FBRUMsR0FBRSxZQUFZLElBQUVBO0FBQUEsVUFBQztBQUFFLGlCQUFPQSxHQUFFLFFBQVEsR0FBRyxTQUFTSCxJQUFFRyxJQUFFO0FBQUMsbUJBQU9BLE1BQUcsU0FBU0gsSUFBRTtBQUFDLHNCQUFPQSxJQUFFO0FBQUEsZ0JBQUMsS0FBSTtBQUFLLHlCQUFPLE9BQU9DLEdBQUUsRUFBRSxFQUFFLE1BQU0sRUFBRTtBQUFBLGdCQUFFLEtBQUk7QUFBTyx5QkFBTyxFQUFFLEVBQUVBLEdBQUUsSUFBRyxHQUFFLEdBQUc7QUFBQSxnQkFBRSxLQUFJO0FBQUkseUJBQU9NLEtBQUU7QUFBQSxnQkFBRSxLQUFJO0FBQUsseUJBQU8sRUFBRSxFQUFFQSxLQUFFLEdBQUUsR0FBRSxHQUFHO0FBQUEsZ0JBQUUsS0FBSTtBQUFNLHlCQUFPYSxHQUFFbEIsR0FBRSxhQUFZSyxJQUFFWSxJQUFFLENBQUM7QUFBQSxnQkFBRSxLQUFJO0FBQU8seUJBQU9DLEdBQUVELElBQUVaLEVBQUM7QUFBQSxnQkFBRSxLQUFJO0FBQUkseUJBQU9OLEdBQUU7QUFBQSxnQkFBRyxLQUFJO0FBQUsseUJBQU8sRUFBRSxFQUFFQSxHQUFFLElBQUcsR0FBRSxHQUFHO0FBQUEsZ0JBQUUsS0FBSTtBQUFJLHlCQUFPLE9BQU9BLEdBQUUsRUFBRTtBQUFBLGdCQUFFLEtBQUk7QUFBSyx5QkFBT21CLEdBQUVsQixHQUFFLGFBQVlELEdBQUUsSUFBR2dCLElBQUUsQ0FBQztBQUFBLGdCQUFFLEtBQUk7QUFBTSx5QkFBT0csR0FBRWxCLEdBQUUsZUFBY0QsR0FBRSxJQUFHZ0IsSUFBRSxDQUFDO0FBQUEsZ0JBQUUsS0FBSTtBQUFPLHlCQUFPQSxHQUFFaEIsR0FBRSxFQUFFO0FBQUEsZ0JBQUUsS0FBSTtBQUFJLHlCQUFPLE9BQU9JLEVBQUM7QUFBQSxnQkFBRSxLQUFJO0FBQUsseUJBQU8sRUFBRSxFQUFFQSxJQUFFLEdBQUUsR0FBRztBQUFBLGdCQUFFLEtBQUk7QUFBSSx5QkFBT2EsR0FBRSxDQUFDO0FBQUEsZ0JBQUUsS0FBSTtBQUFLLHlCQUFPQSxHQUFFLENBQUM7QUFBQSxnQkFBRSxLQUFJO0FBQUkseUJBQU9OLEdBQUVQLElBQUVDLElBQUUsSUFBRTtBQUFBLGdCQUFFLEtBQUk7QUFBSSx5QkFBT00sR0FBRVAsSUFBRUMsSUFBRSxLQUFFO0FBQUEsZ0JBQUUsS0FBSTtBQUFJLHlCQUFPLE9BQU9BLEVBQUM7QUFBQSxnQkFBRSxLQUFJO0FBQUsseUJBQU8sRUFBRSxFQUFFQSxJQUFFLEdBQUUsR0FBRztBQUFBLGdCQUFFLEtBQUk7QUFBSSx5QkFBTyxPQUFPTCxHQUFFLEVBQUU7QUFBQSxnQkFBRSxLQUFJO0FBQUsseUJBQU8sRUFBRSxFQUFFQSxHQUFFLElBQUcsR0FBRSxHQUFHO0FBQUEsZ0JBQUUsS0FBSTtBQUFNLHlCQUFPLEVBQUUsRUFBRUEsR0FBRSxLQUFJLEdBQUUsR0FBRztBQUFBLGdCQUFFLEtBQUk7QUFBSSx5QkFBT0c7QUFBQSxjQUFDO0FBQUMscUJBQU87QUFBQSxZQUFJLEVBQUVKLEVBQUMsS0FBR0ksR0FBRSxRQUFRLEtBQUksRUFBRTtBQUFBLFVBQUMsQ0FBRTtBQUFBLFFBQUMsR0FBRUssR0FBRSxZQUFVLFdBQVU7QUFBQyxpQkFBTyxLQUFHLENBQUMsS0FBSyxNQUFNLEtBQUssR0FBRyxrQkFBa0IsSUFBRSxFQUFFO0FBQUEsUUFBQyxHQUFFQSxHQUFFLE9BQUssU0FBU04sSUFBRWUsSUFBRVAsSUFBRTtBQUFDLGNBQUlDLElBQUVDLEtBQUUsTUFBS0wsS0FBRSxFQUFFLEVBQUVVLEVBQUMsR0FBRVQsS0FBRSxFQUFFTixFQUFDLEdBQUVXLE1BQUdMLEdBQUUsVUFBVSxJQUFFLEtBQUssVUFBVSxLQUFHLEdBQUVNLEtBQUUsT0FBS04sSUFBRU8sS0FBRSxrQ0FBVTtBQUFDLG1CQUFPLEVBQUUsRUFBRUgsSUFBRUosRUFBQztBQUFBLFVBQUMsR0FBMUI7QUFBNEIsa0JBQU9ELElBQUU7QUFBQSxZQUFDLEtBQUs7QUFBRSxjQUFBSSxLQUFFSSxHQUFFLElBQUU7QUFBRztBQUFBLFlBQU0sS0FBSztBQUFFLGNBQUFKLEtBQUVJLEdBQUU7QUFBRTtBQUFBLFlBQU0sS0FBSztBQUFFLGNBQUFKLEtBQUVJLEdBQUUsSUFBRTtBQUFFO0FBQUEsWUFBTSxLQUFLO0FBQUUsY0FBQUosTUFBR0csS0FBRUQsTUFBRztBQUFPO0FBQUEsWUFBTSxLQUFLO0FBQUUsY0FBQUYsTUFBR0csS0FBRUQsTUFBRztBQUFNO0FBQUEsWUFBTSxLQUFLO0FBQUUsY0FBQUYsS0FBRUcsS0FBRTtBQUFFO0FBQUEsWUFBTSxLQUFLO0FBQUUsY0FBQUgsS0FBRUcsS0FBRTtBQUFFO0FBQUEsWUFBTSxLQUFLO0FBQUUsY0FBQUgsS0FBRUcsS0FBRTtBQUFFO0FBQUEsWUFBTTtBQUFRLGNBQUFILEtBQUVHO0FBQUEsVUFBQztBQUFDLGlCQUFPSixLQUFFQyxLQUFFLEVBQUUsRUFBRUEsRUFBQztBQUFBLFFBQUMsR0FBRUgsR0FBRSxjQUFZLFdBQVU7QUFBQyxpQkFBTyxLQUFLLE1BQU0sQ0FBQyxFQUFFO0FBQUEsUUFBRSxHQUFFQSxHQUFFLFVBQVEsV0FBVTtBQUFDLGlCQUFPLEVBQUUsS0FBSyxFQUFFO0FBQUEsUUFBQyxHQUFFQSxHQUFFLFNBQU8sU0FBU1QsSUFBRUMsSUFBRTtBQUFDLGNBQUcsQ0FBQ0Q7QUFBRSxtQkFBTyxLQUFLO0FBQUcsY0FBSUUsS0FBRSxLQUFLLE1BQU0sR0FBRUMsS0FBRSxFQUFFSCxJQUFFQyxJQUFFLElBQUU7QUFBRSxpQkFBT0UsT0FBSUQsR0FBRSxLQUFHQyxLQUFHRDtBQUFBLFFBQUMsR0FBRU8sR0FBRSxRQUFNLFdBQVU7QUFBQyxpQkFBTyxFQUFFLEVBQUUsS0FBSyxJQUFHLElBQUk7QUFBQSxRQUFDLEdBQUVBLEdBQUUsU0FBTyxXQUFVO0FBQUMsaUJBQU8sSUFBSSxLQUFLLEtBQUssUUFBUSxDQUFDO0FBQUEsUUFBQyxHQUFFQSxHQUFFLFNBQU8sV0FBVTtBQUFDLGlCQUFPLEtBQUssUUFBUSxJQUFFLEtBQUssWUFBWSxJQUFFO0FBQUEsUUFBSSxHQUFFQSxHQUFFLGNBQVksV0FBVTtBQUFDLGlCQUFPLEtBQUssR0FBRyxZQUFZO0FBQUEsUUFBQyxHQUFFQSxHQUFFLFdBQVMsV0FBVTtBQUFDLGlCQUFPLEtBQUssR0FBRyxZQUFZO0FBQUEsUUFBQyxHQUFFRDtBQUFBLE1BQUMsRUFBRSxHQUFFLElBQUUsRUFBRTtBQUFVLGFBQU8sRUFBRSxZQUFVLEdBQUUsQ0FBQyxDQUFDLE9BQU0sQ0FBQyxHQUFFLENBQUMsTUFBSyxDQUFDLEdBQUUsQ0FBQyxNQUFLLENBQUMsR0FBRSxDQUFDLE1BQUssQ0FBQyxHQUFFLENBQUMsTUFBSyxDQUFDLEdBQUUsQ0FBQyxNQUFLLENBQUMsR0FBRSxDQUFDLE1BQUssQ0FBQyxHQUFFLENBQUMsTUFBSyxDQUFDLENBQUMsRUFBRSxRQUFTLFNBQVNSLElBQUU7QUFBQyxVQUFFQSxHQUFFLENBQUMsQ0FBQyxJQUFFLFNBQVNDLElBQUU7QUFBQyxpQkFBTyxLQUFLLEdBQUdBLElBQUVELEdBQUUsQ0FBQyxHQUFFQSxHQUFFLENBQUMsQ0FBQztBQUFBLFFBQUM7QUFBQSxNQUFDLENBQUUsR0FBRSxFQUFFLFNBQU8sU0FBU0EsSUFBRUMsSUFBRTtBQUFDLGVBQU9ELEdBQUUsT0FBS0EsR0FBRUMsSUFBRSxHQUFFLENBQUMsR0FBRUQsR0FBRSxLQUFHLE9BQUk7QUFBQSxNQUFDLEdBQUUsRUFBRSxTQUFPLEdBQUUsRUFBRSxVQUFRLEdBQUUsRUFBRSxPQUFLLFNBQVNBLElBQUU7QUFBQyxlQUFPLEVBQUUsTUFBSUEsRUFBQztBQUFBLE1BQUMsR0FBRSxFQUFFLEtBQUcsRUFBRSxDQUFDLEdBQUUsRUFBRSxLQUFHLEdBQUUsRUFBRSxJQUFFLENBQUMsR0FBRTtBQUFBLElBQUMsQ0FBRTtBQUFBO0FBQUE7OztBQ0F0L047QUFBQTtBQUFBLEtBQUMsU0FBUyxHQUFFLEdBQUU7QUFBQyxrQkFBVSxPQUFPLFdBQVMsZUFBYSxPQUFPLFNBQU8sT0FBTyxVQUFRLEVBQUUsSUFBRSxjQUFZLE9BQU8sVUFBUSxPQUFPLE1BQUksT0FBTyxDQUFDLEtBQUcsSUFBRSxlQUFhLE9BQU8sYUFBVyxhQUFXLEtBQUcsTUFBTSxtQkFBaUIsRUFBRTtBQUFBLElBQUMsRUFBRSxTQUFNLFdBQVU7QUFBQztBQUFhLFVBQUksSUFBRSxVQUFTLElBQUUsd0JBQXVCLElBQUU7QUFBZSxhQUFPLFNBQVMsR0FBRSxHQUFFLEdBQUU7QUFBQyxZQUFJLElBQUUsRUFBRTtBQUFVLFVBQUUsTUFBSSxTQUFTcUIsSUFBRTtBQUFDLGNBQUlDLEtBQUUsRUFBQyxNQUFLRCxJQUFFLEtBQUksTUFBRyxNQUFLLFVBQVM7QUFBRSxpQkFBTyxJQUFJLEVBQUVDLEVBQUM7QUFBQSxRQUFDLEdBQUUsRUFBRSxNQUFJLFNBQVNBLElBQUU7QUFBQyxjQUFJQyxLQUFFLEVBQUUsS0FBSyxPQUFPLEdBQUUsRUFBQyxRQUFPLEtBQUssSUFBRyxLQUFJLEtBQUUsQ0FBQztBQUFFLGlCQUFPRCxLQUFFQyxHQUFFLElBQUksS0FBSyxVQUFVLEdBQUUsQ0FBQyxJQUFFQTtBQUFBLFFBQUMsR0FBRSxFQUFFLFFBQU0sV0FBVTtBQUFDLGlCQUFPLEVBQUUsS0FBSyxPQUFPLEdBQUUsRUFBQyxRQUFPLEtBQUssSUFBRyxLQUFJLE1BQUUsQ0FBQztBQUFBLFFBQUM7QUFBRSxZQUFJLElBQUUsRUFBRTtBQUFNLFVBQUUsUUFBTSxTQUFTRixJQUFFO0FBQUMsVUFBQUEsR0FBRSxRQUFNLEtBQUssS0FBRyxPQUFJLEtBQUssT0FBTyxFQUFFLEVBQUVBLEdBQUUsT0FBTyxNQUFJLEtBQUssVUFBUUEsR0FBRSxVQUFTLEVBQUUsS0FBSyxNQUFLQSxFQUFDO0FBQUEsUUFBQztBQUFFLFlBQUksSUFBRSxFQUFFO0FBQUssVUFBRSxPQUFLLFdBQVU7QUFBQyxjQUFHLEtBQUssSUFBRztBQUFDLGdCQUFJQSxLQUFFLEtBQUs7QUFBRyxpQkFBSyxLQUFHQSxHQUFFLGVBQWUsR0FBRSxLQUFLLEtBQUdBLEdBQUUsWUFBWSxHQUFFLEtBQUssS0FBR0EsR0FBRSxXQUFXLEdBQUUsS0FBSyxLQUFHQSxHQUFFLFVBQVUsR0FBRSxLQUFLLEtBQUdBLEdBQUUsWUFBWSxHQUFFLEtBQUssS0FBR0EsR0FBRSxjQUFjLEdBQUUsS0FBSyxLQUFHQSxHQUFFLGNBQWMsR0FBRSxLQUFLLE1BQUlBLEdBQUUsbUJBQW1CO0FBQUEsVUFBQztBQUFNLGNBQUUsS0FBSyxJQUFJO0FB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QVcsR0FBRSxDQUFDO0FBQUEsUUFBQyxHQUE1QyxNQUE4QyxJQUFFLEVBQUU7QUFBVSxVQUFFLGNBQVksV0FBVTtBQUFDLGlCQUFPLEVBQUUsSUFBSSxFQUFFLEtBQUs7QUFBQSxRQUFDLEdBQUUsRUFBRSxVQUFRLFNBQVNBLElBQUU7QUFBQyxjQUFHLENBQUMsS0FBSyxPQUFPLEVBQUUsRUFBRUEsRUFBQztBQUFFLG1CQUFPLEtBQUssSUFBSSxLQUFHQSxLQUFFLEtBQUssUUFBUSxJQUFHLENBQUM7QUFBRSxjQUFJQyxJQUFFQyxJQUFFQyxJQUFFLEdBQUUsSUFBRSxFQUFFLElBQUksR0FBRSxLQUFHRixLQUFFLEtBQUssWUFBWSxHQUFFQyxLQUFFLEtBQUssSUFBR0MsTUFBR0QsS0FBRSxFQUFFLE1BQUksR0FBRyxFQUFFLEtBQUtELEVBQUMsRUFBRSxRQUFRLE1BQU0sR0FBRSxJQUFFLElBQUVFLEdBQUUsV0FBVyxHQUFFQSxHQUFFLFdBQVcsSUFBRSxNQUFJLEtBQUcsSUFBR0EsR0FBRSxJQUFJLEdBQUUsQ0FBQztBQUFHLGlCQUFPLEVBQUUsS0FBSyxHQUFFLE1BQU0sSUFBRTtBQUFBLFFBQUMsR0FBRSxFQUFFLGFBQVcsU0FBU0MsSUFBRTtBQUFDLGlCQUFPLEtBQUssT0FBTyxFQUFFLEVBQUVBLEVBQUMsSUFBRSxLQUFLLElBQUksS0FBRyxJQUFFLEtBQUssSUFBSSxLQUFLLElBQUksSUFBRSxJQUFFQSxLQUFFQSxLQUFFLENBQUM7QUFBQSxRQUFDO0FBQUUsWUFBSSxJQUFFLEVBQUU7QUFBUSxVQUFFLFVBQVEsU0FBU0EsSUFBRUosSUFBRTtBQUFDLGNBQUlDLEtBQUUsS0FBSyxPQUFPLEdBQUVJLEtBQUUsQ0FBQyxDQUFDSixHQUFFLEVBQUVELEVBQUMsS0FBR0E7QUFBRSxpQkFBTSxjQUFZQyxHQUFFLEVBQUVHLEVBQUMsSUFBRUMsS0FBRSxLQUFLLEtBQUssS0FBSyxLQUFLLEtBQUcsS0FBSyxXQUFXLElBQUUsRUFBRSxFQUFFLFFBQVEsS0FBSyxJQUFFLEtBQUssS0FBSyxLQUFLLEtB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R0FBRyxVQUFVLElBQVU7QUFDaEUsY0FBVSxZQUFZO0FBQ3RCLGFBQVMsT0FBTyxXQUFXLFFBQVEsU0FBUyxRQUFRO0FBQ2xELFlBQU0sU0FBUyxTQUFTLGNBQWMsaUJBQWlCO0FBQ3ZELGFBQU8sY0FBYyxHQUFHLEtBQUssU0FBUyxFQUFFLFNBQVMsR0FBRyxHQUFHLENBQUM7QUFDeEQsZ0JBQVUsWUFBWSxNQUFNO0FBQUEsSUFDOUI7QUFBQSxFQUNGO0FBQ0Y7QUFUOEI7QUFBdkIsSUFBTSxtQkFBTjsiLAogICJuYW1lcyI6IFsidCIsICJlIiwgIm4iLCAiciIsICJpIiwgInMiLCAidSIsICJhIiwgIk0iLCAibSIsICJmIiwgImwiLCAiJCIsICJ5IiwgInYiLCAiZyIsICJEIiwgIm8iLCAiZCIsICJjIiwgImgiLCAidCIsICJpIiwgImUiLCAicyIsICJmIiwgIm4iLCAidSIsICJyIiwgIm8iLCAidCIsICJuIiwgImkiLCAibyIsICJyIiwgImUiLCAidSIsICJmIiwgInMiLCAiYSIsICJ0IiwgImkiLCAiZCIsICJuIiwgImUiLCAicyIsICJkYXlqcyIsICJ1dGMiLCAidGltZXpvbmUiLCAiaXNvV2VlayJdCn0K diff --git a/wwwroot/js/calendar.js b/wwwroot/js/calendar.js new file mode 100644 index 0000000..4ebf767 --- /dev/null +++ b/wwwroot/js/calendar.js @@ -0,0 +1,1664 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); + +// node_modules/dayjs/dayjs.min.js +var require_dayjs_min = __commonJS({ + "node_modules/dayjs/dayjs.min.js"(exports, module) { + !function(t, e) { + "object" == typeof exports && "undefined" != typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define(e) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs = e(); + }(exports, function() { + "use strict"; + var t = 1e3, e = 6e4, n = 36e5, r = "millisecond", i = "second", s = "minute", u = "hour", a = "day", o = "week", c = "month", f = "quarter", h = "year", d = "date", l = "Invalid Date", $ = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/, y = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g, M = { name: "en", weekdays: "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), months: "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), ordinal: function(t2) { + var e2 = ["th", "st", "nd", "rd"], n2 = t2 % 100; + return "[" + t2 + (e2[(n2 - 20) % 10] || e2[n2] || e2[0]) + "]"; + } }, m = /* @__PURE__ */ __name(function(t2, e2, n2) { + var r2 = String(t2); + return !r2 || r2.length >= e2 ? t2 : "" + Array(e2 + 1 - r2.length).join(n2) + t2; + }, "m"), v = { s: m, z: function(t2) { + var e2 = -t2.utcOffset(), n2 = Math.abs(e2), r2 = Math.floor(n2 / 60), i2 = n2 % 60; + return (e2 <= 0 ? "+" : "-") + m(r2, 2, "0") + ":" + m(i2, 2, "0"); + }, m: /* @__PURE__ */ __name(function t2(e2, n2) { + if (e2.date() < n2.date()) + return -t2(n2, e2); + var r2 = 12 * (n2.year() - e2.year()) + (n2.month() - e2.month()), i2 = e2.clone().add(r2, c), s2 = n2 - i2 < 0, u2 = e2.clone().add(r2 + (s2 ? -1 : 1), c); + return +(-(r2 + (n2 - i2) / (s2 ? i2 - u2 : u2 - i2)) || 0); + }, "t"), a: function(t2) { + return t2 < 0 ? Math.ceil(t2) || 0 : Math.floor(t2); + }, p: function(t2) { + return { M: c, y: h, w: o, d: a, D: d, h: u, m: s, s: i, ms: r, Q: f }[t2] || String(t2 || "").toLowerCase().replace(/s$/, ""); + }, u: function(t2) { + return void 0 === t2; + } }, g = "en", D = {}; + D[g] = M; + var p = "$isDayjsObject", S = /* @__PURE__ */ __name(function(t2) { + return t2 instanceof _ || !(!t2 || !t2[p]); + }, "S"), w = /* @__PURE__ */ __name(function t2(e2, n2, r2) { + var i2; + if (!e2) + return g; + if ("string" == typeof e2) { + var s2 = e2.toLowerCase(); + D[s2] && (i2 = s2), n2 && (D[s2] = n2, i2 = s2); + var u2 = e2.split("-"); + if (!i2 && u2.length > 1) + return t2(u2[0]); + } else { + var a2 = e2.name; + D[a2] = e2, i2 = a2; + } + return !r2 && i2 && (g = i2), i2 || !r2 && g; + }, "t"), O = /* @__PURE__ */ __name(function(t2, e2) { + if (S(t2)) + return t2.clone(); + var n2 = "object" == typeof e2 ? e2 : {}; + return n2.date = t2, n2.args = arguments, new _(n2); + }, "O"), b = v; + b.l = w, b.i = S, b.w = function(t2, e2) { + return O(t2, { locale: e2.$L, utc: e2.$u, x: e2.$x, $offset: e2.$offset }); + }; + var _ = function() { + function M2(t2) { + this.$L = w(t2.locale, null, true), this.parse(t2), this.$x = this.$x || t2.x || {}, this[p] = true; + } + __name(M2, "M"); + var m2 = M2.prototype; + return m2.parse = function(t2) { + this.$d = function(t3) { + var e2 = t3.date, n2 = t3.utc; + if (null === e2) + return /* @__PURE__ */ new Date(NaN); + if (b.u(e2)) + return /* @__PURE__ */ new Date(); + if (e2 instanceof Date) + return new Date(e2); + if ("string" == typeof e2 && !/Z$/i.test(e2)) { + var r2 = e2.match($); + if (r2) { + var i2 = r2[2] - 1 || 0, s2 = (r2[7] || "0").substring(0, 3); + return n2 ? new Date(Date.UTC(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2)) : new Date(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2); + } + } + return new Date(e2); + }(t2), this.init(); + }, m2.init = function() { + var t2 = this.$d; + this.$y = t2.getFullYear(), this.$M = t2.getMonth(), this.$D = t2.getDate(), this.$W = t2.getDay(), this.$H = t2.getHours(), this.$m = t2.getMinutes(), this.$s = t2.getSeconds(), this.$ms = t2.getMilliseconds(); + }, m2.$utils = function() { + return b; + }, m2.isValid = function() { + return !(this.$d.toString() === l); + }, m2.isSame = function(t2, e2) { + var n2 = O(t2); + return this.startOf(e2) <= n2 && n2 <= this.endOf(e2); + }, m2.isAfter = function(t2, e2) { + return O(t2) < this.startOf(e2); + }, m2.isBefore = function(t2, e2) { + return this.endOf(e2) < O(t2); + }, m2.$g = function(t2, e2, n2) { + return b.u(t2) ? this[e2] : this.set(n2, t2); + }, m2.unix = function() { + return Math.floor(this.valueOf() / 1e3); + }, m2.valueOf = function() { + return this.$d.getTime(); + }, m2.startOf = function(t2, e2) { + var n2 = this, r2 = !!b.u(e2) || e2, f2 = b.p(t2), l2 = /* @__PURE__ */ __name(function(t3, e3) { + var i2 = b.w(n2.$u ? Date.UTC(n2.$y, e3, t3) : new Date(n2.$y, e3, t3), n2); + return r2 ? i2 : i2.endOf(a); + }, "l"), $2 = /* @__PURE__ */ __name(function(t3, e3) { + return b.w(n2.toDate()[t3].apply(n2.toDate("s"), (r2 ? [0, 0, 0, 0] : [23, 59, 59, 999]).slice(e3)), n2); + }, "$"), y2 = this.$W, M3 = this.$M, m3 = this.$D, v2 = "set" + (this.$u ? "UTC" : ""); + switch (f2) { + case h: + return r2 ? l2(1, 0) : l2(31, 11); + case c: + return r2 ? l2(1, M3) : l2(0, M3 + 1); + case o: + var g2 = this.$locale().weekStart || 0, D2 = (y2 < g2 ? y2 + 7 : y2) - g2; + return l2(r2 ? m3 - D2 : m3 + (6 - D2), M3); + case a: + case d: + return $2(v2 + "Hours", 0); + case u: + return $2(v2 + "Minutes", 1); + case s: + return $2(v2 + "Seconds", 2); + case i: + return $2(v2 + "Milliseconds", 3); + default: + return this.clone(); + } + }, m2.endOf = function(t2) { + return this.startOf(t2, false); + }, m2.$set = function(t2, e2) { + var n2, o2 = b.p(t2), f2 = "set" + (this.$u ? "UTC" : ""), l2 = (n2 = {}, n2[a] = f2 + "Date", n2[d] = f2 + "Date", n2[c] = f2 + "Month", n2[h] = f2 + "FullYear", n2[u] = f2 + "Hours", n2[s] = f2 + "Minutes", n2[i] = f2 + "Seconds", n2[r] = f2 + "Milliseconds", n2)[o2], $2 = o2 === a ? this.$D + (e2 - this.$W) : e2; + if (o2 === c || o2 === h) { + var y2 = this.clone().set(d, 1); + y2.$d[l2]($2), y2.init(), this.$d = y2.set(d, Math.min(this.$D, y2.daysInMonth())).$d; + } else + l2 && this.$d[l2]($2); + return this.init(), this; + }, m2.set = function(t2, e2) { + return this.clone().$set(t2, e2); + }, m2.get = function(t2) { + return this[b.p(t2)](); + }, m2.add = function(r2, f2) { + var d2, l2 = this; + r2 = Number(r2); + var $2 = b.p(f2), y2 = /* @__PURE__ */ __name(function(t2) { + var e2 = O(l2); + return b.w(e2.date(e2.date() + Math.round(t2 * r2)), l2); + }, "y"); + if ($2 === c) + return this.set(c, this.$M + r2); + if ($2 === h) + return this.set(h, this.$y + r2); + if ($2 === a) + return y2(1); + if ($2 === o) + return y2(7); + var M3 = (d2 = {}, d2[s] = e, d2[u] = n, d2[i] = t, d2)[$2] || 1, m3 = this.$d.getTime() + r2 * M3; + return b.w(m3, this); + }, m2.subtract = function(t2, e2) { + return this.add(-1 * t2, e2); + }, m2.format = function(t2) { + var e2 = this, n2 = this.$locale(); + if (!this.isValid()) + return n2.invalidDate || l; + var r2 = t2 || "YYYY-MM-DDTHH:mm:ssZ", i2 = b.z(this), s2 = this.$H, u2 = this.$m, a2 = this.$M, o2 = n2.weekdays, c2 = n2.months, f2 = n2.meridiem, h2 = /* @__PURE__ */ __name(function(t3, n3, i3, s3) { + return t3 && (t3[n3] || t3(e2, r2)) || i3[n3].slice(0, s3); + }, "h"), d2 = /* @__PURE__ */ __name(function(t3) { + return b.s(s2 % 12 || 12, t3, "0"); + }, "d"), $2 = f2 || function(t3, e3, n3) { + var r3 = t3 < 12 ? "AM" : "PM"; + return n3 ? r3.toLowerCase() : r3; + }; + return r2.replace(y, function(t3, r3) { + return r3 || function(t4) { + switch (t4) { + case "YY": + return String(e2.$y).slice(-2); + case "YYYY": + return b.s(e2.$y, 4, "0"); + case "M": + return a2 + 1; + case "MM": + return b.s(a2 + 1, 2, "0"); + case "MMM": + return h2(n2.monthsShort, a2, c2, 3); + case "MMMM": + return h2(c2, a2); + case "D": + return e2.$D; + case "DD": + return b.s(e2.$D, 2, "0"); + case "d": + return String(e2.$W); + case "dd": + return h2(n2.weekdaysMin, e2.$W, o2, 2); + case "ddd": + return h2(n2.weekdaysShort, e2.$W, o2, 3); + case "dddd": + return o2[e2.$W]; + case "H": + return String(s2); + case "HH": + return b.s(s2, 2, "0"); + case "h": + return d2(1); + case "hh": + return d2(2); + case "a": + return $2(s2, u2, true); + case "A": + return $2(s2, u2, false); + case "m": + return String(u2); + case "mm": + return b.s(u2, 2, "0"); + case "s": + return String(e2.$s); + case "ss": + return b.s(e2.$s, 2, "0"); + case "SSS": + return b.s(e2.$ms, 3, "0"); + case "Z": + return i2; + } + return null; + }(t3) || i2.replace(":", ""); + }); + }, m2.utcOffset = function() { + return 15 * -Math.round(this.$d.getTimezoneOffset() / 15); + }, m2.diff = function(r2, d2, l2) { + var $2, y2 = this, M3 = b.p(d2), m3 = O(r2), v2 = (m3.utcOffset() - this.utcOffset()) * e, g2 = this - m3, D2 = /* @__PURE__ */ __name(function() { + return b.m(y2, m3); + }, "D"); + switch (M3) { + case h: + $2 = D2() / 12; + break; + case c: + $2 = D2(); + break; + case f: + $2 = D2() / 3; + break; + case o: + $2 = (g2 - v2) / 6048e5; + break; + case a: + $2 = (g2 - v2) / 864e5; + break; + case u: + $2 = g2 / n; + break; + case s: + $2 = g2 / e; + break; + case i: + $2 = g2 / t; + break; + default: + $2 = g2; + } + return l2 ? $2 : b.a($2); + }, m2.daysInMonth = function() { + return this.endOf(c).$D; + }, m2.$locale = function() { + return D[this.$L]; + }, m2.locale = function(t2, e2) { + if (!t2) + return this.$L; + var n2 = this.clone(), r2 = w(t2, e2, true); + return r2 && (n2.$L = r2), n2; + }, m2.clone = function() { + return b.w(this.$d, this); + }, m2.toDate = function() { + return new Date(this.valueOf()); + }, m2.toJSON = function() { + return this.isValid() ? this.toISOString() : null; + }, m2.toISOString = function() { + return this.$d.toISOString(); + }, m2.toString = function() { + return this.$d.toUTCString(); + }, M2; + }(), k = _.prototype; + return O.prototype = k, [["$ms", r], ["$s", i], ["$m", s], ["$H", u], ["$W", a], ["$M", c], ["$y", h], ["$D", d]].forEach(function(t2) { + k[t2[1]] = function(e2) { + return this.$g(e2, t2[0], t2[1]); + }; + }), O.extend = function(t2, e2) { + return t2.$i || (t2(e2, _, O), t2.$i = true), O; + }, O.locale = w, O.isDayjs = S, O.unix = function(t2) { + return O(1e3 * t2); + }, O.en = D[g], O.Ls = D, O.p = {}, O; + }); + } +}); + +// node_modules/dayjs/plugin/utc.js +var require_utc = __commonJS({ + "node_modules/dayjs/plugin/utc.js"(exports, module) { + !function(t, i) { + "object" == typeof exports && "undefined" != typeof module ? module.exports = i() : "function" == typeof define && define.amd ? define(i) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs_plugin_utc = i(); + }(exports, function() { + "use strict"; + var t = "minute", i = /[+-]\d\d(?::?\d\d)?/g, e = /([+-]|\d\d)/g; + return function(s, f, n) { + var u = f.prototype; + n.utc = function(t2) { + var i2 = { date: t2, utc: true, args: arguments }; + return new f(i2); + }, u.utc = function(i2) { + var e2 = n(this.toDate(), { locale: this.$L, utc: true }); + return i2 ? e2.add(this.utcOffset(), t) : e2; + }, u.local = function() { + return n(this.toDate(), { locale: this.$L, utc: false }); + }; + var r = u.parse; + u.parse = function(t2) { + t2.utc && (this.$u = true), this.$utils().u(t2.$offset) || (this.$offset = t2.$offset), r.call(this, t2); + }; + var o = u.init; + u.init = function() { + if (this.$u) { + var t2 = this.$d; + this.$y = t2.getUTCFullYear(), this.$M = t2.getUTCMonth(), this.$D = t2.getUTCDate(), this.$W = t2.getUTCDay(), this.$H = t2.getUTCHours(), this.$m = t2.getUTCMinutes(), this.$s = t2.getUTCSeconds(), this.$ms = t2.getUTCMilliseconds(); + } else + o.call(this); + }; + var a = u.utcOffset; + u.utcOffset = function(s2, f2) { + var n2 = this.$utils().u; + if (n2(s2)) + return this.$u ? 0 : n2(this.$offset) ? a.call(this) : this.$offset; + if ("string" == typeof s2 && (s2 = function(t2) { + void 0 === t2 && (t2 = ""); + var s3 = t2.match(i); + if (!s3) + return null; + var f3 = ("" + s3[0]).match(e) || ["-", 0, 0], n3 = f3[0], u3 = 60 * +f3[1] + +f3[2]; + return 0 === u3 ? 0 : "+" === n3 ? u3 : -u3; + }(s2), null === s2)) + return this; + var u2 = Math.abs(s2) <= 16 ? 60 * s2 : s2; + if (0 === u2) + return this.utc(f2); + var r2 = this.clone(); + if (f2) + return r2.$offset = u2, r2.$u = false, r2; + var o2 = this.$u ? this.toDate().getTimezoneOffset() : -1 * this.utcOffset(); + return (r2 = this.local().add(u2 + o2, t)).$offset = u2, r2.$x.$localOffset = o2, r2; + }; + var h = u.format; + u.format = function(t2) { + var i2 = t2 || (this.$u ? "YYYY-MM-DDTHH:mm:ss[Z]" : ""); + return h.call(this, i2); + }, u.valueOf = function() { + var t2 = this.$utils().u(this.$offset) ? 0 : this.$offset + (this.$x.$localOffset || this.$d.getTimezoneOffset()); + return this.$d.valueOf() - 6e4 * t2; + }, u.isUTC = function() { + return !!this.$u; + }, u.toISOString = function() { + return this.toDate().toISOString(); + }, u.toString = function() { + return this.toDate().toUTCString(); + }; + var l = u.toDate; + u.toDate = function(t2) { + return "s" === t2 && this.$offset ? n(this.format("YYYY-MM-DD HH:mm:ss:SSS")).toDate() : l.call(this); + }; + var c = u.diff; + u.diff = function(t2, i2, e2) { + if (t2 && this.$u === t2.$u) + return c.call(this, t2, i2, e2); + var s2 = this.local(), f2 = n(t2).local(); + return c.call(s2, f2, i2, e2); + }; + }; + }); + } +}); + +// node_modules/dayjs/plugin/timezone.js +var require_timezone = __commonJS({ + "node_modules/dayjs/plugin/timezone.js"(exports, module) { + !function(t, e) { + "object" == typeof exports && "undefined" != typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define(e) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs_plugin_timezone = e(); + }(exports, function() { + "use strict"; + var t = { year: 0, month: 1, day: 2, hour: 3, minute: 4, second: 5 }, e = {}; + return function(n, i, o) { + var r, a = /* @__PURE__ */ __name(function(t2, n2, i2) { + void 0 === i2 && (i2 = {}); + var o2 = new Date(t2), r2 = function(t3, n3) { + void 0 === n3 && (n3 = {}); + var i3 = n3.timeZoneName || "short", o3 = t3 + "|" + i3, r3 = e[o3]; + return r3 || (r3 = new Intl.DateTimeFormat("en-US", { hour12: false, timeZone: t3, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", timeZoneName: i3 }), e[o3] = r3), r3; + }(n2, i2); + return r2.formatToParts(o2); + }, "a"), u = /* @__PURE__ */ __name(function(e2, n2) { + for (var i2 = a(e2, n2), r2 = [], u2 = 0; u2 < i2.length; u2 += 1) { + var f2 = i2[u2], s2 = f2.type, m = f2.value, c = t[s2]; + c >= 0 && (r2[c] = parseInt(m, 10)); + } + var d = r2[3], l = 24 === d ? 0 : d, h = r2[0] + "-" + r2[1] + "-" + r2[2] + " " + l + ":" + r2[4] + ":" + r2[5] + ":000", v = +e2; + return (o.utc(h).valueOf() - (v -= v % 1e3)) / 6e4; + }, "u"), f = i.prototype; + f.tz = function(t2, e2) { + void 0 === t2 && (t2 = r); + var n2, i2 = this.utcOffset(), a2 = this.toDate(), u2 = a2.toLocaleString("en-US", { timeZone: t2 }), f2 = Math.round((a2 - new Date(u2)) / 1e3 / 60), s2 = 15 * -Math.round(a2.getTimezoneOffset() / 15) - f2; + if (!Number(s2)) + n2 = this.utcOffset(0, e2); + else if (n2 = o(u2, { locale: this.$L }).$set("millisecond", this.$ms).utcOffset(s2, true), e2) { + var m = n2.utcOffset(); + n2 = n2.add(i2 - m, "minute"); + } + return n2.$x.$timezone = t2, n2; + }, f.offsetName = function(t2) { + var e2 = this.$x.$timezone || o.tz.guess(), n2 = a(this.valueOf(), e2, { timeZoneName: t2 }).find(function(t3) { + return "timezonename" === t3.type.toLowerCase(); + }); + return n2 && n2.value; + }; + var s = f.startOf; + f.startOf = function(t2, e2) { + if (!this.$x || !this.$x.$timezone) + return s.call(this, t2, e2); + var n2 = o(this.format("YYYY-MM-DD HH:mm:ss:SSS"), { locale: this.$L }); + return s.call(n2, t2, e2).tz(this.$x.$timezone, true); + }, o.tz = function(t2, e2, n2) { + var i2 = n2 && e2, a2 = n2 || e2 || r, f2 = u(+o(), a2); + if ("string" != typeof t2) + return o(t2).tz(a2); + var s2 = function(t3, e3, n3) { + var i3 = t3 - 60 * e3 * 1e3, o2 = u(i3, n3); + if (e3 === o2) + return [i3, e3]; + var r2 = u(i3 -= 60 * (o2 - e3) * 1e3, n3); + return o2 === r2 ? [i3, o2] : [t3 - 60 * Math.min(o2, r2) * 1e3, Math.max(o2, r2)]; + }(o.utc(t2, i2).valueOf(), f2, a2), m = s2[0], c = s2[1], d = o(m).utcOffset(c); + return d.$x.$timezone = a2, d; + }, o.tz.guess = function() { + return Intl.DateTimeFormat().resolvedOptions().timeZone; + }, o.tz.setDefault = function(t2) { + r = t2; + }; + }; + }); + } +}); + +// node_modules/dayjs/plugin/isoWeek.js +var require_isoWeek = __commonJS({ + "node_modules/dayjs/plugin/isoWeek.js"(exports, module) { + !function(e, t) { + "object" == typeof exports && "undefined" != typeof module ? module.exports = t() : "function" == typeof define && define.amd ? define(t) : (e = "undefined" != typeof globalThis ? globalThis : e || self).dayjs_plugin_isoWeek = t(); + }(exports, function() { + "use strict"; + var e = "day"; + return function(t, i, s) { + var a = /* @__PURE__ */ __name(function(t2) { + return t2.add(4 - t2.isoWeekday(), e); + }, "a"), d = i.prototype; + d.isoWeekYear = function() { + return a(this).year(); + }, d.isoWeek = function(t2) { + if (!this.$utils().u(t2)) + return this.add(7 * (t2 - this.isoWeek()), e); + var i2, d2, n2, o, r = a(this), u = (i2 = this.isoWeekYear(), d2 = this.$u, n2 = (d2 ? s.utc : s)().year(i2).startOf("year"), o = 4 - n2.isoWeekday(), n2.isoWeekday() > 4 && (o += 7), n2.add(o, e)); + return r.diff(u, "week") + 1; + }, d.isoWeekday = function(e2) { + return this.$utils().u(e2) ? this.day() || 7 : this.day(this.day() % 7 ? e2 : e2 - 7); + }; + var n = d.startOf; + d.startOf = function(e2, t2) { + var i2 = this.$utils(), s2 = !!i2.u(t2) || t2; + return "isoweek" === i2.p(e2) ? s2 ? this.date(this.date() - (this.isoWeekday() - 1)).startOf("day") : this.date(this.date() - 1 - (this.isoWeekday() - 1) + 7).endOf("day") : n.bind(this)(e2, t2); + }; + }; + }); + } +}); + +// src/core/RenderBuilder.ts +function buildPipeline(renderers) { + return { + async run(context) { + for (const renderer of renderers) { + await renderer.render(context); + } + } + }; +} +__name(buildPipeline, "buildPipeline"); + +// src/core/FilterTemplate.ts +var _FilterTemplate = class _FilterTemplate { + constructor(dateService, entityResolver) { + this.dateService = dateService; + this.entityResolver = entityResolver; + this.fields = []; + } + /** + * Tilføj felt til template + * @param idProperty - Property-navn (bruges på både event og column.dataset) + * @param derivedFrom - Hvis feltet udledes fra anden property (f.eks. date fra start) + */ + addField(idProperty, derivedFrom) { + this.fields.push({ idProperty, derivedFrom }); + return this; + } + /** + * Parse dot-notation string into components + * @example 'resource.teamId' → { entityType: 'resource', property: 'teamId', foreignKey: 'resourceId' } + */ + parseDotNotation(idProperty) { + if (!idProperty.includes(".")) + return null; + const [entityType, property] = idProperty.split("."); + return { + entityType, + property, + foreignKey: entityType + "Id" + // Convention: resource → resourceId + }; + } + /** + * Get dataset key for column lookup + * For dot-notation 'resource.teamId', we look for 'teamId' in dataset + */ + getDatasetKey(idProperty) { + const dotNotation = this.parseDotNotation(idProperty); + if (dotNotation) { + return dotNotation.property; + } + return idProperty; + } + /** + * Byg nøgle fra kolonne + * Læser værdier fra column.dataset[idProperty] + * For dot-notation, uses the property part (resource.teamId → teamId) + */ + buildKeyFromColumn(column) { + return this.fields.map((f) => { + const key = this.getDatasetKey(f.idProperty); + return column.dataset[key] || ""; + }).join(":"); + } + /** + * Byg nøgle fra event + * Læser værdier fra event[idProperty] eller udleder fra derivedFrom + * For dot-notation, resolves via EntityResolver + */ + buildKeyFromEvent(event) { + const eventRecord = event; + return this.fields.map((f) => { + const dotNotation = this.parseDotNotation(f.idProperty); + if (dotNotation) { + return this.resolveDotNotation(eventRecord, dotNotation); + } + if (f.derivedFrom) { + const sourceValue = eventRecord[f.derivedFrom]; + if (sourceValue instanceof Date) { + return this.dateService.getDateKey(sourceValue); + } + return String(sourceValue || ""); + } + return String(eventRecord[f.idProperty] || ""); + }).join(":"); + } + /** + * Resolve dot-notation reference via EntityResolver + */ + resolveDotNotation(eventRecord, dotNotation) { + if (!this.entityResolver) { + console.warn(`FilterTemplate: EntityResolver required for dot-notation '${dotNotation.entityType}.${dotNotation.property}'`); + return ""; + } + const foreignId = eventRecord[dotNotation.foreignKey]; + if (!foreignId) + return ""; + const entity = this.entityResolver.resolve(dotNotation.entityType, String(foreignId)); + if (!entity) + return ""; + return String(entity[dotNotation.property] || ""); + } + /** + * Match event mod kolonne + */ + matches(event, column) { + return this.buildKeyFromEvent(event) === this.buildKeyFromColumn(column); + } +}; +__name(_FilterTemplate, "FilterTemplate"); +var FilterTemplate = _FilterTemplate; + +// src/core/CalendarOrchestrator.ts +var _CalendarOrchestrator = class _CalendarOrchestrator { + constructor(allRenderers, eventRenderer, scheduleRenderer, headerDrawerRenderer, dateService, entityServices) { + this.allRenderers = allRenderers; + this.eventRenderer = eventRenderer; + this.scheduleRenderer = scheduleRenderer; + this.headerDrawerRenderer = headerDrawerRenderer; + this.dateService = dateService; + this.entityServices = entityServices; + } + async render(viewConfig, container) { + const headerContainer = container.querySelector("swp-calendar-header"); + const columnContainer = container.querySelector("swp-day-columns"); + if (!headerContainer || !columnContainer) { + throw new Error("Missing swp-calendar-header or swp-day-columns"); + } + const filter = {}; + for (const grouping of viewConfig.groupings) { + filter[grouping.type] = grouping.values; + } + const filterTemplate = new FilterTemplate(this.dateService); + for (const grouping of viewConfig.groupings) { + if (grouping.idProperty) { + filterTemplate.addField(grouping.idProperty, grouping.derivedFrom); + } + } + const { parentChildMap, childType } = await this.resolveBelongsTo(viewConfig.groupings, filter); + const context = { headerContainer, columnContainer, filter, groupings: viewConfig.groupings, parentChildMap, childType }; + headerContainer.innerHTML = ""; + columnContainer.innerHTML = ""; + const levels = viewConfig.groupings.map((g) => g.type).join(" "); + headerContainer.dataset.levels = levels; + const activeRenderers = this.selectRenderers(viewConfig); + const pipeline = buildPipeline(activeRenderers); + await pipeline.run(context); + await this.scheduleRenderer.render(container, filter); + await this.eventRenderer.render(container, filter, filterTemplate); + await this.headerDrawerRenderer.render(container, filter, filterTemplate); + } + selectRenderers(viewConfig) { + const types = viewConfig.groupings.map((g) => g.type); + return types.map((type) => this.allRenderers.find((r) => r.type === type)).filter((r) => r !== void 0); + } + /** + * Resolve belongsTo relations to build parent-child map + * e.g., belongsTo: 'team.resourceIds' → { team1: ['EMP001', 'EMP002'], team2: [...] } + * Also returns the childType (the grouping type that has belongsTo) + */ + async resolveBelongsTo(groupings, filter) { + const childGrouping = groupings.find((g) => g.belongsTo); + if (!childGrouping?.belongsTo) + return {}; + const [entityType, property] = childGrouping.belongsTo.split("."); + if (!entityType || !property) + return {}; + const parentIds = filter[entityType] || []; + if (parentIds.length === 0) + return {}; + const service = this.entityServices.find( + (s) => s.entityType.toLowerCase() === entityType + ); + if (!service) + return {}; + const allEntities = await service.getAll(); + const entities = allEntities.filter( + (e) => parentIds.includes(e.id) + ); + const map = {}; + for (const entity of entities) { + const entityRecord = entity; + const children = entityRecord[property] || []; + map[entityRecord.id] = children; + } + return { parentChildMap: map, childType: childGrouping.type }; + } +}; +__name(_CalendarOrchestrator, "CalendarOrchestrator"); +var CalendarOrchestrator = _CalendarOrchestrator; + +// src/core/NavigationAnimator.ts +var _NavigationAnimator = class _NavigationAnimator { + constructor(headerTrack, contentTrack, headerDrawer) { + this.headerTrack = headerTrack; + this.contentTrack = contentTrack; + this.headerDrawer = headerDrawer; + } + async slide(direction, renderFn) { + const out = direction === "left" ? "-100%" : "100%"; + const into = direction === "left" ? "100%" : "-100%"; + await this.animateOut(out); + await renderFn(); + await this.animateIn(into); + } + async animateOut(translate) { + const animations = [ + this.headerTrack.animate( + [{ transform: "translateX(0)" }, { transform: `translateX(${translate})` }], + { duration: 200, easing: "ease-in" } + ).finished, + this.contentTrack.animate( + [{ transform: "translateX(0)" }, { transform: `translateX(${translate})` }], + { duration: 200, easing: "ease-in" } + ).finished + ]; + if (this.headerDrawer) { + animations.push( + this.headerDrawer.animate( + [{ transform: "translateX(0)" }, { transform: `translateX(${translate})` }], + { duration: 200, easing: "ease-in" } + ).finished + ); + } + await Promise.all(animations); + } + async animateIn(translate) { + const animations = [ + this.headerTrack.animate( + [{ transform: `translateX(${translate})` }, { transform: "translateX(0)" }], + { duration: 200, easing: "ease-out" } + ).finished, + this.contentTrack.animate( + [{ transform: `translateX(${translate})` }, { transform: "translateX(0)" }], + { duration: 200, easing: "ease-out" } + ).finished + ]; + if (this.headerDrawer) { + animations.push( + this.headerDrawer.animate( + [{ transform: `translateX(${translate})` }, { transform: "translateX(0)" }], + { duration: 200, easing: "ease-out" } + ).finished + ); + } + await Promise.all(animations); + } +}; +__name(_NavigationAnimator, "NavigationAnimator"); +var NavigationAnimator = _NavigationAnimator; + +// src/features/date/DateRenderer.ts +var _DateRenderer = class _DateRenderer { + constructor(dateService) { + this.dateService = dateService; + this.type = "date"; + } + render(context) { + const dates = context.filter["date"] || []; + const resourceIds = context.filter["resource"] || []; + const dateGrouping = context.groupings?.find((g) => g.type === "date"); + const hideHeader = dateGrouping?.hideHeader === true; + const iterations = resourceIds.length || 1; + let columnCount = 0; + for (let r = 0; r < iterations; r++) { + const resourceId = resourceIds[r]; + for (const dateStr of dates) { + const date = this.dateService.parseISO(dateStr); + const segments = { date: dateStr }; + if (resourceId) + segments.resource = resourceId; + const columnKey = this.dateService.buildColumnKey(segments); + const header = document.createElement("swp-day-header"); + header.dataset.date = dateStr; + header.dataset.columnKey = columnKey; + if (resourceId) { + header.dataset.resourceId = resourceId; + } + if (hideHeader) { + header.dataset.hidden = "true"; + } + header.innerHTML = ` + ${this.dateService.getDayName(date, "short")} + ${date.getDate()} + `; + context.headerContainer.appendChild(header); + const column = document.createElement("swp-day-column"); + column.dataset.date = dateStr; + column.dataset.columnKey = columnKey; + if (resourceId) { + column.dataset.resourceId = resourceId; + } + column.innerHTML = ""; + context.columnContainer.appendChild(column); + columnCount++; + } + } + const container = context.columnContainer.closest("swp-calendar-container"); + if (container) { + container.style.setProperty("--grid-columns", String(columnCount)); + } + } +}; +__name(_DateRenderer, "DateRenderer"); +var DateRenderer = _DateRenderer; + +// src/core/DateService.ts +var import_dayjs = __toESM(require_dayjs_min(), 1); +var import_utc = __toESM(require_utc(), 1); +var import_timezone = __toESM(require_timezone(), 1); +var import_isoWeek = __toESM(require_isoWeek(), 1); +import_dayjs.default.extend(import_utc.default); +import_dayjs.default.extend(import_timezone.default); +import_dayjs.default.extend(import_isoWeek.default); +var _DateService = class _DateService { + constructor(config, baseDate) { + this.config = config; + this.timezone = config.timezone; + this.baseDate = baseDate ? (0, import_dayjs.default)(baseDate) : (0, import_dayjs.default)(); + } + /** + * Set a fixed base date (useful for demos with static mock data) + */ + setBaseDate(date) { + this.baseDate = (0, import_dayjs.default)(date); + } + /** + * Get the current base date (either fixed or today) + */ + getBaseDate() { + return this.baseDate.toDate(); + } + parseISO(isoString) { + return (0, import_dayjs.default)(isoString).toDate(); + } + getDayName(date, format = "short") { + return new Intl.DateTimeFormat(this.config.locale, { weekday: format }).format(date); + } + /** + * Get dates starting from a day offset + * @param dayOffset - Day offset from base date + * @param count - Number of consecutive days to return + * @returns Array of date strings in YYYY-MM-DD format + */ + getDatesFromOffset(dayOffset, count) { + const startDate = this.baseDate.add(dayOffset, "day"); + return Array.from( + { length: count }, + (_, i) => startDate.add(i, "day").format("YYYY-MM-DD") + ); + } + /** + * Get specific weekdays from the week containing the offset date + * @param dayOffset - Day offset from base date + * @param workDays - Array of ISO weekday numbers (1=Monday, 7=Sunday) + * @returns Array of date strings in YYYY-MM-DD format + */ + getWorkDaysFromOffset(dayOffset, workDays) { + const targetDate = this.baseDate.add(dayOffset, "day"); + const monday = targetDate.startOf("week").add(1, "day"); + return workDays.map((isoDay) => { + const daysFromMonday = isoDay === 7 ? 6 : isoDay - 1; + return monday.add(daysFromMonday, "day").format("YYYY-MM-DD"); + }); + } + // Legacy methods for backwards compatibility + getWeekDates(weekOffset = 0, days = 7) { + return this.getDatesFromOffset(weekOffset * 7, days); + } + getWorkWeekDates(weekOffset, workDays) { + return this.getWorkDaysFromOffset(weekOffset * 7, workDays); + } + // ============================================ + // FORMATTING + // ============================================ + formatTime(date, showSeconds = false) { + const pattern = showSeconds ? "HH:mm:ss" : "HH:mm"; + return (0, import_dayjs.default)(date).format(pattern); + } + formatTimeRange(start, end) { + return `${this.formatTime(start)} - ${this.formatTime(end)}`; + } + formatDate(date) { + return (0, import_dayjs.default)(date).format("YYYY-MM-DD"); + } + getDateKey(date) { + return this.formatDate(date); + } + // ============================================ + // COLUMN KEY + // ============================================ + /** + * Build a uniform columnKey from grouping segments + * Handles any combination of date, resource, team, etc. + * + * @example + * buildColumnKey({ date: '2025-12-09' }) → "2025-12-09" + * buildColumnKey({ date: '2025-12-09', resource: 'EMP001' }) → "2025-12-09:EMP001" + */ + buildColumnKey(segments) { + const date = segments.date; + const others = Object.entries(segments).filter(([k]) => k !== "date").sort(([a], [b]) => a.localeCompare(b)).map(([, v]) => v); + return date ? [date, ...others].join(":") : others.join(":"); + } + /** + * Parse a columnKey back into segments + * Assumes format: "date:resource:..." or just "date" + */ + parseColumnKey(columnKey) { + const parts = columnKey.split(":"); + return { + date: parts[0], + resource: parts[1] + }; + } + /** + * Extract dateKey from columnKey (first segment) + */ + getDateFromColumnKey(columnKey) { + return columnKey.split(":")[0]; + } + // ============================================ + // TIME CALCULATIONS + // ============================================ + timeToMinutes(timeString) { + const parts = timeString.split(":").map(Number); + const hours = parts[0] || 0; + const minutes = parts[1] || 0; + return hours * 60 + minutes; + } + minutesToTime(totalMinutes) { + const hours = Math.floor(totalMinutes / 60); + const minutes = totalMinutes % 60; + return (0, import_dayjs.default)().hour(hours).minute(minutes).format("HH:mm"); + } + getMinutesSinceMidnight(date) { + const d = (0, import_dayjs.default)(date); + return d.hour() * 60 + d.minute(); + } + // ============================================ + // UTC CONVERSIONS + // ============================================ + toUTC(localDate) { + return import_dayjs.default.tz(localDate, this.timezone).utc().toISOString(); + } + fromUTC(utcString) { + return import_dayjs.default.utc(utcString).tz(this.timezone).toDate(); + } + // ============================================ + // DATE CREATION + // ============================================ + createDateAtTime(baseDate, timeString) { + const totalMinutes = this.timeToMinutes(timeString); + const hours = Math.floor(totalMinutes / 60); + const minutes = totalMinutes % 60; + return (0, import_dayjs.default)(baseDate).startOf("day").hour(hours).minute(minutes).toDate(); + } + getISOWeekDay(date) { + return (0, import_dayjs.default)(date).isoWeekday(); + } +}; +__name(_DateService, "DateService"); +var DateService = _DateService; + +// src/utils/PositionUtils.ts +function calculateEventPosition(start, end, config) { + const startMinutes = start.getHours() * 60 + start.getMinutes(); + const endMinutes = end.getHours() * 60 + end.getMinutes(); + const dayStartMinutes = config.dayStartHour * 60; + const minuteHeight = config.hourHeight / 60; + const top = (startMinutes - dayStartMinutes) * minuteHeight; + const height = (endMinutes - startMinutes) * minuteHeight; + return { top, height }; +} +__name(calculateEventPosition, "calculateEventPosition"); +function minutesToPixels(minutes, config) { + return minutes / 60 * config.hourHeight; +} +__name(minutesToPixels, "minutesToPixels"); +function pixelsToMinutes(pixels, config) { + return pixels / config.hourHeight * 60; +} +__name(pixelsToMinutes, "pixelsToMinutes"); +function snapToGrid(pixels, config) { + const snapPixels = minutesToPixels(config.snapInterval, config); + return Math.round(pixels / snapPixels) * snapPixels; +} +__name(snapToGrid, "snapToGrid"); + +// src/constants/CoreEvents.ts +var CoreEvents = { + // Lifecycle events + INITIALIZED: "core:initialized", + READY: "core:ready", + DESTROYED: "core:destroyed", + // View events + VIEW_CHANGED: "view:changed", + VIEW_RENDERED: "view:rendered", + // Navigation events + DATE_CHANGED: "nav:date-changed", + NAVIGATION_COMPLETED: "nav:navigation-completed", + // Data events + DATA_LOADING: "data:loading", + DATA_LOADED: "data:loaded", + DATA_ERROR: "data:error", + // Grid events + GRID_RENDERED: "grid:rendered", + GRID_CLICKED: "grid:clicked", + // Event management + EVENT_CREATED: "event:created", + EVENT_UPDATED: "event:updated", + EVENT_DELETED: "event:deleted", + EVENT_SELECTED: "event:selected", + // Event drag-drop + EVENT_DRAG_START: "event:drag-start", + EVENT_DRAG_MOVE: "event:drag-move", + EVENT_DRAG_END: "event:drag-end", + EVENT_DRAG_CANCEL: "event:drag-cancel", + EVENT_DRAG_COLUMN_CHANGE: "event:drag-column-change", + // Header drag (timed → header conversion) + EVENT_DRAG_ENTER_HEADER: "event:drag-enter-header", + EVENT_DRAG_MOVE_HEADER: "event:drag-move-header", + EVENT_DRAG_LEAVE_HEADER: "event:drag-leave-header", + // Event resize + EVENT_RESIZE_START: "event:resize-start", + EVENT_RESIZE_END: "event:resize-end", + // Edge scroll + EDGE_SCROLL_TICK: "edge-scroll:tick", + EDGE_SCROLL_STARTED: "edge-scroll:started", + EDGE_SCROLL_STOPPED: "edge-scroll:stopped", + // System events + ERROR: "system:error", + // Sync events + SYNC_STARTED: "sync:started", + SYNC_COMPLETED: "sync:completed", + SYNC_FAILED: "sync:failed", + // Entity events - for audit and sync + ENTITY_SAVED: "entity:saved", + ENTITY_DELETED: "entity:deleted", + // Audit events + AUDIT_LOGGED: "audit:logged", + // Rendering events + EVENTS_RENDERED: "events:rendered" +}; + +// src/features/event/EventLayoutEngine.ts +function eventsOverlap(a, b) { + return a.start < b.end && a.end > b.start; +} +__name(eventsOverlap, "eventsOverlap"); +function eventsWithinThreshold(a, b, thresholdMinutes) { + const thresholdMs = thresholdMinutes * 60 * 1e3; + const startToStartDiff = Math.abs(a.start.getTime() - b.start.getTime()); + if (startToStartDiff <= thresholdMs) + return true; + const bStartsBeforeAEnds = a.end.getTime() - b.start.getTime(); + if (bStartsBeforeAEnds > 0 && bStartsBeforeAEnds <= thresholdMs) + return true; + const aStartsBeforeBEnds = b.end.getTime() - a.start.getTime(); + if (aStartsBeforeBEnds > 0 && aStartsBeforeBEnds <= thresholdMs) + return true; + return false; +} +__name(eventsWithinThreshold, "eventsWithinThreshold"); +function findOverlapGroups(events) { + if (events.length === 0) + return []; + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + const used = /* @__PURE__ */ new Set(); + const groups = []; + for (const event of sorted) { + if (used.has(event.id)) + continue; + const group = [event]; + used.add(event.id); + let expanded = true; + while (expanded) { + expanded = false; + for (const candidate of sorted) { + if (used.has(candidate.id)) + continue; + const connects = group.some((member) => eventsOverlap(member, candidate)); + if (connects) { + group.push(candidate); + used.add(candidate.id); + expanded = true; + } + } + } + groups.push(group); + } + return groups; +} +__name(findOverlapGroups, "findOverlapGroups"); +function findGridCandidates(events, thresholdMinutes) { + if (events.length === 0) + return []; + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + const used = /* @__PURE__ */ new Set(); + const groups = []; + for (const event of sorted) { + if (used.has(event.id)) + continue; + const group = [event]; + used.add(event.id); + let expanded = true; + while (expanded) { + expanded = false; + for (const candidate of sorted) { + if (used.has(candidate.id)) + continue; + const connects = group.some( + (member) => eventsWithinThreshold(member, candidate, thresholdMinutes) + ); + if (connects) { + group.push(candidate); + used.add(candidate.id); + expanded = true; + } + } + } + groups.push(group); + } + return groups; +} +__name(findGridCandidates, "findGridCandidates"); +function calculateStackLevels(events) { + const levels = /* @__PURE__ */ new Map(); + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + for (const event of sorted) { + let maxOverlappingLevel = -1; + for (const [id, level] of levels) { + const other = events.find((e) => e.id === id); + if (other && eventsOverlap(event, other)) { + maxOverlappingLevel = Math.max(maxOverlappingLevel, level); + } + } + levels.set(event.id, maxOverlappingLevel + 1); + } + return levels; +} +__name(calculateStackLevels, "calculateStackLevels"); +function allocateColumns(events) { + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + const columns = []; + for (const event of sorted) { + let placed = false; + for (const column of columns) { + const canFit = !column.some((e) => eventsOverlap(event, e)); + if (canFit) { + column.push(event); + placed = true; + break; + } + } + if (!placed) { + columns.push([event]); + } + } + return columns; +} +__name(allocateColumns, "allocateColumns"); +function calculateColumnLayout(events, config) { + const thresholdMinutes = config.gridStartThresholdMinutes ?? 10; + const result = { + grids: [], + stacked: [] + }; + if (events.length === 0) + return result; + const overlapGroups = findOverlapGroups(events); + for (const overlapGroup of overlapGroups) { + if (overlapGroup.length === 1) { + result.stacked.push({ + event: overlapGroup[0], + stackLevel: 0 + }); + continue; + } + const gridSubgroups = findGridCandidates(overlapGroup, thresholdMinutes); + const largestGridCandidate = gridSubgroups.reduce((max, g) => g.length > max.length ? g : max, gridSubgroups[0]); + if (largestGridCandidate.length === overlapGroup.length) { + const columns = allocateColumns(overlapGroup); + const earliest = overlapGroup.reduce((min, e) => e.start < min.start ? e : min, overlapGroup[0]); + const position = calculateEventPosition(earliest.start, earliest.end, config); + result.grids.push({ + events: overlapGroup, + columns, + stackLevel: 0, + position: { top: position.top } + }); + } else { + const levels = calculateStackLevels(overlapGroup); + for (const event of overlapGroup) { + result.stacked.push({ + event, + stackLevel: levels.get(event.id) ?? 0 + }); + } + } + } + return result; +} +__name(calculateColumnLayout, "calculateColumnLayout"); + +// src/features/event/EventRenderer.ts +var _EventRenderer = class _EventRenderer { + constructor(eventService, dateService, gridConfig, eventBus) { + this.eventService = eventService; + this.dateService = dateService; + this.gridConfig = gridConfig; + this.eventBus = eventBus; + this.container = null; + this.setupListeners(); + } + /** + * Setup listeners for drag-drop and update events + */ + setupListeners() { + this.eventBus.on(CoreEvents.EVENT_DRAG_COLUMN_CHANGE, (e) => { + const payload = e.detail; + this.handleColumnChange(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_MOVE, (e) => { + const payload = e.detail; + this.updateDragTimestamp(payload); + }); + this.eventBus.on(CoreEvents.EVENT_UPDATED, (e) => { + const payload = e.detail; + this.handleEventUpdated(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_END, (e) => { + const payload = e.detail; + this.handleDragEnd(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_LEAVE_HEADER, (e) => { + const payload = e.detail; + this.handleDragLeaveHeader(payload); + }); + } + /** + * Handle EVENT_DRAG_END - remove element if dropped in header + */ + handleDragEnd(payload) { + if (payload.target === "header") { + const element = this.container?.querySelector(`swp-content-viewport swp-event[data-event-id="${payload.swpEvent.eventId}"]`); + element?.remove(); + } + } + /** + * Handle header item leaving header - create swp-event in grid + */ + handleDragLeaveHeader(payload) { + if (payload.source !== "header") + return; + if (!payload.targetColumn || !payload.start || !payload.end) + return; + if (payload.element) { + payload.element.classList.add("drag-ghost"); + payload.element.style.opacity = "0.3"; + payload.element.style.pointerEvents = "none"; + } + const event = { + id: payload.eventId, + title: payload.title || "", + description: "", + start: payload.start, + end: payload.end, + type: "customer", + allDay: false, + syncStatus: "pending" + }; + const element = this.createEventElement(event); + let eventsLayer = payload.targetColumn.querySelector("swp-events-layer"); + if (!eventsLayer) { + eventsLayer = document.createElement("swp-events-layer"); + payload.targetColumn.appendChild(eventsLayer); + } + eventsLayer.appendChild(element); + element.classList.add("dragging"); + } + /** + * Handle EVENT_UPDATED - re-render affected columns + */ + async handleEventUpdated(payload) { + if (payload.sourceColumnKey !== payload.targetColumnKey) { + await this.rerenderColumn(payload.sourceColumnKey); + } + await this.rerenderColumn(payload.targetColumnKey); + } + /** + * Re-render a single column with fresh data from IndexedDB + */ + async rerenderColumn(columnKey) { + const column = this.findColumn(columnKey); + if (!column) + return; + const date = column.dataset.date; + const resourceId = column.dataset.resourceId; + if (!date) + return; + const startDate = new Date(date); + const endDate = new Date(date); + endDate.setHours(23, 59, 59, 999); + const events = resourceId ? await this.eventService.getByResourceAndDateRange(resourceId, startDate, endDate) : await this.eventService.getByDateRange(startDate, endDate); + const timedEvents = events.filter( + (event) => !event.allDay && this.dateService.getDateKey(event.start) === date + ); + let eventsLayer = column.querySelector("swp-events-layer"); + if (!eventsLayer) { + eventsLayer = document.createElement("swp-events-layer"); + column.appendChild(eventsLayer); + } + eventsLayer.innerHTML = ""; + const layout = calculateColumnLayout(timedEvents, this.gridConfig); + layout.grids.forEach((grid) => { + const groupEl = this.renderGridGroup(grid); + eventsLayer.appendChild(groupEl); + }); + layout.stacked.forEach((item) => { + const eventEl = this.renderStackedEvent(item.event, item.stackLevel); + eventsLayer.appendChild(eventEl); + }); + } + /** + * Find a column element by columnKey + */ + findColumn(columnKey) { + if (!this.container) + return null; + return this.container.querySelector(`swp-day-column[data-column-key="${columnKey}"]`); + } + /** + * Handle event moving to a new column during drag + */ + handleColumnChange(payload) { + const eventsLayer = payload.newColumn.querySelector("swp-events-layer"); + if (!eventsLayer) + return; + eventsLayer.appendChild(payload.element); + payload.element.style.top = `${payload.currentY}px`; + } + /** + * Update timestamp display during drag (snapped to grid) + */ + updateDragTimestamp(payload) { + const timeEl = payload.element.querySelector("swp-event-time"); + if (!timeEl) + return; + const snappedY = snapToGrid(payload.currentY, this.gridConfig); + const minutesFromGridStart = pixelsToMinutes(snappedY, this.gridConfig); + const startMinutes = this.gridConfig.dayStartHour * 60 + minutesFromGridStart; + const height = parseFloat(payload.element.style.height) || this.gridConfig.hourHeight; + const durationMinutes = pixelsToMinutes(height, this.gridConfig); + const start = this.minutesToDate(startMinutes); + const end = this.minutesToDate(startMinutes + durationMinutes); + timeEl.textContent = this.dateService.formatTimeRange(start, end); + } + /** + * Convert minutes since midnight to a Date object (today) + */ + minutesToDate(minutes) { + const date = /* @__PURE__ */ new Date(); + date.setHours(Math.floor(minutes / 60) % 24, minutes % 60, 0, 0); + return date; + } + /** + * Render events for visible dates into day columns + * @param container - Calendar container element + * @param filter - Filter with 'date' and optionally 'resource' arrays + * @param filterTemplate - Template for matching events to columns + */ + async render(container, filter, filterTemplate) { + this.container = container; + const visibleDates = filter["date"] || []; + if (visibleDates.length === 0) + return; + const startDate = new Date(visibleDates[0]); + const endDate = new Date(visibleDates[visibleDates.length - 1]); + endDate.setHours(23, 59, 59, 999); + const events = await this.eventService.getByDateRange(startDate, endDate); + const dayColumns = container.querySelector("swp-day-columns"); + if (!dayColumns) + return; + const columns = dayColumns.querySelectorAll("swp-day-column"); + columns.forEach((column) => { + const columnEl = column; + const columnEvents = events.filter((event) => filterTemplate.matches(event, columnEl)); + let eventsLayer = column.querySelector("swp-events-layer"); + if (!eventsLayer) { + eventsLayer = document.createElement("swp-events-layer"); + column.appendChild(eventsLayer); + } + eventsLayer.innerHTML = ""; + const timedEvents = columnEvents.filter((event) => !event.allDay); + const layout = calculateColumnLayout(timedEvents, this.gridConfig); + layout.grids.forEach((grid) => { + const groupEl = this.renderGridGroup(grid); + eventsLayer.appendChild(groupEl); + }); + layout.stacked.forEach((item) => { + const eventEl = this.renderStackedEvent(item.event, item.stackLevel); + eventsLayer.appendChild(eventEl); + }); + }); + } + /** + * Create a single event element + * + * CLEAN approach: + * - Only data-id for lookup + * - Visible content in innerHTML only + */ + createEventElement(event) { + const element = document.createElement("swp-event"); + element.dataset.eventId = event.id; + if (event.resourceId) { + element.dataset.resourceId = event.resourceId; + } + const position = calculateEventPosition(event.start, event.end, this.gridConfig); + element.style.top = `${position.top}px`; + element.style.height = `${position.height}px`; + const colorClass = this.getColorClass(event); + if (colorClass) { + element.classList.add(colorClass); + } + element.innerHTML = ` + ${this.dateService.formatTimeRange(event.start, event.end)} + ${this.escapeHtml(event.title)} + ${event.description ? `${this.escapeHtml(event.description)}` : ""} + `; + return element; + } + /** + * Get color class based on metadata.color or event type + */ + getColorClass(event) { + if (event.metadata?.color) { + return `is-${event.metadata.color}`; + } + const typeColors = { + "customer": "is-blue", + "vacation": "is-green", + "break": "is-amber", + "meeting": "is-purple", + "blocked": "is-red" + }; + return typeColors[event.type] || "is-blue"; + } + /** + * Escape HTML to prevent XSS + */ + escapeHtml(text) { + const div = document.createElement("div"); + div.textContent = text; + return div.innerHTML; + } + /** + * Render a GRID group with side-by-side columns + * Used when multiple events start at the same time + */ + renderGridGroup(layout) { + const group = document.createElement("swp-event-group"); + group.classList.add(`cols-${layout.columns.length}`); + group.style.top = `${layout.position.top}px`; + if (layout.stackLevel > 0) { + group.style.marginLeft = `${layout.stackLevel * 15}px`; + group.style.zIndex = `${100 + layout.stackLevel}`; + } + let maxBottom = 0; + for (const event of layout.events) { + const pos = calculateEventPosition(event.start, event.end, this.gridConfig); + const eventBottom = pos.top + pos.height; + if (eventBottom > maxBottom) + maxBottom = eventBottom; + } + const groupHeight = maxBottom - layout.position.top; + group.style.height = `${groupHeight}px`; + layout.columns.forEach((columnEvents) => { + const wrapper = document.createElement("div"); + wrapper.style.position = "relative"; + columnEvents.forEach((event) => { + const eventEl = this.createEventElement(event); + const pos = calculateEventPosition(event.start, event.end, this.gridConfig); + eventEl.style.top = `${pos.top - layout.position.top}px`; + eventEl.style.position = "absolute"; + eventEl.style.left = "0"; + eventEl.style.right = "0"; + wrapper.appendChild(eventEl); + }); + group.appendChild(wrapper); + }); + return group; + } + /** + * Render a STACKED event with margin-left offset + * Used for overlapping events that don't start at the same time + */ + renderStackedEvent(event, stackLevel) { + const element = this.createEventElement(event); + element.dataset.stackLink = JSON.stringify({ stackLevel }); + if (stackLevel > 0) { + element.style.marginLeft = `${stackLevel * 15}px`; + element.style.zIndex = `${100 + stackLevel}`; + } + return element; + } +}; +__name(_EventRenderer, "EventRenderer"); +var EventRenderer = _EventRenderer; + +// src/core/BaseGroupingRenderer.ts +var _BaseGroupingRenderer = class _BaseGroupingRenderer { + /** + * Main render method - handles common logic + */ + async render(context) { + const allowedIds = context.filter[this.type] || []; + if (allowedIds.length === 0) + return; + const entities = await this.getEntities(allowedIds); + const dateCount = context.filter["date"]?.length || 1; + const childIds = context.childType ? context.filter[context.childType] || [] : []; + for (const entity of entities) { + const entityChildIds = context.parentChildMap?.[entity.id] || []; + const childCount = entityChildIds.filter((id) => childIds.includes(id)).length; + const colspan = childCount * dateCount; + const header = document.createElement(this.config.elementTag); + header.dataset[this.config.idAttribute] = entity.id; + header.style.setProperty(this.config.colspanVar, String(colspan)); + this.renderHeader(entity, header, context); + context.headerContainer.appendChild(header); + } + } + /** + * Override this method for custom header rendering + * Default: just sets textContent to display name + */ + renderHeader(entity, header, _context) { + header.textContent = this.getDisplayName(entity); + } + /** + * Helper to render a single entity header. + * Can be used by subclasses that override render() but want consistent header creation. + */ + createHeader(entity, context) { + const header = document.createElement(this.config.elementTag); + header.dataset[this.config.idAttribute] = entity.id; + this.renderHeader(entity, header, context); + return header; + } +}; +__name(_BaseGroupingRenderer, "BaseGroupingRenderer"); +var BaseGroupingRenderer = _BaseGroupingRenderer; + +// src/features/resource/ResourceRenderer.ts +var _ResourceRenderer = class _ResourceRenderer extends BaseGroupingRenderer { + constructor(resourceService) { + super(); + this.resourceService = resourceService; + this.type = "resource"; + this.config = { + elementTag: "swp-resource-header", + idAttribute: "resourceId", + colspanVar: "--resource-cols" + }; + } + getEntities(ids) { + return this.resourceService.getByIds(ids); + } + getDisplayName(entity) { + return entity.displayName; + } + /** + * Override render to handle: + * 1. Special ordering when parentChildMap exists (resources grouped by parent) + * 2. Different colspan calculation (just dateCount, not childCount * dateCount) + */ + async render(context) { + const resourceIds = context.filter["resource"] || []; + const dateCount = context.filter["date"]?.length || 1; + let orderedResourceIds; + if (context.parentChildMap) { + orderedResourceIds = []; + for (const childIds of Object.values(context.parentChildMap)) { + for (const childId of childIds) { + if (resourceIds.includes(childId)) { + orderedResourceIds.push(childId); + } + } + } + } else { + orderedResourceIds = resourceIds; + } + const resources = await this.getEntities(orderedResourceIds); + const resourceMap = new Map(resources.map((r) => [r.id, r])); + for (const resourceId of orderedResourceIds) { + const resource = resourceMap.get(resourceId); + if (!resource) + continue; + const header = this.createHeader(resource, context); + header.style.gridColumn = `span ${dateCount}`; + context.headerContainer.appendChild(header); + } + } +}; +__name(_ResourceRenderer, "ResourceRenderer"); +var ResourceRenderer = _ResourceRenderer; + +// src/features/team/TeamRenderer.ts +var _TeamRenderer = class _TeamRenderer extends BaseGroupingRenderer { + constructor(teamService) { + super(); + this.teamService = teamService; + this.type = "team"; + this.config = { + elementTag: "swp-team-header", + idAttribute: "teamId", + colspanVar: "--team-cols" + }; + } + getEntities(ids) { + return this.teamService.getByIds(ids); + } + getDisplayName(entity) { + return entity.name; + } +}; +__name(_TeamRenderer, "TeamRenderer"); +var TeamRenderer = _TeamRenderer; + +// src/features/timeaxis/TimeAxisRenderer.ts +var _TimeAxisRenderer = class _TimeAxisRenderer { + render(container, startHour = 6, endHour = 20) { + container.innerHTML = ""; + for (let hour = startHour; hour <= endHour; hour++) { + const marker = document.createElement("swp-hour-marker"); + marker.textContent = `${hour.toString().padStart(2, "0")}:00`; + container.appendChild(marker); + } + } +}; +__name(_TimeAxisRenderer, "TimeAxisRenderer"); +var TimeAxisRenderer = _TimeAxisRenderer; +export { + CalendarOrchestrator, + DateRenderer, + DateService, + EventRenderer, + NavigationAnimator, + ResourceRenderer, + TeamRenderer, + TimeAxisRenderer, + buildPipeline +}; +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vbm9kZV9tb2R1bGVzL2RheWpzL2RheWpzLm1pbi5qcyIsICIuLi8uLi9ub2RlX21vZHVsZXMvZGF5anMvcGx1Z2luL3V0Yy5qcyIsICIuLi8uLi9ub2RlX21vZHVsZXMvZGF5anMvcGx1Z2luL3RpbWV6b25lLmpzIiwgIi4uLy4uL25vZGVfbW9kdWxlcy9kYXlqcy9wbHVnaW4vaXNvV2Vlay5qcyIsICIuLi8uLi9zcmMvY29yZS9SZW5kZXJCdWlsZGVyLnRzIiwgIi4uLy4uL3NyYy9jb3JlL0ZpbHRlclRlbXBsYXRlLnRzIiwgIi4uLy4uL3NyYy9jb3JlL0NhbGVuZGFyT3JjaGVzdHJhdG9yLnRzIiwgIi4uLy4uL3NyYy9jb3JlL05hdmlnYXRpb25BbmltYXRvci50cyIsICIuLi8uLi9zcmMvZmVhdHVyZXMvZGF0ZS9EYXRlUmVuZGVyZXIudHMiLCAiLi4vLi4vc3JjL2NvcmUvRGF0ZVNlcnZpY2UudHMiLCAiLi4vLi4vc3JjL3V0aWxzL1Bvc2l0aW9uVXRpbHMudHMiLCAiLi4vLi4vc3JjL2NvbnN0YW50cy9Db3JlRXZlbnRzLnRzIiwgIi4uLy4uL3NyYy9mZWF0dXJlcy9ldmVudC9FdmVudExheW91dEVuZ2luZS50cyIsICIuLi8uLi9zcmMvZmVhdHVyZXMvZXZlbnQvRXZlbnRSZW5kZXJlci50cyIsICIuLi8uLi9zcmMvY29yZS9CYXNlR3JvdXBpbmdSZW5kZXJlci50cyIsICIuLi8uLi9zcmMvZmVhdHVyZXMvcmVzb3VyY2UvUmVzb3VyY2VSZW5kZXJlci50cyIsICIuLi8uLi9zcmMvZmVhdHVyZXMvdGVhbS9UZWFtUmVuZGVyZXIudHMiLCAiLi4vLi4vc3JjL2ZlYXR1cmVzL3RpbWVheGlzL1RpbWVBeGlzUmVuZGVyZXIudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbIiFmdW5jdGlvbih0LGUpe1wib2JqZWN0XCI9PXR5cGVvZiBleHBvcnRzJiZcInVuZGVmaW5lZFwiIT10eXBlb2YgbW9kdWxlP21vZHVsZS5leHBvcnRzPWUoKTpcImZ1bmN0aW9uXCI9PXR5cGVvZiBkZWZpbmUmJmRlZmluZS5hbWQ/ZGVmaW5lKGUpOih0PVwidW5kZWZpbmVkXCIhPXR5cGVvZiBnbG9iYWxUaGlzP2dsb2JhbFRoaXM6dHx8c2VsZikuZGF5anM9ZSgpfSh0aGlzLChmdW5jdGlvbigpe1widXNlIHN0cmljdFwiO3ZhciB0PTFlMyxlPTZlNCxuPTM2ZTUscj1cIm1pbGxpc2Vjb25kXCIsaT1cInNlY29uZFwiLHM9XCJtaW51dGVcIix1PVwiaG91clwiLGE9XCJkYXlcIixvPVwid2Vla1wiLGM9XCJtb250aFwiLGY9XCJxdWFydGVyXCIsaD1cInllYXJcIixkPVwiZGF0ZVwiLGw9XCJJbnZhbGlkIERhdGVcIiwkPS9eKFxcZHs0fSlbLS9dPyhcXGR7MSwyfSk/Wy0vXT8oXFxkezAsMn0pW1R0XFxzXSooXFxkezEsMn0pPzo/KFxcZHsxLDJ9KT86PyhcXGR7MSwyfSk/Wy46XT8oXFxkKyk/JC8seT0vXFxbKFteXFxdXSspXXxZezEsNH18TXsxLDR9fER7MSwyfXxkezEsNH18SHsxLDJ9fGh7MSwyfXxhfEF8bXsxLDJ9fHN7MSwyfXxaezEsMn18U1NTL2csTT17bmFtZTpcImVuXCIsd2Vla2RheXM6XCJTdW5kYXlfTW9uZGF5X1R1ZXNkYXlfV2VkbmVzZGF5X1RodXJzZGF5X0ZyaWRheV9TYXR1cmRheVwiLnNwbGl0KFwiX1wiKSxtb250aHM6XCJKYW51YXJ5X0ZlYnJ1YXJ5X01hcmNoX0FwcmlsX01heV9KdW5lX0p1bHlfQXVndXN0X1NlcHRlbWJlcl9PY3RvYmVyX05vdmVtYmVyX0RlY2VtYmVyXCIuc3BsaXQoXCJfXCIpLG9yZGluYWw6ZnVuY3Rpb24odCl7dmFyIGU9W1widGhcIixcInN0XCIsXCJuZFwiLFwicmRcIl0sbj10JTEwMDtyZXR1cm5cIltcIit0KyhlWyhuLTIwKSUxMF18fGVbbl18fGVbMF0pK1wiXVwifX0sbT1mdW5jdGlvbih0LGUsbil7dmFyIHI9U3RyaW5nKHQpO3JldHVybiFyfHxyLmxlbmd0aD49ZT90OlwiXCIrQXJyYXkoZSsxLXIubGVuZ3RoKS5qb2luKG4pK3R9LHY9e3M6bSx6OmZ1bmN0aW9uKHQpe3ZhciBlPS10LnV0Y09mZnNldCgpLG49TWF0aC5hYnMoZSkscj1NYXRoLmZsb29yKG4vNjApLGk9biU2MDtyZXR1cm4oZTw9MD9cIitcIjpcIi1cIikrbShyLDIsXCIwXCIpK1wiOlwiK20oaSwyLFwiMFwiKX0sbTpmdW5jdGlvbiB0KGUsbil7aWYoZS5kYXRlKCk8bi5kYXRlKCkpcmV0dXJuLXQobixlKTt2YXIgcj0xMioobi55ZWFyKCktZS55ZWFyKCkpKyhuLm1vbnRoKCktZS5tb250aCgpKSxpPWUuY2xvbmUoKS5hZGQocixjKSxzPW4taTwwLHU9ZS5jbG9uZSgpLmFkZChyKyhzPy0xOjEpLGMpO3JldHVybisoLShyKyhuLWkpLyhzP2ktdTp1LWkpKXx8MCl9LGE6ZnVuY3Rpb24odCl7cmV0dXJuIHQ8MD9NYXRoLmNlaWwodCl8fDA6TWF0aC5mbG9vcih0KX0scDpmdW5jdGlvbih0KXtyZXR1cm57TTpjLHk6aCx3Om8sZDphLEQ6ZCxoOnUsbTpzLHM6aSxtczpyLFE6Zn1bdF18fFN0cmluZyh0fHxcIlwiKS50b0xvd2VyQ2FzZSgpLnJlcGxhY2UoL3MkLyxcIlwiKX0sdTpmdW5jdGlvbih0KXtyZXR1cm4gdm9pZCAwPT09dH19LGc9XCJlblwiLEQ9e307RFtnXT1NO3ZhciBwPVwiJGlzRGF5anNPYmplY3RcIixTPWZ1bmN0aW9uKHQpe3JldHVybiB0IGluc3RhbmNlb2YgX3x8ISghdHx8IXRbcF0pfSx3PWZ1bmN0aW9uIHQoZSxuLHIpe3ZhciBpO2lmKCFlKXJldHVybiBnO2lmKFwic3RyaW5nXCI9PXR5cGVvZiBlKXt2YXIgcz1lLnRvTG93ZXJDYXNlKCk7RFtzXSYmKGk9cyksbiYmKERbc109bixpPXMpO3ZhciB1PWUuc3BsaXQoXCItXCIpO2lmKCFpJiZ1Lmxlbmd0aD4xKXJldHVybiB0KHVbMF0pfWVsc2V7dmFyIGE9ZS5uYW1lO0RbYV09ZSxpPWF9cmV0dXJuIXImJmkmJihnPWkpLGl8fCFyJiZnfSxPPWZ1bmN0aW9uKHQsZSl7aWYoUyh0KSlyZXR1cm4gdC5jbG9uZSgpO3ZhciBuPVwib2JqZWN0XCI9PXR5cGVvZiBlP2U6e307cmV0dXJuIG4uZGF0ZT10LG4uYXJncz1hcmd1bWVudHMsbmV3IF8obil9LGI9djtiLmw9dyxiLmk9UyxiLnc9ZnVuY3Rpb24odCxlKXtyZXR1cm4gTyh0LHtsb2NhbGU6ZS4kTCx1dGM6ZS4kdSx4OmUuJHgsJG9mZnNldDplLiRvZmZzZXR9KX07dmFyIF89ZnVuY3Rpb24oKXtmdW5jdGlvbiBNKHQpe3RoaXMuJEw9dyh0LmxvY2FsZSxudWxsLCEwKSx0aGlzLnBhcnNlKHQpLHRoaXMuJHg9dGhpcy4keHx8dC54fHx7fSx0aGlzW3BdPSEwfXZhciBtPU0ucHJvdG90eXBlO3JldHVybiBtLnBhcnNlPWZ1bmN0aW9uKHQpe3RoaXMuJGQ9ZnVuY3Rpb24odCl7dmFyIGU9dC5kYXRlLG49dC51dGM7aWYobnVsbD09PWUpcmV0dXJuIG5ldyBEYXRlKE5hTik7aWYoYi51KGUpKXJldHVybiBuZXcgRGF0ZTtpZihlIGluc3RhbmNlb2YgRGF0ZSlyZXR1cm4gbmV3IERhdGUoZSk7aWYoXCJzdHJpbmdcIj09dHlwZW9mIGUmJiEvWiQvaS50ZXN0KGUpKXt2YXIgcj1lLm1hdGNoKCQpO2lmKHIpe3ZhciBpPXJbMl0tMXx8MCxzPShyWzddfHxcIjBcIikuc3Vic3RyaW5nKDAsMyk7cmV0dXJuIG4/bmV3IERhdGUoRGF0ZS5VVEMoclsxXSxpLHJbM118fDEscls0XXx8MCxyWzVdfHwwLHJbNl18fDAscykpOm5ldyBEYXRlKHJbMV0saSxyWzNdfHwxLHJbNF18fDAscls1XXx8MCxyWzZdfHwwLHMpfX1yZXR1cm4gbmV3IERhdGUoZSl9KHQpLHRoaXMuaW5pdCgpfSxtLmluaXQ9ZnVuY3Rpb24oKXt2YXIgdD10aGlzLiRkO3RoaXMuJHk9dC5nZXRGdWxsWWVhcigpLHRoaXMuJE09dC5nZXRNb250aCgpLHRoaXMuJEQ9dC5nZXREYXRlKCksdGhpcy4kVz10LmdldERheSgpLHRoaXMuJEg9dC5nZXRIb3VycygpLHRoaXMuJG09dC5nZXRNaW51dGVzKCksdGhpcy4kcz10LmdldFNlY29uZHMoKSx0aGlzLiRtcz10LmdldE1pbGxpc2Vjb25kcygpfSxtLiR1dGlscz1mdW5jdGlvbigpe3JldHVybiBifSxtLmlzVmFsaWQ9ZnVuY3Rpb24oKXtyZXR1cm4hKHRoaXMuJGQudG9TdHJpbmcoKT09PWwpfSxtLmlzU2FtZT1mdW5jdGlvbih0LGUpe3ZhciBuPU8odCk7cmV0dXJuIHRoaXMuc3RhcnRPZihlKTw9biYmbjw9dGhpcy5lbmRPZihlKX0sbS5pc0FmdGVyPWZ1bmN0aW9uKHQsZSl7cmV0dXJuIE8odCk8dGhpcy5zdGFydE9mKGUpfSxtLmlzQmVmb3JlPWZ1bmN0aW9uKHQsZSl7cmV0dXJuIHRoaXMuZW5kT2YoZSk8Tyh0KX0sbS4kZz1mdW5jdGlvbih0LGUsbil7cmV0dXJuIGIudSh0KT90aGlzW2VdOnRoaXMuc2V0KG4sdCl9LG0udW5peD1mdW5jdGlvbigpe3JldHVybiBNYXRoLmZsb29yKHRoaXMudmFsdWVPZigpLzFlMyl9LG0udmFsdWVPZj1mdW5jdGlvbigpe3JldHVybiB0aGlzLiRkLmdldFRpbWUoKX0sbS5zdGFydE9mPWZ1bmN0aW9uKHQsZSl7dmFyIG49dGhpcyxyPSEhYi51KGUpfHxlLGY9Yi5wKHQpLGw9ZnVuY3Rpb24odCxlKXt2YXIgaT1iLncobi4kdT9EYXRlLlVUQyhuLiR5LGUsdCk6bmV3IERhdGUobi4keSxlLHQpLG4pO3JldHVybiByP2k6aS5lbmRPZihhKX0sJD1mdW5jdGlvbih0LGUpe3JldHVybiBiLncobi50b0RhdGUoKVt0XS5hcHBseShuLnRvRGF0ZShcInNcIiksKHI/WzAsMCwwLDBdOlsyMyw1OSw1OSw5OTldKS5zbGljZShlKSksbil9LHk9dGhpcy4kVyxNPXRoaXMuJE0sbT10aGlzLiRELHY9XCJzZXRcIisodGhpcy4kdT9cIlVUQ1wiOlwiXCIpO3N3aXRjaChmKXtjYXNlIGg6cmV0dXJuIHI/bCgxLDApOmwoMzEsMTEpO2Nhc2UgYzpyZXR1cm4gcj9sKDEsTSk6bCgwLE0rMSk7Y2FzZSBvOnZhciBnPXRoaXMuJGxvY2FsZSgpLndlZWtTdGFydHx8MCxEPSh5PGc/eSs3OnkpLWc7cmV0dXJuIGwocj9tLUQ6bSsoNi1EKSxNKTtjYXNlIGE6Y2FzZSBkOnJldHVybiAkKHYrXCJIb3Vyc1wiLDApO2Nhc2UgdTpyZXR1cm4gJCh2K1wiTWludXRlc1wiLDEpO2Nhc2UgczpyZXR1cm4gJCh2K1wiU2Vjb25kc1wiLDIpO2Nhc2UgaTpyZXR1cm4gJCh2K1wiTWlsbGlzZWNvbmRzXCIsMyk7ZGVmYXVsdDpyZXR1cm4gdGhpcy5jbG9uZSgpfX0sbS5lbmRPZj1mdW5jdGlvbih0KXtyZXR1cm4gdGhpcy5zdGFydE9mKHQsITEpfSxtLiRzZXQ9ZnVuY3Rpb24odCxlKXt2YXIgbixvPWIucCh0KSxmPVwic2V0XCIrKHRoaXMuJHU/XCJVVENcIjpcIlwiKSxsPShuPXt9LG5bYV09ZitcIkRhdGVcIixuW2RdPWYrXCJEYXRlXCIsbltjXT1mK1wiTW9udGhcIixuW2hdPWYrXCJGdWxsWWVhclwiLG5bdV09ZitcIkhvdXJzXCIsbltzXT1mK1wiTWludXRlc1wiLG5baV09ZitcIlNlY29uZHNcIixuW3JdPWYrXCJNaWxsaXNlY29uZHNcIixuKVtvXSwkPW89PT1hP3RoaXMuJEQrKGUtdGhpcy4kVyk6ZTtpZihvPT09Y3x8bz09PWgpe3ZhciB5PXRoaXMuY2xvbmUoKS5zZXQoZCwxKTt5LiRkW2xdKCQpLHkuaW5pdCgpLHRoaXMuJGQ9eS5zZXQoZCxNYXRoLm1pbih0aGlzLiRELHkuZGF5c0luTW9udGgoKSkpLiRkfWVsc2UgbCYmdGhpcy4kZFtsXSgkKTtyZXR1cm4gdGhpcy5pbml0KCksdGhpc30sbS5zZXQ9ZnVuY3Rpb24odCxlKXtyZXR1cm4gdGhpcy5jbG9uZSgpLiRzZXQodCxlKX0sbS5nZXQ9ZnVuY3Rpb24odCl7cmV0dXJuIHRoaXNbYi5wKHQpXSgpfSxtLmFkZD1mdW5jdGlvbihyLGYpe3ZhciBkLGw9dGhpcztyPU51bWJlcihyKTt2YXIgJD1iLnAoZikseT1mdW5jdGlvbih0KXt2YXIgZT1PKGwpO3JldHVybiBiLncoZS5kYXRlKGUuZGF0ZSgpK01hdGgucm91bmQodCpyKSksbCl9O2lmKCQ9PT1jKXJldHVybiB0aGlzLnNldChjLHRoaXMuJE0rcik7aWYoJD09PWgpcmV0dXJuIHRoaXMuc2V0KGgsdGhpcy4keStyKTtpZigkPT09YSlyZXR1cm4geSgxKTtpZigkPT09bylyZXR1cm4geSg3KTt2YXIgTT0oZD17fSxkW3NdPWUsZFt1XT1uLGRbaV09dCxkKVskXXx8MSxtPXRoaXMuJGQuZ2V0VGltZSgpK3IqTTtyZXR1cm4gYi53KG0sdGhpcyl9LG0uc3VidHJhY3Q9ZnVuY3Rpb24odCxlKXtyZXR1cm4gdGhpcy5hZGQoLTEqdCxlKX0sbS5mb3JtYXQ9ZnVuY3Rpb24odCl7dmFyIGU9dGhpcyxuPXRoaXMuJGxvY2FsZSgpO2lmKCF0aGlzLmlzVmFsaWQoKSlyZXR1cm4gbi5pbnZhbGlkRGF0ZXx8bDt2YXIgcj10fHxcIllZWVktTU0tRERUSEg6bW06c3NaXCIsaT1iLnoodGhpcykscz10aGlzLiRILHU9dGhpcy4kbSxhPXRoaXMuJE0sbz1uLndlZWtkYXlzLGM9bi5tb250aHMsZj1uLm1lcmlkaWVtLGg9ZnVuY3Rpb24odCxuLGkscyl7cmV0dXJuIHQmJih0W25dfHx0KGUscikpfHxpW25dLnNsaWNlKDAscyl9LGQ9ZnVuY3Rpb24odCl7cmV0dXJuIGIucyhzJTEyfHwxMix0LFwiMFwiKX0sJD1mfHxmdW5jdGlvbih0LGUsbil7dmFyIHI9dDwxMj9cIkFNXCI6XCJQTVwiO3JldHVybiBuP3IudG9Mb3dlckNhc2UoKTpyfTtyZXR1cm4gci5yZXBsYWNlKHksKGZ1bmN0aW9uKHQscil7cmV0dXJuIHJ8fGZ1bmN0aW9uKHQpe3N3aXRjaCh0KXtjYXNlXCJZWVwiOnJldHVybiBTdHJpbmcoZS4keSkuc2xpY2UoLTIpO2Nhc2VcIllZWVlcIjpyZXR1cm4gYi5zKGUuJHksNCxcIjBcIik7Y2FzZVwiTVwiOnJldHVybiBhKzE7Y2FzZVwiTU1cIjpyZXR1cm4gYi5zKGErMSwyLFwiMFwiKTtjYXNlXCJNTU1cIjpyZXR1cm4gaChuLm1vbnRoc1Nob3J0LGEsYywzKTtjYXNlXCJNTU1NXCI6cmV0dXJuIGgoYyxhKTtjYXNlXCJEXCI6cmV0dXJuIGUuJEQ7Y2FzZVwiRERcIjpyZXR1cm4gYi5zKGUuJEQsMixcIjBcIik7Y2FzZVwiZFwiOnJldHVybiBTdHJpbmcoZS4kVyk7Y2FzZVwiZGRcIjpyZXR1cm4gaChuLndlZWtkYXlzTWluLGUuJFcsbywyKTtjYXNlXCJkZGRcIjpyZXR1cm4gaChuLndlZWtkYXlzU2hvcnQsZS4kVyxvLDMpO2Nhc2VcImRkZGRcIjpyZXR1cm4gb1tlLiRXXTtjYXNlXCJIXCI6cmV0dXJuIFN0cmluZyhzKTtjYXNlXCJISFwiOnJldHVybiBiLnMocywyLFwiMFwiKTtjYXNlXCJoXCI6cmV0dXJuIGQoMSk7Y2FzZVwiaGhcIjpyZXR1cm4gZCgyKTtjYXNlXCJhXCI6cmV0dXJuICQocyx1LCEwKTtjYXNlXCJBXCI6cmV0dXJuICQocyx1LCExKTtjYXNlXCJtXCI6cmV0dXJuIFN0cmluZyh1KTtjYXNlXCJtbVwiOnJldHVybiBiLnModSwyLFwiMFwiKTtjYXNlXCJzXCI6cmV0dXJuIFN0cmluZyhlLiRzKTtjYXNlXCJzc1wiOnJldHVybiBiLnMoZS4kcywyLFwiMFwiKTtjYXNlXCJTU1NcIjpyZXR1cm4gYi5zKGUuJG1zLDMsXCIwXCIpO2Nhc2VcIlpcIjpyZXR1cm4gaX1yZXR1cm4gbnVsbH0odCl8fGkucmVwbGFjZShcIjpcIixcIlwiKX0pKX0sbS51dGNPZmZzZXQ9ZnVuY3Rpb24oKXtyZXR1cm4gMTUqLU1hdGgucm91bmQodGhpcy4kZC5nZXRUaW1lem9uZU9mZnNldCgpLzE1KX0sbS5kaWZmPWZ1bmN0aW9uKHIsZCxsKXt2YXIgJCx5PXRoaXMsTT1iLnAoZCksbT1PKHIpLHY9KG0udXRjT2Zmc2V0KCktdGhpcy51dGNPZmZzZXQoKSkqZSxnPXRoaXMtbSxEPWZ1bmN0aW9uKCl7cmV0dXJuIGIubSh5LG0pfTtzd2l0Y2goTSl7Y2FzZSBoOiQ9RCgpLzEyO2JyZWFrO2Nhc2UgYzokPUQoKTticmVhaztjYXNlIGY6JD1EKCkvMzticmVhaztjYXNlIG86JD0oZy12KS82MDQ4ZTU7YnJlYWs7Y2FzZSBhOiQ9KGctdikvODY0ZTU7YnJlYWs7Y2FzZSB1OiQ9Zy9uO2JyZWFrO2Nhc2UgczokPWcvZTticmVhaztjYXNlIGk6JD1nL3Q7YnJlYWs7ZGVmYXVsdDokPWd9cmV0dXJuIGw/JDpiLmEoJCl9LG0uZGF5c0luTW9udGg9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5lbmRPZihjKS4kRH0sbS4kbG9jYWxlPWZ1bmN0aW9uKCl7cmV0dXJuIERbdGhpcy4kTF19LG0ubG9jYWxlPWZ1bmN0aW9uKHQsZSl7aWYoIXQpcmV0dXJuIHRoaXMuJEw7dmFyIG49dGhpcy5jbG9uZSgpLHI9dyh0LGUsITApO3JldHVybiByJiYobi4kTD1yKSxufSxtLmNsb25lPWZ1bmN0aW9uKCl7cmV0dXJuIGIudyh0aGlzLiRkLHRoaXMpfSxtLnRvRGF0ZT1mdW5jdGlvbigpe3JldHVybiBuZXcgRGF0ZSh0aGlzLnZhbHVlT2YoKSl9LG0udG9KU09OPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuaXNWYWxpZCgpP3RoaXMudG9JU09TdHJpbmcoKTpudWxsfSxtLnRvSVNPU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuJGQudG9JU09TdHJpbmcoKX0sbS50b1N0cmluZz1mdW5jdGlvbigpe3JldHVybiB0aGlzLiRkLnRvVVRDU3RyaW5nKCl9LE19KCksaz1fLnByb3RvdHlwZTtyZXR1cm4gTy5wcm90b3R5cGU9ayxbW1wiJG1zXCIscl0sW1wiJHNcIixpXSxbXCIkbVwiLHNdLFtcIiRIXCIsdV0sW1wiJFdcIixhXSxbXCIkTVwiLGNdLFtcIiR5XCIsaF0sW1wiJERcIixkXV0uZm9yRWFjaCgoZnVuY3Rpb24odCl7a1t0WzFdXT1mdW5jdGlvbihlKXtyZXR1cm4gdGhpcy4kZyhlLHRbMF0sdFsxXSl9fSkpLE8uZXh0ZW5kPWZ1bmN0aW9uKHQsZSl7cmV0dXJuIHQuJGl8fCh0KGUsXyxPKSx0LiRpPSEwKSxPfSxPLmxvY2FsZT13LE8uaXNEYXlqcz1TLE8udW5peD1mdW5jdGlvbih0KXtyZXR1cm4gTygxZTMqdCl9LE8uZW49RFtnXSxPLkxzPUQsTy5wPXt9LE99KSk7IiwgIiFmdW5jdGlvbih0LGkpe1wib2JqZWN0XCI9PXR5cGVvZiBleHBvcnRzJiZcInVuZGVmaW5lZFwiIT10eXBlb2YgbW9kdWxlP21vZHVsZS5leHBvcnRzPWkoKTpcImZ1bmN0aW9uXCI9PXR5cGVvZiBkZWZpbmUmJmRlZmluZS5hbWQ/ZGVmaW5lKGkpOih0PVwidW5kZWZpbmVkXCIhPXR5cGVvZiBnbG9iYWxUaGlzP2dsb2JhbFRoaXM6dHx8c2VsZikuZGF5anNfcGx1Z2luX3V0Yz1pKCl9KHRoaXMsKGZ1bmN0aW9uKCl7XCJ1c2Ugc3RyaWN0XCI7dmFyIHQ9XCJtaW51dGVcIixpPS9bKy1dXFxkXFxkKD86Oj9cXGRcXGQpPy9nLGU9LyhbKy1dfFxcZFxcZCkvZztyZXR1cm4gZnVuY3Rpb24ocyxmLG4pe3ZhciB1PWYucHJvdG90eXBlO24udXRjPWZ1bmN0aW9uKHQpe3ZhciBpPXtkYXRlOnQsdXRjOiEwLGFyZ3M6YXJndW1lbnRzfTtyZXR1cm4gbmV3IGYoaSl9LHUudXRjPWZ1bmN0aW9uKGkpe3ZhciBlPW4odGhpcy50b0RhdGUoKSx7bG9jYWxlOnRoaXMuJEwsdXRjOiEwfSk7cmV0dXJuIGk/ZS5hZGQodGhpcy51dGNPZmZzZXQoKSx0KTplfSx1LmxvY2FsPWZ1bmN0aW9uKCl7cmV0dXJuIG4odGhpcy50b0RhdGUoKSx7bG9jYWxlOnRoaXMuJEwsdXRjOiExfSl9O3ZhciByPXUucGFyc2U7dS5wYXJzZT1mdW5jdGlvbih0KXt0LnV0YyYmKHRoaXMuJHU9ITApLHRoaXMuJHV0aWxzKCkudSh0LiRvZmZzZXQpfHwodGhpcy4kb2Zmc2V0PXQuJG9mZnNldCksci5jYWxsKHRoaXMsdCl9O3ZhciBvPXUuaW5pdDt1LmluaXQ9ZnVuY3Rpb24oKXtpZih0aGlzLiR1KXt2YXIgdD10aGlzLiRkO3RoaXMuJHk9dC5nZXRVVENGdWxsWWVhcigpLHRoaXMuJE09dC5nZXRVVENNb250aCgpLHRoaXMuJEQ9dC5nZXRVVENEYXRlKCksdGhpcy4kVz10LmdldFVUQ0RheSgpLHRoaXMuJEg9dC5nZXRVVENIb3VycygpLHRoaXMuJG09dC5nZXRVVENNaW51dGVzKCksdGhpcy4kcz10LmdldFVUQ1NlY29uZHMoKSx0aGlzLiRtcz10LmdldFVUQ01pbGxpc2Vjb25kcygpfWVsc2Ugby5jYWxsKHRoaXMpfTt2YXIgYT11LnV0Y09mZnNldDt1LnV0Y09mZnNldD1mdW5jdGlvbihzLGYpe3ZhciBuPXRoaXMuJHV0aWxzKCkudTtpZihuKHMpKXJldHVybiB0aGlzLiR1PzA6bih0aGlzLiRvZmZzZXQpP2EuY2FsbCh0aGlzKTp0aGlzLiRvZmZzZXQ7aWYoXCJzdHJpbmdcIj09dHlwZW9mIHMmJihzPWZ1bmN0aW9uKHQpe3ZvaWQgMD09PXQmJih0PVwiXCIpO3ZhciBzPXQubWF0Y2goaSk7aWYoIXMpcmV0dXJuIG51bGw7dmFyIGY9KFwiXCIrc1swXSkubWF0Y2goZSl8fFtcIi1cIiwwLDBdLG49ZlswXSx1PTYwKitmWzFdKyArZlsyXTtyZXR1cm4gMD09PXU/MDpcIitcIj09PW4/dTotdX0ocyksbnVsbD09PXMpKXJldHVybiB0aGlzO3ZhciB1PU1hdGguYWJzKHMpPD0xNj82MCpzOnM7aWYoMD09PXUpcmV0dXJuIHRoaXMudXRjKGYpO3ZhciByPXRoaXMuY2xvbmUoKTtpZihmKXJldHVybiByLiRvZmZzZXQ9dSxyLiR1PSExLHI7dmFyIG89dGhpcy4kdT90aGlzLnRvRGF0ZSgpLmdldFRpbWV6b25lT2Zmc2V0KCk6LTEqdGhpcy51dGNPZmZzZXQoKTtyZXR1cm4ocj10aGlzLmxvY2FsKCkuYWRkKHUrbyx0KSkuJG9mZnNldD11LHIuJHguJGxvY2FsT2Zmc2V0PW8scn07dmFyIGg9dS5mb3JtYXQ7dS5mb3JtYXQ9ZnVuY3Rpb24odCl7dmFyIGk9dHx8KHRoaXMuJHU/XCJZWVlZLU1NLUREVEhIOm1tOnNzW1pdXCI6XCJcIik7cmV0dXJuIGguY2FsbCh0aGlzLGkpfSx1LnZhbHVlT2Y9ZnVuY3Rpb24oKXt2YXIgdD10aGlzLiR1dGlscygpLnUodGhpcy4kb2Zmc2V0KT8wOnRoaXMuJG9mZnNldCsodGhpcy4keC4kbG9jYWxPZmZzZXR8fHRoaXMuJGQuZ2V0VGltZXpvbmVPZmZzZXQoKSk7cmV0dXJuIHRoaXMuJGQudmFsdWVPZigpLTZlNCp0fSx1LmlzVVRDPWZ1bmN0aW9uKCl7cmV0dXJuISF0aGlzLiR1fSx1LnRvSVNPU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMudG9EYXRlKCkudG9JU09TdHJpbmcoKX0sdS50b1N0cmluZz1mdW5jdGlvbigpe3JldHVybiB0aGlzLnRvRGF0ZSgpLnRvVVRDU3RyaW5nKCl9O3ZhciBsPXUudG9EYXRlO3UudG9EYXRlPWZ1bmN0aW9uKHQpe3JldHVyblwic1wiPT09dCYmdGhpcy4kb2Zmc2V0P24odGhpcy5mb3JtYXQoXCJZWVlZLU1NLUREIEhIOm1tOnNzOlNTU1wiKSkudG9EYXRlKCk6bC5jYWxsKHRoaXMpfTt2YXIgYz11LmRpZmY7dS5kaWZmPWZ1bmN0aW9uKHQsaSxlKXtpZih0JiZ0aGlzLiR1PT09dC4kdSlyZXR1cm4gYy5jYWxsKHRoaXMsdCxpLGUpO3ZhciBzPXRoaXMubG9jYWwoKSxmPW4odCkubG9jYWwoKTtyZXR1cm4gYy5jYWxsKHMsZixpLGUpfX19KSk7IiwgIiFmdW5jdGlvbih0LGUpe1wib2JqZWN0XCI9PXR5cGVvZiBleHBvcnRzJiZcInVuZGVmaW5lZFwiIT10eXBlb2YgbW9kdWxlP21vZHVsZS5leHBvcnRzPWUoKTpcImZ1bmN0aW9uXCI9PXR5cGVvZiBkZWZpbmUmJmRlZmluZS5hbWQ/ZGVmaW5lKGUpOih0PVwidW5kZWZpbmVkXCIhPXR5cGVvZiBnbG9iYWxUaGlzP2dsb2JhbFRoaXM6dHx8c2VsZikuZGF5anNfcGx1Z2luX3RpbWV6b25lPWUoKX0odGhpcywoZnVuY3Rpb24oKXtcInVzZSBzdHJpY3RcIjt2YXIgdD17eWVhcjowLG1vbnRoOjEsZGF5OjIsaG91cjozLG1pbnV0ZTo0LHNlY29uZDo1fSxlPXt9O3JldHVybiBmdW5jdGlvbihuLGksbyl7dmFyIHIsYT1mdW5jdGlvbih0LG4saSl7dm9pZCAwPT09aSYmKGk9e30pO3ZhciBvPW5ldyBEYXRlKHQpLHI9ZnVuY3Rpb24odCxuKXt2b2lkIDA9PT1uJiYobj17fSk7dmFyIGk9bi50aW1lWm9uZU5hbWV8fFwic2hvcnRcIixvPXQrXCJ8XCIraSxyPWVbb107cmV0dXJuIHJ8fChyPW5ldyBJbnRsLkRhdGVUaW1lRm9ybWF0KFwiZW4tVVNcIix7aG91cjEyOiExLHRpbWVab25lOnQseWVhcjpcIm51bWVyaWNcIixtb250aDpcIjItZGlnaXRcIixkYXk6XCIyLWRpZ2l0XCIsaG91cjpcIjItZGlnaXRcIixtaW51dGU6XCIyLWRpZ2l0XCIsc2Vjb25kOlwiMi1kaWdpdFwiLHRpbWVab25lTmFtZTppfSksZVtvXT1yKSxyfShuLGkpO3JldHVybiByLmZvcm1hdFRvUGFydHMobyl9LHU9ZnVuY3Rpb24oZSxuKXtmb3IodmFyIGk9YShlLG4pLHI9W10sdT0wO3U8aS5sZW5ndGg7dSs9MSl7dmFyIGY9aVt1XSxzPWYudHlwZSxtPWYudmFsdWUsYz10W3NdO2M+PTAmJihyW2NdPXBhcnNlSW50KG0sMTApKX12YXIgZD1yWzNdLGw9MjQ9PT1kPzA6ZCxoPXJbMF0rXCItXCIrclsxXStcIi1cIityWzJdK1wiIFwiK2wrXCI6XCIrcls0XStcIjpcIityWzVdK1wiOjAwMFwiLHY9K2U7cmV0dXJuKG8udXRjKGgpLnZhbHVlT2YoKS0odi09diUxZTMpKS82ZTR9LGY9aS5wcm90b3R5cGU7Zi50ej1mdW5jdGlvbih0LGUpe3ZvaWQgMD09PXQmJih0PXIpO3ZhciBuLGk9dGhpcy51dGNPZmZzZXQoKSxhPXRoaXMudG9EYXRlKCksdT1hLnRvTG9jYWxlU3RyaW5nKFwiZW4tVVNcIix7dGltZVpvbmU6dH0pLGY9TWF0aC5yb3VuZCgoYS1uZXcgRGF0ZSh1KSkvMWUzLzYwKSxzPTE1Ki1NYXRoLnJvdW5kKGEuZ2V0VGltZXpvbmVPZmZzZXQoKS8xNSktZjtpZighTnVtYmVyKHMpKW49dGhpcy51dGNPZmZzZXQoMCxlKTtlbHNlIGlmKG49byh1LHtsb2NhbGU6dGhpcy4kTH0pLiRzZXQoXCJtaWxsaXNlY29uZFwiLHRoaXMuJG1zKS51dGNPZmZzZXQocywhMCksZSl7dmFyIG09bi51dGNPZmZzZXQoKTtuPW4uYWRkKGktbSxcIm1pbnV0ZVwiKX1yZXR1cm4gbi4keC4kdGltZXpvbmU9dCxufSxmLm9mZnNldE5hbWU9ZnVuY3Rpb24odCl7dmFyIGU9dGhpcy4keC4kdGltZXpvbmV8fG8udHouZ3Vlc3MoKSxuPWEodGhpcy52YWx1ZU9mKCksZSx7dGltZVpvbmVOYW1lOnR9KS5maW5kKChmdW5jdGlvbih0KXtyZXR1cm5cInRpbWV6b25lbmFtZVwiPT09dC50eXBlLnRvTG93ZXJDYXNlKCl9KSk7cmV0dXJuIG4mJm4udmFsdWV9O3ZhciBzPWYuc3RhcnRPZjtmLnN0YXJ0T2Y9ZnVuY3Rpb24odCxlKXtpZighdGhpcy4keHx8IXRoaXMuJHguJHRpbWV6b25lKXJldHVybiBzLmNhbGwodGhpcyx0LGUpO3ZhciBuPW8odGhpcy5mb3JtYXQoXCJZWVlZLU1NLUREIEhIOm1tOnNzOlNTU1wiKSx7bG9jYWxlOnRoaXMuJEx9KTtyZXR1cm4gcy5jYWxsKG4sdCxlKS50eih0aGlzLiR4LiR0aW1lem9uZSwhMCl9LG8udHo9ZnVuY3Rpb24odCxlLG4pe3ZhciBpPW4mJmUsYT1ufHxlfHxyLGY9dSgrbygpLGEpO2lmKFwic3RyaW5nXCIhPXR5cGVvZiB0KXJldHVybiBvKHQpLnR6KGEpO3ZhciBzPWZ1bmN0aW9uKHQsZSxuKXt2YXIgaT10LTYwKmUqMWUzLG89dShpLG4pO2lmKGU9PT1vKXJldHVybltpLGVdO3ZhciByPXUoaS09NjAqKG8tZSkqMWUzLG4pO3JldHVybiBvPT09cj9baSxvXTpbdC02MCpNYXRoLm1pbihvLHIpKjFlMyxNYXRoLm1heChvLHIpXX0oby51dGModCxpKS52YWx1ZU9mKCksZixhKSxtPXNbMF0sYz1zWzFdLGQ9byhtKS51dGNPZmZzZXQoYyk7cmV0dXJuIGQuJHguJHRpbWV6b25lPWEsZH0sby50ei5ndWVzcz1mdW5jdGlvbigpe3JldHVybiBJbnRsLkRhdGVUaW1lRm9ybWF0KCkucmVzb2x2ZWRPcHRpb25zKCkudGltZVpvbmV9LG8udHouc2V0RGVmYXVsdD1mdW5jdGlvbih0KXtyPXR9fX0pKTsiLCAiIWZ1bmN0aW9uKGUsdCl7XCJvYmplY3RcIj09dHlwZW9mIGV4cG9ydHMmJlwidW5kZWZpbmVkXCIhPXR5cGVvZiBtb2R1bGU/bW9kdWxlLmV4cG9ydHM9dCgpOlwiZnVuY3Rpb25cIj09dHlwZW9mIGRlZmluZSYmZGVmaW5lLmFtZD9kZWZpbmUodCk6KGU9XCJ1bmRlZmluZWRcIiE9dHlwZW9mIGdsb2JhbFRoaXM/Z2xvYmFsVGhpczplfHxzZWxmKS5kYXlqc19wbHVnaW5faXNvV2Vlaz10KCl9KHRoaXMsKGZ1bmN0aW9uKCl7XCJ1c2Ugc3RyaWN0XCI7dmFyIGU9XCJkYXlcIjtyZXR1cm4gZnVuY3Rpb24odCxpLHMpe3ZhciBhPWZ1bmN0aW9uKHQpe3JldHVybiB0LmFkZCg0LXQuaXNvV2Vla2RheSgpLGUpfSxkPWkucHJvdG90eXBlO2QuaXNvV2Vla1llYXI9ZnVuY3Rpb24oKXtyZXR1cm4gYSh0aGlzKS55ZWFyKCl9LGQuaXNvV2Vlaz1mdW5jdGlvbih0KXtpZighdGhpcy4kdXRpbHMoKS51KHQpKXJldHVybiB0aGlzLmFkZCg3Kih0LXRoaXMuaXNvV2VlaygpKSxlKTt2YXIgaSxkLG4sbyxyPWEodGhpcyksdT0oaT10aGlzLmlzb1dlZWtZZWFyKCksZD10aGlzLiR1LG49KGQ/cy51dGM6cykoKS55ZWFyKGkpLnN0YXJ0T2YoXCJ5ZWFyXCIpLG89NC1uLmlzb1dlZWtkYXkoKSxuLmlzb1dlZWtkYXkoKT40JiYobys9Nyksbi5hZGQobyxlKSk7cmV0dXJuIHIuZGlmZih1LFwid2Vla1wiKSsxfSxkLmlzb1dlZWtkYXk9ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuJHV0aWxzKCkudShlKT90aGlzLmRheSgpfHw3OnRoaXMuZGF5KHRoaXMuZGF5KCklNz9lOmUtNyl9O3ZhciBuPWQuc3RhcnRPZjtkLnN0YXJ0T2Y9ZnVuY3Rpb24oZSx0KXt2YXIgaT10aGlzLiR1dGlscygpLHM9ISFpLnUodCl8fHQ7cmV0dXJuXCJpc293ZWVrXCI9PT1pLnAoZSk/cz90aGlzLmRhdGUodGhpcy5kYXRlKCktKHRoaXMuaXNvV2Vla2RheSgpLTEpKS5zdGFydE9mKFwiZGF5XCIpOnRoaXMuZGF0ZSh0aGlzLmRhdGUoKS0xLSh0aGlzLmlzb1dlZWtkYXkoKS0xKSs3KS5lbmRPZihcImRheVwiKTpuLmJpbmQodGhpcykoZSx0KX19fSkpOyIsICJpbXBvcnQgeyBJUmVuZGVyZXIsIElSZW5kZXJDb250ZXh0IH0gZnJvbSAnLi9JR3JvdXBpbmdSZW5kZXJlcic7XG5cbmV4cG9ydCBpbnRlcmZhY2UgUGlwZWxpbmUge1xuICBydW4oY29udGV4dDogSVJlbmRlckNvbnRleHQpOiBQcm9taXNlPHZvaWQ+O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gYnVpbGRQaXBlbGluZShyZW5kZXJlcnM6IElSZW5kZXJlcltdKTogUGlwZWxpbmUge1xuICByZXR1cm4ge1xuICAgIGFzeW5jIHJ1bihjb250ZXh0OiBJUmVuZGVyQ29udGV4dCkge1xuICAgICAgZm9yIChjb25zdCByZW5kZXJlciBvZiByZW5kZXJlcnMpIHtcbiAgICAgICAgYXdhaXQgcmVuZGVyZXIucmVuZGVyKGNvbnRleHQpO1xuICAgICAgfVxuICAgIH1cbiAgfTtcbn1cbiIsICJpbXBvcnQgeyBJQ2FsZW5kYXJFdmVudCB9IGZyb20gJy4uL3R5cGVzL0NhbGVuZGFyVHlwZXMnO1xyXG5pbXBvcnQgeyBEYXRlU2VydmljZSB9IGZyb20gJy4vRGF0ZVNlcnZpY2UnO1xyXG5pbXBvcnQgeyBJRW50aXR5UmVzb2x2ZXIgfSBmcm9tICcuL0lFbnRpdHlSZXNvbHZlcic7XHJcblxyXG4vKipcclxuICogRmllbGQgZGVmaW5pdGlvbiBmb3IgRmlsdGVyVGVtcGxhdGVcclxuICovXHJcbmludGVyZmFjZSBJRmlsdGVyRmllbGQge1xyXG4gIGlkUHJvcGVydHk6IHN0cmluZztcclxuICBkZXJpdmVkRnJvbT86IHN0cmluZztcclxufVxyXG5cclxuLyoqXHJcbiAqIFBhcnNlZCBkb3Qtbm90YXRpb24gcmVmZXJlbmNlXHJcbiAqL1xyXG5pbnRlcmZhY2UgSURvdE5vdGF0aW9uIHtcclxuICBlbnRpdHlUeXBlOiBzdHJpbmc7ICAgLy8gZS5nLiwgJ3Jlc291cmNlJ1xyXG4gIHByb3BlcnR5OiBzdHJpbmc7ICAgICAvLyBlLmcuLCAndGVhbUlkJ1xyXG4gIGZvcmVpZ25LZXk6IHN0cmluZzsgICAvLyBlLmcuLCAncmVzb3VyY2VJZCdcclxufVxyXG5cclxuLyoqXHJcbiAqIEZpbHRlclRlbXBsYXRlIC0gQnlnZ2VyIG5cdTAwRjhnbGVyIHRpbCBldmVudC1rb2xvbm5lIG1hdGNoaW5nXHJcbiAqXHJcbiAqIFZpZXdDb25maWcgZGVmaW5lcmVyIGh2aWxrZSBmZWx0ZXIgKGlkUHJvcGVydGllcykgZGVyIGluZGdcdTAwRTVyIGkga29sb25uZW5zIG5cdTAwRjhnbGUuXHJcbiAqIFNhbW1lIHRlbXBsYXRlIGJydWdlcyB0aWwgYXQgYnlnZ2Ugblx1MDBGOGdsZSBmb3IgYlx1MDBFNWRlIGtvbG9ubmUgb2cgZXZlbnQuXHJcbiAqXHJcbiAqIFN1cHBvcnRzIGRvdC1ub3RhdGlvbiBmb3IgaGllcmFyY2hpY2FsIHJlbGF0aW9uczpcclxuICogLSAncmVzb3VyY2UudGVhbUlkJyBcdTIxOTIgbG9va3MgdXAgZXZlbnQucmVzb3VyY2VJZCBcdTIxOTIgcmVzb3VyY2UgZW50aXR5IFx1MjE5MiB0ZWFtSWRcclxuICpcclxuICogUHJpbmNpcDogS29sb25uZW5zIG5cdTAwRjhnbGUtdGVtcGxhdGUgYmVzdGVtbWVyIGh2YWQgZGVyIG1hdGNoZXMgcFx1MDBFNS5cclxuICpcclxuICogQHNlZSBkb2NzL2ZpbHRlci10ZW1wbGF0ZS5tZFxyXG4gKi9cclxuZXhwb3J0IGNsYXNzIEZpbHRlclRlbXBsYXRlIHtcclxuICBwcml2YXRlIGZpZWxkczogSUZpbHRlckZpZWxkW10gPSBbXTtcclxuXHJcbiAgY29uc3RydWN0b3IoXHJcbiAgICBwcml2YXRlIGRhdGVTZXJ2aWNlOiBEYXRlU2VydmljZSxcclxuICAgIHByaXZhdGUgZW50aXR5UmVzb2x2ZXI/OiBJRW50aXR5UmVzb2x2ZXJcclxuICApIHt9XHJcblxyXG4gIC8qKlxyXG4gICAqIFRpbGZcdTAwRjhqIGZlbHQgdGlsIHRlbXBsYXRlXHJcbiAgICogQHBhcmFtIGlkUHJvcGVydHkgLSBQcm9wZXJ0eS1uYXZuIChicnVnZXMgcFx1MDBFNSBiXHUwMEU1ZGUgZXZlbnQgb2cgY29sdW1uLmRhdGFzZXQpXHJcbiAgICogQHBhcmFtIGRlcml2ZWRGcm9tIC0gSHZpcyBmZWx0ZXQgdWRsZWRlcyBmcmEgYW5kZW4gcHJvcGVydHkgKGYuZWtzLiBkYXRlIGZyYSBzdGFydClcclxuICAgKi9cclxuICBhZGRGaWVsZChpZFByb3BlcnR5OiBzdHJpbmcsIGRlcml2ZWRGcm9tPzogc3RyaW5nKTogdGhpcyB7XHJcbiAgICB0aGlzLmZpZWxkcy5wdXNoKHsgaWRQcm9wZXJ0eSwgZGVyaXZlZEZyb20gfSk7XHJcbiAgICByZXR1cm4gdGhpcztcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIFBhcnNlIGRvdC1ub3RhdGlvbiBzdHJpbmcgaW50byBjb21wb25lbnRzXHJcbiAgICogQGV4YW1wbGUgJ3Jlc291cmNlLnRlYW1JZCcgXHUyMTkyIHsgZW50aXR5VHlwZTogJ3Jlc291cmNlJywgcHJvcGVydHk6ICd0ZWFtSWQnLCBmb3JlaWduS2V5OiAncmVzb3VyY2VJZCcgfVxyXG4gICAqL1xyXG4gIHByaXZhdGUgcGFyc2VEb3ROb3RhdGlvbihpZFByb3BlcnR5OiBzdHJpbmcpOiBJRG90Tm90YXRpb24gfCBudWxsIHtcclxuICAgIGlmICghaWRQcm9wZXJ0eS5pbmNsdWRlcygnLicpKSByZXR1cm4gbnVsbDtcclxuICAgIGNvbnN0IFtlbnRpdHlUeXBlLCBwcm9wZXJ0eV0gPSBpZFByb3BlcnR5LnNwbGl0KCcuJyk7XHJcbiAgICByZXR1cm4ge1xyXG4gICAgICBlbnRpdHlUeXBlLFxyXG4gICAgICBwcm9wZXJ0eSxcclxuICAgICAgZm9yZWlnbktleTogZW50aXR5VHlwZSArICdJZCcgLy8gQ29udmVudGlvbjogcmVzb3VyY2UgXHUyMTkyIHJlc291cmNlSWRcclxuICAgIH07XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBHZXQgZGF0YXNldCBrZXkgZm9yIGNvbHVtbiBsb29rdXBcclxuICAgKiBGb3IgZG90LW5vdGF0aW9uICdyZXNvdXJjZS50ZWFtSWQnLCB3ZSBsb29rIGZvciAndGVhbUlkJyBpbiBkYXRhc2V0XHJcbiAgICovXHJcbiAgcHJpdmF0ZSBnZXREYXRhc2V0S2V5KGlkUHJvcGVydHk6IHN0cmluZyk6IHN0cmluZyB7XHJcbiAgICBjb25zdCBkb3ROb3RhdGlvbiA9IHRoaXMucGFyc2VEb3ROb3RhdGlvbihpZFByb3BlcnR5KTtcclxuICAgIGlmIChkb3ROb3RhdGlvbikge1xyXG4gICAgICByZXR1cm4gZG90Tm90YXRpb24ucHJvcGVydHk7IC8vICd0ZWFtSWQnXHJcbiAgICB9XHJcbiAgICByZXR1cm4gaWRQcm9wZXJ0eTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIEJ5ZyBuXHUwMEY4Z2xlIGZyYSBrb2xvbm5lXHJcbiAgICogTFx1MDBFNnNlciB2XHUwMEU2cmRpZXIgZnJhIGNvbHVtbi5kYXRhc2V0W2lkUHJvcGVydHldXHJcbiAgICogRm9yIGRvdC1ub3RhdGlvbiwgdXNlcyB0aGUgcHJvcGVydHkgcGFydCAocmVzb3VyY2UudGVhbUlkIFx1MjE5MiB0ZWFtSWQpXHJcbiAgICovXHJcbiAgYnVpbGRLZXlGcm9tQ29sdW1uKGNvbHVtbjogSFRNTEVsZW1lbnQpOiBzdHJpbmcge1xyXG4gICAgcmV0dXJuIHRoaXMuZmllbGRzXHJcbiAgICAgIC5tYXAoZiA9PiB7XHJcbiAgICAgICAgY29uc3Qga2V5ID0gdGhpcy5nZXREYXRhc2V0S2V5KGYuaWRQcm9wZXJ0eSk7XHJcbiAgICAgICAgcmV0dXJuIGNvbHVtbi5kYXRhc2V0W2tleV0gfHwgJyc7XHJcbiAgICAgIH0pXHJcbiAgICAgIC5qb2luKCc6Jyk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBCeWcgblx1MDBGOGdsZSBmcmEgZXZlbnRcclxuICAgKiBMXHUwMEU2c2VyIHZcdTAwRTZyZGllciBmcmEgZXZlbnRbaWRQcm9wZXJ0eV0gZWxsZXIgdWRsZWRlciBmcmEgZGVyaXZlZEZyb21cclxuICAgKiBGb3IgZG90LW5vdGF0aW9uLCByZXNvbHZlcyB2aWEgRW50aXR5UmVzb2x2ZXJcclxuICAgKi9cclxuICBidWlsZEtleUZyb21FdmVudChldmVudDogSUNhbGVuZGFyRXZlbnQpOiBzdHJpbmcge1xyXG4gICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1leHBsaWNpdC1hbnlcclxuICAgIGNvbnN0IGV2ZW50UmVjb3JkID0gZXZlbnQgYXMgYW55O1xyXG4gICAgcmV0dXJuIHRoaXMuZmllbGRzXHJcbiAgICAgIC5tYXAoZiA9PiB7XHJcbiAgICAgICAgLy8gQ2hlY2sgZm9yIGRvdC1ub3RhdGlvbiAoZS5nLiwgJ3Jlc291cmNlLnRlYW1JZCcpXHJcbiAgICAgICAgY29uc3QgZG90Tm90YXRpb24gPSB0aGlzLnBhcnNlRG90Tm90YXRpb24oZi5pZFByb3BlcnR5KTtcclxuICAgICAgICBpZiAoZG90Tm90YXRpb24pIHtcclxuICAgICAgICAgIHJldHVybiB0aGlzLnJlc29sdmVEb3ROb3RhdGlvbihldmVudFJlY29yZCwgZG90Tm90YXRpb24pO1xyXG4gICAgICAgIH1cclxuXHJcbiAgICAgICAgaWYgKGYuZGVyaXZlZEZyb20pIHtcclxuICAgICAgICAgIC8vIFVkbGVkIHZcdTAwRTZyZGkgKGYuZWtzLiBkYXRlIGZyYSBzdGFydClcclxuICAgICAgICAgIGNvbnN0IHNvdXJjZVZhbHVlID0gZXZlbnRSZWNvcmRbZi5kZXJpdmVkRnJvbV07XHJcbiAgICAgICAgICBpZiAoc291cmNlVmFsdWUgaW5zdGFuY2VvZiBEYXRlKSB7XHJcbiAgICAgICAgICAgIHJldHVybiB0aGlzLmRhdGVTZXJ2aWNlLmdldERhdGVLZXkoc291cmNlVmFsdWUpO1xyXG4gICAgICAgICAgfVxyXG4gICAgICAgICAgcmV0dXJuIFN0cmluZyhzb3VyY2VWYWx1ZSB8fCAnJyk7XHJcbiAgICAgICAgfVxyXG4gICAgICAgIHJldHVybiBTdHJpbmcoZXZlbnRSZWNvcmRbZi5pZFByb3BlcnR5XSB8fCAnJyk7XHJcbiAgICAgIH0pXHJcbiAgICAgIC5qb2luKCc6Jyk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBSZXNvbHZlIGRvdC1ub3RhdGlvbiByZWZlcmVuY2UgdmlhIEVudGl0eVJlc29sdmVyXHJcbiAgICovXHJcbiAgcHJpdmF0ZSByZXNvbHZlRG90Tm90YXRpb24oZXZlbnRSZWNvcmQ6IFJlY29yZDxzdHJpbmcsIHVua25vd24+LCBkb3ROb3RhdGlvbjogSURvdE5vdGF0aW9uKTogc3RyaW5nIHtcclxuICAgIGlmICghdGhpcy5lbnRpdHlSZXNvbHZlcikge1xyXG4gICAgICBjb25zb2xlLndhcm4oYEZpbHRlclRlbXBsYXRlOiBFbnRpdHlSZXNvbHZlciByZXF1aXJlZCBmb3IgZG90LW5vdGF0aW9uICcke2RvdE5vdGF0aW9uLmVudGl0eVR5cGV9LiR7ZG90Tm90YXRpb24ucHJvcGVydHl9J2ApO1xyXG4gICAgICByZXR1cm4gJyc7XHJcbiAgICB9XHJcblxyXG4gICAgLy8gR2V0IGZvcmVpZ24ga2V5IHZhbHVlIGZyb20gZXZlbnQgKGUuZy4sIHJlc291cmNlSWQpXHJcbiAgICBjb25zdCBmb3JlaWduSWQgPSBldmVudFJlY29yZFtkb3ROb3RhdGlvbi5mb3JlaWduS2V5XTtcclxuICAgIGlmICghZm9yZWlnbklkKSByZXR1cm4gJyc7XHJcblxyXG4gICAgLy8gUmVzb2x2ZSBlbnRpdHlcclxuICAgIGNvbnN0IGVudGl0eSA9IHRoaXMuZW50aXR5UmVzb2x2ZXIucmVzb2x2ZShkb3ROb3RhdGlvbi5lbnRpdHlUeXBlLCBTdHJpbmcoZm9yZWlnbklkKSk7XHJcbiAgICBpZiAoIWVudGl0eSkgcmV0dXJuICcnO1xyXG5cclxuICAgIC8vIFJldHVybiBwcm9wZXJ0eSB2YWx1ZSBmcm9tIGVudGl0eVxyXG4gICAgcmV0dXJuIFN0cmluZyhlbnRpdHlbZG90Tm90YXRpb24ucHJvcGVydHldIHx8ICcnKTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIE1hdGNoIGV2ZW50IG1vZCBrb2xvbm5lXHJcbiAgICovXHJcbiAgbWF0Y2hlcyhldmVudDogSUNhbGVuZGFyRXZlbnQsIGNvbHVtbjogSFRNTEVsZW1lbnQpOiBib29sZWFuIHtcclxuICAgIHJldHVybiB0aGlzLmJ1aWxkS2V5RnJvbUV2ZW50KGV2ZW50KSA9PT0gdGhpcy5idWlsZEtleUZyb21Db2x1bW4oY29sdW1uKTtcclxuICB9XHJcbn1cclxuIiwgImltcG9ydCB7IElSZW5kZXJlciwgSVJlbmRlckNvbnRleHQgfSBmcm9tICcuL0lHcm91cGluZ1JlbmRlcmVyJztcclxuaW1wb3J0IHsgYnVpbGRQaXBlbGluZSB9IGZyb20gJy4vUmVuZGVyQnVpbGRlcic7XHJcbmltcG9ydCB7IEV2ZW50UmVuZGVyZXIgfSBmcm9tICcuLi9mZWF0dXJlcy9ldmVudC9FdmVudFJlbmRlcmVyJztcclxuaW1wb3J0IHsgU2NoZWR1bGVSZW5kZXJlciB9IGZyb20gJy4uL2ZlYXR1cmVzL3NjaGVkdWxlL1NjaGVkdWxlUmVuZGVyZXInO1xyXG5pbXBvcnQgeyBIZWFkZXJEcmF3ZXJSZW5kZXJlciB9IGZyb20gJy4uL2ZlYXR1cmVzL2hlYWRlcmRyYXdlci9IZWFkZXJEcmF3ZXJSZW5kZXJlcic7XHJcbmltcG9ydCB7IFZpZXdDb25maWcsIEdyb3VwaW5nQ29uZmlnIH0gZnJvbSAnLi9WaWV3Q29uZmlnJztcclxuaW1wb3J0IHsgRmlsdGVyVGVtcGxhdGUgfSBmcm9tICcuL0ZpbHRlclRlbXBsYXRlJztcclxuaW1wb3J0IHsgRGF0ZVNlcnZpY2UgfSBmcm9tICcuL0RhdGVTZXJ2aWNlJztcclxuaW1wb3J0IHsgSUVudGl0eVNlcnZpY2UgfSBmcm9tICcuLi9zdG9yYWdlL0lFbnRpdHlTZXJ2aWNlJztcclxuaW1wb3J0IHsgSVN5bmMgfSBmcm9tICcuLi90eXBlcy9DYWxlbmRhclR5cGVzJztcclxuXHJcbmV4cG9ydCBjbGFzcyBDYWxlbmRhck9yY2hlc3RyYXRvciB7XHJcbiAgY29uc3RydWN0b3IoXHJcbiAgICBwcml2YXRlIGFsbFJlbmRlcmVyczogSVJlbmRlcmVyW10sXHJcbiAgICBwcml2YXRlIGV2ZW50UmVuZGVyZXI6IEV2ZW50UmVuZGVyZXIsXHJcbiAgICBwcml2YXRlIHNjaGVkdWxlUmVuZGVyZXI6IFNjaGVkdWxlUmVuZGVyZXIsXHJcbiAgICBwcml2YXRlIGhlYWRlckRyYXdlclJlbmRlcmVyOiBIZWFkZXJEcmF3ZXJSZW5kZXJlcixcclxuICAgIHByaXZhdGUgZGF0ZVNlcnZpY2U6IERhdGVTZXJ2aWNlLFxyXG4gICAgcHJpdmF0ZSBlbnRpdHlTZXJ2aWNlczogSUVudGl0eVNlcnZpY2U8SVN5bmM+W11cclxuICApIHt9XHJcblxyXG4gIGFzeW5jIHJlbmRlcih2aWV3Q29uZmlnOiBWaWV3Q29uZmlnLCBjb250YWluZXI6IEhUTUxFbGVtZW50KTogUHJvbWlzZTx2b2lkPiB7XHJcbiAgICBjb25zdCBoZWFkZXJDb250YWluZXIgPSBjb250YWluZXIucXVlcnlTZWxlY3Rvcignc3dwLWNhbGVuZGFyLWhlYWRlcicpIGFzIEhUTUxFbGVtZW50O1xyXG4gICAgY29uc3QgY29sdW1uQ29udGFpbmVyID0gY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3IoJ3N3cC1kYXktY29sdW1ucycpIGFzIEhUTUxFbGVtZW50O1xyXG4gICAgaWYgKCFoZWFkZXJDb250YWluZXIgfHwgIWNvbHVtbkNvbnRhaW5lcikge1xyXG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ01pc3Npbmcgc3dwLWNhbGVuZGFyLWhlYWRlciBvciBzd3AtZGF5LWNvbHVtbnMnKTtcclxuICAgIH1cclxuXHJcbiAgICAvLyBCeWcgZmlsdGVyIGZyYSB2aWV3Q29uZmlnXHJcbiAgICBjb25zdCBmaWx0ZXI6IFJlY29yZDxzdHJpbmcsIHN0cmluZ1tdPiA9IHt9O1xyXG4gICAgZm9yIChjb25zdCBncm91cGluZyBvZiB2aWV3Q29uZmlnLmdyb3VwaW5ncykge1xyXG4gICAgICBmaWx0ZXJbZ3JvdXBpbmcudHlwZV0gPSBncm91cGluZy52YWx1ZXM7XHJcbiAgICB9XHJcblxyXG4gICAgLy8gQnlnIEZpbHRlclRlbXBsYXRlIGZyYSB2aWV3Q29uZmlnIGdyb3VwaW5ncyAoa3VuIGRlIG1lZCBpZFByb3BlcnR5KVxyXG4gICAgY29uc3QgZmlsdGVyVGVtcGxhdGUgPSBuZXcgRmlsdGVyVGVtcGxhdGUodGhpcy5kYXRlU2VydmljZSk7XHJcbiAgICBmb3IgKGNvbnN0IGdyb3VwaW5nIG9mIHZpZXdDb25maWcuZ3JvdXBpbmdzKSB7XHJcbiAgICAgIGlmIChncm91cGluZy5pZFByb3BlcnR5KSB7XHJcbiAgICAgICAgZmlsdGVyVGVtcGxhdGUuYWRkRmllbGQoZ3JvdXBpbmcuaWRQcm9wZXJ0eSwgZ3JvdXBpbmcuZGVyaXZlZEZyb20pO1xyXG4gICAgICB9XHJcbiAgICB9XHJcblxyXG4gICAgLy8gUmVzb2x2ZSBiZWxvbmdzVG8gcmVsYXRpb25zIChlLmcuLCB0ZWFtLnJlc291cmNlSWRzKVxyXG4gICAgY29uc3QgeyBwYXJlbnRDaGlsZE1hcCwgY2hpbGRUeXBlIH0gPSBhd2FpdCB0aGlzLnJlc29sdmVCZWxvbmdzVG8odmlld0NvbmZpZy5ncm91cGluZ3MsIGZpbHRlcik7XHJcblxyXG4gICAgY29uc3QgY29udGV4dDogSVJlbmRlckNvbnRleHQgPSB7IGhlYWRlckNvbnRhaW5lciwgY29sdW1uQ29udGFpbmVyLCBmaWx0ZXIsIGdyb3VwaW5nczogdmlld0NvbmZpZy5ncm91cGluZ3MsIHBhcmVudENoaWxkTWFwLCBjaGlsZFR5cGUgfTtcclxuXHJcbiAgICAvLyBDbGVhclxyXG4gICAgaGVhZGVyQ29udGFpbmVyLmlubmVySFRNTCA9ICcnO1xyXG4gICAgY29sdW1uQ29udGFpbmVyLmlubmVySFRNTCA9ICcnO1xyXG5cclxuICAgIC8vIFNcdTAwRTZ0IGRhdGEtbGV2ZWxzIGF0dHJpYnV0IGZvciBDU1MgZ3JpZC1yb3cgc3R5bGluZ1xyXG4gICAgY29uc3QgbGV2ZWxzID0gdmlld0NvbmZpZy5ncm91cGluZ3MubWFwKGcgPT4gZy50eXBlKS5qb2luKCcgJyk7XHJcbiAgICBoZWFkZXJDb250YWluZXIuZGF0YXNldC5sZXZlbHMgPSBsZXZlbHM7XHJcblxyXG4gICAgLy8gVlx1MDBFNmxnIHJlbmRlcmVycyBiYXNlcmV0IHBcdTAwRTUgZ3JvdXBpbmdzIHR5cGVzXHJcbiAgICBjb25zdCBhY3RpdmVSZW5kZXJlcnMgPSB0aGlzLnNlbGVjdFJlbmRlcmVycyh2aWV3Q29uZmlnKTtcclxuXHJcbiAgICAvLyBCeWcgb2cga1x1MDBGOHIgcGlwZWxpbmVcclxuICAgIGNvbnN0IHBpcGVsaW5lID0gYnVpbGRQaXBlbGluZShhY3RpdmVSZW5kZXJlcnMpO1xyXG4gICAgYXdhaXQgcGlwZWxpbmUucnVuKGNvbnRleHQpO1xyXG5cclxuICAgIC8vIFJlbmRlciBzY2hlZHVsZSB1bmF2YWlsYWJsZSB6b25lcyAoZlx1MDBGOHIgZXZlbnRzKVxyXG4gICAgYXdhaXQgdGhpcy5zY2hlZHVsZVJlbmRlcmVyLnJlbmRlcihjb250YWluZXIsIGZpbHRlcik7XHJcblxyXG4gICAgLy8gUmVuZGVyIHRpbWVkIGV2ZW50cyBpbiBncmlkIChtZWQgZmlsdGVyVGVtcGxhdGUgdGlsIG1hdGNoaW5nKVxyXG4gICAgYXdhaXQgdGhpcy5ldmVudFJlbmRlcmVyLnJlbmRlcihjb250YWluZXIsIGZpbHRlciwgZmlsdGVyVGVtcGxhdGUpO1xyXG5cclxuICAgIC8vIFJlbmRlciBhbGxEYXkgZXZlbnRzIGluIGhlYWRlciBkcmF3ZXIgKG1lZCBmaWx0ZXJUZW1wbGF0ZSB0aWwgbWF0Y2hpbmcpXHJcbiAgICBhd2FpdCB0aGlzLmhlYWRlckRyYXdlclJlbmRlcmVyLnJlbmRlcihjb250YWluZXIsIGZpbHRlciwgZmlsdGVyVGVtcGxhdGUpO1xyXG4gIH1cclxuXHJcbiAgcHJpdmF0ZSBzZWxlY3RSZW5kZXJlcnModmlld0NvbmZpZzogVmlld0NvbmZpZyk6IElSZW5kZXJlcltdIHtcclxuICAgIGNvbnN0IHR5cGVzID0gdmlld0NvbmZpZy5ncm91cGluZ3MubWFwKGcgPT4gZy50eXBlKTtcclxuICAgIC8vIFNvcnRcdTAwRTlyIHJlbmRlcmVycyBpIHNhbW1lIHJcdTAwRTZra2VmXHUwMEY4bGdlIHNvbSB2aWV3Q29uZmlnLmdyb3VwaW5nc1xyXG4gICAgcmV0dXJuIHR5cGVzXHJcbiAgICAgIC5tYXAodHlwZSA9PiB0aGlzLmFsbFJlbmRlcmVycy5maW5kKHIgPT4gci50eXBlID09PSB0eXBlKSlcclxuICAgICAgLmZpbHRlcigocik6IHIgaXMgSVJlbmRlcmVyID0+IHIgIT09IHVuZGVmaW5lZCk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBSZXNvbHZlIGJlbG9uZ3NUbyByZWxhdGlvbnMgdG8gYnVpbGQgcGFyZW50LWNoaWxkIG1hcFxyXG4gICAqIGUuZy4sIGJlbG9uZ3NUbzogJ3RlYW0ucmVzb3VyY2VJZHMnIFx1MjE5MiB7IHRlYW0xOiBbJ0VNUDAwMScsICdFTVAwMDInXSwgdGVhbTI6IFsuLi5dIH1cclxuICAgKiBBbHNvIHJldHVybnMgdGhlIGNoaWxkVHlwZSAodGhlIGdyb3VwaW5nIHR5cGUgdGhhdCBoYXMgYmVsb25nc1RvKVxyXG4gICAqL1xyXG4gIHByaXZhdGUgYXN5bmMgcmVzb2x2ZUJlbG9uZ3NUbyhcclxuICAgIGdyb3VwaW5nczogR3JvdXBpbmdDb25maWdbXSxcclxuICAgIGZpbHRlcjogUmVjb3JkPHN0cmluZywgc3RyaW5nW10+XHJcbiAgKTogUHJvbWlzZTx7IHBhcmVudENoaWxkTWFwPzogUmVjb3JkPHN0cmluZywgc3RyaW5nW10+OyBjaGlsZFR5cGU/OiBzdHJpbmcgfT4ge1xyXG4gICAgLy8gRmluZCBncm91cGluZyB3aXRoIGJlbG9uZ3NUb1xyXG4gICAgY29uc3QgY2hpbGRHcm91cGluZyA9IGdyb3VwaW5ncy5maW5kKGcgPT4gZy5iZWxvbmdzVG8pO1xyXG4gICAgaWYgKCFjaGlsZEdyb3VwaW5nPy5iZWxvbmdzVG8pIHJldHVybiB7fTtcclxuXHJcbiAgICAvLyBQYXJzZSBiZWxvbmdzVG86ICd0ZWFtLnJlc291cmNlSWRzJ1xyXG4gICAgY29uc3QgW2VudGl0eVR5cGUsIHByb3BlcnR5XSA9IGNoaWxkR3JvdXBpbmcuYmVsb25nc1RvLnNwbGl0KCcuJyk7XHJcbiAgICBpZiAoIWVudGl0eVR5cGUgfHwgIXByb3BlcnR5KSByZXR1cm4ge307XHJcblxyXG4gICAgLy8gR2V0IHBhcmVudCBJRHMgZnJvbSBmaWx0ZXJcclxuICAgIGNvbnN0IHBhcmVudElkcyA9IGZpbHRlcltlbnRpdHlUeXBlXSB8fCBbXTtcclxuICAgIGlmIChwYXJlbnRJZHMubGVuZ3RoID09PSAwKSByZXR1cm4ge307XHJcblxyXG4gICAgLy8gRmluZCBzZXJ2aWNlIGR5bmFtaXNrIGJhc2VyZXQgcFx1MDBFNSBlbnRpdHlUeXBlIChpbmdlbiBoYXJkY29kZWQgdHlwZSBjaGVjaylcclxuICAgIGNvbnN0IHNlcnZpY2UgPSB0aGlzLmVudGl0eVNlcnZpY2VzLmZpbmQocyA9PlxyXG4gICAgICBzLmVudGl0eVR5cGUudG9Mb3dlckNhc2UoKSA9PT0gZW50aXR5VHlwZVxyXG4gICAgKTtcclxuICAgIGlmICghc2VydmljZSkgcmV0dXJuIHt9O1xyXG5cclxuICAgIC8vIEhlbnQgYWxsZSBlbnRpdGllcyBvZyBmaWx0cmVyIHBcdTAwRTUgcGFyZW50SWRzXHJcbiAgICBjb25zdCBhbGxFbnRpdGllcyA9IGF3YWl0IHNlcnZpY2UuZ2V0QWxsKCk7XHJcbiAgICBjb25zdCBlbnRpdGllcyA9IGFsbEVudGl0aWVzLmZpbHRlcihlID0+XHJcbiAgICAgIHBhcmVudElkcy5pbmNsdWRlcygoZSBhcyB1bmtub3duIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+KS5pZCBhcyBzdHJpbmcpXHJcbiAgICApO1xyXG5cclxuICAgIC8vIEJ5ZyBwYXJlbnQtY2hpbGQgbWFwXHJcbiAgICBjb25zdCBtYXA6IFJlY29yZDxzdHJpbmcsIHN0cmluZ1tdPiA9IHt9O1xyXG4gICAgZm9yIChjb25zdCBlbnRpdHkgb2YgZW50aXRpZXMpIHtcclxuICAgICAgY29uc3QgZW50aXR5UmVjb3JkID0gZW50aXR5IGFzIHVua25vd24gYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj47XHJcbiAgICAgIGNvbnN0IGNoaWxkcmVuID0gKGVudGl0eVJlY29yZFtwcm9wZXJ0eV0gYXMgc3RyaW5nW10pIHx8IFtdO1xyXG4gICAgICBtYXBbZW50aXR5UmVjb3JkLmlkIGFzIHN0cmluZ10gPSBjaGlsZHJlbjtcclxuICAgIH1cclxuXHJcbiAgICByZXR1cm4geyBwYXJlbnRDaGlsZE1hcDogbWFwLCBjaGlsZFR5cGU6IGNoaWxkR3JvdXBpbmcudHlwZSB9O1xyXG4gIH1cclxufVxyXG4iLCAiZXhwb3J0IGNsYXNzIE5hdmlnYXRpb25BbmltYXRvciB7XHJcbiAgY29uc3RydWN0b3IoXHJcbiAgICBwcml2YXRlIGhlYWRlclRyYWNrOiBIVE1MRWxlbWVudCxcclxuICAgIHByaXZhdGUgY29udGVudFRyYWNrOiBIVE1MRWxlbWVudCxcclxuICAgIHByaXZhdGUgaGVhZGVyRHJhd2VyOiBIVE1MRWxlbWVudCB8IG51bGxcclxuICApIHt9XHJcblxyXG4gIGFzeW5jIHNsaWRlKGRpcmVjdGlvbjogJ2xlZnQnIHwgJ3JpZ2h0JywgcmVuZGVyRm46ICgpID0+IFByb21pc2U8dm9pZD4pOiBQcm9taXNlPHZvaWQ+IHtcclxuICAgIGNvbnN0IG91dCA9IGRpcmVjdGlvbiA9PT0gJ2xlZnQnID8gJy0xMDAlJyA6ICcxMDAlJztcclxuICAgIGNvbnN0IGludG8gPSBkaXJlY3Rpb24gPT09ICdsZWZ0JyA/ICcxMDAlJyA6ICctMTAwJSc7XHJcblxyXG4gICAgYXdhaXQgdGhpcy5hbmltYXRlT3V0KG91dCk7XHJcbiAgICBhd2FpdCByZW5kZXJGbigpO1xyXG4gICAgYXdhaXQgdGhpcy5hbmltYXRlSW4oaW50byk7XHJcbiAgfVxyXG5cclxuICBwcml2YXRlIGFzeW5jIGFuaW1hdGVPdXQodHJhbnNsYXRlOiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcclxuICAgIGNvbnN0IGFuaW1hdGlvbnMgPSBbXHJcbiAgICAgIHRoaXMuaGVhZGVyVHJhY2suYW5pbWF0ZShcclxuICAgICAgICBbeyB0cmFuc2Zvcm06ICd0cmFuc2xhdGVYKDApJyB9LCB7IHRyYW5zZm9ybTogYHRyYW5zbGF0ZVgoJHt0cmFuc2xhdGV9KWAgfV0sXHJcbiAgICAgICAgeyBkdXJhdGlvbjogMjAwLCBlYXNpbmc6ICdlYXNlLWluJyB9XHJcbiAgICAgICkuZmluaXNoZWQsXHJcbiAgICAgIHRoaXMuY29udGVudFRyYWNrLmFuaW1hdGUoXHJcbiAgICAgICAgW3sgdHJhbnNmb3JtOiAndHJhbnNsYXRlWCgwKScgfSwgeyB0cmFuc2Zvcm06IGB0cmFuc2xhdGVYKCR7dHJhbnNsYXRlfSlgIH1dLFxyXG4gICAgICAgIHsgZHVyYXRpb246IDIwMCwgZWFzaW5nOiAnZWFzZS1pbicgfVxyXG4gICAgICApLmZpbmlzaGVkXHJcbiAgICBdO1xyXG5cclxuICAgIGlmICh0aGlzLmhlYWRlckRyYXdlcikge1xyXG4gICAgICBhbmltYXRpb25zLnB1c2goXHJcbiAgICAgICAgdGhpcy5oZWFkZXJEcmF3ZXIuYW5pbWF0ZShcclxuICAgICAgICAgIFt7IHRyYW5zZm9ybTogJ3RyYW5zbGF0ZVgoMCknIH0sIHsgdHJhbnNmb3JtOiBgdHJhbnNsYXRlWCgke3RyYW5zbGF0ZX0pYCB9XSxcclxuICAgICAgICAgIHsgZHVyYXRpb246IDIwMCwgZWFzaW5nOiAnZWFzZS1pbicgfVxyXG4gICAgICAgICkuZmluaXNoZWRcclxuICAgICAgKTtcclxuICAgIH1cclxuXHJcbiAgICBhd2FpdCBQcm9taXNlLmFsbChhbmltYXRpb25zKTtcclxuICB9XHJcblxyXG4gIHByaXZhdGUgYXN5bmMgYW5pbWF0ZUluKHRyYW5zbGF0ZTogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XHJcbiAgICBjb25zdCBhbmltYXRpb25zID0gW1xyXG4gICAgICB0aGlzLmhlYWRlclRyYWNrLmFuaW1hdGUoXHJcbiAgICAgICAgW3sgdHJhbnNmb3JtOiBgdHJhbnNsYXRlWCgke3RyYW5zbGF0ZX0pYCB9LCB7IHRyYW5zZm9ybTogJ3RyYW5zbGF0ZVgoMCknIH1dLFxyXG4gICAgICAgIHsgZHVyYXRpb246IDIwMCwgZWFzaW5nOiAnZWFzZS1vdXQnIH1cclxuICAgICAgKS5maW5pc2hlZCxcclxuICAgICAgdGhpcy5jb250ZW50VHJhY2suYW5pbWF0ZShcclxuICAgICAgICBbeyB0cmFuc2Zvcm06IGB0cmFuc2xhdGVYKCR7dHJhbnNsYXRlfSlgIH0sIHsgdHJhbnNmb3JtOiAndHJhbnNsYXRlWCgwKScgfV0sXHJcbiAgICAgICAgeyBkdXJhdGlvbjogMjAwLCBlYXNpbmc6ICdlYXNlLW91dCcgfVxyXG4gICAgICApLmZpbmlzaGVkXHJcbiAgICBdO1xyXG5cclxuICAgIGlmICh0aGlzLmhlYWRlckRyYXdlcikge1xyXG4gICAgICBhbmltYXRpb25zLnB1c2goXHJcbiAgICAgICAgdGhpcy5oZWFkZXJEcmF3ZXIuYW5pbWF0ZShcclxuICAgICAgICAgIFt7IHRyYW5zZm9ybTogYHRyYW5zbGF0ZVgoJHt0cmFuc2xhdGV9KWAgfSwgeyB0cmFuc2Zvcm06ICd0cmFuc2xhdGVYKDApJyB9XSxcclxuICAgICAgICAgIHsgZHVyYXRpb246IDIwMCwgZWFzaW5nOiAnZWFzZS1vdXQnIH1cclxuICAgICAgICApLmZpbmlzaGVkXHJcbiAgICAgICk7XHJcbiAgICB9XHJcblxyXG4gICAgYXdhaXQgUHJvbWlzZS5hbGwoYW5pbWF0aW9ucyk7XHJcbiAgfVxyXG59XHJcbiIsICJpbXBvcnQgeyBJUmVuZGVyZXIsIElSZW5kZXJDb250ZXh0IH0gZnJvbSAnLi4vLi4vY29yZS9JR3JvdXBpbmdSZW5kZXJlcic7XHJcbmltcG9ydCB7IERhdGVTZXJ2aWNlIH0gZnJvbSAnLi4vLi4vY29yZS9EYXRlU2VydmljZSc7XHJcblxyXG5leHBvcnQgY2xhc3MgRGF0ZVJlbmRlcmVyIGltcGxlbWVudHMgSVJlbmRlcmVyIHtcclxuICByZWFkb25seSB0eXBlID0gJ2RhdGUnO1xyXG5cclxuICBjb25zdHJ1Y3Rvcihwcml2YXRlIGRhdGVTZXJ2aWNlOiBEYXRlU2VydmljZSkge31cclxuXHJcbiAgcmVuZGVyKGNvbnRleHQ6IElSZW5kZXJDb250ZXh0KTogdm9pZCB7XHJcbiAgICBjb25zdCBkYXRlcyA9IGNvbnRleHQuZmlsdGVyWydkYXRlJ10gfHwgW107XHJcbiAgICBjb25zdCByZXNvdXJjZUlkcyA9IGNvbnRleHQuZmlsdGVyWydyZXNvdXJjZSddIHx8IFtdO1xyXG5cclxuICAgIC8vIENoZWNrIGlmIGRhdGUgaGVhZGVycyBzaG91bGQgYmUgaGlkZGVuIChlLmcuLCBpbiBkYXkgdmlldylcclxuICAgIGNvbnN0IGRhdGVHcm91cGluZyA9IGNvbnRleHQuZ3JvdXBpbmdzPy5maW5kKGcgPT4gZy50eXBlID09PSAnZGF0ZScpO1xyXG4gICAgY29uc3QgaGlkZUhlYWRlciA9IGRhdGVHcm91cGluZz8uaGlkZUhlYWRlciA9PT0gdHJ1ZTtcclxuXHJcbiAgICAvLyBSZW5kZXIgZGF0ZXMgZm9yIEhWRVIgcmVzb3VyY2UgKGVsbGVyIDEgZ2FuZyBodmlzIGluZ2VuIHJlc291cmNlcylcclxuICAgIGNvbnN0IGl0ZXJhdGlvbnMgPSByZXNvdXJjZUlkcy5sZW5ndGggfHwgMTtcclxuICAgIGxldCBjb2x1bW5Db3VudCA9IDA7XHJcblxyXG4gICAgZm9yIChsZXQgciA9IDA7IHIgPCBpdGVyYXRpb25zOyByKyspIHtcclxuICAgICAgY29uc3QgcmVzb3VyY2VJZCA9IHJlc291cmNlSWRzW3JdOyAvLyB1bmRlZmluZWQgaHZpcyBpbmdlbiByZXNvdXJjZXNcclxuXHJcbiAgICAgIGZvciAoY29uc3QgZGF0ZVN0ciBvZiBkYXRlcykge1xyXG4gICAgICAgIGNvbnN0IGRhdGUgPSB0aGlzLmRhdGVTZXJ2aWNlLnBhcnNlSVNPKGRhdGVTdHIpO1xyXG5cclxuICAgICAgICAvLyBCdWlsZCBjb2x1bW5LZXkgZm9yIHVuaWZvcm0gaWRlbnRpZmljYXRpb25cclxuICAgICAgICBjb25zdCBzZWdtZW50czogUmVjb3JkPHN0cmluZywgc3RyaW5nPiA9IHsgZGF0ZTogZGF0ZVN0ciB9O1xyXG4gICAgICAgIGlmIChyZXNvdXJjZUlkKSBzZWdtZW50cy5yZXNvdXJjZSA9IHJlc291cmNlSWQ7XHJcbiAgICAgICAgY29uc3QgY29sdW1uS2V5ID0gdGhpcy5kYXRlU2VydmljZS5idWlsZENvbHVtbktleShzZWdtZW50cyk7XHJcblxyXG4gICAgICAgIC8vIEhlYWRlclxyXG4gICAgICAgIGNvbnN0IGhlYWRlciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3N3cC1kYXktaGVhZGVyJyk7XHJcbiAgICAgICAgaGVhZGVyLmRhdGFzZXQuZGF0ZSA9IGRhdGVTdHI7XHJcbiAgICAgICAgaGVhZGVyLmRhdGFzZXQuY29sdW1uS2V5ID0gY29sdW1uS2V5O1xyXG4gICAgICAgIGlmIChyZXNvdXJjZUlkKSB7XHJcbiAgICAgICAgICBoZWFkZXIuZGF0YXNldC5yZXNvdXJjZUlkID0gcmVzb3VyY2VJZDtcclxuICAgICAgICB9XHJcbiAgICAgICAgaWYgKGhpZGVIZWFkZXIpIHtcclxuICAgICAgICAgIGhlYWRlci5kYXRhc2V0LmhpZGRlbiA9ICd0cnVlJztcclxuICAgICAgICB9XHJcbiAgICAgICAgaGVhZGVyLmlubmVySFRNTCA9IGBcclxuICAgICAgICAgIDxzd3AtZGF5LW5hbWU+JHt0aGlzLmRhdGVTZXJ2aWNlLmdldERheU5hbWUoZGF0ZSwgJ3Nob3J0Jyl9PC9zd3AtZGF5LW5hbWU+XHJcbiAgICAgICAgICA8c3dwLWRheS1kYXRlPiR7ZGF0ZS5nZXREYXRlKCl9PC9zd3AtZGF5LWRhdGU+XHJcbiAgICAgICAgYDtcclxuICAgICAgICBjb250ZXh0LmhlYWRlckNvbnRhaW5lci5hcHBlbmRDaGlsZChoZWFkZXIpO1xyXG5cclxuICAgICAgICAvLyBDb2x1bW5cclxuICAgICAgICBjb25zdCBjb2x1bW4gPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzd3AtZGF5LWNvbHVtbicpO1xyXG4gICAgICAgIGNvbHVtbi5kYXRhc2V0LmRhdGUgPSBkYXRlU3RyO1xyXG4gICAgICAgIGNvbHVtbi5kYXRhc2V0LmNvbHVtbktleSA9IGNvbHVtbktleTtcclxuICAgICAgICBpZiAocmVzb3VyY2VJZCkge1xyXG4gICAgICAgICAgY29sdW1uLmRhdGFzZXQucmVzb3VyY2VJZCA9IHJlc291cmNlSWQ7XHJcbiAgICAgICAgfVxyXG4gICAgICAgIGNvbHVtbi5pbm5lckhUTUwgPSAnPHN3cC1ldmVudHMtbGF5ZXI+PC9zd3AtZXZlbnRzLWxheWVyPic7XHJcbiAgICAgICAgY29udGV4dC5jb2x1bW5Db250YWluZXIuYXBwZW5kQ2hpbGQoY29sdW1uKTtcclxuXHJcbiAgICAgICAgY29sdW1uQ291bnQrKztcclxuICAgICAgfVxyXG4gICAgfVxyXG5cclxuICAgIC8vIFNldCBncmlkIGNvbHVtbnMgb24gY29udGFpbmVyXHJcbiAgICBjb25zdCBjb250YWluZXIgPSBjb250ZXh0LmNvbHVtbkNvbnRhaW5lci5jbG9zZXN0KCdzd3AtY2FsZW5kYXItY29udGFpbmVyJyk7XHJcbiAgICBpZiAoY29udGFpbmVyKSB7XHJcbiAgICAgIChjb250YWluZXIgYXMgSFRNTEVsZW1lbnQpLnN0eWxlLnNldFByb3BlcnR5KCctLWdyaWQtY29sdW1ucycsIFN0cmluZyhjb2x1bW5Db3VudCkpO1xyXG4gICAgfVxyXG4gIH1cclxufVxyXG4iLCAiaW1wb3J0IGRheWpzIGZyb20gJ2RheWpzJztcclxuaW1wb3J0IHV0YyBmcm9tICdkYXlqcy9wbHVnaW4vdXRjJztcclxuaW1wb3J0IHRpbWV6b25lIGZyb20gJ2RheWpzL3BsdWdpbi90aW1lem9uZSc7XHJcbmltcG9ydCBpc29XZWVrIGZyb20gJ2RheWpzL3BsdWdpbi9pc29XZWVrJztcclxuaW1wb3J0IHsgSVRpbWVGb3JtYXRDb25maWcgfSBmcm9tICcuL0lUaW1lRm9ybWF0Q29uZmlnJztcclxuXHJcbi8vIEVuYWJsZSBkYXlqcyBwbHVnaW5zXHJcbmRheWpzLmV4dGVuZCh1dGMpO1xyXG5kYXlqcy5leHRlbmQodGltZXpvbmUpO1xyXG5kYXlqcy5leHRlbmQoaXNvV2Vlayk7XHJcblxyXG5leHBvcnQgY2xhc3MgRGF0ZVNlcnZpY2Uge1xyXG4gIHByaXZhdGUgdGltZXpvbmU6IHN0cmluZztcclxuICBwcml2YXRlIGJhc2VEYXRlOiBkYXlqcy5EYXlqcztcclxuXHJcbiAgY29uc3RydWN0b3IocHJpdmF0ZSBjb25maWc6IElUaW1lRm9ybWF0Q29uZmlnLCBiYXNlRGF0ZT86IERhdGUpIHtcclxuICAgIHRoaXMudGltZXpvbmUgPSBjb25maWcudGltZXpvbmU7XHJcbiAgICAvLyBBbGxvdyBzZXR0aW5nIGEgZml4ZWQgYmFzZSBkYXRlIGZvciBkZW1vL3Rlc3RpbmcgcHVycG9zZXNcclxuICAgIHRoaXMuYmFzZURhdGUgPSBiYXNlRGF0ZSA/IGRheWpzKGJhc2VEYXRlKSA6IGRheWpzKCk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBTZXQgYSBmaXhlZCBiYXNlIGRhdGUgKHVzZWZ1bCBmb3IgZGVtb3Mgd2l0aCBzdGF0aWMgbW9jayBkYXRhKVxyXG4gICAqL1xyXG4gIHNldEJhc2VEYXRlKGRhdGU6IERhdGUpOiB2b2lkIHtcclxuICAgIHRoaXMuYmFzZURhdGUgPSBkYXlqcyhkYXRlKTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIEdldCB0aGUgY3VycmVudCBiYXNlIGRhdGUgKGVpdGhlciBmaXhlZCBvciB0b2RheSlcclxuICAgKi9cclxuICBnZXRCYXNlRGF0ZSgpOiBEYXRlIHtcclxuICAgIHJldHVybiB0aGlzLmJhc2VEYXRlLnRvRGF0ZSgpO1xyXG4gIH1cclxuXHJcbiAgcGFyc2VJU08oaXNvU3RyaW5nOiBzdHJpbmcpOiBEYXRlIHtcclxuICAgIHJldHVybiBkYXlqcyhpc29TdHJpbmcpLnRvRGF0ZSgpO1xyXG4gIH1cclxuXHJcbiAgZ2V0RGF5TmFtZShkYXRlOiBEYXRlLCBmb3JtYXQ6ICdzaG9ydCcgfCAnbG9uZycgPSAnc2hvcnQnKTogc3RyaW5nIHtcclxuICAgIHJldHVybiBuZXcgSW50bC5EYXRlVGltZUZvcm1hdCh0aGlzLmNvbmZpZy5sb2NhbGUsIHsgd2Vla2RheTogZm9ybWF0IH0pLmZvcm1hdChkYXRlKTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIEdldCBkYXRlcyBzdGFydGluZyBmcm9tIGEgZGF5IG9mZnNldFxyXG4gICAqIEBwYXJhbSBkYXlPZmZzZXQgLSBEYXkgb2Zmc2V0IGZyb20gYmFzZSBkYXRlXHJcbiAgICogQHBhcmFtIGNvdW50IC0gTnVtYmVyIG9mIGNvbnNlY3V0aXZlIGRheXMgdG8gcmV0dXJuXHJcbiAgICogQHJldHVybnMgQXJyYXkgb2YgZGF0ZSBzdHJpbmdzIGluIFlZWVktTU0tREQgZm9ybWF0XHJcbiAgICovXHJcbiAgZ2V0RGF0ZXNGcm9tT2Zmc2V0KGRheU9mZnNldDogbnVtYmVyLCBjb3VudDogbnVtYmVyKTogc3RyaW5nW10ge1xyXG4gICAgY29uc3Qgc3RhcnREYXRlID0gdGhpcy5iYXNlRGF0ZS5hZGQoZGF5T2Zmc2V0LCAnZGF5Jyk7XHJcbiAgICByZXR1cm4gQXJyYXkuZnJvbSh7IGxlbmd0aDogY291bnQgfSwgKF8sIGkpID0+XHJcbiAgICAgIHN0YXJ0RGF0ZS5hZGQoaSwgJ2RheScpLmZvcm1hdCgnWVlZWS1NTS1ERCcpXHJcbiAgICApO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogR2V0IHNwZWNpZmljIHdlZWtkYXlzIGZyb20gdGhlIHdlZWsgY29udGFpbmluZyB0aGUgb2Zmc2V0IGRhdGVcclxuICAgKiBAcGFyYW0gZGF5T2Zmc2V0IC0gRGF5IG9mZnNldCBmcm9tIGJhc2UgZGF0ZVxyXG4gICAqIEBwYXJhbSB3b3JrRGF5cyAtIEFycmF5IG9mIElTTyB3ZWVrZGF5IG51bWJlcnMgKDE9TW9uZGF5LCA3PVN1bmRheSlcclxuICAgKiBAcmV0dXJucyBBcnJheSBvZiBkYXRlIHN0cmluZ3MgaW4gWVlZWS1NTS1ERCBmb3JtYXRcclxuICAgKi9cclxuICBnZXRXb3JrRGF5c0Zyb21PZmZzZXQoZGF5T2Zmc2V0OiBudW1iZXIsIHdvcmtEYXlzOiBudW1iZXJbXSk6IHN0cmluZ1tdIHtcclxuICAgIC8vIEdldCB0aGUgZGF0ZSBhdCBvZmZzZXQsIHRoZW4gZmluZCBpdHMgd2VlaydzIE1vbmRheVxyXG4gICAgY29uc3QgdGFyZ2V0RGF0ZSA9IHRoaXMuYmFzZURhdGUuYWRkKGRheU9mZnNldCwgJ2RheScpO1xyXG4gICAgY29uc3QgbW9uZGF5ID0gdGFyZ2V0RGF0ZS5zdGFydE9mKCd3ZWVrJykuYWRkKDEsICdkYXknKTtcclxuXHJcbiAgICByZXR1cm4gd29ya0RheXMubWFwKGlzb0RheSA9PiB7XHJcbiAgICAgIC8vIElTTzogMT1Nb25kYXksIDc9U3VuZGF5IFx1MjE5MiBkYXlzIGZyb20gTW9uZGF5OiAwLTZcclxuICAgICAgY29uc3QgZGF5c0Zyb21Nb25kYXkgPSBpc29EYXkgPT09IDcgPyA2IDogaXNvRGF5IC0gMTtcclxuICAgICAgcmV0dXJuIG1vbmRheS5hZGQoZGF5c0Zyb21Nb25kYXksICdkYXknKS5mb3JtYXQoJ1lZWVktTU0tREQnKTtcclxuICAgIH0pO1xyXG4gIH1cclxuXHJcbiAgLy8gTGVnYWN5IG1ldGhvZHMgZm9yIGJhY2t3YXJkcyBjb21wYXRpYmlsaXR5XHJcbiAgZ2V0V2Vla0RhdGVzKHdlZWtPZmZzZXQgPSAwLCBkYXlzID0gNyk6IHN0cmluZ1tdIHtcclxuICAgIHJldHVybiB0aGlzLmdldERhdGVzRnJvbU9mZnNldCh3ZWVrT2Zmc2V0ICogNywgZGF5cyk7XHJcbiAgfVxyXG5cclxuICBnZXRXb3JrV2Vla0RhdGVzKHdlZWtPZmZzZXQ6IG51bWJlciwgd29ya0RheXM6IG51bWJlcltdKTogc3RyaW5nW10ge1xyXG4gICAgcmV0dXJuIHRoaXMuZ2V0V29ya0RheXNGcm9tT2Zmc2V0KHdlZWtPZmZzZXQgKiA3LCB3b3JrRGF5cyk7XHJcbiAgfVxyXG5cclxuICAvLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxyXG4gIC8vIEZPUk1BVFRJTkdcclxuICAvLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxyXG5cclxuICBmb3JtYXRUaW1lKGRhdGU6IERhdGUsIHNob3dTZWNvbmRzID0gZmFsc2UpOiBzdHJpbmcge1xyXG4gICAgY29uc3QgcGF0dGVybiA9IHNob3dTZWNvbmRzID8gJ0hIOm1tOnNzJyA6ICdISDptbSc7XHJcbiAgICByZXR1cm4gZGF5anMoZGF0ZSkuZm9ybWF0KHBhdHRlcm4pO1xyXG4gIH1cclxuXHJcbiAgZm9ybWF0VGltZVJhbmdlKHN0YXJ0OiBEYXRlLCBlbmQ6IERhdGUpOiBzdHJpbmcge1xyXG4gICAgcmV0dXJuIGAke3RoaXMuZm9ybWF0VGltZShzdGFydCl9IC0gJHt0aGlzLmZvcm1hdFRpbWUoZW5kKX1gO1xyXG4gIH1cclxuXHJcbiAgZm9ybWF0RGF0ZShkYXRlOiBEYXRlKTogc3RyaW5nIHtcclxuICAgIHJldHVybiBkYXlqcyhkYXRlKS5mb3JtYXQoJ1lZWVktTU0tREQnKTtcclxuICB9XHJcblxyXG4gIGdldERhdGVLZXkoZGF0ZTogRGF0ZSk6IHN0cmluZyB7XHJcbiAgICByZXR1cm4gdGhpcy5mb3JtYXREYXRlKGRhdGUpO1xyXG4gIH1cclxuXHJcbiAgLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cclxuICAvLyBDT0xVTU4gS0VZXHJcbiAgLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cclxuXHJcbiAgLyoqXHJcbiAgICogQnVpbGQgYSB1bmlmb3JtIGNvbHVtbktleSBmcm9tIGdyb3VwaW5nIHNlZ21lbnRzXHJcbiAgICogSGFuZGxlcyBhbnkgY29tYmluYXRpb24gb2YgZGF0ZSwgcmVzb3VyY2UsIHRlYW0sIGV0Yy5cclxuICAgKlxyXG4gICAqIEBleGFtcGxlXHJcbiAgICogYnVpbGRDb2x1bW5LZXkoeyBkYXRlOiAnMjAyNS0xMi0wOScgfSkgXHUyMTkyIFwiMjAyNS0xMi0wOVwiXHJcbiAgICogYnVpbGRDb2x1bW5LZXkoeyBkYXRlOiAnMjAyNS0xMi0wOScsIHJlc291cmNlOiAnRU1QMDAxJyB9KSBcdTIxOTIgXCIyMDI1LTEyLTA5OkVNUDAwMVwiXHJcbiAgICovXHJcbiAgYnVpbGRDb2x1bW5LZXkoc2VnbWVudHM6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4pOiBzdHJpbmcge1xyXG4gICAgLy8gQWx3YXlzIHB1dCBkYXRlIGZpcnN0IGlmIHByZXNlbnQsIHRoZW4gb3RoZXIgc2VnbWVudHMgYWxwaGFiZXRpY2FsbHlcclxuICAgIGNvbnN0IGRhdGUgPSBzZWdtZW50cy5kYXRlO1xyXG4gICAgY29uc3Qgb3RoZXJzID0gT2JqZWN0LmVudHJpZXMoc2VnbWVudHMpXHJcbiAgICAgIC5maWx0ZXIoKFtrXSkgPT4gayAhPT0gJ2RhdGUnKVxyXG4gICAgICAuc29ydCgoW2FdLCBbYl0pID0+IGEubG9jYWxlQ29tcGFyZShiKSlcclxuICAgICAgLm1hcCgoWywgdl0pID0+IHYpO1xyXG5cclxuICAgIHJldHVybiBkYXRlID8gW2RhdGUsIC4uLm90aGVyc10uam9pbignOicpIDogb3RoZXJzLmpvaW4oJzonKTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIFBhcnNlIGEgY29sdW1uS2V5IGJhY2sgaW50byBzZWdtZW50c1xyXG4gICAqIEFzc3VtZXMgZm9ybWF0OiBcImRhdGU6cmVzb3VyY2U6Li4uXCIgb3IganVzdCBcImRhdGVcIlxyXG4gICAqL1xyXG4gIHBhcnNlQ29sdW1uS2V5KGNvbHVtbktleTogc3RyaW5nKTogeyBkYXRlOiBzdHJpbmc7IHJlc291cmNlPzogc3RyaW5nIH0ge1xyXG4gICAgY29uc3QgcGFydHMgPSBjb2x1bW5LZXkuc3BsaXQoJzonKTtcclxuICAgIHJldHVybiB7XHJcbiAgICAgIGRhdGU6IHBhcnRzWzBdLFxyXG4gICAgICByZXNvdXJjZTogcGFydHNbMV1cclxuICAgIH07XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBFeHRyYWN0IGRhdGVLZXkgZnJvbSBjb2x1bW5LZXkgKGZpcnN0IHNlZ21lbnQpXHJcbiAgICovXHJcbiAgZ2V0RGF0ZUZyb21Db2x1bW5LZXkoY29sdW1uS2V5OiBzdHJpbmcpOiBzdHJpbmcge1xyXG4gICAgcmV0dXJuIGNvbHVtbktleS5zcGxpdCgnOicpWzBdO1xyXG4gIH1cclxuXHJcbiAgLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cclxuICAvLyBUSU1FIENBTENVTEFUSU9OU1xyXG4gIC8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XHJcblxyXG4gIHRpbWVUb01pbnV0ZXModGltZVN0cmluZzogc3RyaW5nKTogbnVtYmVyIHtcclxuICAgIGNvbnN0IHBhcnRzID0gdGltZVN0cmluZy5zcGxpdCgnOicpLm1hcChOdW1iZXIpO1xyXG4gICAgY29uc3QgaG91cnMgPSBwYXJ0c1swXSB8fCAwO1xyXG4gICAgY29uc3QgbWludXRlcyA9IHBhcnRzWzFdIHx8IDA7XHJcbiAgICByZXR1cm4gaG91cnMgKiA2MCArIG1pbnV0ZXM7XHJcbiAgfVxyXG5cclxuICBtaW51dGVzVG9UaW1lKHRvdGFsTWludXRlczogbnVtYmVyKTogc3RyaW5nIHtcclxuICAgIGNvbnN0IGhvdXJzID0gTWF0aC5mbG9vcih0b3RhbE1pbnV0ZXMgLyA2MCk7XHJcbiAgICBjb25zdCBtaW51dGVzID0gdG90YWxNaW51dGVzICUgNjA7XHJcbiAgICByZXR1cm4gZGF5anMoKS5ob3VyKGhvdXJzKS5taW51dGUobWludXRlcykuZm9ybWF0KCdISDptbScpO1xyXG4gIH1cclxuXHJcbiAgZ2V0TWludXRlc1NpbmNlTWlkbmlnaHQoZGF0ZTogRGF0ZSk6IG51bWJlciB7XHJcbiAgICBjb25zdCBkID0gZGF5anMoZGF0ZSk7XHJcbiAgICByZXR1cm4gZC5ob3VyKCkgKiA2MCArIGQubWludXRlKCk7XHJcbiAgfVxyXG5cclxuICAvLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxyXG4gIC8vIFVUQyBDT05WRVJTSU9OU1xyXG4gIC8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XHJcblxyXG4gIHRvVVRDKGxvY2FsRGF0ZTogRGF0ZSk6IHN0cmluZyB7XHJcbiAgICByZXR1cm4gZGF5anMudHoobG9jYWxEYXRlLCB0aGlzLnRpbWV6b25lKS51dGMoKS50b0lTT1N0cmluZygpO1xyXG4gIH1cclxuXHJcbiAgZnJvbVVUQyh1dGNTdHJpbmc6IHN0cmluZyk6IERhdGUge1xyXG4gICAgcmV0dXJuIGRheWpzLnV0Yyh1dGNTdHJpbmcpLnR6KHRoaXMudGltZXpvbmUpLnRvRGF0ZSgpO1xyXG4gIH1cclxuXHJcbiAgLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cclxuICAvLyBEQVRFIENSRUFUSU9OXHJcbiAgLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cclxuXHJcbiAgY3JlYXRlRGF0ZUF0VGltZShiYXNlRGF0ZTogRGF0ZSB8IHN0cmluZywgdGltZVN0cmluZzogc3RyaW5nKTogRGF0ZSB7XHJcbiAgICBjb25zdCB0b3RhbE1pbnV0ZXMgPSB0aGlzLnRpbWVUb01pbnV0ZXModGltZVN0cmluZyk7XHJcbiAgICBjb25zdCBob3VycyA9IE1hdGguZmxvb3IodG90YWxNaW51dGVzIC8gNjApO1xyXG4gICAgY29uc3QgbWludXRlcyA9IHRvdGFsTWludXRlcyAlIDYwO1xyXG4gICAgcmV0dXJuIGRheWpzKGJhc2VEYXRlKS5zdGFydE9mKCdkYXknKS5ob3VyKGhvdXJzKS5taW51dGUobWludXRlcykudG9EYXRlKCk7XHJcbiAgfVxyXG5cclxuICBnZXRJU09XZWVrRGF5KGRhdGU6IERhdGUgfCBzdHJpbmcpOiBudW1iZXIge1xyXG4gICAgcmV0dXJuIGRheWpzKGRhdGUpLmlzb1dlZWtkYXkoKTsgIC8vIDE9TW9uZGF5LCA3PVN1bmRheVxyXG4gIH1cclxufVxyXG4iLCAiLyoqXG4gKiBQb3NpdGlvblV0aWxzIC0gUGl4ZWwvcG9zaXRpb24gY2FsY3VsYXRpb25zIGZvciBjYWxlbmRhciBncmlkXG4gKlxuICogUkVTUE9OU0lCSUxJVFk6IENvbnZlcnQgYmV0d2VlbiB0aW1lIGFuZCBwaXhlbCBwb3NpdGlvbnNcbiAqIE5PVEU6IERhdGUgZm9ybWF0dGluZyBiZWxvbmdzIGluIERhdGVTZXJ2aWNlLCBub3QgaGVyZVxuICovXG5cbmltcG9ydCB7IElHcmlkQ29uZmlnIH0gZnJvbSAnLi4vY29yZS9JR3JpZENvbmZpZyc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgRXZlbnRQb3NpdGlvbiB7XG4gIHRvcDogbnVtYmVyOyAgICAvLyBwaXhlbHMgZnJvbSBkYXkgc3RhcnRcbiAgaGVpZ2h0OiBudW1iZXI7IC8vIHBpeGVsc1xufVxuXG4vKipcbiAqIENhbGN1bGF0ZSBwaXhlbCBwb3NpdGlvbiBmb3IgYW4gZXZlbnQgYmFzZWQgb24gaXRzIHRpbWVzXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjYWxjdWxhdGVFdmVudFBvc2l0aW9uKFxuICBzdGFydDogRGF0ZSxcbiAgZW5kOiBEYXRlLFxuICBjb25maWc6IElHcmlkQ29uZmlnXG4pOiBFdmVudFBvc2l0aW9uIHtcbiAgY29uc3Qgc3RhcnRNaW51dGVzID0gc3RhcnQuZ2V0SG91cnMoKSAqIDYwICsgc3RhcnQuZ2V0TWludXRlcygpO1xuICBjb25zdCBlbmRNaW51dGVzID0gZW5kLmdldEhvdXJzKCkgKiA2MCArIGVuZC5nZXRNaW51dGVzKCk7XG5cbiAgY29uc3QgZGF5U3RhcnRNaW51dGVzID0gY29uZmlnLmRheVN0YXJ0SG91ciAqIDYwO1xuICBjb25zdCBtaW51dGVIZWlnaHQgPSBjb25maWcuaG91ckhlaWdodCAvIDYwO1xuXG4gIGNvbnN0IHRvcCA9IChzdGFydE1pbnV0ZXMgLSBkYXlTdGFydE1pbnV0ZXMpICogbWludXRlSGVpZ2h0O1xuICBjb25zdCBoZWlnaHQgPSAoZW5kTWludXRlcyAtIHN0YXJ0TWludXRlcykgKiBtaW51dGVIZWlnaHQ7XG5cbiAgcmV0dXJuIHsgdG9wLCBoZWlnaHQgfTtcbn1cblxuLyoqXG4gKiBDb252ZXJ0IG1pbnV0ZXMgdG8gcGl4ZWxzXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBtaW51dGVzVG9QaXhlbHMobWludXRlczogbnVtYmVyLCBjb25maWc6IElHcmlkQ29uZmlnKTogbnVtYmVyIHtcbiAgcmV0dXJuIChtaW51dGVzIC8gNjApICogY29uZmlnLmhvdXJIZWlnaHQ7XG59XG5cbi8qKlxuICogQ29udmVydCBwaXhlbHMgdG8gbWludXRlc1xuICovXG5leHBvcnQgZnVuY3Rpb24gcGl4ZWxzVG9NaW51dGVzKHBpeGVsczogbnVtYmVyLCBjb25maWc6IElHcmlkQ29uZmlnKTogbnVtYmVyIHtcbiAgcmV0dXJuIChwaXhlbHMgLyBjb25maWcuaG91ckhlaWdodCkgKiA2MDtcbn1cblxuLyoqXG4gKiBTbmFwIHBpeGVsIHBvc2l0aW9uIHRvIGdyaWQgaW50ZXJ2YWxcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNuYXBUb0dyaWQocGl4ZWxzOiBudW1iZXIsIGNvbmZpZzogSUdyaWRDb25maWcpOiBudW1iZXIge1xuICBjb25zdCBzbmFwUGl4ZWxzID0gbWludXRlc1RvUGl4ZWxzKGNvbmZpZy5zbmFwSW50ZXJ2YWwsIGNvbmZpZyk7XG4gIHJldHVybiBNYXRoLnJvdW5kKHBpeGVscyAvIHNuYXBQaXhlbHMpICogc25hcFBpeGVscztcbn1cbiIsICIvKipcbiAqIENvcmVFdmVudHMgLSBDb25zb2xpZGF0ZWQgZXNzZW50aWFsIGV2ZW50cyBmb3IgdGhlIGNhbGVuZGFyXG4gKi9cbmV4cG9ydCBjb25zdCBDb3JlRXZlbnRzID0ge1xuICAvLyBMaWZlY3ljbGUgZXZlbnRzXG4gIElOSVRJQUxJWkVEOiAnY29yZTppbml0aWFsaXplZCcsXG4gIFJFQURZOiAnY29yZTpyZWFkeScsXG4gIERFU1RST1lFRDogJ2NvcmU6ZGVzdHJveWVkJyxcblxuICAvLyBWaWV3IGV2ZW50c1xuICBWSUVXX0NIQU5HRUQ6ICd2aWV3OmNoYW5nZWQnLFxuICBWSUVXX1JFTkRFUkVEOiAndmlldzpyZW5kZXJlZCcsXG5cbiAgLy8gTmF2aWdhdGlvbiBldmVudHNcbiAgREFURV9DSEFOR0VEOiAnbmF2OmRhdGUtY2hhbmdlZCcsXG4gIE5BVklHQVRJT05fQ09NUExFVEVEOiAnbmF2Om5hdmlnYXRpb24tY29tcGxldGVkJyxcblxuICAvLyBEYXRhIGV2ZW50c1xuICBEQVRBX0xPQURJTkc6ICdkYXRhOmxvYWRpbmcnLFxuICBEQVRBX0xPQURFRDogJ2RhdGE6bG9hZGVkJyxcbiAgREFUQV9FUlJPUjogJ2RhdGE6ZXJyb3InLFxuXG4gIC8vIEdyaWQgZXZlbnRzXG4gIEdSSURfUkVOREVSRUQ6ICdncmlkOnJlbmRlcmVkJyxcbiAgR1JJRF9DTElDS0VEOiAnZ3JpZDpjbGlja2VkJyxcblxuICAvLyBFdmVudCBtYW5hZ2VtZW50XG4gIEVWRU5UX0NSRUFURUQ6ICdldmVudDpjcmVhdGVkJyxcbiAgRVZFTlRfVVBEQVRFRDogJ2V2ZW50OnVwZGF0ZWQnLFxuICBFVkVOVF9ERUxFVEVEOiAnZXZlbnQ6ZGVsZXRlZCcsXG4gIEVWRU5UX1NFTEVDVEVEOiAnZXZlbnQ6c2VsZWN0ZWQnLFxuXG4gIC8vIEV2ZW50IGRyYWctZHJvcFxuICBFVkVOVF9EUkFHX1NUQVJUOiAnZXZlbnQ6ZHJhZy1zdGFydCcsXG4gIEVWRU5UX0RSQUdfTU9WRTogJ2V2ZW50OmRyYWctbW92ZScsXG4gIEVWRU5UX0RSQUdfRU5EOiAnZXZlbnQ6ZHJhZy1lbmQnLFxuICBFVkVOVF9EUkFHX0NBTkNFTDogJ2V2ZW50OmRyYWctY2FuY2VsJyxcbiAgRVZFTlRfRFJBR19DT0xVTU5fQ0hBTkdFOiAnZXZlbnQ6ZHJhZy1jb2x1bW4tY2hhbmdlJyxcblxuICAvLyBIZWFkZXIgZHJhZyAodGltZWQgXHUyMTkyIGhlYWRlciBjb252ZXJzaW9uKVxuICBFVkVOVF9EUkFHX0VOVEVSX0hFQURFUjogJ2V2ZW50OmRyYWctZW50ZXItaGVhZGVyJyxcbiAgRVZFTlRfRFJBR19NT1ZFX0hFQURFUjogJ2V2ZW50OmRyYWctbW92ZS1oZWFkZXInLFxuICBFVkVOVF9EUkFHX0xFQVZFX0hFQURFUjogJ2V2ZW50OmRyYWctbGVhdmUtaGVhZGVyJyxcblxuICAvLyBFdmVudCByZXNpemVcbiAgRVZFTlRfUkVTSVpFX1NUQVJUOiAnZXZlbnQ6cmVzaXplLXN0YXJ0JyxcbiAgRVZFTlRfUkVTSVpFX0VORDogJ2V2ZW50OnJlc2l6ZS1lbmQnLFxuXG4gIC8vIEVkZ2Ugc2Nyb2xsXG4gIEVER0VfU0NST0xMX1RJQ0s6ICdlZGdlLXNjcm9sbDp0aWNrJyxcbiAgRURHRV9TQ1JPTExfU1RBUlRFRDogJ2VkZ2Utc2Nyb2xsOnN0YXJ0ZWQnLFxuICBFREdFX1NDUk9MTF9TVE9QUEVEOiAnZWRnZS1zY3JvbGw6c3RvcHBlZCcsXG5cbiAgLy8gU3lzdGVtIGV2ZW50c1xuICBFUlJPUjogJ3N5c3RlbTplcnJvcicsXG5cbiAgLy8gU3luYyBldmVudHNcbiAgU1lOQ19TVEFSVEVEOiAnc3luYzpzdGFydGVkJyxcbiAgU1lOQ19DT01QTEVURUQ6ICdzeW5jOmNvbXBsZXRlZCcsXG4gIFNZTkNfRkFJTEVEOiAnc3luYzpmYWlsZWQnLFxuXG4gIC8vIEVudGl0eSBldmVudHMgLSBmb3IgYXVkaXQgYW5kIHN5bmNcbiAgRU5USVRZX1NBVkVEOiAnZW50aXR5OnNhdmVkJyxcbiAgRU5USVRZX0RFTEVURUQ6ICdlbnRpdHk6ZGVsZXRlZCcsXG5cbiAgLy8gQXVkaXQgZXZlbnRzXG4gIEFVRElUX0xPR0dFRDogJ2F1ZGl0OmxvZ2dlZCcsXG5cbiAgLy8gUmVuZGVyaW5nIGV2ZW50c1xuICBFVkVOVFNfUkVOREVSRUQ6ICdldmVudHM6cmVuZGVyZWQnXG59IGFzIGNvbnN0O1xuIiwgIi8qKlxyXG4gKiBFdmVudExheW91dEVuZ2luZSAtIFNpbXBsaWZpZWQgc3RhY2tpbmcvZ3JvdXBpbmcgYWxnb3JpdGhtXHJcbiAqXHJcbiAqIFN1cHBvcnRzIHR3byBsYXlvdXQgbW9kZXM6XHJcbiAqIC0gR1JJRDogRXZlbnRzIHN0YXJ0aW5nIGF0IHNhbWUgdGltZSByZW5kZXJlZCBzaWRlLWJ5LXNpZGVcclxuICogLSBTVEFDS0lORzogT3ZlcmxhcHBpbmcgZXZlbnRzIHdpdGggbWFyZ2luLWxlZnQgb2Zmc2V0ICgxNXB4IHBlciBsZXZlbClcclxuICpcclxuICogTm8gcHJldi9uZXh0IGNoYWlucywgc2luZ2xlLXBhc3MgZ3JlZWR5IGFsZ29yaXRobVxyXG4gKi9cclxuXHJcbmltcG9ydCB7IElDYWxlbmRhckV2ZW50IH0gZnJvbSAnLi4vLi4vdHlwZXMvQ2FsZW5kYXJUeXBlcyc7XHJcbmltcG9ydCB7IElHcmlkQ29uZmlnIH0gZnJvbSAnLi4vLi4vY29yZS9JR3JpZENvbmZpZyc7XHJcbmltcG9ydCB7IGNhbGN1bGF0ZUV2ZW50UG9zaXRpb24gfSBmcm9tICcuLi8uLi91dGlscy9Qb3NpdGlvblV0aWxzJztcclxuaW1wb3J0IHsgSUNvbHVtbkxheW91dCwgSUdyaWRHcm91cExheW91dCwgSVN0YWNrZWRFdmVudExheW91dCB9IGZyb20gJy4vRXZlbnRMYXlvdXRUeXBlcyc7XHJcblxyXG4vKipcclxuICogQ2hlY2sgaWYgdHdvIGV2ZW50cyBvdmVybGFwIChzdHJpY3QgLSB0b3VjaGluZyBhdCBib3VuZGFyeSA9IE5PVCBvdmVybGFwcGluZylcclxuICogVGhpcyBtYXRjaGVzIFNjZW5hcmlvIDg6IGVuZD09PXN0YXJ0IGlzIE5PVCBvdmVybGFwXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gZXZlbnRzT3ZlcmxhcChhOiBJQ2FsZW5kYXJFdmVudCwgYjogSUNhbGVuZGFyRXZlbnQpOiBib29sZWFuIHtcclxuICByZXR1cm4gYS5zdGFydCA8IGIuZW5kICYmIGEuZW5kID4gYi5zdGFydDtcclxufVxyXG5cclxuLyoqXHJcbiAqIENoZWNrIGlmIHR3byBldmVudHMgYXJlIHdpdGhpbiB0aHJlc2hvbGQgZm9yIGdyaWQgZ3JvdXBpbmcuXHJcbiAqIFRoaXMgaW5jbHVkZXM6XHJcbiAqIDEuIFN0YXJ0LXRvLXN0YXJ0OiBFdmVudHMgc3RhcnQgd2l0aGluIHRocmVzaG9sZCBvZiBlYWNoIG90aGVyXHJcbiAqIDIuIEVuZC10by1zdGFydDogT25lIGV2ZW50IHN0YXJ0cyB3aXRoaW4gdGhyZXNob2xkIGJlZm9yZSBhbm90aGVyIGVuZHNcclxuICovXHJcbmZ1bmN0aW9uIGV2ZW50c1dpdGhpblRocmVzaG9sZChhOiBJQ2FsZW5kYXJFdmVudCwgYjogSUNhbGVuZGFyRXZlbnQsIHRocmVzaG9sZE1pbnV0ZXM6IG51bWJlcik6IGJvb2xlYW4ge1xyXG4gIGNvbnN0IHRocmVzaG9sZE1zID0gdGhyZXNob2xkTWludXRlcyAqIDYwICogMTAwMDtcclxuXHJcbiAgLy8gU3RhcnQtdG8tc3RhcnQ6IGJvdGggZXZlbnRzIHN0YXJ0IHdpdGhpbiB0aHJlc2hvbGRcclxuICBjb25zdCBzdGFydFRvU3RhcnREaWZmID0gTWF0aC5hYnMoYS5zdGFydC5nZXRUaW1lKCkgLSBiLnN0YXJ0LmdldFRpbWUoKSk7XHJcbiAgaWYgKHN0YXJ0VG9TdGFydERpZmYgPD0gdGhyZXNob2xkTXMpIHJldHVybiB0cnVlO1xyXG5cclxuICAvLyBFbmQtdG8tc3RhcnQ6IG9uZSBldmVudCBzdGFydHMgd2l0aGluIHRocmVzaG9sZCBiZWZvcmUgdGhlIG90aGVyIGVuZHNcclxuICAvLyBCIHN0YXJ0cyB3aXRoaW4gdGhyZXNob2xkIGJlZm9yZSBBIGVuZHNcclxuICBjb25zdCBiU3RhcnRzQmVmb3JlQUVuZHMgPSBhLmVuZC5nZXRUaW1lKCkgLSBiLnN0YXJ0LmdldFRpbWUoKTtcclxuICBpZiAoYlN0YXJ0c0JlZm9yZUFFbmRzID4gMCAmJiBiU3RhcnRzQmVmb3JlQUVuZHMgPD0gdGhyZXNob2xkTXMpIHJldHVybiB0cnVlO1xyXG5cclxuICAvLyBBIHN0YXJ0cyB3aXRoaW4gdGhyZXNob2xkIGJlZm9yZSBCIGVuZHNcclxuICBjb25zdCBhU3RhcnRzQmVmb3JlQkVuZHMgPSBiLmVuZC5nZXRUaW1lKCkgLSBhLnN0YXJ0LmdldFRpbWUoKTtcclxuICBpZiAoYVN0YXJ0c0JlZm9yZUJFbmRzID4gMCAmJiBhU3RhcnRzQmVmb3JlQkVuZHMgPD0gdGhyZXNob2xkTXMpIHJldHVybiB0cnVlO1xyXG5cclxuICByZXR1cm4gZmFsc2U7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBDaGVjayBpZiBhbGwgZXZlbnRzIGluIGEgZ3JvdXAgc3RhcnQgd2l0aGluIHRocmVzaG9sZCBvZiBlYWNoIG90aGVyXHJcbiAqL1xyXG5mdW5jdGlvbiBhbGxTdGFydFdpdGhpblRocmVzaG9sZChldmVudHM6IElDYWxlbmRhckV2ZW50W10sIHRocmVzaG9sZE1pbnV0ZXM6IG51bWJlcik6IGJvb2xlYW4ge1xyXG4gIGlmIChldmVudHMubGVuZ3RoIDw9IDEpIHJldHVybiB0cnVlO1xyXG5cclxuICAvLyBGaW5kIGVhcmxpZXN0IGFuZCBsYXRlc3Qgc3RhcnQgdGltZXNcclxuICBsZXQgZWFybGllc3QgPSBldmVudHNbMF0uc3RhcnQuZ2V0VGltZSgpO1xyXG4gIGxldCBsYXRlc3QgPSBldmVudHNbMF0uc3RhcnQuZ2V0VGltZSgpO1xyXG5cclxuICBmb3IgKGNvbnN0IGV2ZW50IG9mIGV2ZW50cykge1xyXG4gICAgY29uc3QgdGltZSA9IGV2ZW50LnN0YXJ0LmdldFRpbWUoKTtcclxuICAgIGlmICh0aW1lIDwgZWFybGllc3QpIGVhcmxpZXN0ID0gdGltZTtcclxuICAgIGlmICh0aW1lID4gbGF0ZXN0KSBsYXRlc3QgPSB0aW1lO1xyXG4gIH1cclxuXHJcbiAgY29uc3QgZGlmZk1pbnV0ZXMgPSAobGF0ZXN0IC0gZWFybGllc3QpIC8gKDEwMDAgKiA2MCk7XHJcbiAgcmV0dXJuIGRpZmZNaW51dGVzIDw9IHRocmVzaG9sZE1pbnV0ZXM7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBGaW5kIGdyb3VwcyBvZiBvdmVybGFwcGluZyBldmVudHMgKGNvbm5lY3RlZCBieSBvdmVybGFwIGNoYWluKVxyXG4gKiBFdmVudHMgYXJlIGdyb3VwZWQgaWYgdGhleSBvdmVybGFwIHdpdGggYW55IGV2ZW50IGluIHRoZSBncm91cFxyXG4gKi9cclxuZnVuY3Rpb24gZmluZE92ZXJsYXBHcm91cHMoZXZlbnRzOiBJQ2FsZW5kYXJFdmVudFtdKTogSUNhbGVuZGFyRXZlbnRbXVtdIHtcclxuICBpZiAoZXZlbnRzLmxlbmd0aCA9PT0gMCkgcmV0dXJuIFtdO1xyXG5cclxuICBjb25zdCBzb3J0ZWQgPSBbLi4uZXZlbnRzXS5zb3J0KChhLCBiKSA9PiBhLnN0YXJ0LmdldFRpbWUoKSAtIGIuc3RhcnQuZ2V0VGltZSgpKTtcclxuICBjb25zdCB1c2VkID0gbmV3IFNldDxzdHJpbmc+KCk7XHJcbiAgY29uc3QgZ3JvdXBzOiBJQ2FsZW5kYXJFdmVudFtdW10gPSBbXTtcclxuXHJcbiAgZm9yIChjb25zdCBldmVudCBvZiBzb3J0ZWQpIHtcclxuICAgIGlmICh1c2VkLmhhcyhldmVudC5pZCkpIGNvbnRpbnVlO1xyXG5cclxuICAgIC8vIFN0YXJ0IGEgbmV3IGdyb3VwIHdpdGggdGhpcyBldmVudFxyXG4gICAgY29uc3QgZ3JvdXA6IElDYWxlbmRhckV2ZW50W10gPSBbZXZlbnRdO1xyXG4gICAgdXNlZC5hZGQoZXZlbnQuaWQpO1xyXG5cclxuICAgIC8vIEV4cGFuZCBncm91cCBieSBmaW5kaW5nIGFsbCBjb25uZWN0ZWQgZXZlbnRzICh2aWEgb3ZlcmxhcClcclxuICAgIGxldCBleHBhbmRlZCA9IHRydWU7XHJcbiAgICB3aGlsZSAoZXhwYW5kZWQpIHtcclxuICAgICAgZXhwYW5kZWQgPSBmYWxzZTtcclxuICAgICAgZm9yIChjb25zdCBjYW5kaWRhdGUgb2Ygc29ydGVkKSB7XHJcbiAgICAgICAgaWYgKHVzZWQuaGFzKGNhbmRpZGF0ZS5pZCkpIGNvbnRpbnVlO1xyXG5cclxuICAgICAgICAvLyBDaGVjayBpZiBjYW5kaWRhdGUgb3ZlcmxhcHMgd2l0aCBhbnkgZXZlbnQgaW4gZ3JvdXBcclxuICAgICAgICBjb25zdCBjb25uZWN0cyA9IGdyb3VwLnNvbWUobWVtYmVyID0+IGV2ZW50c092ZXJsYXAobWVtYmVyLCBjYW5kaWRhdGUpKTtcclxuXHJcbiAgICAgICAgaWYgKGNvbm5lY3RzKSB7XHJcbiAgICAgICAgICBncm91cC5wdXNoKGNhbmRpZGF0ZSk7XHJcbiAgICAgICAgICB1c2VkLmFkZChjYW5kaWRhdGUuaWQpO1xyXG4gICAgICAgICAgZXhwYW5kZWQgPSB0cnVlO1xyXG4gICAgICAgIH1cclxuICAgICAgfVxyXG4gICAgfVxyXG5cclxuICAgIGdyb3Vwcy5wdXNoKGdyb3VwKTtcclxuICB9XHJcblxyXG4gIHJldHVybiBncm91cHM7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBGaW5kIGdyaWQgY2FuZGlkYXRlcyB3aXRoaW4gYSBncm91cCAtIGV2ZW50cyBjb25uZWN0ZWQgdmlhIHRocmVzaG9sZCBjaGFpblxyXG4gKiBVc2VzIFYxIGxvZ2ljOiBldmVudHMgYXJlIGNvbm5lY3RlZCBpZiB3aXRoaW4gdGhyZXNob2xkIChubyBvdmVybGFwIHJlcXVpcmVtZW50KVxyXG4gKi9cclxuZnVuY3Rpb24gZmluZEdyaWRDYW5kaWRhdGVzKFxyXG4gIGV2ZW50czogSUNhbGVuZGFyRXZlbnRbXSxcclxuICB0aHJlc2hvbGRNaW51dGVzOiBudW1iZXJcclxuKTogSUNhbGVuZGFyRXZlbnRbXVtdIHtcclxuICBpZiAoZXZlbnRzLmxlbmd0aCA9PT0gMCkgcmV0dXJuIFtdO1xyXG5cclxuICBjb25zdCBzb3J0ZWQgPSBbLi4uZXZlbnRzXS5zb3J0KChhLCBiKSA9PiBhLnN0YXJ0LmdldFRpbWUoKSAtIGIuc3RhcnQuZ2V0VGltZSgpKTtcclxuICBjb25zdCB1c2VkID0gbmV3IFNldDxzdHJpbmc+KCk7XHJcbiAgY29uc3QgZ3JvdXBzOiBJQ2FsZW5kYXJFdmVudFtdW10gPSBbXTtcclxuXHJcbiAgZm9yIChjb25zdCBldmVudCBvZiBzb3J0ZWQpIHtcclxuICAgIGlmICh1c2VkLmhhcyhldmVudC5pZCkpIGNvbnRpbnVlO1xyXG5cclxuICAgIGNvbnN0IGdyb3VwOiBJQ2FsZW5kYXJFdmVudFtdID0gW2V2ZW50XTtcclxuICAgIHVzZWQuYWRkKGV2ZW50LmlkKTtcclxuXHJcbiAgICAvLyBFeHBhbmQgYnkgdGhyZXNob2xkIGNoYWluIChWMSBsb2dpYzogbm8gb3ZlcmxhcCByZXF1aXJlbWVudCwganVzdCB0aHJlc2hvbGQpXHJcbiAgICBsZXQgZXhwYW5kZWQgPSB0cnVlO1xyXG4gICAgd2hpbGUgKGV4cGFuZGVkKSB7XHJcbiAgICAgIGV4cGFuZGVkID0gZmFsc2U7XHJcbiAgICAgIGZvciAoY29uc3QgY2FuZGlkYXRlIG9mIHNvcnRlZCkge1xyXG4gICAgICAgIGlmICh1c2VkLmhhcyhjYW5kaWRhdGUuaWQpKSBjb250aW51ZTtcclxuXHJcbiAgICAgICAgY29uc3QgY29ubmVjdHMgPSBncm91cC5zb21lKG1lbWJlciA9PlxyXG4gICAgICAgICAgZXZlbnRzV2l0aGluVGhyZXNob2xkKG1lbWJlciwgY2FuZGlkYXRlLCB0aHJlc2hvbGRNaW51dGVzKVxyXG4gICAgICAgICk7XHJcblxyXG4gICAgICAgIGlmIChjb25uZWN0cykge1xyXG4gICAgICAgICAgZ3JvdXAucHVzaChjYW5kaWRhdGUpO1xyXG4gICAgICAgICAgdXNlZC5hZGQoY2FuZGlkYXRlLmlkKTtcclxuICAgICAgICAgIGV4cGFuZGVkID0gdHJ1ZTtcclxuICAgICAgICB9XHJcbiAgICAgIH1cclxuICAgIH1cclxuXHJcbiAgICBncm91cHMucHVzaChncm91cCk7XHJcbiAgfVxyXG5cclxuICByZXR1cm4gZ3JvdXBzO1xyXG59XHJcblxyXG4vKipcclxuICogQ2FsY3VsYXRlIHN0YWNrIGxldmVscyBmb3Igb3ZlcmxhcHBpbmcgZXZlbnRzIHVzaW5nIGdyZWVkeSBhbGdvcml0aG1cclxuICogRm9yIGVhY2ggZXZlbnQ6IGxldmVsID0gbWF4KG92ZXJsYXBwaW5nIGFscmVhZHktcHJvY2Vzc2VkIGV2ZW50cykgKyAxXHJcbiAqL1xyXG5mdW5jdGlvbiBjYWxjdWxhdGVTdGFja0xldmVscyhldmVudHM6IElDYWxlbmRhckV2ZW50W10pOiBNYXA8c3RyaW5nLCBudW1iZXI+IHtcclxuICBjb25zdCBsZXZlbHMgPSBuZXcgTWFwPHN0cmluZywgbnVtYmVyPigpO1xyXG4gIGNvbnN0IHNvcnRlZCA9IFsuLi5ldmVudHNdLnNvcnQoKGEsIGIpID0+IGEuc3RhcnQuZ2V0VGltZSgpIC0gYi5zdGFydC5nZXRUaW1lKCkpO1xyXG5cclxuICBmb3IgKGNvbnN0IGV2ZW50IG9mIHNvcnRlZCkge1xyXG4gICAgbGV0IG1heE92ZXJsYXBwaW5nTGV2ZWwgPSAtMTtcclxuXHJcbiAgICAvLyBGaW5kIG1heCBsZXZlbCBhbW9uZyBvdmVybGFwcGluZyBldmVudHMgYWxyZWFkeSBwcm9jZXNzZWRcclxuICAgIGZvciAoY29uc3QgW2lkLCBsZXZlbF0gb2YgbGV2ZWxzKSB7XHJcbiAgICAgIGNvbnN0IG90aGVyID0gZXZlbnRzLmZpbmQoZSA9PiBlLmlkID09PSBpZCk7XHJcbiAgICAgIGlmIChvdGhlciAmJiBldmVudHNPdmVybGFwKGV2ZW50LCBvdGhlcikpIHtcclxuICAgICAgICBtYXhPdmVybGFwcGluZ0xldmVsID0gTWF0aC5tYXgobWF4T3ZlcmxhcHBpbmdMZXZlbCwgbGV2ZWwpO1xyXG4gICAgICB9XHJcbiAgICB9XHJcblxyXG4gICAgbGV2ZWxzLnNldChldmVudC5pZCwgbWF4T3ZlcmxhcHBpbmdMZXZlbCArIDEpO1xyXG4gIH1cclxuXHJcbiAgcmV0dXJuIGxldmVscztcclxufVxyXG5cclxuLyoqXHJcbiAqIEFsbG9jYXRlIGV2ZW50cyB0byBjb2x1bW5zIGZvciBHUklEIGxheW91dCB1c2luZyBncmVlZHkgYWxnb3JpdGhtXHJcbiAqIE5vbi1vdmVybGFwcGluZyBldmVudHMgY2FuIHNoYXJlIGEgY29sdW1uIHRvIG1pbmltaXplIHRvdGFsIGNvbHVtbnNcclxuICovXHJcbmZ1bmN0aW9uIGFsbG9jYXRlQ29sdW1ucyhldmVudHM6IElDYWxlbmRhckV2ZW50W10pOiBJQ2FsZW5kYXJFdmVudFtdW10ge1xyXG4gIGNvbnN0IHNvcnRlZCA9IFsuLi5ldmVudHNdLnNvcnQoKGEsIGIpID0+IGEuc3RhcnQuZ2V0VGltZSgpIC0gYi5zdGFydC5nZXRUaW1lKCkpO1xyXG4gIGNvbnN0IGNvbHVtbnM6IElDYWxlbmRhckV2ZW50W11bXSA9IFtdO1xyXG5cclxuICBmb3IgKGNvbnN0IGV2ZW50IG9mIHNvcnRlZCkge1xyXG4gICAgLy8gRmluZCBmaXJzdCBjb2x1bW4gd2hlcmUgZXZlbnQgZG9lc24ndCBvdmVybGFwIHdpdGggZXhpc3RpbmcgZXZlbnRzXHJcbiAgICBsZXQgcGxhY2VkID0gZmFsc2U7XHJcbiAgICBmb3IgKGNvbnN0IGNvbHVtbiBvZiBjb2x1bW5zKSB7XHJcbiAgICAgIGNvbnN0IGNhbkZpdCA9ICFjb2x1bW4uc29tZShlID0+IGV2ZW50c092ZXJsYXAoZXZlbnQsIGUpKTtcclxuICAgICAgaWYgKGNhbkZpdCkge1xyXG4gICAgICAgIGNvbHVtbi5wdXNoKGV2ZW50KTtcclxuICAgICAgICBwbGFjZWQgPSB0cnVlO1xyXG4gICAgICAgIGJyZWFrO1xyXG4gICAgICB9XHJcbiAgICB9XHJcblxyXG4gICAgLy8gTm8gc3VpdGFibGUgY29sdW1uIGZvdW5kLCBjcmVhdGUgbmV3IG9uZVxyXG4gICAgaWYgKCFwbGFjZWQpIHtcclxuICAgICAgY29sdW1ucy5wdXNoKFtldmVudF0pO1xyXG4gICAgfVxyXG4gIH1cclxuXHJcbiAgcmV0dXJuIGNvbHVtbnM7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBNYWluIGVudHJ5IHBvaW50OiBDYWxjdWxhdGUgY29tcGxldGUgbGF5b3V0IGZvciBhIGNvbHVtbidzIGV2ZW50c1xyXG4gKlxyXG4gKiBBbGdvcml0aG06XHJcbiAqIDEuIEZpbmQgb3ZlcmxhcCBncm91cHMgKGV2ZW50cyBjb25uZWN0ZWQgYnkgb3ZlcmxhcCBjaGFpbilcclxuICogMi4gRm9yIGVhY2ggb3ZlcmxhcCBncm91cCwgZmluZCBncmlkIGNhbmRpZGF0ZXMgKGV2ZW50cyB3aXRoaW4gdGhyZXNob2xkIGNoYWluKVxyXG4gKiAzLiBJZiBhbGwgZXZlbnRzIGluIG92ZXJsYXAgZ3JvdXAgZm9ybSBhIHNpbmdsZSBncmlkIGNhbmRpZGF0ZSBcdTIxOTIgR1JJRCBtb2RlXHJcbiAqIDQuIE90aGVyd2lzZSBcdTIxOTIgU1RBQ0tJTkcgbW9kZSB3aXRoIGNhbGN1bGF0ZWQgbGV2ZWxzXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gY2FsY3VsYXRlQ29sdW1uTGF5b3V0KFxyXG4gIGV2ZW50czogSUNhbGVuZGFyRXZlbnRbXSxcclxuICBjb25maWc6IElHcmlkQ29uZmlnXHJcbik6IElDb2x1bW5MYXlvdXQge1xyXG4gIGNvbnN0IHRocmVzaG9sZE1pbnV0ZXMgPSBjb25maWcuZ3JpZFN0YXJ0VGhyZXNob2xkTWludXRlcyA/PyAxMDtcclxuXHJcbiAgY29uc3QgcmVzdWx0OiBJQ29sdW1uTGF5b3V0ID0ge1xyXG4gICAgZ3JpZHM6IFtdLFxyXG4gICAgc3RhY2tlZDogW11cclxuICB9O1xyXG5cclxuICBpZiAoZXZlbnRzLmxlbmd0aCA9PT0gMCkgcmV0dXJuIHJlc3VsdDtcclxuXHJcbiAgLy8gRmluZCBhbGwgb3ZlcmxhcHBpbmcgZXZlbnQgZ3JvdXBzXHJcbiAgY29uc3Qgb3ZlcmxhcEdyb3VwcyA9IGZpbmRPdmVybGFwR3JvdXBzKGV2ZW50cyk7XHJcblxyXG4gIGZvciAoY29uc3Qgb3ZlcmxhcEdyb3VwIG9mIG92ZXJsYXBHcm91cHMpIHtcclxuICAgIGlmIChvdmVybGFwR3JvdXAubGVuZ3RoID09PSAxKSB7XHJcbiAgICAgIC8vIFNpbmdsZSBldmVudCAtIG5vIGdyb3VwaW5nIG5lZWRlZFxyXG4gICAgICByZXN1bHQuc3RhY2tlZC5wdXNoKHtcclxuICAgICAgICBldmVudDogb3ZlcmxhcEdyb3VwWzBdLFxyXG4gICAgICAgIHN0YWNrTGV2ZWw6IDBcclxuICAgICAgfSk7XHJcbiAgICAgIGNvbnRpbnVlO1xyXG4gICAgfVxyXG5cclxuICAgIC8vIFdpdGhpbiB0aGlzIG92ZXJsYXAgZ3JvdXAsIGZpbmQgZ3JpZCBjYW5kaWRhdGVzICh0aHJlc2hvbGQtY29ubmVjdGVkIHN1Ymdyb3VwcylcclxuICAgIGNvbnN0IGdyaWRTdWJncm91cHMgPSBmaW5kR3JpZENhbmRpZGF0ZXMob3ZlcmxhcEdyb3VwLCB0aHJlc2hvbGRNaW51dGVzKTtcclxuXHJcbiAgICAvLyBDaGVjayBpZiB0aGUgRU5USVJFIG92ZXJsYXAgZ3JvdXAgZm9ybXMgYSBzaW5nbGUgZ3JpZCBjYW5kaWRhdGVcclxuICAgIC8vIFRoaXMgaGFwcGVucyB3aGVuIGFsbCBldmVudHMgYXJlIGNvbm5lY3RlZCB2aWEgdGhyZXNob2xkIGNoYWluXHJcbiAgICBjb25zdCBsYXJnZXN0R3JpZENhbmRpZGF0ZSA9IGdyaWRTdWJncm91cHMucmVkdWNlKChtYXgsIGcpID0+XHJcbiAgICAgIGcubGVuZ3RoID4gbWF4Lmxlbmd0aCA/IGcgOiBtYXgsIGdyaWRTdWJncm91cHNbMF0pO1xyXG5cclxuICAgIGlmIChsYXJnZXN0R3JpZENhbmRpZGF0ZS5sZW5ndGggPT09IG92ZXJsYXBHcm91cC5sZW5ndGgpIHtcclxuICAgICAgLy8gQWxsIGV2ZW50cyBpbiBvdmVybGFwIGdyb3VwIGFyZSBjb25uZWN0ZWQgdmlhIHRocmVzaG9sZCBjaGFpbiBcdTIxOTIgR1JJRCBtb2RlXHJcbiAgICAgIGNvbnN0IGNvbHVtbnMgPSBhbGxvY2F0ZUNvbHVtbnMob3ZlcmxhcEdyb3VwKTtcclxuICAgICAgY29uc3QgZWFybGllc3QgPSBvdmVybGFwR3JvdXAucmVkdWNlKChtaW4sIGUpID0+XHJcbiAgICAgICAgZS5zdGFydCA8IG1pbi5zdGFydCA/IGUgOiBtaW4sIG92ZXJsYXBHcm91cFswXSk7XHJcbiAgICAgIGNvbnN0IHBvc2l0aW9uID0gY2FsY3VsYXRlRXZlbnRQb3NpdGlvbihlYXJsaWVzdC5zdGFydCwgZWFybGllc3QuZW5kLCBjb25maWcpO1xyXG5cclxuICAgICAgcmVzdWx0LmdyaWRzLnB1c2goe1xyXG4gICAgICAgIGV2ZW50czogb3ZlcmxhcEdyb3VwLFxyXG4gICAgICAgIGNvbHVtbnMsXHJcbiAgICAgICAgc3RhY2tMZXZlbDogMCxcclxuICAgICAgICBwb3NpdGlvbjogeyB0b3A6IHBvc2l0aW9uLnRvcCB9XHJcbiAgICAgIH0pO1xyXG4gICAgfSBlbHNlIHtcclxuICAgICAgLy8gTm90IGFsbCBldmVudHMgY29ubmVjdGVkIHZpYSB0aHJlc2hvbGQgXHUyMTkyIFNUQUNLSU5HIG1vZGVcclxuICAgICAgY29uc3QgbGV2ZWxzID0gY2FsY3VsYXRlU3RhY2tMZXZlbHMob3ZlcmxhcEdyb3VwKTtcclxuICAgICAgZm9yIChjb25zdCBldmVudCBvZiBvdmVybGFwR3JvdXApIHtcclxuICAgICAgICByZXN1bHQuc3RhY2tlZC5wdXNoKHtcclxuICAgICAgICAgIGV2ZW50LFxyXG4gICAgICAgICAgc3RhY2tMZXZlbDogbGV2ZWxzLmdldChldmVudC5pZCkgPz8gMFxyXG4gICAgICAgIH0pO1xyXG4gICAgICB9XHJcbiAgICB9XHJcbiAgfVxyXG5cclxuICByZXR1cm4gcmVzdWx0O1xyXG59XHJcbiIsICJpbXBvcnQgeyBJQ2FsZW5kYXJFdmVudCwgSUV2ZW50QnVzLCBJRXZlbnRVcGRhdGVkUGF5bG9hZCB9IGZyb20gJy4uLy4uL3R5cGVzL0NhbGVuZGFyVHlwZXMnO1xyXG5pbXBvcnQgeyBFdmVudFNlcnZpY2UgfSBmcm9tICcuLi8uLi9zdG9yYWdlL2V2ZW50cy9FdmVudFNlcnZpY2UnO1xyXG5pbXBvcnQgeyBEYXRlU2VydmljZSB9IGZyb20gJy4uLy4uL2NvcmUvRGF0ZVNlcnZpY2UnO1xyXG5pbXBvcnQgeyBJR3JpZENvbmZpZyB9IGZyb20gJy4uLy4uL2NvcmUvSUdyaWRDb25maWcnO1xyXG5pbXBvcnQgeyBjYWxjdWxhdGVFdmVudFBvc2l0aW9uLCBzbmFwVG9HcmlkLCBwaXhlbHNUb01pbnV0ZXMgfSBmcm9tICcuLi8uLi91dGlscy9Qb3NpdGlvblV0aWxzJztcclxuaW1wb3J0IHsgQ29yZUV2ZW50cyB9IGZyb20gJy4uLy4uL2NvbnN0YW50cy9Db3JlRXZlbnRzJztcclxuaW1wb3J0IHsgSURyYWdDb2x1bW5DaGFuZ2VQYXlsb2FkLCBJRHJhZ01vdmVQYXlsb2FkLCBJRHJhZ0VuZFBheWxvYWQsIElEcmFnTGVhdmVIZWFkZXJQYXlsb2FkIH0gZnJvbSAnLi4vLi4vdHlwZXMvRHJhZ1R5cGVzJztcclxuaW1wb3J0IHsgY2FsY3VsYXRlQ29sdW1uTGF5b3V0IH0gZnJvbSAnLi9FdmVudExheW91dEVuZ2luZSc7XHJcbmltcG9ydCB7IElHcmlkR3JvdXBMYXlvdXQgfSBmcm9tICcuL0V2ZW50TGF5b3V0VHlwZXMnO1xyXG5pbXBvcnQgeyBGaWx0ZXJUZW1wbGF0ZSB9IGZyb20gJy4uLy4uL2NvcmUvRmlsdGVyVGVtcGxhdGUnO1xyXG5cclxuLyoqXHJcbiAqIEV2ZW50UmVuZGVyZXIgLSBSZW5kZXJzIGNhbGVuZGFyIGV2ZW50cyB0byB0aGUgRE9NXHJcbiAqXHJcbiAqIENMRUFOIGFwcHJvYWNoOlxyXG4gKiAtIE9ubHkgZGF0YS1pZCBhdHRyaWJ1dGUgb24gZXZlbnQgZWxlbWVudFxyXG4gKiAtIGlubmVySFRNTCBjb250YWlucyBvbmx5IHZpc2libGUgY29udGVudFxyXG4gKiAtIEV2ZW50IGRhdGEgcmV0cmlldmVkIHZpYSBFdmVudFNlcnZpY2Ugd2hlbiBuZWVkZWRcclxuICovXHJcbmV4cG9ydCBjbGFzcyBFdmVudFJlbmRlcmVyIHtcclxuICBwcml2YXRlIGNvbnRhaW5lcjogSFRNTEVsZW1lbnQgfCBudWxsID0gbnVsbDtcclxuXHJcbiAgY29uc3RydWN0b3IoXHJcbiAgICBwcml2YXRlIGV2ZW50U2VydmljZTogRXZlbnRTZXJ2aWNlLFxyXG4gICAgcHJpdmF0ZSBkYXRlU2VydmljZTogRGF0ZVNlcnZpY2UsXHJcbiAgICBwcml2YXRlIGdyaWRDb25maWc6IElHcmlkQ29uZmlnLFxyXG4gICAgcHJpdmF0ZSBldmVudEJ1czogSUV2ZW50QnVzXHJcbiAgKSB7XHJcbiAgICB0aGlzLnNldHVwTGlzdGVuZXJzKCk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBTZXR1cCBsaXN0ZW5lcnMgZm9yIGRyYWctZHJvcCBhbmQgdXBkYXRlIGV2ZW50c1xyXG4gICAqL1xyXG4gIHByaXZhdGUgc2V0dXBMaXN0ZW5lcnMoKTogdm9pZCB7XHJcbiAgICB0aGlzLmV2ZW50QnVzLm9uKENvcmVFdmVudHMuRVZFTlRfRFJBR19DT0xVTU5fQ0hBTkdFLCAoZSkgPT4ge1xyXG4gICAgICBjb25zdCBwYXlsb2FkID0gKGUgYXMgQ3VzdG9tRXZlbnQ8SURyYWdDb2x1bW5DaGFuZ2VQYXlsb2FkPikuZGV0YWlsO1xyXG4gICAgICB0aGlzLmhhbmRsZUNvbHVtbkNoYW5nZShwYXlsb2FkKTtcclxuICAgIH0pO1xyXG5cclxuICAgIHRoaXMuZXZlbnRCdXMub24oQ29yZUV2ZW50cy5FVkVOVF9EUkFHX01PVkUsIChlKSA9PiB7XHJcbiAgICAgIGNvbnN0IHBheWxvYWQgPSAoZSBhcyBDdXN0b21FdmVudDxJRHJhZ01vdmVQYXlsb2FkPikuZGV0YWlsO1xyXG4gICAgICB0aGlzLnVwZGF0ZURyYWdUaW1lc3RhbXAocGF5bG9hZCk7XHJcbiAgICB9KTtcclxuXHJcbiAgICB0aGlzLmV2ZW50QnVzLm9uKENvcmVFdmVudHMuRVZFTlRfVVBEQVRFRCwgKGUpID0+IHtcclxuICAgICAgY29uc3QgcGF5bG9hZCA9IChlIGFzIEN1c3RvbUV2ZW50PElFdmVudFVwZGF0ZWRQYXlsb2FkPikuZGV0YWlsO1xyXG4gICAgICB0aGlzLmhhbmRsZUV2ZW50VXBkYXRlZChwYXlsb2FkKTtcclxuICAgIH0pO1xyXG5cclxuICAgIHRoaXMuZXZlbnRCdXMub24oQ29yZUV2ZW50cy5FVkVOVF9EUkFHX0VORCwgKGUpID0+IHtcclxuICAgICAgY29uc3QgcGF5bG9hZCA9IChlIGFzIEN1c3RvbUV2ZW50PElEcmFnRW5kUGF5bG9hZD4pLmRldGFpbDtcclxuICAgICAgdGhpcy5oYW5kbGVEcmFnRW5kKHBheWxvYWQpO1xyXG4gICAgfSk7XHJcblxyXG4gICAgdGhpcy5ldmVudEJ1cy5vbihDb3JlRXZlbnRzLkVWRU5UX0RSQUdfTEVBVkVfSEVBREVSLCAoZSkgPT4ge1xyXG4gICAgICBjb25zdCBwYXlsb2FkID0gKGUgYXMgQ3VzdG9tRXZlbnQ8SURyYWdMZWF2ZUhlYWRlclBheWxvYWQ+KS5kZXRhaWw7XHJcbiAgICAgIHRoaXMuaGFuZGxlRHJhZ0xlYXZlSGVhZGVyKHBheWxvYWQpO1xyXG4gICAgfSk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBIYW5kbGUgRVZFTlRfRFJBR19FTkQgLSByZW1vdmUgZWxlbWVudCBpZiBkcm9wcGVkIGluIGhlYWRlclxyXG4gICAqL1xyXG4gIHByaXZhdGUgaGFuZGxlRHJhZ0VuZChwYXlsb2FkOiBJRHJhZ0VuZFBheWxvYWQpOiB2b2lkIHtcclxuICAgIGlmIChwYXlsb2FkLnRhcmdldCA9PT0gJ2hlYWRlcicpIHtcclxuICAgICAgLy8gRXZlbnQgd2FzIGRyb3BwZWQgaW4gaGVhZGVyIGRyYXdlciAtIHJlbW92ZSBmcm9tIGdyaWRcclxuICAgICAgY29uc3QgZWxlbWVudCA9IHRoaXMuY29udGFpbmVyPy5xdWVyeVNlbGVjdG9yKGBzd3AtY29udGVudC12aWV3cG9ydCBzd3AtZXZlbnRbZGF0YS1ldmVudC1pZD1cIiR7cGF5bG9hZC5zd3BFdmVudC5ldmVudElkfVwiXWApO1xyXG4gICAgICBlbGVtZW50Py5yZW1vdmUoKTtcclxuICAgIH1cclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIEhhbmRsZSBoZWFkZXIgaXRlbSBsZWF2aW5nIGhlYWRlciAtIGNyZWF0ZSBzd3AtZXZlbnQgaW4gZ3JpZFxyXG4gICAqL1xyXG4gIHByaXZhdGUgaGFuZGxlRHJhZ0xlYXZlSGVhZGVyKHBheWxvYWQ6IElEcmFnTGVhdmVIZWFkZXJQYXlsb2FkKTogdm9pZCB7XHJcbiAgICAvLyBPbmx5IGhhbmRsZSB3aGVuIHNvdXJjZSBpcyBoZWFkZXIgKGhlYWRlciBpdGVtIGRyYWdnZWQgdG8gZ3JpZClcclxuICAgIGlmIChwYXlsb2FkLnNvdXJjZSAhPT0gJ2hlYWRlcicpIHJldHVybjtcclxuICAgIGlmICghcGF5bG9hZC50YXJnZXRDb2x1bW4gfHwgIXBheWxvYWQuc3RhcnQgfHwgIXBheWxvYWQuZW5kKSByZXR1cm47XHJcblxyXG4gICAgLy8gVHVybiBoZWFkZXIgaXRlbSBpbnRvIGdob3N0IChzdGF5cyB2aXNpYmxlIGJ1dCBmYWRlZClcclxuICAgIGlmIChwYXlsb2FkLmVsZW1lbnQpIHtcclxuICAgICAgcGF5bG9hZC5lbGVtZW50LmNsYXNzTGlzdC5hZGQoJ2RyYWctZ2hvc3QnKTtcclxuICAgICAgcGF5bG9hZC5lbGVtZW50LnN0eWxlLm9wYWNpdHkgPSAnMC4zJztcclxuICAgICAgcGF5bG9hZC5lbGVtZW50LnN0eWxlLnBvaW50ZXJFdmVudHMgPSAnbm9uZSc7XHJcbiAgICB9XHJcblxyXG4gICAgLy8gQ3JlYXRlIGV2ZW50IG9iamVjdCBmcm9tIGhlYWRlciBpdGVtIGRhdGFcclxuICAgIGNvbnN0IGV2ZW50OiBJQ2FsZW5kYXJFdmVudCA9IHtcclxuICAgICAgaWQ6IHBheWxvYWQuZXZlbnRJZCxcclxuICAgICAgdGl0bGU6IHBheWxvYWQudGl0bGUgfHwgJycsXHJcbiAgICAgIGRlc2NyaXB0aW9uOiAnJyxcclxuICAgICAgc3RhcnQ6IHBheWxvYWQuc3RhcnQsXHJcbiAgICAgIGVuZDogcGF5bG9hZC5lbmQsXHJcbiAgICAgIHR5cGU6ICdjdXN0b21lcicsXHJcbiAgICAgIGFsbERheTogZmFsc2UsXHJcbiAgICAgIHN5bmNTdGF0dXM6ICdwZW5kaW5nJ1xyXG4gICAgfTtcclxuXHJcbiAgICAvLyBDcmVhdGUgc3dwLWV2ZW50IGVsZW1lbnQgdXNpbmcgZXhpc3RpbmcgbWV0aG9kXHJcbiAgICBjb25zdCBlbGVtZW50ID0gdGhpcy5jcmVhdGVFdmVudEVsZW1lbnQoZXZlbnQpO1xyXG5cclxuICAgIC8vIEFkZCB0byB0YXJnZXQgY29sdW1uXHJcbiAgICBsZXQgZXZlbnRzTGF5ZXIgPSBwYXlsb2FkLnRhcmdldENvbHVtbi5xdWVyeVNlbGVjdG9yKCdzd3AtZXZlbnRzLWxheWVyJyk7XHJcbiAgICBpZiAoIWV2ZW50c0xheWVyKSB7XHJcbiAgICAgIGV2ZW50c0xheWVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3dwLWV2ZW50cy1sYXllcicpO1xyXG4gICAgICBwYXlsb2FkLnRhcmdldENvbHVtbi5hcHBlbmRDaGlsZChldmVudHNMYXllcik7XHJcbiAgICB9XHJcbiAgICBldmVudHNMYXllci5hcHBlbmRDaGlsZChlbGVtZW50KTtcclxuXHJcbiAgICAvLyBNYXJrIGFzIGRyYWdnaW5nIHNvIERyYWdEcm9wTWFuYWdlciBjYW4gY29udGludWUgd2l0aCBpdFxyXG4gICAgZWxlbWVudC5jbGFzc0xpc3QuYWRkKCdkcmFnZ2luZycpO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogSGFuZGxlIEVWRU5UX1VQREFURUQgLSByZS1yZW5kZXIgYWZmZWN0ZWQgY29sdW1uc1xyXG4gICAqL1xyXG4gIHByaXZhdGUgYXN5bmMgaGFuZGxlRXZlbnRVcGRhdGVkKHBheWxvYWQ6IElFdmVudFVwZGF0ZWRQYXlsb2FkKTogUHJvbWlzZTx2b2lkPiB7XHJcbiAgICAvLyBSZS1yZW5kZXIgc291cmNlIGNvbHVtbiAoaWYgZGlmZmVyZW50IGZyb20gdGFyZ2V0KVxyXG4gICAgaWYgKHBheWxvYWQuc291cmNlQ29sdW1uS2V5ICE9PSBwYXlsb2FkLnRhcmdldENvbHVtbktleSkge1xyXG4gICAgICBhd2FpdCB0aGlzLnJlcmVuZGVyQ29sdW1uKHBheWxvYWQuc291cmNlQ29sdW1uS2V5KTtcclxuICAgIH1cclxuXHJcbiAgICAvLyBSZS1yZW5kZXIgdGFyZ2V0IGNvbHVtblxyXG4gICAgYXdhaXQgdGhpcy5yZXJlbmRlckNvbHVtbihwYXlsb2FkLnRhcmdldENvbHVtbktleSk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBSZS1yZW5kZXIgYSBzaW5nbGUgY29sdW1uIHdpdGggZnJlc2ggZGF0YSBmcm9tIEluZGV4ZWREQlxyXG4gICAqL1xyXG4gIHByaXZhdGUgYXN5bmMgcmVyZW5kZXJDb2x1bW4oY29sdW1uS2V5OiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcclxuICAgIGNvbnN0IGNvbHVtbiA9IHRoaXMuZmluZENvbHVtbihjb2x1bW5LZXkpO1xyXG4gICAgaWYgKCFjb2x1bW4pIHJldHVybjtcclxuXHJcbiAgICAvLyBSZWFkIGRhdGUgYW5kIHJlc291cmNlSWQgZGlyZWN0bHkgZnJvbSBjb2x1bW4gYXR0cmlidXRlcyAoY29sdW1uS2V5IGlzIG9wYXF1ZSlcclxuICAgIGNvbnN0IGRhdGUgPSBjb2x1bW4uZGF0YXNldC5kYXRlO1xyXG4gICAgY29uc3QgcmVzb3VyY2VJZCA9IGNvbHVtbi5kYXRhc2V0LnJlc291cmNlSWQ7XHJcblxyXG4gICAgaWYgKCFkYXRlKSByZXR1cm47XHJcblxyXG4gICAgLy8gR2V0IGRhdGUgcmFuZ2UgZm9yIHRoaXMgZGF5XHJcbiAgICBjb25zdCBzdGFydERhdGUgPSBuZXcgRGF0ZShkYXRlKTtcclxuICAgIGNvbnN0IGVuZERhdGUgPSBuZXcgRGF0ZShkYXRlKTtcclxuICAgIGVuZERhdGUuc2V0SG91cnMoMjMsIDU5LCA1OSwgOTk5KTtcclxuXHJcbiAgICAvLyBGZXRjaCBldmVudHMgZnJvbSBJbmRleGVkREJcclxuICAgIGNvbnN0IGV2ZW50cyA9IHJlc291cmNlSWRcclxuICAgICAgPyBhd2FpdCB0aGlzLmV2ZW50U2VydmljZS5nZXRCeVJlc291cmNlQW5kRGF0ZVJhbmdlKHJlc291cmNlSWQsIHN0YXJ0RGF0ZSwgZW5kRGF0ZSlcclxuICAgICAgOiBhd2FpdCB0aGlzLmV2ZW50U2VydmljZS5nZXRCeURhdGVSYW5nZShzdGFydERhdGUsIGVuZERhdGUpO1xyXG5cclxuICAgIC8vIEZpbHRlciB0byB0aW1lZCBldmVudHMgYW5kIG1hdGNoIGRhdGUgZXhhY3RseVxyXG4gICAgY29uc3QgdGltZWRFdmVudHMgPSBldmVudHMuZmlsdGVyKGV2ZW50ID0+XHJcbiAgICAgICFldmVudC5hbGxEYXkgJiYgdGhpcy5kYXRlU2VydmljZS5nZXREYXRlS2V5KGV2ZW50LnN0YXJ0KSA9PT0gZGF0ZVxyXG4gICAgKTtcclxuXHJcbiAgICAvLyBHZXQgb3IgY3JlYXRlIGV2ZW50cyBsYXllclxyXG4gICAgbGV0IGV2ZW50c0xheWVyID0gY29sdW1uLnF1ZXJ5U2VsZWN0b3IoJ3N3cC1ldmVudHMtbGF5ZXInKTtcclxuICAgIGlmICghZXZlbnRzTGF5ZXIpIHtcclxuICAgICAgZXZlbnRzTGF5ZXIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzd3AtZXZlbnRzLWxheWVyJyk7XHJcbiAgICAgIGNvbHVtbi5hcHBlbmRDaGlsZChldmVudHNMYXllcik7XHJcbiAgICB9XHJcblxyXG4gICAgLy8gQ2xlYXIgZXhpc3RpbmcgZXZlbnRzXHJcbiAgICBldmVudHNMYXllci5pbm5lckhUTUwgPSAnJztcclxuXHJcbiAgICAvLyBDYWxjdWxhdGUgbGF5b3V0IHdpdGggc3RhY2tpbmcvZ3JvdXBpbmdcclxuICAgIGNvbnN0IGxheW91dCA9IGNhbGN1bGF0ZUNvbHVtbkxheW91dCh0aW1lZEV2ZW50cywgdGhpcy5ncmlkQ29uZmlnKTtcclxuXHJcbiAgICAvLyBSZW5kZXIgR1JJRCBncm91cHNcclxuICAgIGxheW91dC5ncmlkcy5mb3JFYWNoKGdyaWQgPT4ge1xyXG4gICAgICBjb25zdCBncm91cEVsID0gdGhpcy5yZW5kZXJHcmlkR3JvdXAoZ3JpZCk7XHJcbiAgICAgIGV2ZW50c0xheWVyIS5hcHBlbmRDaGlsZChncm91cEVsKTtcclxuICAgIH0pO1xyXG5cclxuICAgIC8vIFJlbmRlciBTVEFDS0VEIGV2ZW50c1xyXG4gICAgbGF5b3V0LnN0YWNrZWQuZm9yRWFjaChpdGVtID0+IHtcclxuICAgICAgY29uc3QgZXZlbnRFbCA9IHRoaXMucmVuZGVyU3RhY2tlZEV2ZW50KGl0ZW0uZXZlbnQsIGl0ZW0uc3RhY2tMZXZlbCk7XHJcbiAgICAgIGV2ZW50c0xheWVyIS5hcHBlbmRDaGlsZChldmVudEVsKTtcclxuICAgIH0pO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogRmluZCBhIGNvbHVtbiBlbGVtZW50IGJ5IGNvbHVtbktleVxyXG4gICAqL1xyXG4gIHByaXZhdGUgZmluZENvbHVtbihjb2x1bW5LZXk6IHN0cmluZyk6IEhUTUxFbGVtZW50IHwgbnVsbCB7XHJcbiAgICBpZiAoIXRoaXMuY29udGFpbmVyKSByZXR1cm4gbnVsbDtcclxuICAgIHJldHVybiB0aGlzLmNvbnRhaW5lci5xdWVyeVNlbGVjdG9yKGBzd3AtZGF5LWNvbHVtbltkYXRhLWNvbHVtbi1rZXk9XCIke2NvbHVtbktleX1cIl1gKSBhcyBIVE1MRWxlbWVudDtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIEhhbmRsZSBldmVudCBtb3ZpbmcgdG8gYSBuZXcgY29sdW1uIGR1cmluZyBkcmFnXHJcbiAgICovXHJcbiAgcHJpdmF0ZSBoYW5kbGVDb2x1bW5DaGFuZ2UocGF5bG9hZDogSURyYWdDb2x1bW5DaGFuZ2VQYXlsb2FkKTogdm9pZCB7XHJcbiAgICBjb25zdCBldmVudHNMYXllciA9IHBheWxvYWQubmV3Q29sdW1uLnF1ZXJ5U2VsZWN0b3IoJ3N3cC1ldmVudHMtbGF5ZXInKTtcclxuICAgIGlmICghZXZlbnRzTGF5ZXIpIHJldHVybjtcclxuXHJcbiAgICAvLyBNb3ZlIGVsZW1lbnQgdG8gbmV3IGNvbHVtblxyXG4gICAgZXZlbnRzTGF5ZXIuYXBwZW5kQ2hpbGQocGF5bG9hZC5lbGVtZW50KTtcclxuXHJcbiAgICAvLyBQcmVzZXJ2ZSBZIHBvc2l0aW9uXHJcbiAgICBwYXlsb2FkLmVsZW1lbnQuc3R5bGUudG9wID0gYCR7cGF5bG9hZC5jdXJyZW50WX1weGA7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBVcGRhdGUgdGltZXN0YW1wIGRpc3BsYXkgZHVyaW5nIGRyYWcgKHNuYXBwZWQgdG8gZ3JpZClcclxuICAgKi9cclxuICBwcml2YXRlIHVwZGF0ZURyYWdUaW1lc3RhbXAocGF5bG9hZDogSURyYWdNb3ZlUGF5bG9hZCk6IHZvaWQge1xyXG4gICAgY29uc3QgdGltZUVsID0gcGF5bG9hZC5lbGVtZW50LnF1ZXJ5U2VsZWN0b3IoJ3N3cC1ldmVudC10aW1lJyk7XHJcbiAgICBpZiAoIXRpbWVFbCkgcmV0dXJuO1xyXG5cclxuICAgIC8vIFNuYXAgcG9zaXRpb24gdG8gZ3JpZCBpbnRlcnZhbFxyXG4gICAgY29uc3Qgc25hcHBlZFkgPSBzbmFwVG9HcmlkKHBheWxvYWQuY3VycmVudFksIHRoaXMuZ3JpZENvbmZpZyk7XHJcblxyXG4gICAgLy8gQ2FsY3VsYXRlIG5ldyBzdGFydCB0aW1lXHJcbiAgICBjb25zdCBtaW51dGVzRnJvbUdyaWRTdGFydCA9IHBpeGVsc1RvTWludXRlcyhzbmFwcGVkWSwgdGhpcy5ncmlkQ29uZmlnKTtcclxuICAgIGNvbnN0IHN0YXJ0TWludXRlcyA9ICh0aGlzLmdyaWRDb25maWcuZGF5U3RhcnRIb3VyICogNjApICsgbWludXRlc0Zyb21HcmlkU3RhcnQ7XHJcblxyXG4gICAgLy8gS2VlcCBvcmlnaW5hbCBkdXJhdGlvbiAoZnJvbSBlbGVtZW50IGhlaWdodClcclxuICAgIGNvbnN0IGhlaWdodCA9IHBhcnNlRmxvYXQocGF5bG9hZC5lbGVtZW50LnN0eWxlLmhlaWdodCkgfHwgdGhpcy5ncmlkQ29uZmlnLmhvdXJIZWlnaHQ7XHJcbiAgICBjb25zdCBkdXJhdGlvbk1pbnV0ZXMgPSBwaXhlbHNUb01pbnV0ZXMoaGVpZ2h0LCB0aGlzLmdyaWRDb25maWcpO1xyXG5cclxuICAgIC8vIENyZWF0ZSBEYXRlIG9iamVjdHMgZm9yIGNvbnNpc3RlbnQgZm9ybWF0dGluZyB2aWEgRGF0ZVNlcnZpY2VcclxuICAgIGNvbnN0IHN0YXJ0ID0gdGhpcy5taW51dGVzVG9EYXRlKHN0YXJ0TWludXRlcyk7XHJcbiAgICBjb25zdCBlbmQgPSB0aGlzLm1pbnV0ZXNUb0RhdGUoc3RhcnRNaW51dGVzICsgZHVyYXRpb25NaW51dGVzKTtcclxuXHJcbiAgICB0aW1lRWwudGV4dENvbnRlbnQgPSB0aGlzLmRhdGVTZXJ2aWNlLmZvcm1hdFRpbWVSYW5nZShzdGFydCwgZW5kKTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIENvbnZlcnQgbWludXRlcyBzaW5jZSBtaWRuaWdodCB0byBhIERhdGUgb2JqZWN0ICh0b2RheSlcclxuICAgKi9cclxuICBwcml2YXRlIG1pbnV0ZXNUb0RhdGUobWludXRlczogbnVtYmVyKTogRGF0ZSB7XHJcbiAgICBjb25zdCBkYXRlID0gbmV3IERhdGUoKTtcclxuICAgIGRhdGUuc2V0SG91cnMoTWF0aC5mbG9vcihtaW51dGVzIC8gNjApICUgMjQsIG1pbnV0ZXMgJSA2MCwgMCwgMCk7XHJcbiAgICByZXR1cm4gZGF0ZTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIFJlbmRlciBldmVudHMgZm9yIHZpc2libGUgZGF0ZXMgaW50byBkYXkgY29sdW1uc1xyXG4gICAqIEBwYXJhbSBjb250YWluZXIgLSBDYWxlbmRhciBjb250YWluZXIgZWxlbWVudFxyXG4gICAqIEBwYXJhbSBmaWx0ZXIgLSBGaWx0ZXIgd2l0aCAnZGF0ZScgYW5kIG9wdGlvbmFsbHkgJ3Jlc291cmNlJyBhcnJheXNcclxuICAgKiBAcGFyYW0gZmlsdGVyVGVtcGxhdGUgLSBUZW1wbGF0ZSBmb3IgbWF0Y2hpbmcgZXZlbnRzIHRvIGNvbHVtbnNcclxuICAgKi9cclxuICBhc3luYyByZW5kZXIoY29udGFpbmVyOiBIVE1MRWxlbWVudCwgZmlsdGVyOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmdbXT4sIGZpbHRlclRlbXBsYXRlOiBGaWx0ZXJUZW1wbGF0ZSk6IFByb21pc2U8dm9pZD4ge1xyXG4gICAgLy8gU3RvcmUgY29udGFpbmVyIHJlZmVyZW5jZSBmb3IgbGF0ZXIgcmUtcmVuZGVyc1xyXG4gICAgdGhpcy5jb250YWluZXIgPSBjb250YWluZXI7XHJcblxyXG4gICAgY29uc3QgdmlzaWJsZURhdGVzID0gZmlsdGVyWydkYXRlJ10gfHwgW107XHJcblxyXG4gICAgaWYgKHZpc2libGVEYXRlcy5sZW5ndGggPT09IDApIHJldHVybjtcclxuXHJcbiAgICAvLyBHZXQgZGF0ZSByYW5nZSBmb3IgcXVlcnlcclxuICAgIGNvbnN0IHN0YXJ0RGF0ZSA9IG5ldyBEYXRlKHZpc2libGVEYXRlc1swXSk7XHJcbiAgICBjb25zdCBlbmREYXRlID0gbmV3IERhdGUodmlzaWJsZURhdGVzW3Zpc2libGVEYXRlcy5sZW5ndGggLSAxXSk7XHJcbiAgICBlbmREYXRlLnNldEhvdXJzKDIzLCA1OSwgNTksIDk5OSk7XHJcblxyXG4gICAgLy8gRmV0Y2ggZXZlbnRzIGZyb20gSW5kZXhlZERCXHJcbiAgICBjb25zdCBldmVudHMgPSBhd2FpdCB0aGlzLmV2ZW50U2VydmljZS5nZXRCeURhdGVSYW5nZShzdGFydERhdGUsIGVuZERhdGUpO1xyXG5cclxuICAgIC8vIEZpbmQgZGF5IGNvbHVtbnNcclxuICAgIGNvbnN0IGRheUNvbHVtbnMgPSBjb250YWluZXIucXVlcnlTZWxlY3Rvcignc3dwLWRheS1jb2x1bW5zJyk7XHJcbiAgICBpZiAoIWRheUNvbHVtbnMpIHJldHVybjtcclxuXHJcbiAgICBjb25zdCBjb2x1bW5zID0gZGF5Q29sdW1ucy5xdWVyeVNlbGVjdG9yQWxsKCdzd3AtZGF5LWNvbHVtbicpO1xyXG5cclxuICAgIC8vIFJlbmRlciBldmVudHMgaW50byBlYWNoIGNvbHVtbiBiYXNlZCBvbiBGaWx0ZXJUZW1wbGF0ZSBtYXRjaGluZ1xyXG4gICAgY29sdW1ucy5mb3JFYWNoKGNvbHVtbiA9PiB7XHJcbiAgICAgIGNvbnN0IGNvbHVtbkVsID0gY29sdW1uIGFzIEhUTUxFbGVtZW50O1xyXG5cclxuICAgICAgLy8gVXNlIEZpbHRlclRlbXBsYXRlIGZvciBtYXRjaGluZyAtIG9ubHkgZmllbGRzIGluIHRlbXBsYXRlIGFyZSBjaGVja2VkXHJcbiAgICAgIGNvbnN0IGNvbHVtbkV2ZW50cyA9IGV2ZW50cy5maWx0ZXIoZXZlbnQgPT4gZmlsdGVyVGVtcGxhdGUubWF0Y2hlcyhldmVudCwgY29sdW1uRWwpKTtcclxuXHJcbiAgICAgIC8vIEdldCBvciBjcmVhdGUgZXZlbnRzIGxheWVyXHJcbiAgICAgIGxldCBldmVudHNMYXllciA9IGNvbHVtbi5xdWVyeVNlbGVjdG9yKCdzd3AtZXZlbnRzLWxheWVyJyk7XHJcbiAgICAgIGlmICghZXZlbnRzTGF5ZXIpIHtcclxuICAgICAgICBldmVudHNMYXllciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3N3cC1ldmVudHMtbGF5ZXInKTtcclxuICAgICAgICBjb2x1bW4uYXBwZW5kQ2hpbGQoZXZlbnRzTGF5ZXIpO1xyXG4gICAgICB9XHJcblxyXG4gICAgICAvLyBDbGVhciBleGlzdGluZyBldmVudHNcclxuICAgICAgZXZlbnRzTGF5ZXIuaW5uZXJIVE1MID0gJyc7XHJcblxyXG4gICAgICAvLyBGaWx0ZXIgdG8gdGltZWQgZXZlbnRzIG9ubHlcclxuICAgICAgY29uc3QgdGltZWRFdmVudHMgPSBjb2x1bW5FdmVudHMuZmlsdGVyKGV2ZW50ID0+ICFldmVudC5hbGxEYXkpO1xyXG5cclxuICAgICAgLy8gQ2FsY3VsYXRlIGxheW91dCB3aXRoIHN0YWNraW5nL2dyb3VwaW5nXHJcbiAgICAgIGNvbnN0IGxheW91dCA9IGNhbGN1bGF0ZUNvbHVtbkxheW91dCh0aW1lZEV2ZW50cywgdGhpcy5ncmlkQ29uZmlnKTtcclxuXHJcbiAgICAgIC8vIFJlbmRlciBHUklEIGdyb3VwcyAoc2ltdWx0YW5lb3VzIGV2ZW50cyBzaWRlLWJ5LXNpZGUpXHJcbiAgICAgIGxheW91dC5ncmlkcy5mb3JFYWNoKGdyaWQgPT4ge1xyXG4gICAgICAgIGNvbnN0IGdyb3VwRWwgPSB0aGlzLnJlbmRlckdyaWRHcm91cChncmlkKTtcclxuICAgICAgICBldmVudHNMYXllciEuYXBwZW5kQ2hpbGQoZ3JvdXBFbCk7XHJcbiAgICAgIH0pO1xyXG5cclxuICAgICAgLy8gUmVuZGVyIFNUQUNLRUQgZXZlbnRzIChvdmVybGFwcGluZyB3aXRoIG1hcmdpbiBvZmZzZXQpXHJcbiAgICAgIGxheW91dC5zdGFja2VkLmZvckVhY2goaXRlbSA9PiB7XHJcbiAgICAgICAgY29uc3QgZXZlbnRFbCA9IHRoaXMucmVuZGVyU3RhY2tlZEV2ZW50KGl0ZW0uZXZlbnQsIGl0ZW0uc3RhY2tMZXZlbCk7XHJcbiAgICAgICAgZXZlbnRzTGF5ZXIhLmFwcGVuZENoaWxkKGV2ZW50RWwpO1xyXG4gICAgICB9KTtcclxuICAgIH0pO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogQ3JlYXRlIGEgc2luZ2xlIGV2ZW50IGVsZW1lbnRcclxuICAgKlxyXG4gICAqIENMRUFOIGFwcHJvYWNoOlxyXG4gICAqIC0gT25seSBkYXRhLWlkIGZvciBsb29rdXBcclxuICAgKiAtIFZpc2libGUgY29udGVudCBpbiBpbm5lckhUTUwgb25seVxyXG4gICAqL1xyXG4gIHByaXZhdGUgY3JlYXRlRXZlbnRFbGVtZW50KGV2ZW50OiBJQ2FsZW5kYXJFdmVudCk6IEhUTUxFbGVtZW50IHtcclxuICAgIGNvbnN0IGVsZW1lbnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzd3AtZXZlbnQnKTtcclxuXHJcbiAgICAvLyBEYXRhIGF0dHJpYnV0ZXMgZm9yIFN3cEV2ZW50IGNvbXBhdGliaWxpdHlcclxuICAgIGVsZW1lbnQuZGF0YXNldC5ldmVudElkID0gZXZlbnQuaWQ7XHJcbiAgICBpZiAoZXZlbnQucmVzb3VyY2VJZCkge1xyXG4gICAgICBlbGVtZW50LmRhdGFzZXQucmVzb3VyY2VJZCA9IGV2ZW50LnJlc291cmNlSWQ7XHJcbiAgICB9XHJcblxyXG4gICAgLy8gQ2FsY3VsYXRlIHBvc2l0aW9uXHJcbiAgICBjb25zdCBwb3NpdGlvbiA9IGNhbGN1bGF0ZUV2ZW50UG9zaXRpb24oZXZlbnQuc3RhcnQsIGV2ZW50LmVuZCwgdGhpcy5ncmlkQ29uZmlnKTtcclxuICAgIGVsZW1lbnQuc3R5bGUudG9wID0gYCR7cG9zaXRpb24udG9wfXB4YDtcclxuICAgIGVsZW1lbnQuc3R5bGUuaGVpZ2h0ID0gYCR7cG9zaXRpb24uaGVpZ2h0fXB4YDtcclxuXHJcbiAgICAvLyBDb2xvciBjbGFzcyBiYXNlZCBvbiBldmVudCB0eXBlXHJcbiAgICBjb25zdCBjb2xvckNsYXNzID0gdGhpcy5nZXRDb2xvckNsYXNzKGV2ZW50KTtcclxuICAgIGlmIChjb2xvckNsYXNzKSB7XHJcbiAgICAgIGVsZW1lbnQuY2xhc3NMaXN0LmFkZChjb2xvckNsYXNzKTtcclxuICAgIH1cclxuXHJcbiAgICAvLyBWaXNpYmxlIGNvbnRlbnQgb25seVxyXG4gICAgZWxlbWVudC5pbm5lckhUTUwgPSBgXHJcbiAgICAgIDxzd3AtZXZlbnQtdGltZT4ke3RoaXMuZGF0ZVNlcnZpY2UuZm9ybWF0VGltZVJhbmdlKGV2ZW50LnN0YXJ0LCBldmVudC5lbmQpfTwvc3dwLWV2ZW50LXRpbWU+XHJcbiAgICAgIDxzd3AtZXZlbnQtdGl0bGU+JHt0aGlzLmVzY2FwZUh0bWwoZXZlbnQudGl0bGUpfTwvc3dwLWV2ZW50LXRpdGxlPlxyXG4gICAgICAke2V2ZW50LmRlc2NyaXB0aW9uID8gYDxzd3AtZXZlbnQtZGVzY3JpcHRpb24+JHt0aGlzLmVzY2FwZUh0bWwoZXZlbnQuZGVzY3JpcHRpb24pfTwvc3dwLWV2ZW50LWRlc2NyaXB0aW9uPmAgOiAnJ31cclxuICAgIGA7XHJcblxyXG4gICAgcmV0dXJuIGVsZW1lbnQ7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBHZXQgY29sb3IgY2xhc3MgYmFzZWQgb24gbWV0YWRhdGEuY29sb3Igb3IgZXZlbnQgdHlwZVxyXG4gICAqL1xyXG4gIHByaXZhdGUgZ2V0Q29sb3JDbGFzcyhldmVudDogSUNhbGVuZGFyRXZlbnQpOiBzdHJpbmcge1xyXG4gICAgLy8gQ2hlY2sgbWV0YWRhdGEuY29sb3IgZmlyc3RcclxuICAgIGlmIChldmVudC5tZXRhZGF0YT8uY29sb3IpIHtcclxuICAgICAgcmV0dXJuIGBpcy0ke2V2ZW50Lm1ldGFkYXRhLmNvbG9yfWA7XHJcbiAgICB9XHJcblxyXG4gICAgLy8gRmFsbGJhY2sgdG8gdHlwZS1iYXNlZCBjb2xvclxyXG4gICAgY29uc3QgdHlwZUNvbG9yczogUmVjb3JkPHN0cmluZywgc3RyaW5nPiA9IHtcclxuICAgICAgJ2N1c3RvbWVyJzogJ2lzLWJsdWUnLFxyXG4gICAgICAndmFjYXRpb24nOiAnaXMtZ3JlZW4nLFxyXG4gICAgICAnYnJlYWsnOiAnaXMtYW1iZXInLFxyXG4gICAgICAnbWVldGluZyc6ICdpcy1wdXJwbGUnLFxyXG4gICAgICAnYmxvY2tlZCc6ICdpcy1yZWQnXHJcbiAgICB9O1xyXG4gICAgcmV0dXJuIHR5cGVDb2xvcnNbZXZlbnQudHlwZV0gfHwgJ2lzLWJsdWUnO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogRXNjYXBlIEhUTUwgdG8gcHJldmVudCBYU1NcclxuICAgKi9cclxuICBwcml2YXRlIGVzY2FwZUh0bWwodGV4dDogc3RyaW5nKTogc3RyaW5nIHtcclxuICAgIGNvbnN0IGRpdiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2RpdicpO1xyXG4gICAgZGl2LnRleHRDb250ZW50ID0gdGV4dDtcclxuICAgIHJldHVybiBkaXYuaW5uZXJIVE1MO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogUmVuZGVyIGEgR1JJRCBncm91cCB3aXRoIHNpZGUtYnktc2lkZSBjb2x1bW5zXHJcbiAgICogVXNlZCB3aGVuIG11bHRpcGxlIGV2ZW50cyBzdGFydCBhdCB0aGUgc2FtZSB0aW1lXHJcbiAgICovXHJcbiAgcHJpdmF0ZSByZW5kZXJHcmlkR3JvdXAobGF5b3V0OiBJR3JpZEdyb3VwTGF5b3V0KTogSFRNTEVsZW1lbnQge1xyXG4gICAgY29uc3QgZ3JvdXAgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzd3AtZXZlbnQtZ3JvdXAnKTtcclxuICAgIGdyb3VwLmNsYXNzTGlzdC5hZGQoYGNvbHMtJHtsYXlvdXQuY29sdW1ucy5sZW5ndGh9YCk7XHJcbiAgICBncm91cC5zdHlsZS50b3AgPSBgJHtsYXlvdXQucG9zaXRpb24udG9wfXB4YDtcclxuXHJcbiAgICAvLyBTdGFjayBsZXZlbCBzdHlsaW5nIGZvciBlbnRpcmUgZ3JvdXAgKGlmIG5lc3RlZCBpbiBhbm90aGVyIGV2ZW50KVxyXG4gICAgaWYgKGxheW91dC5zdGFja0xldmVsID4gMCkge1xyXG4gICAgICBncm91cC5zdHlsZS5tYXJnaW5MZWZ0ID0gYCR7bGF5b3V0LnN0YWNrTGV2ZWwgKiAxNX1weGA7XHJcbiAgICAgIGdyb3VwLnN0eWxlLnpJbmRleCA9IGAkezEwMCArIGxheW91dC5zdGFja0xldmVsfWA7XHJcbiAgICB9XHJcblxyXG4gICAgLy8gQ2FsY3VsYXRlIHRoZSBoZWlnaHQgbmVlZGVkIGZvciB0aGUgZ3JvdXAgKHRhbGxlc3QgZXZlbnQpXHJcbiAgICBsZXQgbWF4Qm90dG9tID0gMDtcclxuICAgIGZvciAoY29uc3QgZXZlbnQgb2YgbGF5b3V0LmV2ZW50cykge1xyXG4gICAgICBjb25zdCBwb3MgPSBjYWxjdWxhdGVFdmVudFBvc2l0aW9uKGV2ZW50LnN0YXJ0LCBldmVudC5lbmQsIHRoaXMuZ3JpZENvbmZpZyk7XHJcbiAgICAgIGNvbnN0IGV2ZW50Qm90dG9tID0gcG9zLnRvcCArIHBvcy5oZWlnaHQ7XHJcbiAgICAgIGlmIChldmVudEJvdHRvbSA+IG1heEJvdHRvbSkgbWF4Qm90dG9tID0gZXZlbnRCb3R0b207XHJcbiAgICB9XHJcbiAgICBjb25zdCBncm91cEhlaWdodCA9IG1heEJvdHRvbSAtIGxheW91dC5wb3NpdGlvbi50b3A7XHJcbiAgICBncm91cC5zdHlsZS5oZWlnaHQgPSBgJHtncm91cEhlaWdodH1weGA7XHJcblxyXG4gICAgLy8gQ3JlYXRlIHdyYXBwZXIgZGl2IGZvciBlYWNoIGNvbHVtblxyXG4gICAgbGF5b3V0LmNvbHVtbnMuZm9yRWFjaChjb2x1bW5FdmVudHMgPT4ge1xyXG4gICAgICBjb25zdCB3cmFwcGVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnZGl2Jyk7XHJcbiAgICAgIHdyYXBwZXIuc3R5bGUucG9zaXRpb24gPSAncmVsYXRpdmUnO1xyXG5cclxuICAgICAgY29sdW1uRXZlbnRzLmZvckVhY2goZXZlbnQgPT4ge1xyXG4gICAgICAgIGNvbnN0IGV2ZW50RWwgPSB0aGlzLmNyZWF0ZUV2ZW50RWxlbWVudChldmVudCk7XHJcbiAgICAgICAgLy8gUG9zaXRpb24gcmVsYXRpdmUgdG8gZ3JvdXAgdG9wXHJcbiAgICAgICAgY29uc3QgcG9zID0gY2FsY3VsYXRlRXZlbnRQb3NpdGlvbihldmVudC5zdGFydCwgZXZlbnQuZW5kLCB0aGlzLmdyaWRDb25maWcpO1xyXG4gICAgICAgIGV2ZW50RWwuc3R5bGUudG9wID0gYCR7cG9zLnRvcCAtIGxheW91dC5wb3NpdGlvbi50b3B9cHhgO1xyXG4gICAgICAgIGV2ZW50RWwuc3R5bGUucG9zaXRpb24gPSAnYWJzb2x1dGUnO1xyXG4gICAgICAgIGV2ZW50RWwuc3R5bGUubGVmdCA9ICcwJztcclxuICAgICAgICBldmVudEVsLnN0eWxlLnJpZ2h0ID0gJzAnO1xyXG4gICAgICAgIHdyYXBwZXIuYXBwZW5kQ2hpbGQoZXZlbnRFbCk7XHJcbiAgICAgIH0pO1xyXG5cclxuICAgICAgZ3JvdXAuYXBwZW5kQ2hpbGQod3JhcHBlcik7XHJcbiAgICB9KTtcclxuXHJcbiAgICByZXR1cm4gZ3JvdXA7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBSZW5kZXIgYSBTVEFDS0VEIGV2ZW50IHdpdGggbWFyZ2luLWxlZnQgb2Zmc2V0XHJcbiAgICogVXNlZCBmb3Igb3ZlcmxhcHBpbmcgZXZlbnRzIHRoYXQgZG9uJ3Qgc3RhcnQgYXQgdGhlIHNhbWUgdGltZVxyXG4gICAqL1xyXG4gIHByaXZhdGUgcmVuZGVyU3RhY2tlZEV2ZW50KGV2ZW50OiBJQ2FsZW5kYXJFdmVudCwgc3RhY2tMZXZlbDogbnVtYmVyKTogSFRNTEVsZW1lbnQge1xyXG4gICAgY29uc3QgZWxlbWVudCA9IHRoaXMuY3JlYXRlRXZlbnRFbGVtZW50KGV2ZW50KTtcclxuXHJcbiAgICAvLyBBZGQgc3RhY2sgbWV0YWRhdGEgZm9yIGRyYWctZHJvcCBhbmQgb3RoZXIgZmVhdHVyZXNcclxuICAgIGVsZW1lbnQuZGF0YXNldC5zdGFja0xpbmsgPSBKU09OLnN0cmluZ2lmeSh7IHN0YWNrTGV2ZWwgfSk7XHJcblxyXG4gICAgLy8gVmlzdWFsIHN0eWxpbmcgYmFzZWQgb24gc3RhY2sgbGV2ZWxcclxuICAgIGlmIChzdGFja0xldmVsID4gMCkge1xyXG4gICAgICBlbGVtZW50LnN0eWxlLm1hcmdpbkxlZnQgPSBgJHtzdGFja0xldmVsICogMTV9cHhgO1xyXG4gICAgICBlbGVtZW50LnN0eWxlLnpJbmRleCA9IGAkezEwMCArIHN0YWNrTGV2ZWx9YDtcclxuICAgIH1cclxuXHJcbiAgICByZXR1cm4gZWxlbWVudDtcclxuICB9XHJcbn1cclxuIiwgImltcG9ydCB7IElSZW5kZXJlciwgSVJlbmRlckNvbnRleHQgfSBmcm9tICcuL0lHcm91cGluZ1JlbmRlcmVyJztcclxuXHJcbi8qKlxyXG4gKiBFbnRpdHkgbXVzdCBoYXZlIGlkXHJcbiAqL1xyXG5leHBvcnQgaW50ZXJmYWNlIElHcm91cGluZ0VudGl0eSB7XHJcbiAgaWQ6IHN0cmluZztcclxufVxyXG5cclxuLyoqXHJcbiAqIENvbmZpZ3VyYXRpb24gZm9yIGEgZ3JvdXBpbmcgcmVuZGVyZXJcclxuICovXHJcbmV4cG9ydCBpbnRlcmZhY2UgSUdyb3VwaW5nUmVuZGVyZXJDb25maWcge1xyXG4gIGVsZW1lbnRUYWc6IHN0cmluZzsgICAgICAvLyBlLmcuLCAnc3dwLXRlYW0taGVhZGVyJ1xyXG4gIGlkQXR0cmlidXRlOiBzdHJpbmc7ICAgICAvLyBlLmcuLCAndGVhbUlkJyAtPiBkYXRhLXRlYW0taWRcclxuICBjb2xzcGFuVmFyOiBzdHJpbmc7ICAgICAgLy8gZS5nLiwgJy0tdGVhbS1jb2xzJ1xyXG59XHJcblxyXG4vKipcclxuICogQWJzdHJhY3QgYmFzZSBjbGFzcyBmb3IgZ3JvdXBpbmcgcmVuZGVyZXJzXHJcbiAqXHJcbiAqIEhhbmRsZXM6XHJcbiAqIC0gRmV0Y2hpbmcgZW50aXRpZXMgYnkgSURzXHJcbiAqIC0gQ2FsY3VsYXRpbmcgY29sc3BhbiBmcm9tIHBhcmVudENoaWxkTWFwXHJcbiAqIC0gQ3JlYXRpbmcgaGVhZGVyIGVsZW1lbnRzXHJcbiAqIC0gQXBwZW5kaW5nIHRvIGNvbnRhaW5lclxyXG4gKlxyXG4gKiBTdWJjbGFzc2VzIG92ZXJyaWRlOlxyXG4gKiAtIHJlbmRlckhlYWRlcigpIGZvciBjdXN0b20gY29udGVudFxyXG4gKiAtIGdldERpc3BsYXlOYW1lKCkgZm9yIGVudGl0eSBkaXNwbGF5IHRleHRcclxuICovXHJcbmV4cG9ydCBhYnN0cmFjdCBjbGFzcyBCYXNlR3JvdXBpbmdSZW5kZXJlcjxUIGV4dGVuZHMgSUdyb3VwaW5nRW50aXR5PiBpbXBsZW1lbnRzIElSZW5kZXJlciB7XHJcbiAgYWJzdHJhY3QgcmVhZG9ubHkgdHlwZTogc3RyaW5nO1xyXG4gIHByb3RlY3RlZCBhYnN0cmFjdCByZWFkb25seSBjb25maWc6IElHcm91cGluZ1JlbmRlcmVyQ29uZmlnO1xyXG5cclxuICAvKipcclxuICAgKiBGZXRjaCBlbnRpdGllcyBmcm9tIHNlcnZpY2VcclxuICAgKi9cclxuICBwcm90ZWN0ZWQgYWJzdHJhY3QgZ2V0RW50aXRpZXMoaWRzOiBzdHJpbmdbXSk6IFByb21pc2U8VFtdPjtcclxuXHJcbiAgLyoqXHJcbiAgICogR2V0IGRpc3BsYXkgbmFtZSBmb3IgZW50aXR5XHJcbiAgICovXHJcbiAgcHJvdGVjdGVkIGFic3RyYWN0IGdldERpc3BsYXlOYW1lKGVudGl0eTogVCk6IHN0cmluZztcclxuXHJcbiAgLyoqXHJcbiAgICogTWFpbiByZW5kZXIgbWV0aG9kIC0gaGFuZGxlcyBjb21tb24gbG9naWNcclxuICAgKi9cclxuICBhc3luYyByZW5kZXIoY29udGV4dDogSVJlbmRlckNvbnRleHQpOiBQcm9taXNlPHZvaWQ+IHtcclxuICAgIGNvbnN0IGFsbG93ZWRJZHMgPSBjb250ZXh0LmZpbHRlclt0aGlzLnR5cGVdIHx8IFtdO1xyXG4gICAgaWYgKGFsbG93ZWRJZHMubGVuZ3RoID09PSAwKSByZXR1cm47XHJcblxyXG4gICAgY29uc3QgZW50aXRpZXMgPSBhd2FpdCB0aGlzLmdldEVudGl0aWVzKGFsbG93ZWRJZHMpO1xyXG4gICAgY29uc3QgZGF0ZUNvdW50ID0gY29udGV4dC5maWx0ZXJbJ2RhdGUnXT8ubGVuZ3RoIHx8IDE7XHJcbiAgICBjb25zdCBjaGlsZElkcyA9IGNvbnRleHQuY2hpbGRUeXBlID8gY29udGV4dC5maWx0ZXJbY29udGV4dC5jaGlsZFR5cGVdIHx8IFtdIDogW107XHJcblxyXG4gICAgZm9yIChjb25zdCBlbnRpdHkgb2YgZW50aXRpZXMpIHtcclxuICAgICAgY29uc3QgZW50aXR5Q2hpbGRJZHMgPSBjb250ZXh0LnBhcmVudENoaWxkTWFwPy5bZW50aXR5LmlkXSB8fCBbXTtcclxuICAgICAgY29uc3QgY2hpbGRDb3VudCA9IGVudGl0eUNoaWxkSWRzLmZpbHRlcihpZCA9PiBjaGlsZElkcy5pbmNsdWRlcyhpZCkpLmxlbmd0aDtcclxuICAgICAgY29uc3QgY29sc3BhbiA9IGNoaWxkQ291bnQgKiBkYXRlQ291bnQ7XHJcblxyXG4gICAgICBjb25zdCBoZWFkZXIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KHRoaXMuY29uZmlnLmVsZW1lbnRUYWcpO1xyXG4gICAgICBoZWFkZXIuZGF0YXNldFt0aGlzLmNvbmZpZy5pZEF0dHJpYnV0ZV0gPSBlbnRpdHkuaWQ7XHJcbiAgICAgIGhlYWRlci5zdHlsZS5zZXRQcm9wZXJ0eSh0aGlzLmNvbmZpZy5jb2xzcGFuVmFyLCBTdHJpbmcoY29sc3BhbikpO1xyXG5cclxuICAgICAgLy8gQWxsb3cgc3ViY2xhc3MgdG8gY3VzdG9taXplIGhlYWRlciBjb250ZW50XHJcbiAgICAgIHRoaXMucmVuZGVySGVhZGVyKGVudGl0eSwgaGVhZGVyLCBjb250ZXh0KTtcclxuXHJcbiAgICAgIGNvbnRleHQuaGVhZGVyQ29udGFpbmVyLmFwcGVuZENoaWxkKGhlYWRlcik7XHJcbiAgICB9XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBPdmVycmlkZSB0aGlzIG1ldGhvZCBmb3IgY3VzdG9tIGhlYWRlciByZW5kZXJpbmdcclxuICAgKiBEZWZhdWx0OiBqdXN0IHNldHMgdGV4dENvbnRlbnQgdG8gZGlzcGxheSBuYW1lXHJcbiAgICovXHJcbiAgcHJvdGVjdGVkIHJlbmRlckhlYWRlcihlbnRpdHk6IFQsIGhlYWRlcjogSFRNTEVsZW1lbnQsIF9jb250ZXh0OiBJUmVuZGVyQ29udGV4dCk6IHZvaWQge1xyXG4gICAgaGVhZGVyLnRleHRDb250ZW50ID0gdGhpcy5nZXREaXNwbGF5TmFtZShlbnRpdHkpO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogSGVscGVyIHRvIHJlbmRlciBhIHNpbmdsZSBlbnRpdHkgaGVhZGVyLlxyXG4gICAqIENhbiBiZSB1c2VkIGJ5IHN1YmNsYXNzZXMgdGhhdCBvdmVycmlkZSByZW5kZXIoKSBidXQgd2FudCBjb25zaXN0ZW50IGhlYWRlciBjcmVhdGlvbi5cclxuICAgKi9cclxuICBwcm90ZWN0ZWQgY3JlYXRlSGVhZGVyKGVudGl0eTogVCwgY29udGV4dDogSVJlbmRlckNvbnRleHQpOiBIVE1MRWxlbWVudCB7XHJcbiAgICBjb25zdCBoZWFkZXIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KHRoaXMuY29uZmlnLmVsZW1lbnRUYWcpO1xyXG4gICAgaGVhZGVyLmRhdGFzZXRbdGhpcy5jb25maWcuaWRBdHRyaWJ1dGVdID0gZW50aXR5LmlkO1xyXG4gICAgdGhpcy5yZW5kZXJIZWFkZXIoZW50aXR5LCBoZWFkZXIsIGNvbnRleHQpO1xyXG4gICAgcmV0dXJuIGhlYWRlcjtcclxuICB9XHJcbn1cclxuIiwgImltcG9ydCB7IElSZW5kZXJDb250ZXh0IH0gZnJvbSAnLi4vLi4vY29yZS9JR3JvdXBpbmdSZW5kZXJlcic7XHJcbmltcG9ydCB7IEJhc2VHcm91cGluZ1JlbmRlcmVyLCBJR3JvdXBpbmdSZW5kZXJlckNvbmZpZyB9IGZyb20gJy4uLy4uL2NvcmUvQmFzZUdyb3VwaW5nUmVuZGVyZXInO1xyXG5pbXBvcnQgeyBSZXNvdXJjZVNlcnZpY2UgfSBmcm9tICcuLi8uLi9zdG9yYWdlL3Jlc291cmNlcy9SZXNvdXJjZVNlcnZpY2UnO1xyXG5pbXBvcnQgeyBJUmVzb3VyY2UgfSBmcm9tICcuLi8uLi90eXBlcy9DYWxlbmRhclR5cGVzJztcclxuXHJcbmV4cG9ydCBjbGFzcyBSZXNvdXJjZVJlbmRlcmVyIGV4dGVuZHMgQmFzZUdyb3VwaW5nUmVuZGVyZXI8SVJlc291cmNlPiB7XHJcbiAgcmVhZG9ubHkgdHlwZSA9ICdyZXNvdXJjZSc7XHJcblxyXG4gIHByb3RlY3RlZCByZWFkb25seSBjb25maWc6IElHcm91cGluZ1JlbmRlcmVyQ29uZmlnID0ge1xyXG4gICAgZWxlbWVudFRhZzogJ3N3cC1yZXNvdXJjZS1oZWFkZXInLFxyXG4gICAgaWRBdHRyaWJ1dGU6ICdyZXNvdXJjZUlkJyxcclxuICAgIGNvbHNwYW5WYXI6ICctLXJlc291cmNlLWNvbHMnXHJcbiAgfTtcclxuXHJcbiAgY29uc3RydWN0b3IocHJpdmF0ZSByZXNvdXJjZVNlcnZpY2U6IFJlc291cmNlU2VydmljZSkge1xyXG4gICAgc3VwZXIoKTtcclxuICB9XHJcblxyXG4gIHByb3RlY3RlZCBnZXRFbnRpdGllcyhpZHM6IHN0cmluZ1tdKTogUHJvbWlzZTxJUmVzb3VyY2VbXT4ge1xyXG4gICAgcmV0dXJuIHRoaXMucmVzb3VyY2VTZXJ2aWNlLmdldEJ5SWRzKGlkcyk7XHJcbiAgfVxyXG5cclxuICBwcm90ZWN0ZWQgZ2V0RGlzcGxheU5hbWUoZW50aXR5OiBJUmVzb3VyY2UpOiBzdHJpbmcge1xyXG4gICAgcmV0dXJuIGVudGl0eS5kaXNwbGF5TmFtZTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIE92ZXJyaWRlIHJlbmRlciB0byBoYW5kbGU6XHJcbiAgICogMS4gU3BlY2lhbCBvcmRlcmluZyB3aGVuIHBhcmVudENoaWxkTWFwIGV4aXN0cyAocmVzb3VyY2VzIGdyb3VwZWQgYnkgcGFyZW50KVxyXG4gICAqIDIuIERpZmZlcmVudCBjb2xzcGFuIGNhbGN1bGF0aW9uIChqdXN0IGRhdGVDb3VudCwgbm90IGNoaWxkQ291bnQgKiBkYXRlQ291bnQpXHJcbiAgICovXHJcbiAgYXN5bmMgcmVuZGVyKGNvbnRleHQ6IElSZW5kZXJDb250ZXh0KTogUHJvbWlzZTx2b2lkPiB7XHJcbiAgICBjb25zdCByZXNvdXJjZUlkcyA9IGNvbnRleHQuZmlsdGVyWydyZXNvdXJjZSddIHx8IFtdO1xyXG4gICAgY29uc3QgZGF0ZUNvdW50ID0gY29udGV4dC5maWx0ZXJbJ2RhdGUnXT8ubGVuZ3RoIHx8IDE7XHJcblxyXG4gICAgLy8gRGV0ZXJtaW5lIHJlbmRlciBvcmRlciBiYXNlZCBvbiBwYXJlbnRDaGlsZE1hcFxyXG4gICAgLy8gSWYgcGFyZW50Q2hpbGRNYXAgZXhpc3RzLCByZW5kZXIgcmVzb3VyY2VzIGdyb3VwZWQgYnkgcGFyZW50IChlLmcuLCB0ZWFtKVxyXG4gICAgLy8gT3RoZXJ3aXNlLCByZW5kZXIgaW4gZmlsdGVyIG9yZGVyXHJcbiAgICBsZXQgb3JkZXJlZFJlc291cmNlSWRzOiBzdHJpbmdbXTtcclxuXHJcbiAgICBpZiAoY29udGV4dC5wYXJlbnRDaGlsZE1hcCkge1xyXG4gICAgICAvLyBSZW5kZXIgcmVzb3VyY2VzIGluIHBhcmVudC1jaGlsZCBvcmRlclxyXG4gICAgICBvcmRlcmVkUmVzb3VyY2VJZHMgPSBbXTtcclxuICAgICAgZm9yIChjb25zdCBjaGlsZElkcyBvZiBPYmplY3QudmFsdWVzKGNvbnRleHQucGFyZW50Q2hpbGRNYXApKSB7XHJcbiAgICAgICAgZm9yIChjb25zdCBjaGlsZElkIG9mIGNoaWxkSWRzKSB7XHJcbiAgICAgICAgICBpZiAocmVzb3VyY2VJZHMuaW5jbHVkZXMoY2hpbGRJZCkpIHtcclxuICAgICAgICAgICAgb3JkZXJlZFJlc291cmNlSWRzLnB1c2goY2hpbGRJZCk7XHJcbiAgICAgICAgICB9XHJcbiAgICAgICAgfVxyXG4gICAgICB9XHJcbiAgICB9IGVsc2Uge1xyXG4gICAgICBvcmRlcmVkUmVzb3VyY2VJZHMgPSByZXNvdXJjZUlkcztcclxuICAgIH1cclxuXHJcbiAgICBjb25zdCByZXNvdXJjZXMgPSBhd2FpdCB0aGlzLmdldEVudGl0aWVzKG9yZGVyZWRSZXNvdXJjZUlkcyk7XHJcblxyXG4gICAgLy8gQ3JlYXRlIGEgbWFwIGZvciBxdWljayBsb29rdXAgdG8gcHJlc2VydmUgb3JkZXJcclxuICAgIGNvbnN0IHJlc291cmNlTWFwID0gbmV3IE1hcChyZXNvdXJjZXMubWFwKHIgPT4gW3IuaWQsIHJdKSk7XHJcblxyXG4gICAgZm9yIChjb25zdCByZXNvdXJjZUlkIG9mIG9yZGVyZWRSZXNvdXJjZUlkcykge1xyXG4gICAgICBjb25zdCByZXNvdXJjZSA9IHJlc291cmNlTWFwLmdldChyZXNvdXJjZUlkKTtcclxuICAgICAgaWYgKCFyZXNvdXJjZSkgY29udGludWU7XHJcblxyXG4gICAgICBjb25zdCBoZWFkZXIgPSB0aGlzLmNyZWF0ZUhlYWRlcihyZXNvdXJjZSwgY29udGV4dCk7XHJcbiAgICAgIGhlYWRlci5zdHlsZS5ncmlkQ29sdW1uID0gYHNwYW4gJHtkYXRlQ291bnR9YDtcclxuICAgICAgY29udGV4dC5oZWFkZXJDb250YWluZXIuYXBwZW5kQ2hpbGQoaGVhZGVyKTtcclxuICAgIH1cclxuICB9XHJcbn1cclxuIiwgImltcG9ydCB7IEJhc2VHcm91cGluZ1JlbmRlcmVyLCBJR3JvdXBpbmdSZW5kZXJlckNvbmZpZyB9IGZyb20gJy4uLy4uL2NvcmUvQmFzZUdyb3VwaW5nUmVuZGVyZXInO1xyXG5pbXBvcnQgeyBUZWFtU2VydmljZSB9IGZyb20gJy4uLy4uL3N0b3JhZ2UvdGVhbXMvVGVhbVNlcnZpY2UnO1xyXG5pbXBvcnQgeyBJVGVhbSB9IGZyb20gJy4uLy4uL3R5cGVzL0NhbGVuZGFyVHlwZXMnO1xyXG5cclxuZXhwb3J0IGNsYXNzIFRlYW1SZW5kZXJlciBleHRlbmRzIEJhc2VHcm91cGluZ1JlbmRlcmVyPElUZWFtPiB7XHJcbiAgcmVhZG9ubHkgdHlwZSA9ICd0ZWFtJztcclxuXHJcbiAgcHJvdGVjdGVkIHJlYWRvbmx5IGNvbmZpZzogSUdyb3VwaW5nUmVuZGVyZXJDb25maWcgPSB7XHJcbiAgICBlbGVtZW50VGFnOiAnc3dwLXRlYW0taGVhZGVyJyxcclxuICAgIGlkQXR0cmlidXRlOiAndGVhbUlkJyxcclxuICAgIGNvbHNwYW5WYXI6ICctLXRlYW0tY29scydcclxuICB9O1xyXG5cclxuICBjb25zdHJ1Y3Rvcihwcml2YXRlIHRlYW1TZXJ2aWNlOiBUZWFtU2VydmljZSkge1xyXG4gICAgc3VwZXIoKTtcclxuICB9XHJcblxyXG4gIHByb3RlY3RlZCBnZXRFbnRpdGllcyhpZHM6IHN0cmluZ1tdKTogUHJvbWlzZTxJVGVhbVtdPiB7XHJcbiAgICByZXR1cm4gdGhpcy50ZWFtU2VydmljZS5nZXRCeUlkcyhpZHMpO1xyXG4gIH1cclxuXHJcbiAgcHJvdGVjdGVkIGdldERpc3BsYXlOYW1lKGVudGl0eTogSVRlYW0pOiBzdHJpbmcge1xyXG4gICAgcmV0dXJuIGVudGl0eS5uYW1lO1xyXG4gIH1cclxufVxyXG4iLCAiZXhwb3J0IGNsYXNzIFRpbWVBeGlzUmVuZGVyZXIge1xyXG4gIHJlbmRlcihjb250YWluZXI6IEhUTUxFbGVtZW50LCBzdGFydEhvdXIgPSA2LCBlbmRIb3VyID0gMjApOiB2b2lkIHtcclxuICAgIGNvbnRhaW5lci5pbm5lckhUTUwgPSAnJztcclxuICAgIGZvciAobGV0IGhvdXIgPSBzdGFydEhvdXI7IGhvdXIgPD0gZW5kSG91cjsgaG91cisrKSB7XHJcbiAgICAgIGNvbnN0IG1hcmtlciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3N3cC1ob3VyLW1hcmtlcicpO1xyXG4gICAgICBtYXJrZXIudGV4dENvbnRlbnQgPSBgJHtob3VyLnRvU3RyaW5nKCkucGFkU3RhcnQoMiwgJzAnKX06MDBgO1xyXG4gICAgICBjb250YWluZXIuYXBwZW5kQ2hpbGQobWFya2VyKTtcclxuICAgIH1cclxuICB9XHJcbn1cclxuIl0sCiAgIm1hcHBpbmdzIjogIjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBO0FBQUE7QUFBQSxLQUFDLFNBQVMsR0FBRSxHQUFFO0FBQUMsa0JBQVUsT0FBTyxXQUFTLGVBQWEsT0FBTyxTQUFPLE9BQU8sVUFBUSxFQUFFLElBQUUsY0FBWSxPQUFPLFVBQVEsT0FBTyxNQUFJLE9BQU8sQ0FBQyxLQUFHLElBQUUsZUFBYSxPQUFPLGFBQVcsYUFBVyxLQUFHLE1BQU0sUUFBTSxFQUFFO0FBQUEsSUFBQyxFQUFFLFNBQU0sV0FBVTtBQUFDO0FBQWEsVUFBSSxJQUFFLEtBQUksSUFBRSxLQUFJLElBQUUsTUFBSyxJQUFFLGVBQWMsSUFBRSxVQUFTLElBQUUsVUFBUyxJQUFFLFFBQU8sSUFBRSxPQUFNLElBQUUsUUFBTyxJQUFFLFNBQVEsSUFBRSxXQUFVLElBQUUsUUFBTyxJQUFFLFFBQU8sSUFBRSxnQkFBZSxJQUFFLDhGQUE2RixJQUFFLHVGQUFzRixJQUFFLEVBQUMsTUFBSyxNQUFLLFVBQVMsMkRBQTJELE1BQU0sR0FBRyxHQUFFLFFBQU8sd0ZBQXdGLE1BQU0sR0FBRyxHQUFFLFNBQVEsU0FBU0EsSUFBRTtBQUFDLFlBQUlDLEtBQUUsQ0FBQyxNQUFLLE1BQUssTUFBSyxJQUFJLEdBQUVDLEtBQUVGLEtBQUU7QUFBSSxlQUFNLE1BQUlBLE1BQUdDLElBQUdDLEtBQUUsTUFBSSxFQUFFLEtBQUdELEdBQUVDLEVBQUMsS0FBR0QsR0FBRSxDQUFDLEtBQUc7QUFBQSxNQUFHLEVBQUMsR0FBRSxJQUFFLGdDQUFTRCxJQUFF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QUFPLE9BQU87QUFBQSxFQUNoQjtBQUNGO0FBcEI4RDtBQUF2RCxJQUFNLGVBQU47OztBQ0pBLElBQU0sb0JBQU4sTUFBTSxrQkFBaUI7QUFBQSxFQUM1QixPQUFPLFdBQXdCLFlBQVksR0FBRyxVQUFVLElBQVU7QUFDaEUsY0FBVSxZQUFZO0FBQ3RCLGFBQVMsT0FBTyxXQUFXLFFBQVEsU0FBUyxRQUFRO0FBQ2xELFlBQU0sU0FBUyxTQUFTLGNBQWMsaUJBQWlCO0FBQ3ZELGFBQU8sY0FBYyxHQUFHLEtBQUssU0FBUyxFQUFFLFNBQVMsR0FBRyxHQUFHLENBQUM7QUFDeEQsZ0JBQVUsWUFBWSxNQUFNO0FBQUEsSUFDOUI7QUFBQSxFQUNGO0FBQ0Y7QUFUOEI7QUFBdkIsSUFBTSxtQkFBTjsiLAogICJuYW1lcyI6IFsidCIsICJlIiwgIm4iLCAiciIsICJpIiwgInMiLCAidSIsICJhIiwgIk0iLCAibSIsICJmIiwgImwiLCAiJCIsICJ5IiwgInYiLCAiZyIsICJEIiwgIm8iLCAiZCIsICJjIiwgImgiLCAidCIsICJpIiwgImUiLCAicyIsICJmIiwgIm4iLCAidSIsICJyIiwgIm8iLCAidCIsICJuIiwgImkiLCAibyIsICJyIiwgImUiLCAidSIsICJmIiwgInMiLCAiYSIsICJ0IiwgImkiLCAiZCIsICJuIiwgImUiLCAicyIsICJkYXlqcyIsICJ1dGMiLCAidGltZXpvbmUiLCAiaXNvV2VlayJdCn0K diff --git a/wwwroot/js/components/NavigationButtons.d.ts b/wwwroot/js/components/NavigationButtons.d.ts new file mode 100644 index 0000000..75f5002 --- /dev/null +++ b/wwwroot/js/components/NavigationButtons.d.ts @@ -0,0 +1,63 @@ +import { IEventBus } from '../types/CalendarTypes'; +import { DateService } from '../utils/DateService'; +import { Configuration } from '../configurations/CalendarConfig'; +/** + * NavigationButtons - Manages navigation button UI and navigation logic + * + * RESPONSIBILITY: + * =============== + * This manager owns all logic related to the UI element + * and performs the actual navigation calculations. + * + * RESPONSIBILITIES: + * - Handles button clicks on swp-nav-button elements + * - Validates navigation actions (prev, next, today) + * - Calculates next/previous dates based on current view + * - Emits NAVIGATION_COMPLETED events with new date + * - Manages button UI listeners + * + * EVENT FLOW: + * =========== + * User clicks button → calculateNewDate() → emit NAVIGATION_COMPLETED → GridManager re-renders + */ +export declare class NavigationButtons { + private eventBus; + private buttonListeners; + private dateService; + private config; + private currentDate; + private currentView; + constructor(eventBus: IEventBus, dateService: DateService, config: Configuration); + /** + * Subscribe to events + */ + private subscribeToEvents; + /** + * Setup click listeners on all navigation buttons + */ + private setupButtonListeners; + /** + * Handle navigation action + */ + private handleNavigation; + /** + * Navigate in specified direction + */ + private navigate; + /** + * Navigate to next period + */ + private navigateNext; + /** + * Navigate to previous period + */ + private navigatePrevious; + /** + * Navigate to today + */ + private navigateToday; + /** + * Validate if string is a valid navigation action + */ + private isValidAction; +} diff --git a/wwwroot/js/components/NavigationButtons.js b/wwwroot/js/components/NavigationButtons.js new file mode 100644 index 0000000..1f53d45 --- /dev/null +++ b/wwwroot/js/components/NavigationButtons.js @@ -0,0 +1,131 @@ +import { CoreEvents } from '../constants/CoreEvents'; +/** + * NavigationButtons - Manages navigation button UI and navigation logic + * + * RESPONSIBILITY: + * =============== + * This manager owns all logic related to the UI element + * and performs the actual navigation calculations. + * + * RESPONSIBILITIES: + * - Handles button clicks on swp-nav-button elements + * - Validates navigation actions (prev, next, today) + * - Calculates next/previous dates based on current view + * - Emits NAVIGATION_COMPLETED events with new date + * - Manages button UI listeners + * + * EVENT FLOW: + * =========== + * User clicks button → calculateNewDate() → emit NAVIGATION_COMPLETED → GridManager re-renders + */ +export class NavigationButtons { + constructor(eventBus, dateService, config) { + this.buttonListeners = new Map(); + this.currentDate = new Date(); + this.currentView = 'week'; + this.eventBus = eventBus; + this.dateService = dateService; + this.config = config; + this.setupButtonListeners(); + this.subscribeToEvents(); + } + /** + * Subscribe to events + */ + subscribeToEvents() { + // Listen for view changes + this.eventBus.on(CoreEvents.VIEW_CHANGED, (e) => { + const detail = e.detail; + this.currentView = detail.currentView; + }); + } + /** + * Setup click listeners on all navigation buttons + */ + setupButtonListeners() { + const buttons = document.querySelectorAll('swp-nav-button[data-action]'); + buttons.forEach(button => { + const clickHandler = (event) => { + event.preventDefault(); + const action = button.getAttribute('data-action'); + if (action && this.isValidAction(action)) { + this.handleNavigation(action); + } + }; + button.addEventListener('click', clickHandler); + this.buttonListeners.set(button, clickHandler); + }); + } + /** + * Handle navigation action + */ + handleNavigation(action) { + switch (action) { + case 'prev': + this.navigatePrevious(); + break; + case 'next': + this.navigateNext(); + break; + case 'today': + this.navigateToday(); + break; + } + } + /** + * Navigate in specified direction + */ + navigate(direction) { + const offset = direction === 'next' ? 1 : -1; + let newDate; + switch (this.currentView) { + case 'week': + newDate = this.dateService.addWeeks(this.currentDate, offset); + break; + case 'month': + newDate = this.dateService.addMonths(this.currentDate, offset); + break; + case 'day': + newDate = this.dateService.addDays(this.currentDate, offset); + break; + default: + newDate = this.dateService.addWeeks(this.currentDate, offset); + } + this.currentDate = newDate; + const payload = { + direction: direction, + newDate: newDate + }; + this.eventBus.emit(CoreEvents.NAV_BUTTON_CLICKED, payload); + } + /** + * Navigate to next period + */ + navigateNext() { + this.navigate('next'); + } + /** + * Navigate to previous period + */ + navigatePrevious() { + this.navigate('previous'); + } + /** + * Navigate to today + */ + navigateToday() { + this.currentDate = new Date(); + const payload = { + direction: 'today', + newDate: this.currentDate + }; + this.eventBus.emit(CoreEvents.NAV_BUTTON_CLICKED, payload); + } + /** + * Validate if string is a valid navigation action + */ + isValidAction(action) { + return ['prev', 'next', 'today'].includes(action); + } +} +//# sourceMappingURL=NavigationButtons.js.map \ No newline at end of file diff --git a/wwwroot/js/components/NavigationButtons.js.map b/wwwroot/js/components/NavigationButtons.js.map new file mode 100644 index 0000000..85a7e6d --- /dev/null +++ b/wwwroot/js/components/NavigationButtons.js.map @@ -0,0 +1 @@ +{"version":3,"file":"NavigationButtons.js","sourceRoot":"","sources":["../../../src/components/NavigationButtons.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/components/ViewSelector.d.ts b/wwwroot/js/components/ViewSelector.d.ts new file mode 100644 index 0000000..04b1853 --- /dev/null +++ b/wwwroot/js/components/ViewSelector.d.ts @@ -0,0 +1,70 @@ +import { IEventBus } from '../types/CalendarTypes'; +import { Configuration } from '../configurations/CalendarConfig'; +/** + * ViewSelectorManager - Manages view selector UI and state + * + * RESPONSIBILITY: + * =============== + * This manager owns all logic related to the UI element. + * It follows the principle that each functional UI element has its own manager. + * + * RESPONSIBILITIES: + * - Handles button clicks on swp-view-button elements + * - Manages current view state (day/week/month) + * - Validates view values + * - Emits VIEW_CHANGED and VIEW_RENDERED events + * - Updates button UI states (data-active attributes) + * + * EVENT FLOW: + * =========== + * User clicks button → changeView() → validate → update state → emit event → update UI + * + * IMPLEMENTATION STATUS: + * ====================== + * - Week view: FULLY IMPLEMENTED + * - Day view: NOT IMPLEMENTED (button exists but no rendering) + * - Month view: NOT IMPLEMENTED (button exists but no rendering) + * + * SUBSCRIBERS: + * ============ + * - GridRenderer: Uses view parameter (currently only supports 'week') + * - Future: DayRenderer, MonthRenderer when implemented + */ +export declare class ViewSelector { + private eventBus; + private config; + private buttonListeners; + constructor(eventBus: IEventBus, config: Configuration); + /** + * Setup click listeners on all view selector buttons + */ + private setupButtonListeners; + /** + * Setup event bus listeners + */ + private setupEventListeners; + /** + * Change the active view + */ + private changeView; + /** + * Update button states (data-active attributes) + */ + private updateButtonStates; + /** + * Initialize view on INITIALIZED event + */ + private initializeView; + /** + * Emit VIEW_RENDERED event + */ + private emitViewRendered; + /** + * Refresh current view on DATE_CHANGED event + */ + private refreshCurrentView; + /** + * Validate if string is a valid CalendarView type + */ + private isValidView; +} diff --git a/wwwroot/js/components/ViewSelector.js b/wwwroot/js/components/ViewSelector.js new file mode 100644 index 0000000..a54939c --- /dev/null +++ b/wwwroot/js/components/ViewSelector.js @@ -0,0 +1,130 @@ +import { CoreEvents } from '../constants/CoreEvents'; +/** + * ViewSelectorManager - Manages view selector UI and state + * + * RESPONSIBILITY: + * =============== + * This manager owns all logic related to the UI element. + * It follows the principle that each functional UI element has its own manager. + * + * RESPONSIBILITIES: + * - Handles button clicks on swp-view-button elements + * - Manages current view state (day/week/month) + * - Validates view values + * - Emits VIEW_CHANGED and VIEW_RENDERED events + * - Updates button UI states (data-active attributes) + * + * EVENT FLOW: + * =========== + * User clicks button → changeView() → validate → update state → emit event → update UI + * + * IMPLEMENTATION STATUS: + * ====================== + * - Week view: FULLY IMPLEMENTED + * - Day view: NOT IMPLEMENTED (button exists but no rendering) + * - Month view: NOT IMPLEMENTED (button exists but no rendering) + * + * SUBSCRIBERS: + * ============ + * - GridRenderer: Uses view parameter (currently only supports 'week') + * - Future: DayRenderer, MonthRenderer when implemented + */ +export class ViewSelector { + constructor(eventBus, config) { + this.buttonListeners = new Map(); + this.eventBus = eventBus; + this.config = config; + this.setupButtonListeners(); + this.setupEventListeners(); + } + /** + * Setup click listeners on all view selector buttons + */ + setupButtonListeners() { + const buttons = document.querySelectorAll('swp-view-button[data-view]'); + buttons.forEach(button => { + const clickHandler = (event) => { + event.preventDefault(); + const view = button.getAttribute('data-view'); + if (view && this.isValidView(view)) { + this.changeView(view); + } + }; + button.addEventListener('click', clickHandler); + this.buttonListeners.set(button, clickHandler); + }); + // Initialize button states + this.updateButtonStates(); + } + /** + * Setup event bus listeners + */ + setupEventListeners() { + this.eventBus.on(CoreEvents.INITIALIZED, () => { + this.initializeView(); + }); + this.eventBus.on(CoreEvents.DATE_CHANGED, () => { + this.refreshCurrentView(); + }); + } + /** + * Change the active view + */ + changeView(newView) { + if (newView === this.config.currentView) { + return; // No change + } + const previousView = this.config.currentView; + this.config.currentView = newView; + // Update button UI states + this.updateButtonStates(); + // Emit event for subscribers + this.eventBus.emit(CoreEvents.VIEW_CHANGED, { + previousView, + currentView: newView + }); + } + /** + * Update button states (data-active attributes) + */ + updateButtonStates() { + const buttons = document.querySelectorAll('swp-view-button[data-view]'); + buttons.forEach(button => { + const buttonView = button.getAttribute('data-view'); + if (buttonView === this.config.currentView) { + button.setAttribute('data-active', 'true'); + } + else { + button.removeAttribute('data-active'); + } + }); + } + /** + * Initialize view on INITIALIZED event + */ + initializeView() { + this.updateButtonStates(); + this.emitViewRendered(); + } + /** + * Emit VIEW_RENDERED event + */ + emitViewRendered() { + this.eventBus.emit(CoreEvents.VIEW_RENDERED, { + view: this.config.currentView + }); + } + /** + * Refresh current view on DATE_CHANGED event + */ + refreshCurrentView() { + this.emitViewRendered(); + } + /** + * Validate if string is a valid CalendarView type + */ + isValidView(view) { + return ['day', 'week', 'month'].includes(view); + } +} +//# sourceMappingURL=ViewSelector.js.map \ No newline at end of file diff --git a/wwwroot/js/components/ViewSelector.js.map b/wwwroot/js/components/ViewSelector.js.map new file mode 100644 index 0000000..291c1a2 --- /dev/null +++ b/wwwroot/js/components/ViewSelector.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ViewSelector.js","sourceRoot":"","sources":["../../../src/components/ViewSelector.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/components/WorkweekPresets.d.ts b/wwwroot/js/components/WorkweekPresets.d.ts new file mode 100644 index 0000000..7b96030 --- /dev/null +++ b/wwwroot/js/components/WorkweekPresets.d.ts @@ -0,0 +1,47 @@ +import { IEventBus } from '../types/CalendarTypes'; +import { Configuration } from '../configurations/CalendarConfig'; +/** + * WorkweekPresetsManager - Manages workweek preset UI and state + * + * RESPONSIBILITY: + * =============== + * This manager owns all logic related to the UI element. + * It follows the principle that each functional UI element has its own manager. + * + * RESPONSIBILITIES: + * - Owns WORK_WEEK_PRESETS data + * - Handles button clicks on swp-preset-button elements + * - Manages current workweek preset state + * - Validates preset IDs + * - Emits WORKWEEK_CHANGED events + * - Updates button UI states (data-active attributes) + * + * EVENT FLOW: + * =========== + * User clicks button → changePreset() → validate → update state → emit event → update UI + * + * SUBSCRIBERS: + * ============ + * - ConfigManager: Updates CSS variables (--grid-columns) + * - GridManager: Re-renders grid with new column count + * - CalendarManager: Relays to header update (via workweek:header-update) + * - HeaderManager: Updates date headers + */ +export declare class WorkweekPresets { + private eventBus; + private config; + private buttonListeners; + constructor(eventBus: IEventBus, config: Configuration); + /** + * Setup click listeners on all workweek preset buttons + */ + private setupButtonListeners; + /** + * Change the active workweek preset + */ + private changePreset; + /** + * Update button states (data-active attributes) + */ + private updateButtonStates; +} diff --git a/wwwroot/js/components/WorkweekPresets.js b/wwwroot/js/components/WorkweekPresets.js new file mode 100644 index 0000000..e7e953f --- /dev/null +++ b/wwwroot/js/components/WorkweekPresets.js @@ -0,0 +1,95 @@ +import { CoreEvents } from '../constants/CoreEvents'; +import { WORK_WEEK_PRESETS } from '../configurations/CalendarConfig'; +/** + * WorkweekPresetsManager - Manages workweek preset UI and state + * + * RESPONSIBILITY: + * =============== + * This manager owns all logic related to the UI element. + * It follows the principle that each functional UI element has its own manager. + * + * RESPONSIBILITIES: + * - Owns WORK_WEEK_PRESETS data + * - Handles button clicks on swp-preset-button elements + * - Manages current workweek preset state + * - Validates preset IDs + * - Emits WORKWEEK_CHANGED events + * - Updates button UI states (data-active attributes) + * + * EVENT FLOW: + * =========== + * User clicks button → changePreset() → validate → update state → emit event → update UI + * + * SUBSCRIBERS: + * ============ + * - ConfigManager: Updates CSS variables (--grid-columns) + * - GridManager: Re-renders grid with new column count + * - CalendarManager: Relays to header update (via workweek:header-update) + * - HeaderManager: Updates date headers + */ +export class WorkweekPresets { + constructor(eventBus, config) { + this.buttonListeners = new Map(); + this.eventBus = eventBus; + this.config = config; + this.setupButtonListeners(); + } + /** + * Setup click listeners on all workweek preset buttons + */ + setupButtonListeners() { + const buttons = document.querySelectorAll('swp-preset-button[data-workweek]'); + buttons.forEach(button => { + const clickHandler = (event) => { + event.preventDefault(); + const presetId = button.getAttribute('data-workweek'); + if (presetId) { + this.changePreset(presetId); + } + }; + button.addEventListener('click', clickHandler); + this.buttonListeners.set(button, clickHandler); + }); + // Initialize button states + this.updateButtonStates(); + } + /** + * Change the active workweek preset + */ + changePreset(presetId) { + if (!WORK_WEEK_PRESETS[presetId]) { + console.warn(`Invalid preset ID "${presetId}"`); + return; + } + if (presetId === this.config.currentWorkWeek) { + return; // No change + } + const previousPresetId = this.config.currentWorkWeek; + this.config.currentWorkWeek = presetId; + const settings = WORK_WEEK_PRESETS[presetId]; + // Update button UI states + this.updateButtonStates(); + // Emit event for subscribers + this.eventBus.emit(CoreEvents.WORKWEEK_CHANGED, { + workWeekId: presetId, + previousWorkWeekId: previousPresetId, + settings: settings + }); + } + /** + * Update button states (data-active attributes) + */ + updateButtonStates() { + const buttons = document.querySelectorAll('swp-preset-button[data-workweek]'); + buttons.forEach(button => { + const buttonPresetId = button.getAttribute('data-workweek'); + if (buttonPresetId === this.config.currentWorkWeek) { + button.setAttribute('data-active', 'true'); + } + else { + button.removeAttribute('data-active'); + } + }); + } +} +//# sourceMappingURL=WorkweekPresets.js.map \ No newline at end of file diff --git a/wwwroot/js/components/WorkweekPresets.js.map b/wwwroot/js/components/WorkweekPresets.js.map new file mode 100644 index 0000000..10f34a5 --- /dev/null +++ b/wwwroot/js/components/WorkweekPresets.js.map @@ -0,0 +1 @@ +{"version":3,"file":"WorkweekPresets.js","sourceRoot":"","sources":["../../../src/components/WorkweekPresets.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD,OAAO,EAAE,iBAAiB,EAAiB,MAAM,kCAAkC,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,OAAO,eAAe;IAK1B,YAAY,QAAmB,EAAE,MAAqB;QAF9C,oBAAe,GAAgC,IAAI,GAAG,EAAE,CAAC;QAG/D,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QAErB,IAAI,CAAC,oBAAoB,EAAE,CAAC;IAC9B,CAAC;IAED;;OAEG;IACK,oBAAoB;QAC1B,MAAM,OAAO,GAAG,QAAQ,CAAC,gBAAgB,CAAC,kCAAkC,CAAC,CAAC;QAE9E,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;YACvB,MAAM,YAAY,GAAG,CAAC,KAAY,EAAE,EAAE;gBACpC,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;gBACtD,IAAI,QAAQ,EAAE,CAAC;oBACb,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;gBAC9B,CAAC;YACH,CAAC,CAAC;YAEF,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;YAC/C,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;QAEH,2BAA2B;QAC3B,IAAI,CAAC,kBAAkB,EAAE,CAAC;IAC5B,CAAC;IAED;;OAEG;IACK,YAAY,CAAC,QAAgB;QACnC,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjC,OAAO,CAAC,IAAI,CAAC,sBAAsB,QAAQ,GAAG,CAAC,CAAC;YAChD,OAAO;QACT,CAAC;QAED,IAAI,QAAQ,KAAK,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC;YAC7C,OAAO,CAAC,YAAY;QACtB,CAAC;QAED,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC;QACrD,IAAI,CAAC,MAAM,CAAC,eAAe,GAAG,QAAQ,CAAC;QAEvC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QAE7C,0BAA0B;QAC1B,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAE1B,6BAA6B;QAC7B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,gBAAgB,EAAE;YAC9C,UAAU,EAAE,QAAQ;YACpB,kBAAkB,EAAE,gBAAgB;YACpC,QAAQ,EAAE,QAAQ;SACnB,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACK,kBAAkB;QACxB,MAAM,OAAO,GAAG,QAAQ,CAAC,gBAAgB,CAAC,kCAAkC,CAAC,CAAC;QAE9E,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;YACvB,MAAM,cAAc,GAAG,MAAM,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;YAE5D,IAAI,cAAc,KAAK,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC;gBACnD,MAAM,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;YAC7C,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC;YACxC,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;CAEF"} \ No newline at end of file diff --git a/wwwroot/js/configuration/CalendarConfig.d.ts b/wwwroot/js/configuration/CalendarConfig.d.ts new file mode 100644 index 0000000..92b98dc --- /dev/null +++ b/wwwroot/js/configuration/CalendarConfig.d.ts @@ -0,0 +1,44 @@ +import { ICalendarConfig } from './ICalendarConfig'; +import { IGridSettings } from './GridSettings'; +import { IDateViewSettings } from './DateViewSettings'; +import { ITimeFormatConfig } from './TimeFormatConfig'; +import { IWorkWeekSettings } from './WorkWeekSettings'; +/** + * All-day event layout constants + */ +export declare const ALL_DAY_CONSTANTS: { + readonly EVENT_HEIGHT: 22; + readonly EVENT_GAP: 2; + readonly CONTAINER_PADDING: 4; + readonly MAX_COLLAPSED_ROWS: 4; + readonly SINGLE_ROW_HEIGHT: number; +}; +/** + * Work week presets + */ +export declare const WORK_WEEK_PRESETS: { + [key: string]: IWorkWeekSettings; +}; +/** + * Configuration - DTO container for all configuration + * Pure data object loaded from JSON via ConfigManager + */ +export declare class Configuration { + private static _instance; + config: ICalendarConfig; + gridSettings: IGridSettings; + dateViewSettings: IDateViewSettings; + timeFormatConfig: ITimeFormatConfig; + currentWorkWeek: string; + selectedDate: Date; + constructor(config: ICalendarConfig, gridSettings: IGridSettings, dateViewSettings: IDateViewSettings, timeFormatConfig: ITimeFormatConfig, currentWorkWeek: string, selectedDate?: Date); + /** + * Get the current Configuration instance + * Used by web components that can't use dependency injection + */ + static getInstance(): Configuration; + getWorkWeekSettings(): IWorkWeekSettings; + setWorkWeek(workWeekId: string): void; + setSelectedDate(date: Date): void; +} +export { Configuration as CalendarConfig }; diff --git a/wwwroot/js/configuration/CalendarConfig.js b/wwwroot/js/configuration/CalendarConfig.js new file mode 100644 index 0000000..8c769a3 --- /dev/null +++ b/wwwroot/js/configuration/CalendarConfig.js @@ -0,0 +1,90 @@ +/** + * All-day event layout constants + */ +export const ALL_DAY_CONSTANTS = { + EVENT_HEIGHT: 22, + EVENT_GAP: 2, + CONTAINER_PADDING: 4, + MAX_COLLAPSED_ROWS: 4, + get SINGLE_ROW_HEIGHT() { + return this.EVENT_HEIGHT + this.EVENT_GAP; // 28px + } +}; +/** + * Work week presets + */ +export const WORK_WEEK_PRESETS = { + 'standard': { + id: 'standard', + workDays: [1, 2, 3, 4, 5], + totalDays: 5, + firstWorkDay: 1 + }, + 'compressed': { + id: 'compressed', + workDays: [1, 2, 3, 4], + totalDays: 4, + firstWorkDay: 1 + }, + 'midweek': { + id: 'midweek', + workDays: [3, 4, 5], + totalDays: 3, + firstWorkDay: 3 + }, + 'weekend': { + id: 'weekend', + workDays: [6, 7], + totalDays: 2, + firstWorkDay: 6 + }, + 'fullweek': { + id: 'fullweek', + workDays: [1, 2, 3, 4, 5, 6, 7], + totalDays: 7, + firstWorkDay: 1 + } +}; +/** + * Configuration - DTO container for all configuration + * Pure data object loaded from JSON via ConfigManager + */ +export class Configuration { + constructor(config, gridSettings, dateViewSettings, timeFormatConfig, currentWorkWeek, selectedDate = new Date()) { + this.config = config; + this.gridSettings = gridSettings; + this.dateViewSettings = dateViewSettings; + this.timeFormatConfig = timeFormatConfig; + this.currentWorkWeek = currentWorkWeek; + this.selectedDate = selectedDate; + // Store as singleton instance for web components + Configuration._instance = this; + } + /** + * Get the current Configuration instance + * Used by web components that can't use dependency injection + */ + static getInstance() { + if (!Configuration._instance) { + throw new Error('Configuration has not been initialized. Call ConfigManager.load() first.'); + } + return Configuration._instance; + } + // Helper methods + getWorkWeekSettings() { + return WORK_WEEK_PRESETS[this.currentWorkWeek] || WORK_WEEK_PRESETS['standard']; + } + setWorkWeek(workWeekId) { + if (WORK_WEEK_PRESETS[workWeekId]) { + this.currentWorkWeek = workWeekId; + this.dateViewSettings.weekDays = WORK_WEEK_PRESETS[workWeekId].totalDays; + } + } + setSelectedDate(date) { + this.selectedDate = date; + } +} +Configuration._instance = null; +// Backward compatibility alias +export { Configuration as CalendarConfig }; +//# sourceMappingURL=CalendarConfig.js.map \ No newline at end of file diff --git a/wwwroot/js/configuration/CalendarConfig.js.map b/wwwroot/js/configuration/CalendarConfig.js.map new file mode 100644 index 0000000..c9c14a2 --- /dev/null +++ b/wwwroot/js/configuration/CalendarConfig.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CalendarConfig.js","sourceRoot":"","sources":["../../../src/configuration/CalendarConfig.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/configuration/ConfigManager.d.ts b/wwwroot/js/configuration/ConfigManager.d.ts new file mode 100644 index 0000000..efc52f3 --- /dev/null +++ b/wwwroot/js/configuration/ConfigManager.d.ts @@ -0,0 +1,11 @@ +import { Configuration } from './CalendarConfig'; +/** + * ConfigManager - Static configuration loader + * Loads JSON and creates Configuration instance + */ +export declare class ConfigManager { + /** + * Load configuration from JSON and create Configuration instance + */ + static load(): Promise; +} diff --git a/wwwroot/js/configuration/ConfigManager.js b/wwwroot/js/configuration/ConfigManager.js new file mode 100644 index 0000000..7f90a7d --- /dev/null +++ b/wwwroot/js/configuration/ConfigManager.js @@ -0,0 +1,43 @@ +import { Configuration } from './CalendarConfig'; +import { TimeFormatter } from '../utils/TimeFormatter'; +/** + * ConfigManager - Static configuration loader + * Loads JSON and creates Configuration instance + */ +export class ConfigManager { + /** + * Load configuration from JSON and create Configuration instance + */ + static async load() { + const response = await fetch('/wwwroot/data/calendar-config.json'); + if (!response.ok) { + throw new Error(`Failed to load config: ${response.statusText}`); + } + const data = await response.json(); + // Build main config + const mainConfig = { + scrollbarWidth: data.scrollbar.width, + scrollbarColor: data.scrollbar.color, + scrollbarTrackColor: data.scrollbar.trackColor, + scrollbarHoverColor: data.scrollbar.hoverColor, + scrollbarBorderRadius: data.scrollbar.borderRadius, + allowDrag: data.interaction.allowDrag, + allowResize: data.interaction.allowResize, + allowCreate: data.interaction.allowCreate, + apiEndpoint: data.api.endpoint, + dateFormat: data.api.dateFormat, + timeFormat: data.api.timeFormat, + enableSearch: data.features.enableSearch, + enableTouch: data.features.enableTouch, + defaultEventDuration: data.eventDefaults.defaultEventDuration, + minEventDuration: data.gridSettings.snapInterval, + maxEventDuration: data.eventDefaults.maxEventDuration + }; + // Create Configuration instance + const config = new Configuration(mainConfig, data.gridSettings, data.dateViewSettings, data.timeFormatConfig, data.currentWorkWeek); + // Configure TimeFormatter + TimeFormatter.configure(config.timeFormatConfig); + return config; + } +} +//# sourceMappingURL=ConfigManager.js.map \ No newline at end of file diff --git a/wwwroot/js/configuration/ConfigManager.js.map b/wwwroot/js/configuration/ConfigManager.js.map new file mode 100644 index 0000000..0bc0010 --- /dev/null +++ b/wwwroot/js/configuration/ConfigManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ConfigManager.js","sourceRoot":"","sources":["../../../src/configuration/ConfigManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD;;;GAGG;AACH,MAAM,OAAO,aAAa;IACxB;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,IAAI;QACf,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACnE,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,0BAA0B,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QACnE,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAEnC,oBAAoB;QACpB,MAAM,UAAU,GAAoB;YAClC,cAAc,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK;YACpC,cAAc,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK;YACpC,mBAAmB,EAAE,IAAI,CAAC,SAAS,CAAC,UAAU;YAC9C,mBAAmB,EAAE,IAAI,CAAC,SAAS,CAAC,UAAU;YAC9C,qBAAqB,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY;YAClD,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC,SAAS;YACrC,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,WAAW;YACzC,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,WAAW;YACzC,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ;YAC9B,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU;YAC/B,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU;YAC/B,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,YAAY;YACxC,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,WAAW;YACtC,oBAAoB,EAAE,IAAI,CAAC,aAAa,CAAC,oBAAoB;YAC7D,gBAAgB,EAAE,IAAI,CAAC,YAAY,CAAC,YAAY;YAChD,gBAAgB,EAAE,IAAI,CAAC,aAAa,CAAC,gBAAgB;SACtD,CAAC;QAEF,gCAAgC;QAChC,MAAM,MAAM,GAAG,IAAI,aAAa,CAC9B,UAAU,EACV,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,gBAAgB,EACrB,IAAI,CAAC,gBAAgB,EACrB,IAAI,CAAC,eAAe,CACrB,CAAC;QAEF,0BAA0B;QAC1B,aAAa,CAAC,SAAS,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAEjD,OAAO,MAAM,CAAC;IAChB,CAAC;CACF"} \ No newline at end of file diff --git a/wwwroot/js/configuration/DateViewSettings.d.ts b/wwwroot/js/configuration/DateViewSettings.d.ts new file mode 100644 index 0000000..5459f66 --- /dev/null +++ b/wwwroot/js/configuration/DateViewSettings.d.ts @@ -0,0 +1,10 @@ +import { ViewPeriod } from '../types/CalendarTypes'; +/** + * View settings for date-based calendar mode + */ +export interface IDateViewSettings { + period: ViewPeriod; + weekDays: number; + firstDayOfWeek: number; + showAllDay: boolean; +} diff --git a/wwwroot/js/configuration/DateViewSettings.js b/wwwroot/js/configuration/DateViewSettings.js new file mode 100644 index 0000000..cb2b894 --- /dev/null +++ b/wwwroot/js/configuration/DateViewSettings.js @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=DateViewSettings.js.map \ No newline at end of file diff --git a/wwwroot/js/configuration/DateViewSettings.js.map b/wwwroot/js/configuration/DateViewSettings.js.map new file mode 100644 index 0000000..cf1d286 --- /dev/null +++ b/wwwroot/js/configuration/DateViewSettings.js.map @@ -0,0 +1 @@ +{"version":3,"file":"DateViewSettings.js","sourceRoot":"","sources":["../../../src/configuration/DateViewSettings.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/wwwroot/js/configuration/GridSettings.d.ts b/wwwroot/js/configuration/GridSettings.d.ts new file mode 100644 index 0000000..0db6981 --- /dev/null +++ b/wwwroot/js/configuration/GridSettings.d.ts @@ -0,0 +1,22 @@ +/** + * Grid display settings interface + */ +export interface IGridSettings { + dayStartHour: number; + dayEndHour: number; + workStartHour: number; + workEndHour: number; + hourHeight: number; + snapInterval: number; + fitToWidth: boolean; + scrollToHour: number | null; + gridStartThresholdMinutes: number; + showCurrentTime: boolean; + showWorkHours: boolean; +} +/** + * Grid settings utility functions + */ +export declare namespace GridSettingsUtils { + function isValidSnapInterval(interval: number): boolean; +} diff --git a/wwwroot/js/configuration/GridSettings.js b/wwwroot/js/configuration/GridSettings.js new file mode 100644 index 0000000..c7e399e --- /dev/null +++ b/wwwroot/js/configuration/GridSettings.js @@ -0,0 +1,11 @@ +/** + * Grid settings utility functions + */ +export var GridSettingsUtils; +(function (GridSettingsUtils) { + function isValidSnapInterval(interval) { + return [5, 10, 15, 30, 60].includes(interval); + } + GridSettingsUtils.isValidSnapInterval = isValidSnapInterval; +})(GridSettingsUtils || (GridSettingsUtils = {})); +//# sourceMappingURL=GridSettings.js.map \ No newline at end of file diff --git a/wwwroot/js/configuration/GridSettings.js.map b/wwwroot/js/configuration/GridSettings.js.map new file mode 100644 index 0000000..f8a3c86 --- /dev/null +++ b/wwwroot/js/configuration/GridSettings.js.map @@ -0,0 +1 @@ +{"version":3,"file":"GridSettings.js","sourceRoot":"","sources":["../../../src/configuration/GridSettings.ts"],"names":[],"mappings":"AAiBA;;GAEG;AACH,MAAM,KAAW,iBAAiB,CAIjC;AAJD,WAAiB,iBAAiB;IAChC,SAAgB,mBAAmB,CAAC,QAAgB;QAClD,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAChD,CAAC;IAFe,qCAAmB,sBAElC,CAAA;AACH,CAAC,EAJgB,iBAAiB,KAAjB,iBAAiB,QAIjC"} \ No newline at end of file diff --git a/wwwroot/js/configuration/ICalendarConfig.d.ts b/wwwroot/js/configuration/ICalendarConfig.d.ts new file mode 100644 index 0000000..4c66c10 --- /dev/null +++ b/wwwroot/js/configuration/ICalendarConfig.d.ts @@ -0,0 +1,21 @@ +/** + * Main calendar configuration interface + */ +export interface ICalendarConfig { + scrollbarWidth: number; + scrollbarColor: string; + scrollbarTrackColor: string; + scrollbarHoverColor: string; + scrollbarBorderRadius: number; + allowDrag: boolean; + allowResize: boolean; + allowCreate: boolean; + apiEndpoint: string; + dateFormat: string; + timeFormat: string; + enableSearch: boolean; + enableTouch: boolean; + defaultEventDuration: number; + minEventDuration: number; + maxEventDuration: number; +} diff --git a/wwwroot/js/configuration/ICalendarConfig.js b/wwwroot/js/configuration/ICalendarConfig.js new file mode 100644 index 0000000..769ac97 --- /dev/null +++ b/wwwroot/js/configuration/ICalendarConfig.js @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=ICalendarConfig.js.map \ No newline at end of file diff --git a/wwwroot/js/configuration/ICalendarConfig.js.map b/wwwroot/js/configuration/ICalendarConfig.js.map new file mode 100644 index 0000000..46eb186 --- /dev/null +++ b/wwwroot/js/configuration/ICalendarConfig.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ICalendarConfig.js","sourceRoot":"","sources":["../../../src/configuration/ICalendarConfig.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/wwwroot/js/configuration/TimeFormatConfig.d.ts b/wwwroot/js/configuration/TimeFormatConfig.d.ts new file mode 100644 index 0000000..d1f26c1 --- /dev/null +++ b/wwwroot/js/configuration/TimeFormatConfig.d.ts @@ -0,0 +1,10 @@ +/** + * Time format configuration settings + */ +export interface ITimeFormatConfig { + timezone: string; + use24HourFormat: boolean; + locale: string; + dateFormat: 'locale' | 'technical'; + showSeconds: boolean; +} diff --git a/wwwroot/js/configuration/TimeFormatConfig.js b/wwwroot/js/configuration/TimeFormatConfig.js new file mode 100644 index 0000000..31213da --- /dev/null +++ b/wwwroot/js/configuration/TimeFormatConfig.js @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=TimeFormatConfig.js.map \ No newline at end of file diff --git a/wwwroot/js/configuration/TimeFormatConfig.js.map b/wwwroot/js/configuration/TimeFormatConfig.js.map new file mode 100644 index 0000000..8306905 --- /dev/null +++ b/wwwroot/js/configuration/TimeFormatConfig.js.map @@ -0,0 +1 @@ +{"version":3,"file":"TimeFormatConfig.js","sourceRoot":"","sources":["../../../src/configuration/TimeFormatConfig.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/wwwroot/js/configuration/WorkWeekSettings.d.ts b/wwwroot/js/configuration/WorkWeekSettings.d.ts new file mode 100644 index 0000000..b971f20 --- /dev/null +++ b/wwwroot/js/configuration/WorkWeekSettings.d.ts @@ -0,0 +1,9 @@ +/** + * Work week configuration settings + */ +export interface IWorkWeekSettings { + id: string; + workDays: number[]; + totalDays: number; + firstWorkDay: number; +} diff --git a/wwwroot/js/configuration/WorkWeekSettings.js b/wwwroot/js/configuration/WorkWeekSettings.js new file mode 100644 index 0000000..1b2eefc --- /dev/null +++ b/wwwroot/js/configuration/WorkWeekSettings.js @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=WorkWeekSettings.js.map \ No newline at end of file diff --git a/wwwroot/js/configuration/WorkWeekSettings.js.map b/wwwroot/js/configuration/WorkWeekSettings.js.map new file mode 100644 index 0000000..52447fd --- /dev/null +++ b/wwwroot/js/configuration/WorkWeekSettings.js.map @@ -0,0 +1 @@ +{"version":3,"file":"WorkWeekSettings.js","sourceRoot":"","sources":["../../../src/configuration/WorkWeekSettings.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/wwwroot/js/configurations/CalendarConfig.d.ts b/wwwroot/js/configurations/CalendarConfig.d.ts new file mode 100644 index 0000000..d883b53 --- /dev/null +++ b/wwwroot/js/configurations/CalendarConfig.d.ts @@ -0,0 +1,46 @@ +import { ICalendarConfig } from './ICalendarConfig'; +import { IGridSettings } from './GridSettings'; +import { IDateViewSettings } from './DateViewSettings'; +import { ITimeFormatConfig } from './TimeFormatConfig'; +import { IWorkWeekSettings } from './WorkWeekSettings'; +import { CalendarView } from '../types/CalendarTypes'; +/** + * All-day event layout constants + */ +export declare const ALL_DAY_CONSTANTS: { + readonly EVENT_HEIGHT: 22; + readonly EVENT_GAP: 2; + readonly CONTAINER_PADDING: 4; + readonly MAX_COLLAPSED_ROWS: 4; + readonly SINGLE_ROW_HEIGHT: number; +}; +/** + * Work week presets - Configuration data + */ +export declare const WORK_WEEK_PRESETS: { + [key: string]: IWorkWeekSettings; +}; +/** + * Configuration - DTO container for all configuration + * Pure data object loaded from JSON via ConfigManager + */ +export declare class Configuration { + private static _instance; + config: ICalendarConfig; + gridSettings: IGridSettings; + dateViewSettings: IDateViewSettings; + timeFormatConfig: ITimeFormatConfig; + currentWorkWeek: string; + currentView: CalendarView; + selectedDate: Date; + apiEndpoint: string; + constructor(config: ICalendarConfig, gridSettings: IGridSettings, dateViewSettings: IDateViewSettings, timeFormatConfig: ITimeFormatConfig, currentWorkWeek: string, currentView: CalendarView, selectedDate?: Date); + /** + * Get the current Configuration instance + * Used by web components that can't use dependency injection + */ + static getInstance(): Configuration; + setSelectedDate(date: Date): void; + getWorkWeekSettings(): IWorkWeekSettings; +} +export { Configuration as CalendarConfig }; diff --git a/wwwroot/js/configurations/CalendarConfig.js b/wwwroot/js/configurations/CalendarConfig.js new file mode 100644 index 0000000..89d9237 --- /dev/null +++ b/wwwroot/js/configurations/CalendarConfig.js @@ -0,0 +1,85 @@ +/** + * All-day event layout constants + */ +export const ALL_DAY_CONSTANTS = { + EVENT_HEIGHT: 22, + EVENT_GAP: 2, + CONTAINER_PADDING: 4, + MAX_COLLAPSED_ROWS: 4, + get SINGLE_ROW_HEIGHT() { + return this.EVENT_HEIGHT + this.EVENT_GAP; // 28px + } +}; +/** + * Work week presets - Configuration data + */ +export const WORK_WEEK_PRESETS = { + 'standard': { + id: 'standard', + workDays: [1, 2, 3, 4, 5], + totalDays: 5, + firstWorkDay: 1 + }, + 'compressed': { + id: 'compressed', + workDays: [1, 2, 3, 4], + totalDays: 4, + firstWorkDay: 1 + }, + 'midweek': { + id: 'midweek', + workDays: [3, 4, 5], + totalDays: 3, + firstWorkDay: 3 + }, + 'weekend': { + id: 'weekend', + workDays: [6, 7], + totalDays: 2, + firstWorkDay: 6 + }, + 'fullweek': { + id: 'fullweek', + workDays: [1, 2, 3, 4, 5, 6, 7], + totalDays: 7, + firstWorkDay: 1 + } +}; +/** + * Configuration - DTO container for all configuration + * Pure data object loaded from JSON via ConfigManager + */ +export class Configuration { + constructor(config, gridSettings, dateViewSettings, timeFormatConfig, currentWorkWeek, currentView, selectedDate = new Date()) { + this.apiEndpoint = '/api'; + this.config = config; + this.gridSettings = gridSettings; + this.dateViewSettings = dateViewSettings; + this.timeFormatConfig = timeFormatConfig; + this.currentWorkWeek = currentWorkWeek; + this.currentView = currentView; + this.selectedDate = selectedDate; + // Store as singleton instance for web components + Configuration._instance = this; + } + /** + * Get the current Configuration instance + * Used by web components that can't use dependency injection + */ + static getInstance() { + if (!Configuration._instance) { + throw new Error('Configuration has not been initialized. Call ConfigManager.load() first.'); + } + return Configuration._instance; + } + setSelectedDate(date) { + this.selectedDate = date; + } + getWorkWeekSettings() { + return WORK_WEEK_PRESETS[this.currentWorkWeek] || WORK_WEEK_PRESETS['standard']; + } +} +Configuration._instance = null; +// Backward compatibility alias +export { Configuration as CalendarConfig }; +//# sourceMappingURL=CalendarConfig.js.map \ No newline at end of file diff --git a/wwwroot/js/configurations/CalendarConfig.js.map b/wwwroot/js/configurations/CalendarConfig.js.map new file mode 100644 index 0000000..e563232 --- /dev/null +++ b/wwwroot/js/configurations/CalendarConfig.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CalendarConfig.js","sourceRoot":"","sources":["../../../src/configurations/CalendarConfig.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,YAAY,EAAE,EAAE;IAChB,SAAS,EAAE,CAAC;IACZ,iBAAiB,EAAE,CAAC;IACpB,kBAAkB,EAAE,CAAC;IACrB,IAAI,iBAAiB;QACnB,OAAO,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO;IACpD,CAAC;CACO,CAAC;AAEX;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAyC;IACrE,UAAU,EAAE;QACV,EAAE,EAAE,UAAU;QACd,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACzB,SAAS,EAAE,CAAC;QACZ,YAAY,EAAE,CAAC;KAChB;IACD,YAAY,EAAE;QACZ,EAAE,EAAE,YAAY;QAChB,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACtB,SAAS,EAAE,CAAC;QACZ,YAAY,EAAE,CAAC;KAChB;IACD,SAAS,EAAE;QACT,EAAE,EAAE,SAAS;QACb,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACnB,SAAS,EAAE,CAAC;QACZ,YAAY,EAAE,CAAC;KAChB;IACD,SAAS,EAAE;QACT,EAAE,EAAE,SAAS;QACb,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAChB,SAAS,EAAE,CAAC;QACZ,YAAY,EAAE,CAAC;KAChB;IACD,UAAU,EAAE;QACV,EAAE,EAAE,UAAU;QACd,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAC/B,SAAS,EAAE,CAAC;QACZ,YAAY,EAAE,CAAC;KAChB;CACF,CAAC;AAEF;;;GAGG;AACH,MAAM,OAAO,aAAa;IAYxB,YACE,MAAuB,EACvB,YAA2B,EAC3B,gBAAmC,EACnC,gBAAmC,EACnC,eAAuB,EACvB,WAAyB,EACzB,eAAqB,IAAI,IAAI,EAAE;QAT1B,gBAAW,GAAW,MAAM,CAAC;QAWlC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;QACzC,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;QACzC,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;QACvC,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QAEjC,iDAAiD;QACjD,aAAa,CAAC,SAAS,GAAG,IAAI,CAAC;IACjC,CAAC;IAED;;;OAGG;IACI,MAAM,CAAC,WAAW;QACvB,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,0EAA0E,CAAC,CAAC;QAC9F,CAAC;QACD,OAAO,aAAa,CAAC,SAAS,CAAC;IACjC,CAAC;IAED,eAAe,CAAC,IAAU;QACxB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;IAC3B,CAAC;IAED,mBAAmB;QACjB,OAAO,iBAAiB,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAClF,CAAC;;AAjDc,uBAAS,GAAyB,IAAI,AAA7B,CAA8B;AAoDxD,+BAA+B;AAC/B,OAAO,EAAE,aAAa,IAAI,cAAc,EAAE,CAAC"} \ No newline at end of file diff --git a/wwwroot/js/configurations/ConfigManager.d.ts b/wwwroot/js/configurations/ConfigManager.d.ts new file mode 100644 index 0000000..1123edd --- /dev/null +++ b/wwwroot/js/configurations/ConfigManager.d.ts @@ -0,0 +1,28 @@ +import { Configuration } from './CalendarConfig'; +import { IEventBus } from '../types/CalendarTypes'; +/** + * ConfigManager - Configuration loader and CSS property manager + * Loads JSON and creates Configuration instance + * Listens to events and manages CSS custom properties for dynamic styling + */ +export declare class ConfigManager { + private eventBus; + private config; + constructor(eventBus: IEventBus, config: Configuration); + /** + * Setup event listeners for dynamic CSS updates + */ + private setupEventListeners; + /** + * Sync grid-related CSS variables from configuration + */ + private syncGridCSSVariables; + /** + * Sync workweek-related CSS variables + */ + private syncWorkweekCSSVariables; + /** + * Load configuration from JSON and create Configuration instance + */ + static load(): Promise; +} diff --git a/wwwroot/js/configurations/ConfigManager.js b/wwwroot/js/configurations/ConfigManager.js new file mode 100644 index 0000000..1c75db8 --- /dev/null +++ b/wwwroot/js/configurations/ConfigManager.js @@ -0,0 +1,80 @@ +import { Configuration } from './CalendarConfig'; +import { TimeFormatter } from '../utils/TimeFormatter'; +import { CoreEvents } from '../constants/CoreEvents'; +/** + * ConfigManager - Configuration loader and CSS property manager + * Loads JSON and creates Configuration instance + * Listens to events and manages CSS custom properties for dynamic styling + */ +export class ConfigManager { + constructor(eventBus, config) { + this.eventBus = eventBus; + this.config = config; + this.setupEventListeners(); + this.syncGridCSSVariables(); + this.syncWorkweekCSSVariables(); + } + /** + * Setup event listeners for dynamic CSS updates + */ + setupEventListeners() { + // Listen to workweek changes and update CSS accordingly + this.eventBus.on(CoreEvents.WORKWEEK_CHANGED, (event) => { + const { settings } = event.detail; + this.syncWorkweekCSSVariables(settings); + }); + } + /** + * Sync grid-related CSS variables from configuration + */ + syncGridCSSVariables() { + const gridSettings = this.config.gridSettings; + document.documentElement.style.setProperty('--hour-height', `${gridSettings.hourHeight}px`); + document.documentElement.style.setProperty('--day-start-hour', gridSettings.dayStartHour.toString()); + document.documentElement.style.setProperty('--day-end-hour', gridSettings.dayEndHour.toString()); + document.documentElement.style.setProperty('--work-start-hour', gridSettings.workStartHour.toString()); + document.documentElement.style.setProperty('--work-end-hour', gridSettings.workEndHour.toString()); + } + /** + * Sync workweek-related CSS variables + */ + syncWorkweekCSSVariables(workWeekSettings) { + const settings = workWeekSettings || this.config.getWorkWeekSettings(); + document.documentElement.style.setProperty('--grid-columns', settings.totalDays.toString()); + } + /** + * Load configuration from JSON and create Configuration instance + */ + static async load() { + const response = await fetch('/wwwroot/data/calendar-config.json'); + if (!response.ok) { + throw new Error(`Failed to load config: ${response.statusText}`); + } + const data = await response.json(); + // Build main config + const mainConfig = { + scrollbarWidth: data.scrollbar.width, + scrollbarColor: data.scrollbar.color, + scrollbarTrackColor: data.scrollbar.trackColor, + scrollbarHoverColor: data.scrollbar.hoverColor, + scrollbarBorderRadius: data.scrollbar.borderRadius, + allowDrag: data.interaction.allowDrag, + allowResize: data.interaction.allowResize, + allowCreate: data.interaction.allowCreate, + apiEndpoint: data.api.endpoint, + dateFormat: data.api.dateFormat, + timeFormat: data.api.timeFormat, + enableSearch: data.features.enableSearch, + enableTouch: data.features.enableTouch, + defaultEventDuration: data.eventDefaults.defaultEventDuration, + minEventDuration: data.gridSettings.snapInterval, + maxEventDuration: data.eventDefaults.maxEventDuration + }; + // Create Configuration instance + const config = new Configuration(mainConfig, data.gridSettings, data.dateViewSettings, data.timeFormatConfig, data.currentWorkWeek, data.currentView || 'week'); + // Configure TimeFormatter + TimeFormatter.configure(config.timeFormatConfig); + return config; + } +} +//# sourceMappingURL=ConfigManager.js.map \ No newline at end of file diff --git a/wwwroot/js/configurations/ConfigManager.js.map b/wwwroot/js/configurations/ConfigManager.js.map new file mode 100644 index 0000000..71e69a0 --- /dev/null +++ b/wwwroot/js/configurations/ConfigManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ConfigManager.js","sourceRoot":"","sources":["../../../src/configurations/ConfigManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/configurations/DateViewSettings.d.ts b/wwwroot/js/configurations/DateViewSettings.d.ts new file mode 100644 index 0000000..5459f66 --- /dev/null +++ b/wwwroot/js/configurations/DateViewSettings.d.ts @@ -0,0 +1,10 @@ +import { ViewPeriod } from '../types/CalendarTypes'; +/** + * View settings for date-based calendar mode + */ +export interface IDateViewSettings { + period: ViewPeriod; + weekDays: number; + firstDayOfWeek: number; + showAllDay: boolean; +} diff --git a/wwwroot/js/configurations/DateViewSettings.js b/wwwroot/js/configurations/DateViewSettings.js new file mode 100644 index 0000000..cb2b894 --- /dev/null +++ b/wwwroot/js/configurations/DateViewSettings.js @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=DateViewSettings.js.map \ No newline at end of file diff --git a/wwwroot/js/configurations/DateViewSettings.js.map b/wwwroot/js/configurations/DateViewSettings.js.map new file mode 100644 index 0000000..385c982 --- /dev/null +++ b/wwwroot/js/configurations/DateViewSettings.js.map @@ -0,0 +1 @@ +{"version":3,"file":"DateViewSettings.js","sourceRoot":"","sources":["../../../src/configurations/DateViewSettings.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/wwwroot/js/configurations/GridSettings.d.ts b/wwwroot/js/configurations/GridSettings.d.ts new file mode 100644 index 0000000..0db6981 --- /dev/null +++ b/wwwroot/js/configurations/GridSettings.d.ts @@ -0,0 +1,22 @@ +/** + * Grid display settings interface + */ +export interface IGridSettings { + dayStartHour: number; + dayEndHour: number; + workStartHour: number; + workEndHour: number; + hourHeight: number; + snapInterval: number; + fitToWidth: boolean; + scrollToHour: number | null; + gridStartThresholdMinutes: number; + showCurrentTime: boolean; + showWorkHours: boolean; +} +/** + * Grid settings utility functions + */ +export declare namespace GridSettingsUtils { + function isValidSnapInterval(interval: number): boolean; +} diff --git a/wwwroot/js/configurations/GridSettings.js b/wwwroot/js/configurations/GridSettings.js new file mode 100644 index 0000000..c7e399e --- /dev/null +++ b/wwwroot/js/configurations/GridSettings.js @@ -0,0 +1,11 @@ +/** + * Grid settings utility functions + */ +export var GridSettingsUtils; +(function (GridSettingsUtils) { + function isValidSnapInterval(interval) { + return [5, 10, 15, 30, 60].includes(interval); + } + GridSettingsUtils.isValidSnapInterval = isValidSnapInterval; +})(GridSettingsUtils || (GridSettingsUtils = {})); +//# sourceMappingURL=GridSettings.js.map \ No newline at end of file diff --git a/wwwroot/js/configurations/GridSettings.js.map b/wwwroot/js/configurations/GridSettings.js.map new file mode 100644 index 0000000..cdfbb83 --- /dev/null +++ b/wwwroot/js/configurations/GridSettings.js.map @@ -0,0 +1 @@ +{"version":3,"file":"GridSettings.js","sourceRoot":"","sources":["../../../src/configurations/GridSettings.ts"],"names":[],"mappings":"AAiBA;;GAEG;AACH,MAAM,KAAW,iBAAiB,CAIjC;AAJD,WAAiB,iBAAiB;IAChC,SAAgB,mBAAmB,CAAC,QAAgB;QAClD,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAChD,CAAC;IAFe,qCAAmB,sBAElC,CAAA;AACH,CAAC,EAJgB,iBAAiB,KAAjB,iBAAiB,QAIjC"} \ No newline at end of file diff --git a/wwwroot/js/configurations/ICalendarConfig.d.ts b/wwwroot/js/configurations/ICalendarConfig.d.ts new file mode 100644 index 0000000..4c66c10 --- /dev/null +++ b/wwwroot/js/configurations/ICalendarConfig.d.ts @@ -0,0 +1,21 @@ +/** + * Main calendar configuration interface + */ +export interface ICalendarConfig { + scrollbarWidth: number; + scrollbarColor: string; + scrollbarTrackColor: string; + scrollbarHoverColor: string; + scrollbarBorderRadius: number; + allowDrag: boolean; + allowResize: boolean; + allowCreate: boolean; + apiEndpoint: string; + dateFormat: string; + timeFormat: string; + enableSearch: boolean; + enableTouch: boolean; + defaultEventDuration: number; + minEventDuration: number; + maxEventDuration: number; +} diff --git a/wwwroot/js/configurations/ICalendarConfig.js b/wwwroot/js/configurations/ICalendarConfig.js new file mode 100644 index 0000000..769ac97 --- /dev/null +++ b/wwwroot/js/configurations/ICalendarConfig.js @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=ICalendarConfig.js.map \ No newline at end of file diff --git a/wwwroot/js/configurations/ICalendarConfig.js.map b/wwwroot/js/configurations/ICalendarConfig.js.map new file mode 100644 index 0000000..f3e954b --- /dev/null +++ b/wwwroot/js/configurations/ICalendarConfig.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ICalendarConfig.js","sourceRoot":"","sources":["../../../src/configurations/ICalendarConfig.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/wwwroot/js/configurations/TimeFormatConfig.d.ts b/wwwroot/js/configurations/TimeFormatConfig.d.ts new file mode 100644 index 0000000..d1f26c1 --- /dev/null +++ b/wwwroot/js/configurations/TimeFormatConfig.d.ts @@ -0,0 +1,10 @@ +/** + * Time format configuration settings + */ +export interface ITimeFormatConfig { + timezone: string; + use24HourFormat: boolean; + locale: string; + dateFormat: 'locale' | 'technical'; + showSeconds: boolean; +} diff --git a/wwwroot/js/configurations/TimeFormatConfig.js b/wwwroot/js/configurations/TimeFormatConfig.js new file mode 100644 index 0000000..31213da --- /dev/null +++ b/wwwroot/js/configurations/TimeFormatConfig.js @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=TimeFormatConfig.js.map \ No newline at end of file diff --git a/wwwroot/js/configurations/TimeFormatConfig.js.map b/wwwroot/js/configurations/TimeFormatConfig.js.map new file mode 100644 index 0000000..c94321c --- /dev/null +++ b/wwwroot/js/configurations/TimeFormatConfig.js.map @@ -0,0 +1 @@ +{"version":3,"file":"TimeFormatConfig.js","sourceRoot":"","sources":["../../../src/configurations/TimeFormatConfig.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/wwwroot/js/configurations/WorkWeekSettings.d.ts b/wwwroot/js/configurations/WorkWeekSettings.d.ts new file mode 100644 index 0000000..b971f20 --- /dev/null +++ b/wwwroot/js/configurations/WorkWeekSettings.d.ts @@ -0,0 +1,9 @@ +/** + * Work week configuration settings + */ +export interface IWorkWeekSettings { + id: string; + workDays: number[]; + totalDays: number; + firstWorkDay: number; +} diff --git a/wwwroot/js/configurations/WorkWeekSettings.js b/wwwroot/js/configurations/WorkWeekSettings.js new file mode 100644 index 0000000..1b2eefc --- /dev/null +++ b/wwwroot/js/configurations/WorkWeekSettings.js @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=WorkWeekSettings.js.map \ No newline at end of file diff --git a/wwwroot/js/configurations/WorkWeekSettings.js.map b/wwwroot/js/configurations/WorkWeekSettings.js.map new file mode 100644 index 0000000..9fe597d --- /dev/null +++ b/wwwroot/js/configurations/WorkWeekSettings.js.map @@ -0,0 +1 @@ +{"version":3,"file":"WorkWeekSettings.js","sourceRoot":"","sources":["../../../src/configurations/WorkWeekSettings.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/wwwroot/js/constants/CoreEvents.d.ts b/wwwroot/js/constants/CoreEvents.d.ts new file mode 100644 index 0000000..7e98d27 --- /dev/null +++ b/wwwroot/js/constants/CoreEvents.d.ts @@ -0,0 +1,37 @@ +/** + * CoreEvents - Consolidated essential events for the calendar + * Reduces complexity from 102+ events to ~20 core events + */ +export declare const CoreEvents: { + readonly INITIALIZED: "core:initialized"; + readonly READY: "core:ready"; + readonly DESTROYED: "core:destroyed"; + readonly VIEW_CHANGED: "view:changed"; + readonly VIEW_RENDERED: "view:rendered"; + readonly WORKWEEK_CHANGED: "workweek:changed"; + readonly NAV_BUTTON_CLICKED: "nav:button-clicked"; + readonly DATE_CHANGED: "nav:date-changed"; + readonly NAVIGATION_COMPLETED: "nav:navigation-completed"; + readonly NAVIGATE_TO_EVENT: "nav:navigate-to-event"; + readonly DATA_LOADING: "data:loading"; + readonly DATA_LOADED: "data:loaded"; + readonly DATA_ERROR: "data:error"; + readonly EVENTS_FILTERED: "data:events-filtered"; + readonly REMOTE_UPDATE_RECEIVED: "data:remote-update"; + readonly GRID_RENDERED: "grid:rendered"; + readonly GRID_CLICKED: "grid:clicked"; + readonly CELL_SELECTED: "grid:cell-selected"; + readonly EVENT_CREATED: "event:created"; + readonly EVENT_UPDATED: "event:updated"; + readonly EVENT_DELETED: "event:deleted"; + readonly EVENT_SELECTED: "event:selected"; + readonly ERROR: "system:error"; + readonly REFRESH_REQUESTED: "system:refresh"; + readonly OFFLINE_MODE_CHANGED: "system:offline-mode-changed"; + readonly SYNC_STARTED: "sync:started"; + readonly SYNC_COMPLETED: "sync:completed"; + readonly SYNC_FAILED: "sync:failed"; + readonly SYNC_RETRY: "sync:retry"; + readonly FILTER_CHANGED: "filter:changed"; + readonly EVENTS_RENDERED: "events:rendered"; +}; diff --git a/wwwroot/js/constants/CoreEvents.js b/wwwroot/js/constants/CoreEvents.js new file mode 100644 index 0000000..f72a48a --- /dev/null +++ b/wwwroot/js/constants/CoreEvents.js @@ -0,0 +1,48 @@ +/** + * CoreEvents - Consolidated essential events for the calendar + * Reduces complexity from 102+ events to ~20 core events + */ +export const CoreEvents = { + // Lifecycle events (3) + INITIALIZED: 'core:initialized', + READY: 'core:ready', + DESTROYED: 'core:destroyed', + // View events (3) + VIEW_CHANGED: 'view:changed', + VIEW_RENDERED: 'view:rendered', + WORKWEEK_CHANGED: 'workweek:changed', + // Navigation events (4) + NAV_BUTTON_CLICKED: 'nav:button-clicked', + DATE_CHANGED: 'nav:date-changed', + NAVIGATION_COMPLETED: 'nav:navigation-completed', + NAVIGATE_TO_EVENT: 'nav:navigate-to-event', + // Data events (5) + DATA_LOADING: 'data:loading', + DATA_LOADED: 'data:loaded', + DATA_ERROR: 'data:error', + EVENTS_FILTERED: 'data:events-filtered', + REMOTE_UPDATE_RECEIVED: 'data:remote-update', + // Grid events (3) + GRID_RENDERED: 'grid:rendered', + GRID_CLICKED: 'grid:clicked', + CELL_SELECTED: 'grid:cell-selected', + // Event management (4) + EVENT_CREATED: 'event:created', + EVENT_UPDATED: 'event:updated', + EVENT_DELETED: 'event:deleted', + EVENT_SELECTED: 'event:selected', + // System events (3) + ERROR: 'system:error', + REFRESH_REQUESTED: 'system:refresh', + OFFLINE_MODE_CHANGED: 'system:offline-mode-changed', + // Sync events (4) + SYNC_STARTED: 'sync:started', + SYNC_COMPLETED: 'sync:completed', + SYNC_FAILED: 'sync:failed', + SYNC_RETRY: 'sync:retry', + // Filter events (1) + FILTER_CHANGED: 'filter:changed', + // Rendering events (1) + EVENTS_RENDERED: 'events:rendered' +}; +//# sourceMappingURL=CoreEvents.js.map \ No newline at end of file diff --git a/wwwroot/js/constants/CoreEvents.js.map b/wwwroot/js/constants/CoreEvents.js.map new file mode 100644 index 0000000..d32cee3 --- /dev/null +++ b/wwwroot/js/constants/CoreEvents.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CoreEvents.js","sourceRoot":"","sources":["../../../src/constants/CoreEvents.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,uBAAuB;IACvB,WAAW,EAAE,kBAAkB;IAC/B,KAAK,EAAE,YAAY;IACnB,SAAS,EAAE,gBAAgB;IAE3B,kBAAkB;IAClB,YAAY,EAAE,cAAc;IAC5B,aAAa,EAAE,eAAe;IAC9B,gBAAgB,EAAE,kBAAkB;IAEpC,wBAAwB;IACxB,kBAAkB,EAAE,oBAAoB;IACxC,YAAY,EAAE,kBAAkB;IAChC,oBAAoB,EAAE,0BAA0B;IAChD,iBAAiB,EAAE,uBAAuB;IAE1C,kBAAkB;IAClB,YAAY,EAAE,cAAc;IAC5B,WAAW,EAAE,aAAa;IAC1B,UAAU,EAAE,YAAY;IACxB,eAAe,EAAE,sBAAsB;IACvC,sBAAsB,EAAE,oBAAoB;IAE5C,kBAAkB;IAClB,aAAa,EAAE,eAAe;IAC9B,YAAY,EAAE,cAAc;IAC5B,aAAa,EAAE,oBAAoB;IAEnC,uBAAuB;IACvB,aAAa,EAAE,eAAe;IAC9B,aAAa,EAAE,eAAe;IAC9B,aAAa,EAAE,eAAe;IAC9B,cAAc,EAAE,gBAAgB;IAEhC,oBAAoB;IACpB,KAAK,EAAE,cAAc;IACrB,iBAAiB,EAAE,gBAAgB;IACnC,oBAAoB,EAAE,6BAA6B;IAEnD,kBAAkB;IAClB,YAAY,EAAE,cAAc;IAC5B,cAAc,EAAE,gBAAgB;IAChC,WAAW,EAAE,aAAa;IAC1B,UAAU,EAAE,YAAY;IAExB,oBAAoB;IACpB,cAAc,EAAE,gBAAgB;IAEhC,uBAAuB;IACvB,eAAe,EAAE,iBAAiB;CAC1B,CAAC"} \ No newline at end of file diff --git a/wwwroot/js/core/CalendarConfig.d.ts b/wwwroot/js/core/CalendarConfig.d.ts new file mode 100644 index 0000000..c19e1f3 --- /dev/null +++ b/wwwroot/js/core/CalendarConfig.d.ts @@ -0,0 +1,225 @@ +import { CalendarConfig as ICalendarConfig, ViewPeriod, CalendarMode } from '../types/CalendarTypes'; +/** + * All-day event layout constants + */ +export declare const ALL_DAY_CONSTANTS: { + readonly EVENT_HEIGHT: 22; + readonly EVENT_GAP: 2; + readonly CONTAINER_PADDING: 4; + readonly MAX_COLLAPSED_ROWS: 4; + readonly SINGLE_ROW_HEIGHT: number; +}; +/** + * Layout and timing settings for the calendar grid + */ +interface GridSettings { + dayStartHour: number; + dayEndHour: number; + workStartHour: number; + workEndHour: number; + hourHeight: number; + snapInterval: number; + fitToWidth: boolean; + scrollToHour: number | null; + gridStartThresholdMinutes: number; + showCurrentTime: boolean; + showWorkHours: boolean; +} +/** + * View settings for date-based calendar mode + */ +interface DateViewSettings { + period: ViewPeriod; + weekDays: number; + firstDayOfWeek: number; + showAllDay: boolean; +} +/** + * Work week configuration settings + */ +interface WorkWeekSettings { + id: string; + workDays: number[]; + totalDays: number; + firstWorkDay: number; +} +/** + * View settings for resource-based calendar mode + */ +interface ResourceViewSettings { + maxResources: number; + showAvatars: boolean; + avatarSize: number; + resourceNameFormat: 'full' | 'short'; + showResourceDetails: boolean; + showAllDay: boolean; +} +/** + * Time format configuration settings + */ +interface TimeFormatConfig { + timezone: string; + use24HourFormat: boolean; + locale: string; + dateFormat: 'locale' | 'technical'; + showSeconds: boolean; +} +/** + * Calendar configuration management + */ +export declare class CalendarConfig { + private config; + private calendarMode; + private selectedDate; + private gridSettings; + private dateViewSettings; + private resourceViewSettings; + private currentWorkWeek; + private timeFormatConfig; + constructor(); + /** + * Load calendar type and date from URL parameters + */ + private loadCalendarType; + /** + * Load configuration from DOM data attributes + */ + private loadFromDOM; + /** + * Get a config value + */ + get(key: K): ICalendarConfig[K]; + /** + * Set a config value + */ + set(key: K, value: ICalendarConfig[K]): void; + /** + * Update multiple config values + */ + update(updates: Partial): void; + /** + * Get all config + */ + getAll(): ICalendarConfig; + /** + * Calculate derived values + */ + get minuteHeight(): number; + get totalHours(): number; + get totalMinutes(): number; + get slotsPerHour(): number; + get totalSlots(): number; + get slotHeight(): number; + /** + * Validate snap interval + */ + isValidSnapInterval(interval: number): boolean; + /** + * Get grid display settings + */ + getGridSettings(): GridSettings; + /** + * Update grid display settings + */ + updateGridSettings(updates: Partial): void; + /** + * Get date view settings + */ + getDateViewSettings(): DateViewSettings; + /** + * Update date view settings + */ + updateDateViewSettings(updates: Partial): void; + /** + * Get resource view settings + */ + getResourceViewSettings(): ResourceViewSettings; + /** + * Update resource view settings + */ + updateResourceViewSettings(updates: Partial): void; + /** + * Check if current mode is resource-based + */ + isResourceMode(): boolean; + /** + * Check if current mode is date-based + */ + isDateMode(): boolean; + /** + * Get calendar mode + */ + getCalendarMode(): CalendarMode; + /** + * Set calendar mode + */ + setCalendarMode(mode: CalendarMode): void; + /** + * Get selected date + */ + getSelectedDate(): Date | null; + /** + * Set selected date + * Note: Does not emit events - caller is responsible for event emission + */ + setSelectedDate(date: Date): void; + /** + * Get work week presets + */ + private getWorkWeekPresets; + /** + * Get current work week settings + */ + getWorkWeekSettings(): WorkWeekSettings; + /** + * Set work week preset + * Note: Does not emit events - caller is responsible for event emission + */ + setWorkWeek(workWeekId: string): void; + /** + * Get current work week ID + */ + getCurrentWorkWeek(): string; + /** + * Get time format settings + */ + getTimeFormatSettings(): TimeFormatConfig; + /** + * Update time format settings + */ + updateTimeFormatSettings(updates: Partial): void; + /** + * Set timezone (convenience method) + */ + setTimezone(timezone: string): void; + /** + * Set 12/24 hour format (convenience method) + */ + set24HourFormat(use24Hour: boolean): void; + /** + * Get configured timezone + */ + getTimezone(): string; + /** + * Get configured locale + */ + getLocale(): string; + /** + * Check if using 24-hour format + */ + is24HourFormat(): boolean; + /** + * Set date format (convenience method) + */ + setDateFormat(format: 'locale' | 'technical'): void; + /** + * Set whether to show seconds (convenience method) + */ + setShowSeconds(show: boolean): void; + /** + * Get current date format + */ + getDateFormat(): 'locale' | 'technical'; +} +export declare const calendarConfig: CalendarConfig; +export {}; diff --git a/wwwroot/js/core/CalendarConfig.js b/wwwroot/js/core/CalendarConfig.js new file mode 100644 index 0000000..010ca10 --- /dev/null +++ b/wwwroot/js/core/CalendarConfig.js @@ -0,0 +1,421 @@ +// Calendar configuration management +import { eventBus } from './EventBus'; +import { CoreEvents } from '../constants/CoreEvents'; +import { TimeFormatter } from '../utils/TimeFormatter'; +/** + * All-day event layout constants + */ +export const ALL_DAY_CONSTANTS = { + EVENT_HEIGHT: 22, // Height of single all-day event + EVENT_GAP: 2, // Gap between stacked events + CONTAINER_PADDING: 4, // Container padding (top + bottom) + get SINGLE_ROW_HEIGHT() { + return this.EVENT_HEIGHT + this.EVENT_GAP + this.CONTAINER_PADDING; // 28px + } +}; +/** + * Calendar configuration management + */ +export class CalendarConfig { + constructor() { + this.calendarMode = 'date'; + this.selectedDate = null; + this.currentWorkWeek = 'standard'; + this.config = { + // Scrollbar styling + scrollbarWidth: 16, // Width of scrollbar in pixels + scrollbarColor: '#666', // Scrollbar thumb color + scrollbarTrackColor: '#f0f0f0', // Scrollbar track color + scrollbarHoverColor: '#b53f7aff', // Scrollbar thumb hover color + scrollbarBorderRadius: 6, // Border radius for scrollbar thumb + // Interaction settings + allowDrag: true, + allowResize: true, + allowCreate: true, + // API settings + apiEndpoint: '/api/events', + dateFormat: 'YYYY-MM-DD', + timeFormat: 'HH:mm', + // Feature flags + enableSearch: true, + enableTouch: true, + // Event defaults + defaultEventDuration: 60, // Minutes + minEventDuration: 15, // Will be same as snapInterval + maxEventDuration: 480 // 8 hours + }; + // Grid display settings + this.gridSettings = { + hourHeight: 60, + dayStartHour: 0, + dayEndHour: 24, + workStartHour: 8, + workEndHour: 17, + snapInterval: 15, + showCurrentTime: true, + showWorkHours: true, + fitToWidth: false, + scrollToHour: 8 + }; + // Date view settings + this.dateViewSettings = { + period: 'week', + weekDays: 7, + firstDayOfWeek: 1, + showAllDay: true + }; + // Resource view settings + this.resourceViewSettings = { + maxResources: 10, + showAvatars: true, + avatarSize: 32, + resourceNameFormat: 'full', + showResourceDetails: true, + showAllDay: true + }; + // Time format settings - default to Denmark + this.timeFormatConfig = { + timezone: 'Europe/Copenhagen', + use24HourFormat: true, + locale: 'da-DK' + }; + // Set computed values + this.config.minEventDuration = this.gridSettings.snapInterval; + // Initialize TimeFormatter with default settings + TimeFormatter.configure(this.timeFormatConfig); + // Load calendar type from URL parameter + this.loadCalendarType(); + // Load from data attributes + this.loadFromDOM(); + } + /** + * Load calendar type and date from URL parameters + */ + loadCalendarType() { + const urlParams = new URLSearchParams(window.location.search); + const typeParam = urlParams.get('type'); + const dateParam = urlParams.get('date'); + // Set calendar mode + if (typeParam === 'resource' || typeParam === 'date') { + this.calendarMode = typeParam; + } + else { + this.calendarMode = 'date'; // Default + } + // Set selected date + if (dateParam) { + const parsedDate = new Date(dateParam); + if (!isNaN(parsedDate.getTime())) { + this.selectedDate = parsedDate; + } + else { + this.selectedDate = new Date(); + } + } + else { + this.selectedDate = new Date(); // Default to today + } + } + /** + * Load configuration from DOM data attributes + */ + loadFromDOM() { + const calendar = document.querySelector('swp-calendar'); + if (!calendar) + return; + // Read data attributes + const attrs = calendar.dataset; + // Update date view settings + if (attrs.view) + this.dateViewSettings.period = attrs.view; + if (attrs.weekDays) + this.dateViewSettings.weekDays = parseInt(attrs.weekDays); + // Update grid settings + if (attrs.snapInterval) + this.gridSettings.snapInterval = parseInt(attrs.snapInterval); + if (attrs.dayStartHour) + this.gridSettings.dayStartHour = parseInt(attrs.dayStartHour); + if (attrs.dayEndHour) + this.gridSettings.dayEndHour = parseInt(attrs.dayEndHour); + if (attrs.hourHeight) + this.gridSettings.hourHeight = parseInt(attrs.hourHeight); + if (attrs.fitToWidth !== undefined) + this.gridSettings.fitToWidth = attrs.fitToWidth === 'true'; + // Update computed values + this.config.minEventDuration = this.gridSettings.snapInterval; + } + /** + * Get a config value + */ + get(key) { + return this.config[key]; + } + /** + * Set a config value + */ + set(key, value) { + const oldValue = this.config[key]; + this.config[key] = value; + // Update computed values handled in specific update methods + // Emit config update event + eventBus.emit(CoreEvents.REFRESH_REQUESTED, { + key, + value, + oldValue + }); + } + /** + * Update multiple config values + */ + update(updates) { + Object.entries(updates).forEach(([key, value]) => { + this.set(key, value); + }); + } + /** + * Get all config + */ + getAll() { + return { ...this.config }; + } + /** + * Calculate derived values + */ + get minuteHeight() { + return this.gridSettings.hourHeight / 60; + } + get totalHours() { + return this.gridSettings.dayEndHour - this.gridSettings.dayStartHour; + } + get totalMinutes() { + return this.totalHours * 60; + } + get slotsPerHour() { + return 60 / this.gridSettings.snapInterval; + } + get totalSlots() { + return this.totalHours * this.slotsPerHour; + } + get slotHeight() { + return this.gridSettings.hourHeight / this.slotsPerHour; + } + /** + * Validate snap interval + */ + isValidSnapInterval(interval) { + return [5, 10, 15, 30, 60].includes(interval); + } + /** + * Get grid display settings + */ + getGridSettings() { + return { ...this.gridSettings }; + } + /** + * Update grid display settings + */ + updateGridSettings(updates) { + this.gridSettings = { ...this.gridSettings, ...updates }; + // Update computed values + if (updates.snapInterval) { + this.config.minEventDuration = updates.snapInterval; + } + // Grid settings changes trigger general refresh - avoid specific event + eventBus.emit(CoreEvents.REFRESH_REQUESTED, { + key: 'gridSettings', + value: this.gridSettings + }); + } + /** + * Get date view settings + */ + getDateViewSettings() { + return { ...this.dateViewSettings }; + } + /** + * Update date view settings + */ + updateDateViewSettings(updates) { + this.dateViewSettings = { ...this.dateViewSettings, ...updates }; + // Date view settings changes trigger general refresh - avoid specific event + eventBus.emit(CoreEvents.REFRESH_REQUESTED, { + key: 'dateViewSettings', + value: this.dateViewSettings + }); + } + /** + * Get resource view settings + */ + getResourceViewSettings() { + return { ...this.resourceViewSettings }; + } + /** + * Update resource view settings + */ + updateResourceViewSettings(updates) { + this.resourceViewSettings = { ...this.resourceViewSettings, ...updates }; + // Resource view settings changes trigger general refresh - avoid specific event + eventBus.emit(CoreEvents.REFRESH_REQUESTED, { + key: 'resourceViewSettings', + value: this.resourceViewSettings + }); + } + /** + * Check if current mode is resource-based + */ + isResourceMode() { + return this.calendarMode === 'resource'; + } + /** + * Check if current mode is date-based + */ + isDateMode() { + return this.calendarMode === 'date'; + } + /** + * Get calendar mode + */ + getCalendarMode() { + return this.calendarMode; + } + /** + * Set calendar mode + */ + setCalendarMode(mode) { + const oldMode = this.calendarMode; + this.calendarMode = mode; + // Emit calendar mode change event + eventBus.emit(CoreEvents.VIEW_CHANGED, { + oldType: oldMode, + newType: mode + }); + } + /** + * Get selected date + */ + getSelectedDate() { + return this.selectedDate; + } + /** + * Set selected date + */ + setSelectedDate(date) { + this.selectedDate = date; + // Emit date change event + eventBus.emit(CoreEvents.DATE_CHANGED, { + date: date + }); + } + /** + * Get work week presets + */ + getWorkWeekPresets() { + return { + 'standard': { + id: 'standard', + workDays: [1, 2, 3, 4, 5], // Monday-Friday (ISO) + totalDays: 5, + firstWorkDay: 1 + }, + 'compressed': { + id: 'compressed', + workDays: [1, 2, 3, 4], // Monday-Thursday (ISO) + totalDays: 4, + firstWorkDay: 1 + }, + 'midweek': { + id: 'midweek', + workDays: [3, 4, 5], // Wednesday-Friday (ISO) + totalDays: 3, + firstWorkDay: 3 + }, + 'weekend': { + id: 'weekend', + workDays: [6, 7], // Saturday-Sunday (ISO) + totalDays: 2, + firstWorkDay: 6 + }, + 'fullweek': { + id: 'fullweek', + workDays: [1, 2, 3, 4, 5, 6, 7], // Monday-Sunday (ISO) + totalDays: 7, + firstWorkDay: 1 + } + }; + } + /** + * Get current work week settings + */ + getWorkWeekSettings() { + const presets = this.getWorkWeekPresets(); + return presets[this.currentWorkWeek] || presets['standard']; + } + /** + * Set work week preset + */ + setWorkWeek(workWeekId) { + const presets = this.getWorkWeekPresets(); + if (presets[workWeekId]) { + this.currentWorkWeek = workWeekId; + // Update dateViewSettings to match work week + this.dateViewSettings.weekDays = presets[workWeekId].totalDays; + // Emit work week change event + eventBus.emit(CoreEvents.WORKWEEK_CHANGED, { + workWeekId: workWeekId, + settings: presets[workWeekId] + }); + } + } + /** + * Get current work week ID + */ + getCurrentWorkWeek() { + return this.currentWorkWeek; + } + /** + * Get time format settings + */ + getTimeFormatSettings() { + return { ...this.timeFormatConfig }; + } + /** + * Update time format settings + */ + updateTimeFormatSettings(updates) { + this.timeFormatConfig = { ...this.timeFormatConfig, ...updates }; + // Update TimeFormatter with new settings + TimeFormatter.configure(this.timeFormatConfig); + // Emit time format change event + eventBus.emit(CoreEvents.REFRESH_REQUESTED, { + key: 'timeFormatSettings', + value: this.timeFormatConfig + }); + } + /** + * Set timezone (convenience method) + */ + setTimezone(timezone) { + this.updateTimeFormatSettings({ timezone }); + } + /** + * Set 12/24 hour format (convenience method) + */ + set24HourFormat(use24Hour) { + this.updateTimeFormatSettings({ use24HourFormat: use24Hour }); + } + /** + * Get configured timezone + */ + getTimezone() { + return this.timeFormatConfig.timezone; + } + /** + * Check if using 24-hour format + */ + is24HourFormat() { + return this.timeFormatConfig.use24HourFormat; + } +} +// Create singleton instance +export const calendarConfig = new CalendarConfig(); +//# sourceMappingURL=CalendarConfig.js.map \ No newline at end of file diff --git a/wwwroot/js/core/CalendarConfig.js.map b/wwwroot/js/core/CalendarConfig.js.map new file mode 100644 index 0000000..d0e1710 --- /dev/null +++ b/wwwroot/js/core/CalendarConfig.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CalendarConfig.js","sourceRoot":"","sources":["../../../src/core/CalendarConfig.ts"],"names":[],"mappings":"AAAA,oCAAoC;AAEpC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD,OAAO,EAAE,aAAa,EAAsB,MAAM,wBAAwB,CAAC;AAE3E;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,YAAY,EAAE,EAAE,EAAK,iCAAiC;IACtD,SAAS,EAAE,CAAC,EAAS,6BAA6B;IAClD,iBAAiB,EAAE,CAAC,EAAE,mCAAmC;IACzD,IAAI,iBAAiB;QACnB,OAAO,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,iBAAiB,CAAC,CAAC,OAAO;IAC7E,CAAC;CACO,CAAC;AAgEX;;GAEG;AACH,MAAM,OAAO,cAAc;IAUzB;QARQ,iBAAY,GAAiB,MAAM,CAAC;QACpC,iBAAY,GAAgB,IAAI,CAAC;QAIjC,oBAAe,GAAW,UAAU,CAAC;QAI3C,IAAI,CAAC,MAAM,GAAG;YACZ,oBAAoB;YACpB,cAAc,EAAE,EAAE,EAAM,+BAA+B;YACvD,cAAc,EAAE,MAAM,EAAE,wBAAwB;YAChD,mBAAmB,EAAE,SAAS,EAAE,wBAAwB;YACxD,mBAAmB,EAAE,WAAW,EAAE,8BAA8B;YAChE,qBAAqB,EAAE,CAAC,EAAE,oCAAoC;YAE9D,uBAAuB;YACvB,SAAS,EAAE,IAAI;YACf,WAAW,EAAE,IAAI;YACjB,WAAW,EAAE,IAAI;YAEjB,eAAe;YACf,WAAW,EAAE,aAAa;YAC1B,UAAU,EAAE,YAAY;YACxB,UAAU,EAAE,OAAO;YAEnB,gBAAgB;YAChB,YAAY,EAAE,IAAI;YAClB,WAAW,EAAE,IAAI;YAEjB,iBAAiB;YACjB,oBAAoB,EAAE,EAAE,EAAE,UAAU;YACpC,gBAAgB,EAAE,EAAE,EAAM,+BAA+B;YACzD,gBAAgB,EAAE,GAAG,CAAK,UAAU;SACrC,CAAC;QAEF,wBAAwB;QACxB,IAAI,CAAC,YAAY,GAAG;YAClB,UAAU,EAAE,EAAE;YACd,YAAY,EAAE,CAAC;YACf,UAAU,EAAE,EAAE;YACd,aAAa,EAAE,CAAC;YAChB,WAAW,EAAE,EAAE;YACf,YAAY,EAAE,EAAE;YAChB,eAAe,EAAE,IAAI;YACrB,aAAa,EAAE,IAAI;YACnB,UAAU,EAAE,KAAK;YACjB,YAAY,EAAE,CAAC;SAChB,CAAC;QAEF,qBAAqB;QACrB,IAAI,CAAC,gBAAgB,GAAG;YACtB,MAAM,EAAE,MAAM;YACd,QAAQ,EAAE,CAAC;YACX,cAAc,EAAE,CAAC;YACjB,UAAU,EAAE,IAAI;SACjB,CAAC;QAEF,yBAAyB;QACzB,IAAI,CAAC,oBAAoB,GAAG;YAC1B,YAAY,EAAE,EAAE;YAChB,WAAW,EAAE,IAAI;YACjB,UAAU,EAAE,EAAE;YACd,kBAAkB,EAAE,MAAM;YAC1B,mBAAmB,EAAE,IAAI;YACzB,UAAU,EAAE,IAAI;SACjB,CAAC;QAEF,4CAA4C;QAC5C,IAAI,CAAC,gBAAgB,GAAG;YACtB,QAAQ,EAAE,mBAAmB;YAC7B,eAAe,EAAE,IAAI;YACrB,MAAM,EAAE,OAAO;SAChB,CAAC;QAEF,sBAAsB;QACtB,IAAI,CAAC,MAAM,CAAC,gBAAgB,GAAG,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC;QAE9D,iDAAiD;QACjD,aAAa,CAAC,SAAS,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAE/C,wCAAwC;QACxC,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAExB,4BAA4B;QAC5B,IAAI,CAAC,WAAW,EAAE,CAAC;IACrB,CAAC;IAED;;OAEG;IACK,gBAAgB;QACtB,MAAM,SAAS,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC9D,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACxC,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAExC,oBAAoB;QACpB,IAAI,SAAS,KAAK,UAAU,IAAI,SAAS,KAAK,MAAM,EAAE,CAAC;YACrD,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;QAChC,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,CAAC,UAAU;QACxC,CAAC;QAED,oBAAoB;QACpB,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC;YACvC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;gBACjC,IAAI,CAAC,YAAY,GAAG,UAAU,CAAC;YACjC,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,YAAY,GAAG,IAAI,IAAI,EAAE,CAAC;YACjC,CAAC;QACH,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,YAAY,GAAG,IAAI,IAAI,EAAE,CAAC,CAAC,mBAAmB;QACrD,CAAC;IACH,CAAC;IAED;;OAEG;IACK,WAAW;QACjB,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,cAAc,CAAgB,CAAC;QACvE,IAAI,CAAC,QAAQ;YAAE,OAAO;QAEtB,uBAAuB;QACvB,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC;QAE/B,4BAA4B;QAC5B,IAAI,KAAK,CAAC,IAAI;YAAE,IAAI,CAAC,gBAAgB,CAAC,MAAM,GAAG,KAAK,CAAC,IAAkB,CAAC;QACxE,IAAI,KAAK,CAAC,QAAQ;YAAE,IAAI,CAAC,gBAAgB,CAAC,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAE9E,uBAAuB;QACvB,IAAI,KAAK,CAAC,YAAY;YAAE,IAAI,CAAC,YAAY,CAAC,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACtF,IAAI,KAAK,CAAC,YAAY;YAAE,IAAI,CAAC,YAAY,CAAC,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACtF,IAAI,KAAK,CAAC,UAAU;YAAE,IAAI,CAAC,YAAY,CAAC,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAChF,IAAI,KAAK,CAAC,UAAU;YAAE,IAAI,CAAC,YAAY,CAAC,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAChF,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS;YAAE,IAAI,CAAC,YAAY,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,KAAK,MAAM,CAAC;QAE/F,yBAAyB;QACzB,IAAI,CAAC,MAAM,CAAC,gBAAgB,GAAG,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC;IAChE,CAAC;IAED;;OAEG;IACH,GAAG,CAAkC,GAAM;QACzC,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAED;;OAEG;IACH,GAAG,CAAkC,GAAM,EAAE,KAAyB;QACpE,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QAEzB,4DAA4D;QAE5D,2BAA2B;QAC3B,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,iBAAiB,EAAE;YAC1C,GAAG;YACH,KAAK;YACL,QAAQ;SACT,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,OAAiC;QACtC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;YAC/C,IAAI,CAAC,GAAG,CAAC,GAA4B,EAAE,KAAK,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,MAAM;QACJ,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;IAC5B,CAAC;IAED;;OAEG;IAEH,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,YAAY,CAAC,UAAU,GAAG,EAAE,CAAC;IAC3C,CAAC;IAED,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,YAAY,CAAC,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC;IACvE,CAAC;IAED,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;IAC9B,CAAC;IAED,IAAI,YAAY;QACd,OAAO,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC;IAC7C,CAAC;IAED,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC;IAC7C,CAAC;IAED,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,YAAY,CAAC,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC;IAC1D,CAAC;IAED;;OAEG;IACH,mBAAmB,CAAC,QAAgB;QAClC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAChD,CAAC;IAED;;OAEG;IACH,eAAe;QACb,OAAO,EAAE,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;IAClC,CAAC;IAED;;OAEG;IACH,kBAAkB,CAAC,OAA8B;QAC/C,IAAI,CAAC,YAAY,GAAG,EAAE,GAAG,IAAI,CAAC,YAAY,EAAE,GAAG,OAAO,EAAE,CAAC;QAEzD,yBAAyB;QACzB,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;YACzB,IAAI,CAAC,MAAM,CAAC,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC;QACtD,CAAC;QAED,uEAAuE;QACvE,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,iBAAiB,EAAE;YAC1C,GAAG,EAAE,cAAc;YACnB,KAAK,EAAE,IAAI,CAAC,YAAY;SACzB,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,mBAAmB;QACjB,OAAO,EAAE,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;IACtC,CAAC;IAED;;OAEG;IACH,sBAAsB,CAAC,OAAkC;QACvD,IAAI,CAAC,gBAAgB,GAAG,EAAE,GAAG,IAAI,CAAC,gBAAgB,EAAE,GAAG,OAAO,EAAE,CAAC;QAEjE,4EAA4E;QAC5E,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,iBAAiB,EAAE;YAC1C,GAAG,EAAE,kBAAkB;YACvB,KAAK,EAAE,IAAI,CAAC,gBAAgB;SAC7B,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,uBAAuB;QACrB,OAAO,EAAE,GAAG,IAAI,CAAC,oBAAoB,EAAE,CAAC;IAC1C,CAAC;IAED;;OAEG;IACH,0BAA0B,CAAC,OAAsC;QAC/D,IAAI,CAAC,oBAAoB,GAAG,EAAE,GAAG,IAAI,CAAC,oBAAoB,EAAE,GAAG,OAAO,EAAE,CAAC;QAEzE,gFAAgF;QAChF,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,iBAAiB,EAAE;YAC1C,GAAG,EAAE,sBAAsB;YAC3B,KAAK,EAAE,IAAI,CAAC,oBAAoB;SACjC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,cAAc;QACZ,OAAO,IAAI,CAAC,YAAY,KAAK,UAAU,CAAC;IAC1C,CAAC;IAED;;OAEG;IACH,UAAU;QACR,OAAO,IAAI,CAAC,YAAY,KAAK,MAAM,CAAC;IACtC,CAAC;IAGD;;OAEG;IACH,eAAe;QACb,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAED;;OAEG;IACH,eAAe,CAAC,IAAkB;QAChC,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC;QAClC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QAEzB,kCAAkC;QAClC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE;YACrC,OAAO,EAAE,OAAO;YAChB,OAAO,EAAE,IAAI;SACd,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,eAAe;QACb,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAED;;OAEG;IACH,eAAe,CAAC,IAAU;QACxB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QAEzB,yBAAyB;QACzB,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE;YACrC,IAAI,EAAE,IAAI;SACX,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACK,kBAAkB;QACxB,OAAO;YACL,UAAU,EAAE;gBACV,EAAE,EAAE,UAAU;gBACd,QAAQ,EAAE,CAAC,CAAC,EAAC,CAAC,EAAC,CAAC,EAAC,CAAC,EAAC,CAAC,CAAC,EAAE,sBAAsB;gBAC7C,SAAS,EAAE,CAAC;gBACZ,YAAY,EAAE,CAAC;aAChB;YACD,YAAY,EAAE;gBACZ,EAAE,EAAE,YAAY;gBAChB,QAAQ,EAAE,CAAC,CAAC,EAAC,CAAC,EAAC,CAAC,EAAC,CAAC,CAAC,EAAE,wBAAwB;gBAC7C,SAAS,EAAE,CAAC;gBACZ,YAAY,EAAE,CAAC;aAChB;YACD,SAAS,EAAE;gBACT,EAAE,EAAE,SAAS;gBACb,QAAQ,EAAE,CAAC,CAAC,EAAC,CAAC,EAAC,CAAC,CAAC,EAAE,yBAAyB;gBAC5C,SAAS,EAAE,CAAC;gBACZ,YAAY,EAAE,CAAC;aAChB;YACD,SAAS,EAAE;gBACT,EAAE,EAAE,SAAS;gBACb,QAAQ,EAAE,CAAC,CAAC,EAAC,CAAC,CAAC,EAAE,wBAAwB;gBACzC,SAAS,EAAE,CAAC;gBACZ,YAAY,EAAE,CAAC;aAChB;YACD,UAAU,EAAE;gBACV,EAAE,EAAE,UAAU;gBACd,QAAQ,EAAE,CAAC,CAAC,EAAC,CAAC,EAAC,CAAC,EAAC,CAAC,EAAC,CAAC,EAAC,CAAC,EAAC,CAAC,CAAC,EAAE,sBAAsB;gBACjD,SAAS,EAAE,CAAC;gBACZ,YAAY,EAAE,CAAC;aAChB;SACF,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,mBAAmB;QACjB,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1C,OAAO,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IAC9D,CAAC;IAED;;OAEG;IACH,WAAW,CAAC,UAAkB;QAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1C,IAAI,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YACxB,IAAI,CAAC,eAAe,GAAG,UAAU,CAAC;YAElC,6CAA6C;YAC7C,IAAI,CAAC,gBAAgB,CAAC,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC;YAE/D,8BAA8B;YAC9B,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,gBAAgB,EAAE;gBACzC,UAAU,EAAE,UAAU;gBACtB,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC;aAC9B,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED;;OAEG;IACH,kBAAkB;QAChB,OAAO,IAAI,CAAC,eAAe,CAAC;IAC9B,CAAC;IAED;;OAEG;IACH,qBAAqB;QACnB,OAAO,EAAE,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;IACtC,CAAC;IAED;;OAEG;IACH,wBAAwB,CAAC,OAAkC;QACzD,IAAI,CAAC,gBAAgB,GAAG,EAAE,GAAG,IAAI,CAAC,gBAAgB,EAAE,GAAG,OAAO,EAAE,CAAC;QAEjE,yCAAyC;QACzC,aAAa,CAAC,SAAS,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAE/C,gCAAgC;QAChC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,iBAAiB,EAAE;YAC1C,GAAG,EAAE,oBAAoB;YACzB,KAAK,EAAE,IAAI,CAAC,gBAAgB;SAC7B,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,WAAW,CAAC,QAAgB;QAC1B,IAAI,CAAC,wBAAwB,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED;;OAEG;IACH,eAAe,CAAC,SAAkB;QAChC,IAAI,CAAC,wBAAwB,CAAC,EAAE,eAAe,EAAE,SAAS,EAAE,CAAC,CAAC;IAChE,CAAC;IAED;;OAEG;IACH,WAAW;QACT,OAAO,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC;IACxC,CAAC;IAED;;OAEG;IACH,cAAc;QACZ,OAAO,IAAI,CAAC,gBAAgB,CAAC,eAAe,CAAC;IAC/C,CAAC;CAEF;AAED,4BAA4B;AAC5B,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,cAAc,EAAE,CAAC"} \ No newline at end of file diff --git a/wwwroot/js/core/EventBus.d.ts b/wwwroot/js/core/EventBus.d.ts new file mode 100644 index 0000000..93273d5 --- /dev/null +++ b/wwwroot/js/core/EventBus.d.ts @@ -0,0 +1,60 @@ +import { IEventLogEntry, IEventBus } from '../types/CalendarTypes'; +/** + * Central event dispatcher for calendar using DOM CustomEvents + * Provides logging and debugging capabilities + */ +export declare class EventBus implements IEventBus { + private eventLog; + private debug; + private listeners; + private logConfig; + /** + * Subscribe to an event via DOM addEventListener + */ + on(eventType: string, handler: EventListener, options?: AddEventListenerOptions): () => void; + /** + * Subscribe to an event once + */ + once(eventType: string, handler: EventListener): () => void; + /** + * Unsubscribe from an event + */ + off(eventType: string, handler: EventListener): void; + /** + * Emit an event via DOM CustomEvent + */ + emit(eventType: string, detail?: unknown): boolean; + /** + * Log event with console grouping + */ + private logEventWithGrouping; + /** + * Extract category from event type + */ + private extractCategory; + /** + * Get styling for different categories + */ + private getCategoryStyle; + /** + * Configure logging for specific categories + */ + setLogConfig(config: { + [key: string]: boolean; + }): void; + /** + * Get current log configuration + */ + getLogConfig(): { + [key: string]: boolean; + }; + /** + * Get event history + */ + getEventLog(eventType?: string): IEventLogEntry[]; + /** + * Enable/disable debug mode + */ + setDebug(enabled: boolean): void; +} +export declare const eventBus: EventBus; diff --git a/wwwroot/js/core/EventBus.js b/wwwroot/js/core/EventBus.js new file mode 100644 index 0000000..07b721e --- /dev/null +++ b/wwwroot/js/core/EventBus.js @@ -0,0 +1,158 @@ +/** + * Central event dispatcher for calendar using DOM CustomEvents + * Provides logging and debugging capabilities + */ +export class EventBus { + constructor() { + this.eventLog = []; + this.debug = false; + this.listeners = new Set(); + // Log configuration for different categories + this.logConfig = { + calendar: true, + grid: true, + event: true, + scroll: true, + navigation: true, + view: true, + default: true + }; + } + /** + * Subscribe to an event via DOM addEventListener + */ + on(eventType, handler, options) { + document.addEventListener(eventType, handler, options); + // Track for cleanup + this.listeners.add({ eventType, handler, options }); + // Return unsubscribe function + return () => this.off(eventType, handler); + } + /** + * Subscribe to an event once + */ + once(eventType, handler) { + return this.on(eventType, handler, { once: true }); + } + /** + * Unsubscribe from an event + */ + off(eventType, handler) { + document.removeEventListener(eventType, handler); + // Remove from tracking + for (const listener of this.listeners) { + if (listener.eventType === eventType && listener.handler === handler) { + this.listeners.delete(listener); + break; + } + } + } + /** + * Emit an event via DOM CustomEvent + */ + emit(eventType, detail = {}) { + // Validate eventType + if (!eventType) { + return false; + } + const event = new CustomEvent(eventType, { + detail: detail ?? {}, + bubbles: true, + cancelable: true + }); + // Log event with grouping + if (this.debug) { + this.logEventWithGrouping(eventType, detail); + } + this.eventLog.push({ + type: eventType, + detail: detail ?? {}, + timestamp: Date.now() + }); + // Emit on document (only DOM events now) + return !document.dispatchEvent(event); + } + /** + * Log event with console grouping + */ + logEventWithGrouping(eventType, detail) { + // Extract category from event type (e.g., 'calendar:datechanged' → 'calendar') + const category = this.extractCategory(eventType); + // Only log if category is enabled + if (!this.logConfig[category]) { + return; + } + // Get category emoji and color + const { emoji, color } = this.getCategoryStyle(category); + // Use collapsed group to reduce visual noise + } + /** + * Extract category from event type + */ + extractCategory(eventType) { + if (!eventType) { + return 'unknown'; + } + if (eventType.includes(':')) { + return eventType.split(':')[0]; + } + // Fallback: try to detect category from event name patterns + const lowerType = eventType.toLowerCase(); + if (lowerType.includes('grid') || lowerType.includes('rendered')) + return 'grid'; + if (lowerType.includes('event') || lowerType.includes('sync')) + return 'event'; + if (lowerType.includes('scroll')) + return 'scroll'; + if (lowerType.includes('nav') || lowerType.includes('date')) + return 'navigation'; + if (lowerType.includes('view')) + return 'view'; + return 'default'; + } + /** + * Get styling for different categories + */ + getCategoryStyle(category) { + const styles = { + calendar: { emoji: '🗓️', color: '#2196F3' }, + grid: { emoji: '📊', color: '#4CAF50' }, + event: { emoji: '📅', color: '#FF9800' }, + scroll: { emoji: '📜', color: '#9C27B0' }, + navigation: { emoji: '🧭', color: '#F44336' }, + view: { emoji: '👁️', color: '#00BCD4' }, + default: { emoji: '📢', color: '#607D8B' } + }; + return styles[category] || styles.default; + } + /** + * Configure logging for specific categories + */ + setLogConfig(config) { + this.logConfig = { ...this.logConfig, ...config }; + } + /** + * Get current log configuration + */ + getLogConfig() { + return { ...this.logConfig }; + } + /** + * Get event history + */ + getEventLog(eventType) { + if (eventType) { + return this.eventLog.filter(e => e.type === eventType); + } + return this.eventLog; + } + /** + * Enable/disable debug mode + */ + setDebug(enabled) { + this.debug = enabled; + } +} +// Create singleton instance +export const eventBus = new EventBus(); +//# sourceMappingURL=EventBus.js.map \ No newline at end of file diff --git a/wwwroot/js/core/EventBus.js.map b/wwwroot/js/core/EventBus.js.map new file mode 100644 index 0000000..36bb9bc --- /dev/null +++ b/wwwroot/js/core/EventBus.js.map @@ -0,0 +1 @@ +{"version":3,"file":"EventBus.js","sourceRoot":"","sources":["../../../src/core/EventBus.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/datasources/DateColumnDataSource.d.ts b/wwwroot/js/datasources/DateColumnDataSource.d.ts new file mode 100644 index 0000000..3807ff2 --- /dev/null +++ b/wwwroot/js/datasources/DateColumnDataSource.d.ts @@ -0,0 +1,55 @@ +import { IColumnDataSource } from '../types/ColumnDataSource'; +import { DateService } from '../utils/DateService'; +import { Configuration } from '../configurations/CalendarConfig'; +import { CalendarView } from '../types/CalendarTypes'; +/** + * DateColumnDataSource - Provides date-based columns + * + * Calculates which dates to display based on: + * - Current date + * - Current view (day/week/month) + * - Workweek settings + */ +export declare class DateColumnDataSource implements IColumnDataSource { + private dateService; + private config; + private currentDate; + private currentView; + constructor(dateService: DateService, config: Configuration, currentDate: Date, currentView: CalendarView); + /** + * Get columns (dates) to display + */ + getColumns(): Date[]; + /** + * Get type of datasource + */ + getType(): 'date' | 'resource'; + /** + * Update current date + */ + setCurrentDate(date: Date): void; + /** + * Update current view + */ + setCurrentView(view: CalendarView): void; + /** + * Get dates for week view based on workweek settings + */ + private getWeekDates; + /** + * Get all dates in current month + */ + private getMonthDates; + /** + * Get ISO week start (Monday) + */ + private getISOWeekStart; + /** + * Get month start + */ + private getMonthStart; + /** + * Get month end + */ + private getMonthEnd; +} diff --git a/wwwroot/js/datasources/DateColumnDataSource.js b/wwwroot/js/datasources/DateColumnDataSource.js new file mode 100644 index 0000000..eefe010 --- /dev/null +++ b/wwwroot/js/datasources/DateColumnDataSource.js @@ -0,0 +1,94 @@ +/** + * DateColumnDataSource - Provides date-based columns + * + * Calculates which dates to display based on: + * - Current date + * - Current view (day/week/month) + * - Workweek settings + */ +export class DateColumnDataSource { + constructor(dateService, config, currentDate, currentView) { + this.dateService = dateService; + this.config = config; + this.currentDate = currentDate; + this.currentView = currentView; + } + /** + * Get columns (dates) to display + */ + getColumns() { + switch (this.currentView) { + case 'week': + return this.getWeekDates(); + case 'month': + return this.getMonthDates(); + case 'day': + return [this.currentDate]; + default: + return this.getWeekDates(); + } + } + /** + * Get type of datasource + */ + getType() { + return 'date'; + } + /** + * Update current date + */ + setCurrentDate(date) { + this.currentDate = date; + } + /** + * Update current view + */ + setCurrentView(view) { + this.currentView = view; + } + /** + * Get dates for week view based on workweek settings + */ + getWeekDates() { + const weekStart = this.getISOWeekStart(this.currentDate); + const workWeekSettings = this.config.getWorkWeekSettings(); + return this.dateService.getWorkWeekDates(weekStart, workWeekSettings.workDays); + } + /** + * Get all dates in current month + */ + getMonthDates() { + const dates = []; + const monthStart = this.getMonthStart(this.currentDate); + const monthEnd = this.getMonthEnd(this.currentDate); + const totalDays = Math.ceil((monthEnd.getTime() - monthStart.getTime()) / (1000 * 60 * 60 * 24)) + 1; + for (let i = 0; i < totalDays; i++) { + dates.push(this.dateService.addDays(monthStart, i)); + } + return dates; + } + /** + * Get ISO week start (Monday) + */ + getISOWeekStart(date) { + const weekBounds = this.dateService.getWeekBounds(date); + return this.dateService.startOfDay(weekBounds.start); + } + /** + * Get month start + */ + getMonthStart(date) { + const year = date.getFullYear(); + const month = date.getMonth(); + return this.dateService.startOfDay(new Date(year, month, 1)); + } + /** + * Get month end + */ + getMonthEnd(date) { + const nextMonth = this.dateService.addMonths(date, 1); + const firstOfNextMonth = this.getMonthStart(nextMonth); + return this.dateService.endOfDay(this.dateService.addDays(firstOfNextMonth, -1)); + } +} +//# sourceMappingURL=DateColumnDataSource.js.map \ No newline at end of file diff --git a/wwwroot/js/datasources/DateColumnDataSource.js.map b/wwwroot/js/datasources/DateColumnDataSource.js.map new file mode 100644 index 0000000..b40c024 --- /dev/null +++ b/wwwroot/js/datasources/DateColumnDataSource.js.map @@ -0,0 +1 @@ +{"version":3,"file":"DateColumnDataSource.js","sourceRoot":"","sources":["../../../src/datasources/DateColumnDataSource.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/demo.js b/wwwroot/js/demo.js new file mode 100644 index 0000000..2ab50eb --- /dev/null +++ b/wwwroot/js/demo.js @@ -0,0 +1,6489 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); + +// node_modules/dayjs/dayjs.min.js +var require_dayjs_min = __commonJS({ + "node_modules/dayjs/dayjs.min.js"(exports, module) { + !function(t, e) { + "object" == typeof exports && "undefined" != typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define(e) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs = e(); + }(exports, function() { + "use strict"; + var t = 1e3, e = 6e4, n = 36e5, r = "millisecond", i = "second", s = "minute", u = "hour", a = "day", o = "week", c = "month", f = "quarter", h = "year", d = "date", l = "Invalid Date", $ = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/, y = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g, M = { name: "en", weekdays: "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), months: "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), ordinal: function(t2) { + var e2 = ["th", "st", "nd", "rd"], n2 = t2 % 100; + return "[" + t2 + (e2[(n2 - 20) % 10] || e2[n2] || e2[0]) + "]"; + } }, m = /* @__PURE__ */ __name(function(t2, e2, n2) { + var r2 = String(t2); + return !r2 || r2.length >= e2 ? t2 : "" + Array(e2 + 1 - r2.length).join(n2) + t2; + }, "m"), v = { s: m, z: function(t2) { + var e2 = -t2.utcOffset(), n2 = Math.abs(e2), r2 = Math.floor(n2 / 60), i2 = n2 % 60; + return (e2 <= 0 ? "+" : "-") + m(r2, 2, "0") + ":" + m(i2, 2, "0"); + }, m: /* @__PURE__ */ __name(function t2(e2, n2) { + if (e2.date() < n2.date()) + return -t2(n2, e2); + var r2 = 12 * (n2.year() - e2.year()) + (n2.month() - e2.month()), i2 = e2.clone().add(r2, c), s2 = n2 - i2 < 0, u2 = e2.clone().add(r2 + (s2 ? -1 : 1), c); + return +(-(r2 + (n2 - i2) / (s2 ? i2 - u2 : u2 - i2)) || 0); + }, "t"), a: function(t2) { + return t2 < 0 ? Math.ceil(t2) || 0 : Math.floor(t2); + }, p: function(t2) { + return { M: c, y: h, w: o, d: a, D: d, h: u, m: s, s: i, ms: r, Q: f }[t2] || String(t2 || "").toLowerCase().replace(/s$/, ""); + }, u: function(t2) { + return void 0 === t2; + } }, g = "en", D = {}; + D[g] = M; + var p = "$isDayjsObject", S = /* @__PURE__ */ __name(function(t2) { + return t2 instanceof _ || !(!t2 || !t2[p]); + }, "S"), w = /* @__PURE__ */ __name(function t2(e2, n2, r2) { + var i2; + if (!e2) + return g; + if ("string" == typeof e2) { + var s2 = e2.toLowerCase(); + D[s2] && (i2 = s2), n2 && (D[s2] = n2, i2 = s2); + var u2 = e2.split("-"); + if (!i2 && u2.length > 1) + return t2(u2[0]); + } else { + var a2 = e2.name; + D[a2] = e2, i2 = a2; + } + return !r2 && i2 && (g = i2), i2 || !r2 && g; + }, "t"), O = /* @__PURE__ */ __name(function(t2, e2) { + if (S(t2)) + return t2.clone(); + var n2 = "object" == typeof e2 ? e2 : {}; + return n2.date = t2, n2.args = arguments, new _(n2); + }, "O"), b = v; + b.l = w, b.i = S, b.w = function(t2, e2) { + return O(t2, { locale: e2.$L, utc: e2.$u, x: e2.$x, $offset: e2.$offset }); + }; + var _ = function() { + function M2(t2) { + this.$L = w(t2.locale, null, true), this.parse(t2), this.$x = this.$x || t2.x || {}, this[p] = true; + } + __name(M2, "M"); + var m2 = M2.prototype; + return m2.parse = function(t2) { + this.$d = function(t3) { + var e2 = t3.date, n2 = t3.utc; + if (null === e2) + return /* @__PURE__ */ new Date(NaN); + if (b.u(e2)) + return /* @__PURE__ */ new Date(); + if (e2 instanceof Date) + return new Date(e2); + if ("string" == typeof e2 && !/Z$/i.test(e2)) { + var r2 = e2.match($); + if (r2) { + var i2 = r2[2] - 1 || 0, s2 = (r2[7] || "0").substring(0, 3); + return n2 ? new Date(Date.UTC(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2)) : new Date(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2); + } + } + return new Date(e2); + }(t2), this.init(); + }, m2.init = function() { + var t2 = this.$d; + this.$y = t2.getFullYear(), this.$M = t2.getMonth(), this.$D = t2.getDate(), this.$W = t2.getDay(), this.$H = t2.getHours(), this.$m = t2.getMinutes(), this.$s = t2.getSeconds(), this.$ms = t2.getMilliseconds(); + }, m2.$utils = function() { + return b; + }, m2.isValid = function() { + return !(this.$d.toString() === l); + }, m2.isSame = function(t2, e2) { + var n2 = O(t2); + return this.startOf(e2) <= n2 && n2 <= this.endOf(e2); + }, m2.isAfter = function(t2, e2) { + return O(t2) < this.startOf(e2); + }, m2.isBefore = function(t2, e2) { + return this.endOf(e2) < O(t2); + }, m2.$g = function(t2, e2, n2) { + return b.u(t2) ? this[e2] : this.set(n2, t2); + }, m2.unix = function() { + return Math.floor(this.valueOf() / 1e3); + }, m2.valueOf = function() { + return this.$d.getTime(); + }, m2.startOf = function(t2, e2) { + var n2 = this, r2 = !!b.u(e2) || e2, f2 = b.p(t2), l2 = /* @__PURE__ */ __name(function(t3, e3) { + var i2 = b.w(n2.$u ? Date.UTC(n2.$y, e3, t3) : new Date(n2.$y, e3, t3), n2); + return r2 ? i2 : i2.endOf(a); + }, "l"), $2 = /* @__PURE__ */ __name(function(t3, e3) { + return b.w(n2.toDate()[t3].apply(n2.toDate("s"), (r2 ? [0, 0, 0, 0] : [23, 59, 59, 999]).slice(e3)), n2); + }, "$"), y2 = this.$W, M3 = this.$M, m3 = this.$D, v2 = "set" + (this.$u ? "UTC" : ""); + switch (f2) { + case h: + return r2 ? l2(1, 0) : l2(31, 11); + case c: + return r2 ? l2(1, M3) : l2(0, M3 + 1); + case o: + var g2 = this.$locale().weekStart || 0, D2 = (y2 < g2 ? y2 + 7 : y2) - g2; + return l2(r2 ? m3 - D2 : m3 + (6 - D2), M3); + case a: + case d: + return $2(v2 + "Hours", 0); + case u: + return $2(v2 + "Minutes", 1); + case s: + return $2(v2 + "Seconds", 2); + case i: + return $2(v2 + "Milliseconds", 3); + default: + return this.clone(); + } + }, m2.endOf = function(t2) { + return this.startOf(t2, false); + }, m2.$set = function(t2, e2) { + var n2, o2 = b.p(t2), f2 = "set" + (this.$u ? "UTC" : ""), l2 = (n2 = {}, n2[a] = f2 + "Date", n2[d] = f2 + "Date", n2[c] = f2 + "Month", n2[h] = f2 + "FullYear", n2[u] = f2 + "Hours", n2[s] = f2 + "Minutes", n2[i] = f2 + "Seconds", n2[r] = f2 + "Milliseconds", n2)[o2], $2 = o2 === a ? this.$D + (e2 - this.$W) : e2; + if (o2 === c || o2 === h) { + var y2 = this.clone().set(d, 1); + y2.$d[l2]($2), y2.init(), this.$d = y2.set(d, Math.min(this.$D, y2.daysInMonth())).$d; + } else + l2 && this.$d[l2]($2); + return this.init(), this; + }, m2.set = function(t2, e2) { + return this.clone().$set(t2, e2); + }, m2.get = function(t2) { + return this[b.p(t2)](); + }, m2.add = function(r2, f2) { + var d2, l2 = this; + r2 = Number(r2); + var $2 = b.p(f2), y2 = /* @__PURE__ */ __name(function(t2) { + var e2 = O(l2); + return b.w(e2.date(e2.date() + Math.round(t2 * r2)), l2); + }, "y"); + if ($2 === c) + return this.set(c, this.$M + r2); + if ($2 === h) + return this.set(h, this.$y + r2); + if ($2 === a) + return y2(1); + if ($2 === o) + return y2(7); + var M3 = (d2 = {}, d2[s] = e, d2[u] = n, d2[i] = t, d2)[$2] || 1, m3 = this.$d.getTime() + r2 * M3; + return b.w(m3, this); + }, m2.subtract = function(t2, e2) { + return this.add(-1 * t2, e2); + }, m2.format = function(t2) { + var e2 = this, n2 = this.$locale(); + if (!this.isValid()) + return n2.invalidDate || l; + var r2 = t2 || "YYYY-MM-DDTHH:mm:ssZ", i2 = b.z(this), s2 = this.$H, u2 = this.$m, a2 = this.$M, o2 = n2.weekdays, c2 = n2.months, f2 = n2.meridiem, h2 = /* @__PURE__ */ __name(function(t3, n3, i3, s3) { + return t3 && (t3[n3] || t3(e2, r2)) || i3[n3].slice(0, s3); + }, "h"), d2 = /* @__PURE__ */ __name(function(t3) { + return b.s(s2 % 12 || 12, t3, "0"); + }, "d"), $2 = f2 || function(t3, e3, n3) { + var r3 = t3 < 12 ? "AM" : "PM"; + return n3 ? r3.toLowerCase() : r3; + }; + return r2.replace(y, function(t3, r3) { + return r3 || function(t4) { + switch (t4) { + case "YY": + return String(e2.$y).slice(-2); + case "YYYY": + return b.s(e2.$y, 4, "0"); + case "M": + return a2 + 1; + case "MM": + return b.s(a2 + 1, 2, "0"); + case "MMM": + return h2(n2.monthsShort, a2, c2, 3); + case "MMMM": + return h2(c2, a2); + case "D": + return e2.$D; + case "DD": + return b.s(e2.$D, 2, "0"); + case "d": + return String(e2.$W); + case "dd": + return h2(n2.weekdaysMin, e2.$W, o2, 2); + case "ddd": + return h2(n2.weekdaysShort, e2.$W, o2, 3); + case "dddd": + return o2[e2.$W]; + case "H": + return String(s2); + case "HH": + return b.s(s2, 2, "0"); + case "h": + return d2(1); + case "hh": + return d2(2); + case "a": + return $2(s2, u2, true); + case "A": + return $2(s2, u2, false); + case "m": + return String(u2); + case "mm": + return b.s(u2, 2, "0"); + case "s": + return String(e2.$s); + case "ss": + return b.s(e2.$s, 2, "0"); + case "SSS": + return b.s(e2.$ms, 3, "0"); + case "Z": + return i2; + } + return null; + }(t3) || i2.replace(":", ""); + }); + }, m2.utcOffset = function() { + return 15 * -Math.round(this.$d.getTimezoneOffset() / 15); + }, m2.diff = function(r2, d2, l2) { + var $2, y2 = this, M3 = b.p(d2), m3 = O(r2), v2 = (m3.utcOffset() - this.utcOffset()) * e, g2 = this - m3, D2 = /* @__PURE__ */ __name(function() { + return b.m(y2, m3); + }, "D"); + switch (M3) { + case h: + $2 = D2() / 12; + break; + case c: + $2 = D2(); + break; + case f: + $2 = D2() / 3; + break; + case o: + $2 = (g2 - v2) / 6048e5; + break; + case a: + $2 = (g2 - v2) / 864e5; + break; + case u: + $2 = g2 / n; + break; + case s: + $2 = g2 / e; + break; + case i: + $2 = g2 / t; + break; + default: + $2 = g2; + } + return l2 ? $2 : b.a($2); + }, m2.daysInMonth = function() { + return this.endOf(c).$D; + }, m2.$locale = function() { + return D[this.$L]; + }, m2.locale = function(t2, e2) { + if (!t2) + return this.$L; + var n2 = this.clone(), r2 = w(t2, e2, true); + return r2 && (n2.$L = r2), n2; + }, m2.clone = function() { + return b.w(this.$d, this); + }, m2.toDate = function() { + return new Date(this.valueOf()); + }, m2.toJSON = function() { + return this.isValid() ? this.toISOString() : null; + }, m2.toISOString = function() { + return this.$d.toISOString(); + }, m2.toString = function() { + return this.$d.toUTCString(); + }, M2; + }(), k = _.prototype; + return O.prototype = k, [["$ms", r], ["$s", i], ["$m", s], ["$H", u], ["$W", a], ["$M", c], ["$y", h], ["$D", d]].forEach(function(t2) { + k[t2[1]] = function(e2) { + return this.$g(e2, t2[0], t2[1]); + }; + }), O.extend = function(t2, e2) { + return t2.$i || (t2(e2, _, O), t2.$i = true), O; + }, O.locale = w, O.isDayjs = S, O.unix = function(t2) { + return O(1e3 * t2); + }, O.en = D[g], O.Ls = D, O.p = {}, O; + }); + } +}); + +// node_modules/dayjs/plugin/utc.js +var require_utc = __commonJS({ + "node_modules/dayjs/plugin/utc.js"(exports, module) { + !function(t, i) { + "object" == typeof exports && "undefined" != typeof module ? module.exports = i() : "function" == typeof define && define.amd ? define(i) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs_plugin_utc = i(); + }(exports, function() { + "use strict"; + var t = "minute", i = /[+-]\d\d(?::?\d\d)?/g, e = /([+-]|\d\d)/g; + return function(s, f, n) { + var u = f.prototype; + n.utc = function(t2) { + var i2 = { date: t2, utc: true, args: arguments }; + return new f(i2); + }, u.utc = function(i2) { + var e2 = n(this.toDate(), { locale: this.$L, utc: true }); + return i2 ? e2.add(this.utcOffset(), t) : e2; + }, u.local = function() { + return n(this.toDate(), { locale: this.$L, utc: false }); + }; + var r = u.parse; + u.parse = function(t2) { + t2.utc && (this.$u = true), this.$utils().u(t2.$offset) || (this.$offset = t2.$offset), r.call(this, t2); + }; + var o = u.init; + u.init = function() { + if (this.$u) { + var t2 = this.$d; + this.$y = t2.getUTCFullYear(), this.$M = t2.getUTCMonth(), this.$D = t2.getUTCDate(), this.$W = t2.getUTCDay(), this.$H = t2.getUTCHours(), this.$m = t2.getUTCMinutes(), this.$s = t2.getUTCSeconds(), this.$ms = t2.getUTCMilliseconds(); + } else + o.call(this); + }; + var a = u.utcOffset; + u.utcOffset = function(s2, f2) { + var n2 = this.$utils().u; + if (n2(s2)) + return this.$u ? 0 : n2(this.$offset) ? a.call(this) : this.$offset; + if ("string" == typeof s2 && (s2 = function(t2) { + void 0 === t2 && (t2 = ""); + var s3 = t2.match(i); + if (!s3) + return null; + var f3 = ("" + s3[0]).match(e) || ["-", 0, 0], n3 = f3[0], u3 = 60 * +f3[1] + +f3[2]; + return 0 === u3 ? 0 : "+" === n3 ? u3 : -u3; + }(s2), null === s2)) + return this; + var u2 = Math.abs(s2) <= 16 ? 60 * s2 : s2; + if (0 === u2) + return this.utc(f2); + var r2 = this.clone(); + if (f2) + return r2.$offset = u2, r2.$u = false, r2; + var o2 = this.$u ? this.toDate().getTimezoneOffset() : -1 * this.utcOffset(); + return (r2 = this.local().add(u2 + o2, t)).$offset = u2, r2.$x.$localOffset = o2, r2; + }; + var h = u.format; + u.format = function(t2) { + var i2 = t2 || (this.$u ? "YYYY-MM-DDTHH:mm:ss[Z]" : ""); + return h.call(this, i2); + }, u.valueOf = function() { + var t2 = this.$utils().u(this.$offset) ? 0 : this.$offset + (this.$x.$localOffset || this.$d.getTimezoneOffset()); + return this.$d.valueOf() - 6e4 * t2; + }, u.isUTC = function() { + return !!this.$u; + }, u.toISOString = function() { + return this.toDate().toISOString(); + }, u.toString = function() { + return this.toDate().toUTCString(); + }; + var l = u.toDate; + u.toDate = function(t2) { + return "s" === t2 && this.$offset ? n(this.format("YYYY-MM-DD HH:mm:ss:SSS")).toDate() : l.call(this); + }; + var c = u.diff; + u.diff = function(t2, i2, e2) { + if (t2 && this.$u === t2.$u) + return c.call(this, t2, i2, e2); + var s2 = this.local(), f2 = n(t2).local(); + return c.call(s2, f2, i2, e2); + }; + }; + }); + } +}); + +// node_modules/dayjs/plugin/timezone.js +var require_timezone = __commonJS({ + "node_modules/dayjs/plugin/timezone.js"(exports, module) { + !function(t, e) { + "object" == typeof exports && "undefined" != typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define(e) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs_plugin_timezone = e(); + }(exports, function() { + "use strict"; + var t = { year: 0, month: 1, day: 2, hour: 3, minute: 4, second: 5 }, e = {}; + return function(n, i, o) { + var r, a = /* @__PURE__ */ __name(function(t2, n2, i2) { + void 0 === i2 && (i2 = {}); + var o2 = new Date(t2), r2 = function(t3, n3) { + void 0 === n3 && (n3 = {}); + var i3 = n3.timeZoneName || "short", o3 = t3 + "|" + i3, r3 = e[o3]; + return r3 || (r3 = new Intl.DateTimeFormat("en-US", { hour12: false, timeZone: t3, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", timeZoneName: i3 }), e[o3] = r3), r3; + }(n2, i2); + return r2.formatToParts(o2); + }, "a"), u = /* @__PURE__ */ __name(function(e2, n2) { + for (var i2 = a(e2, n2), r2 = [], u2 = 0; u2 < i2.length; u2 += 1) { + var f2 = i2[u2], s2 = f2.type, m = f2.value, c = t[s2]; + c >= 0 && (r2[c] = parseInt(m, 10)); + } + var d = r2[3], l = 24 === d ? 0 : d, h = r2[0] + "-" + r2[1] + "-" + r2[2] + " " + l + ":" + r2[4] + ":" + r2[5] + ":000", v = +e2; + return (o.utc(h).valueOf() - (v -= v % 1e3)) / 6e4; + }, "u"), f = i.prototype; + f.tz = function(t2, e2) { + void 0 === t2 && (t2 = r); + var n2, i2 = this.utcOffset(), a2 = this.toDate(), u2 = a2.toLocaleString("en-US", { timeZone: t2 }), f2 = Math.round((a2 - new Date(u2)) / 1e3 / 60), s2 = 15 * -Math.round(a2.getTimezoneOffset() / 15) - f2; + if (!Number(s2)) + n2 = this.utcOffset(0, e2); + else if (n2 = o(u2, { locale: this.$L }).$set("millisecond", this.$ms).utcOffset(s2, true), e2) { + var m = n2.utcOffset(); + n2 = n2.add(i2 - m, "minute"); + } + return n2.$x.$timezone = t2, n2; + }, f.offsetName = function(t2) { + var e2 = this.$x.$timezone || o.tz.guess(), n2 = a(this.valueOf(), e2, { timeZoneName: t2 }).find(function(t3) { + return "timezonename" === t3.type.toLowerCase(); + }); + return n2 && n2.value; + }; + var s = f.startOf; + f.startOf = function(t2, e2) { + if (!this.$x || !this.$x.$timezone) + return s.call(this, t2, e2); + var n2 = o(this.format("YYYY-MM-DD HH:mm:ss:SSS"), { locale: this.$L }); + return s.call(n2, t2, e2).tz(this.$x.$timezone, true); + }, o.tz = function(t2, e2, n2) { + var i2 = n2 && e2, a2 = n2 || e2 || r, f2 = u(+o(), a2); + if ("string" != typeof t2) + return o(t2).tz(a2); + var s2 = function(t3, e3, n3) { + var i3 = t3 - 60 * e3 * 1e3, o2 = u(i3, n3); + if (e3 === o2) + return [i3, e3]; + var r2 = u(i3 -= 60 * (o2 - e3) * 1e3, n3); + return o2 === r2 ? [i3, o2] : [t3 - 60 * Math.min(o2, r2) * 1e3, Math.max(o2, r2)]; + }(o.utc(t2, i2).valueOf(), f2, a2), m = s2[0], c = s2[1], d = o(m).utcOffset(c); + return d.$x.$timezone = a2, d; + }, o.tz.guess = function() { + return Intl.DateTimeFormat().resolvedOptions().timeZone; + }, o.tz.setDefault = function(t2) { + r = t2; + }; + }; + }); + } +}); + +// node_modules/dayjs/plugin/isoWeek.js +var require_isoWeek = __commonJS({ + "node_modules/dayjs/plugin/isoWeek.js"(exports, module) { + !function(e, t) { + "object" == typeof exports && "undefined" != typeof module ? module.exports = t() : "function" == typeof define && define.amd ? define(t) : (e = "undefined" != typeof globalThis ? globalThis : e || self).dayjs_plugin_isoWeek = t(); + }(exports, function() { + "use strict"; + var e = "day"; + return function(t, i, s) { + var a = /* @__PURE__ */ __name(function(t2) { + return t2.add(4 - t2.isoWeekday(), e); + }, "a"), d = i.prototype; + d.isoWeekYear = function() { + return a(this).year(); + }, d.isoWeek = function(t2) { + if (!this.$utils().u(t2)) + return this.add(7 * (t2 - this.isoWeek()), e); + var i2, d2, n2, o, r = a(this), u = (i2 = this.isoWeekYear(), d2 = this.$u, n2 = (d2 ? s.utc : s)().year(i2).startOf("year"), o = 4 - n2.isoWeekday(), n2.isoWeekday() > 4 && (o += 7), n2.add(o, e)); + return r.diff(u, "week") + 1; + }, d.isoWeekday = function(e2) { + return this.$utils().u(e2) ? this.day() || 7 : this.day(this.day() % 7 ? e2 : e2 - 7); + }; + var n = d.startOf; + d.startOf = function(e2, t2) { + var i2 = this.$utils(), s2 = !!i2.u(t2) || t2; + return "isoweek" === i2.p(e2) ? s2 ? this.date(this.date() - (this.isoWeekday() - 1)).startOf("day") : this.date(this.date() - 1 - (this.isoWeekday() - 1) + 7).endOf("day") : n.bind(this)(e2, t2); + }; + }; + }); + } +}); + +// node_modules/@novadi/core/dist/token.js +var tokenCounter = 0; +function Token(description) { + const id = ++tokenCounter; + const sym = Symbol(description ? `Token(${description})` : `Token#${id}`); + const token2 = { + symbol: sym, + description, + toString() { + return description ? `Token<${description}>` : `Token<#${id}>`; + } + }; + return token2; +} +__name(Token, "Token"); + +// node_modules/@novadi/core/dist/errors.js +var _ContainerError = class _ContainerError extends Error { + constructor(message) { + super(message); + this.name = "ContainerError"; + } +}; +__name(_ContainerError, "ContainerError"); +var ContainerError = _ContainerError; +var _BindingNotFoundError = class _BindingNotFoundError extends ContainerError { + constructor(tokenDescription, path = []) { + const pathStr = path.length > 0 ? ` + Dependency path: ${path.join(" -> ")}` : ""; + super(`Token "${tokenDescription}" is not bound or registered in the container.${pathStr}`); + this.name = "BindingNotFoundError"; + } +}; +__name(_BindingNotFoundError, "BindingNotFoundError"); +var BindingNotFoundError = _BindingNotFoundError; +var _CircularDependencyError = class _CircularDependencyError extends ContainerError { + constructor(path) { + super(`Circular dependency detected: ${path.join(" -> ")}`); + this.name = "CircularDependencyError"; + } +}; +__name(_CircularDependencyError, "CircularDependencyError"); +var CircularDependencyError = _CircularDependencyError; + +// node_modules/@novadi/core/dist/autowire.js +var paramNameCache = /* @__PURE__ */ new WeakMap(); +function extractParameterNames(constructor) { + const cached = paramNameCache.get(constructor); + if (cached) { + return cached; + } + const fnStr = constructor.toString(); + const match = fnStr.match(/constructor\s*\(([^)]*)\)/) || fnStr.match(/^[^(]*\(([^)]*)\)/); + if (!match || !match[1]) { + return []; + } + const params = match[1].split(",").map((param) => param.trim()).filter((param) => param.length > 0).map((param) => { + let name = param.split(/[:=]/)[0].trim(); + name = name.replace(/^((public|private|protected|readonly)\s+)+/, ""); + if (name.includes("{") || name.includes("[")) { + return null; + } + return name; + }).filter((name) => name !== null); + paramNameCache.set(constructor, params); + return params; +} +__name(extractParameterNames, "extractParameterNames"); +function resolveByMap(constructor, container2, options) { + if (!options.map) { + throw new Error("AutoWire map strategy requires options.map to be defined"); + } + const paramNames = extractParameterNames(constructor); + const resolvedDeps = []; + for (const paramName of paramNames) { + const resolver = options.map[paramName]; + if (resolver === void 0) { + if (options.strict) { + throw new Error(`Cannot resolve parameter "${paramName}" on ${constructor.name}. Not found in autowire map. Add it to the map: .autoWire({ map: { ${paramName}: ... } })`); + } else { + resolvedDeps.push(void 0); + } + continue; + } + if (typeof resolver === "function") { + resolvedDeps.push(resolver(container2)); + } else { + resolvedDeps.push(container2.resolve(resolver)); + } + } + return resolvedDeps; +} +__name(resolveByMap, "resolveByMap"); +function resolveByMapResolvers(_constructor, container2, options) { + if (!options.mapResolvers || options.mapResolvers.length === 0) { + return []; + } + const resolvedDeps = []; + for (let i = 0; i < options.mapResolvers.length; i++) { + const resolver = options.mapResolvers[i]; + if (resolver === void 0) { + resolvedDeps.push(void 0); + } else if (typeof resolver === "function") { + resolvedDeps.push(resolver(container2)); + } else { + resolvedDeps.push(container2.resolve(resolver)); + } + } + return resolvedDeps; +} +__name(resolveByMapResolvers, "resolveByMapResolvers"); +function autowire(constructor, container2, options) { + const opts = { + by: "paramName", + strict: false, + ...options + }; + if (opts.mapResolvers && opts.mapResolvers.length > 0) { + return resolveByMapResolvers(constructor, container2, opts); + } + if (opts.map && Object.keys(opts.map).length > 0) { + return resolveByMap(constructor, container2, opts); + } + return []; +} +__name(autowire, "autowire"); + +// node_modules/@novadi/core/dist/builder.js +var _RegistrationBuilder = class _RegistrationBuilder { + constructor(pending, registrations) { + this.registrations = registrations; + this.configs = []; + this.defaultLifetime = "singleton"; + this.pending = pending; + } + /** + * Bind this registration to a token or interface type + * + * @overload + * @param {Token} token - Explicit token for binding + * + * @overload + * @param {string} typeName - Interface type name (auto-generated by transformer) + */ + as(tokenOrTypeName) { + if (tokenOrTypeName && typeof tokenOrTypeName === "object" && "symbol" in tokenOrTypeName) { + const config = { + token: tokenOrTypeName, + type: this.pending.type, + value: this.pending.value, + factory: this.pending.factory, + constructor: this.pending.constructor, + lifetime: this.defaultLifetime + }; + this.configs.push(config); + this.registrations.push(config); + return this; + } else { + const config = { + token: null, + // Will be set during build() + type: this.pending.type, + value: this.pending.value, + factory: this.pending.factory, + constructor: this.pending.constructor, + lifetime: this.defaultLifetime, + interfaceType: tokenOrTypeName + }; + this.configs.push(config); + this.registrations.push(config); + return this; + } + } + /** + * Register as default implementation for an interface + * Combines as() + asDefault() + */ + asDefaultInterface(typeName) { + this.as("TInterface", typeName); + return this.asDefault(); + } + /** + * Register as a keyed interface implementation + * Combines as() + keyed() + */ + asKeyedInterface(key, typeName) { + this.as("TInterface", typeName); + return this.keyed(key); + } + /** + * Register as multiple implemented interfaces + */ + asImplementedInterfaces(tokens) { + if (tokens.length === 0) { + return this; + } + if (this.configs.length > 0) { + for (const config of this.configs) { + config.lifetime = "singleton"; + config.additionalTokens = config.additionalTokens || []; + config.additionalTokens.push(...tokens); + } + return this; + } + const firstConfig = { + token: tokens[0], + type: this.pending.type, + value: this.pending.value, + factory: this.pending.factory, + constructor: this.pending.constructor, + lifetime: "singleton" + }; + this.configs.push(firstConfig); + this.registrations.push(firstConfig); + for (let i = 1; i < tokens.length; i++) { + firstConfig.additionalTokens = firstConfig.additionalTokens || []; + firstConfig.additionalTokens.push(tokens[i]); + } + return this; + } + /** + * Set singleton lifetime (one instance for entire container) + */ + singleInstance() { + for (const config of this.configs) { + config.lifetime = "singleton"; + } + return this; + } + /** + * Set per-request lifetime (one instance per resolve call tree) + */ + instancePerRequest() { + for (const config of this.configs) { + config.lifetime = "per-request"; + } + return this; + } + /** + * Set transient lifetime (new instance every time) + * Alias for default behavior + */ + instancePerDependency() { + for (const config of this.configs) { + config.lifetime = "transient"; + } + return this; + } + /** + * Name this registration for named resolution + */ + named(name) { + for (const config of this.configs) { + config.name = name; + } + return this; + } + /** + * Key this registration for keyed resolution + */ + keyed(key) { + for (const config of this.configs) { + config.key = key; + } + return this; + } + /** + * Mark this as default registration + * Default registrations don't override existing ones + */ + asDefault() { + for (const config of this.configs) { + config.isDefault = true; + } + return this; + } + /** + * Only register if token not already registered + */ + ifNotRegistered() { + for (const config of this.configs) { + config.ifNotRegistered = true; + } + return this; + } + /** + * Specify parameter values for constructor (primitives and constants) + * Use this for non-DI parameters like strings, numbers, config values + */ + withParameters(parameters) { + for (const config of this.configs) { + config.parameterValues = parameters; + } + return this; + } + /** + * Enable automatic dependency injection (autowiring) + * Supports three strategies: paramName (default), map, and class + * + * @example + * ```ts + * // Strategy 1: paramName (default, requires non-minified code in dev) + * builder.registerType(EventBus).as().autoWire() + * + * // Strategy 2: map (minify-safe, explicit) + * builder.registerType(EventBus).as().autoWire({ + * map: { + * logger: (c) => c.resolveType() + * } + * }) + * + * // Strategy 3: class (requires build-time codegen) + * builder.registerType(EventBus).as().autoWire({ by: 'class' }) + * ``` + */ + autoWire(options) { + for (const config of this.configs) { + config.autowireOptions = options || { by: "paramName", strict: false }; + } + return this; + } +}; +__name(_RegistrationBuilder, "RegistrationBuilder"); +var RegistrationBuilder = _RegistrationBuilder; +var _Builder = class _Builder { + constructor(baseContainer) { + this.baseContainer = baseContainer; + this.registrations = []; + } + /** + * Register a class constructor + */ + registerType(constructor) { + const pending = { + type: "type", + value: null, + constructor + }; + return new RegistrationBuilder(pending, this.registrations); + } + /** + * Register a pre-created instance + */ + registerInstance(instance) { + const pending = { + type: "instance", + value: instance, + constructor: void 0 + }; + return new RegistrationBuilder(pending, this.registrations); + } + /** + * Register a factory function + */ + register(factory) { + const pending = { + type: "factory", + value: null, + factory, + constructor: void 0 + }; + return new RegistrationBuilder(pending, this.registrations); + } + /** + * Register a module (function that adds multiple registrations) + */ + module(moduleFunc) { + moduleFunc(this); + return this; + } + /** + * Resolve interface type names to tokens + * @internal + */ + resolveInterfaceTokens(container2) { + for (const config of this.registrations) { + if (config.interfaceType !== void 0 && !config.token) { + config.token = container2.interfaceToken(config.interfaceType); + } + } + } + /** + * Identify tokens that have non-default registrations + * @internal + */ + identifyNonDefaultTokens() { + const tokensWithNonDefaults = /* @__PURE__ */ new Set(); + for (const config of this.registrations) { + if (!config.isDefault && !config.name && config.key === void 0) { + tokensWithNonDefaults.add(config.token); + } + } + return tokensWithNonDefaults; + } + /** + * Check if registration should be skipped + * @internal + */ + shouldSkipRegistration(config, tokensWithNonDefaults, registeredTokens) { + if (config.isDefault && !config.name && config.key === void 0 && tokensWithNonDefaults.has(config.token)) { + return true; + } + if (config.ifNotRegistered && registeredTokens.has(config.token)) { + return true; + } + if (config.isDefault && registeredTokens.has(config.token)) { + return true; + } + return false; + } + /** + * Create binding token for registration (named, keyed, or multi) + * @internal + */ + createBindingToken(config, namedRegistrations, keyedRegistrations, multiRegistrations) { + if (config.name) { + const bindingToken = Token(`__named_${config.name}`); + namedRegistrations.set(config.name, { ...config, token: bindingToken }); + return bindingToken; + } else if (config.key !== void 0) { + const keyStr = typeof config.key === "symbol" ? config.key.toString() : config.key; + const bindingToken = Token(`__keyed_${keyStr}`); + keyedRegistrations.set(config.key, { ...config, token: bindingToken }); + return bindingToken; + } else { + if (multiRegistrations.has(config.token)) { + const bindingToken = Token(`__multi_${config.token.toString()}_${multiRegistrations.get(config.token).length}`); + multiRegistrations.get(config.token).push(bindingToken); + return bindingToken; + } else { + multiRegistrations.set(config.token, [config.token]); + return config.token; + } + } + } + /** + * Register additional interfaces for a config + * @internal + */ + registerAdditionalInterfaces(container2, config, bindingToken, registeredTokens) { + if (config.additionalTokens) { + for (const additionalToken of config.additionalTokens) { + container2.bindFactory(additionalToken, (c) => c.resolve(bindingToken), { lifetime: config.lifetime }); + registeredTokens.add(additionalToken); + } + } + } + /** + * Build the container with all registered bindings + */ + build() { + const container2 = this.baseContainer.createChild(); + this.resolveInterfaceTokens(container2); + const registeredTokens = /* @__PURE__ */ new Set(); + const namedRegistrations = /* @__PURE__ */ new Map(); + const keyedRegistrations = /* @__PURE__ */ new Map(); + const multiRegistrations = /* @__PURE__ */ new Map(); + const tokensWithNonDefaults = this.identifyNonDefaultTokens(); + for (const config of this.registrations) { + if (this.shouldSkipRegistration(config, tokensWithNonDefaults, registeredTokens)) { + continue; + } + const bindingToken = this.createBindingToken(config, namedRegistrations, keyedRegistrations, multiRegistrations); + this.applyRegistration(container2, { ...config, token: bindingToken }); + registeredTokens.add(config.token); + this.registerAdditionalInterfaces(container2, config, bindingToken, registeredTokens); + } + ; + container2.__namedRegistrations = namedRegistrations; + container2.__keyedRegistrations = keyedRegistrations; + container2.__multiRegistrations = multiRegistrations; + return container2; + } + /** + * Analyze constructor to detect dependencies + * @internal + */ + analyzeConstructor(constructor) { + const constructorStr = constructor.toString(); + const hasDependencies = /constructor\s*\([^)]+\)/.test(constructorStr); + return { hasDependencies }; + } + /** + * Create optimized factory for zero-dependency constructors + * @internal + */ + createOptimizedFactory(container2, config, options) { + if (config.lifetime === "singleton") { + const instance = new config.constructor(); + container2.bindValue(config.token, instance); + } else if (config.lifetime === "transient") { + const ctor = config.constructor; + const fastFactory = /* @__PURE__ */ __name(() => new ctor(), "fastFactory"); + container2.fastTransientCache.set(config.token, fastFactory); + container2.bindFactory(config.token, fastFactory, options); + } else { + const factory = /* @__PURE__ */ __name(() => new config.constructor(), "factory"); + container2.bindFactory(config.token, factory, options); + } + } + /** + * Create autowire factory + * @internal + */ + createAutoWireFactory(container2, config, options) { + const factory = /* @__PURE__ */ __name((c) => { + const resolvedDeps = autowire(config.constructor, c, config.autowireOptions); + return new config.constructor(...resolvedDeps); + }, "factory"); + container2.bindFactory(config.token, factory, options); + } + /** + * Create withParameters factory + * @internal + */ + createParameterFactory(container2, config, options) { + const factory = /* @__PURE__ */ __name(() => { + const values = Object.values(config.parameterValues); + return new config.constructor(...values); + }, "factory"); + container2.bindFactory(config.token, factory, options); + } + /** + * Apply type registration (class constructor) + * @internal + */ + applyTypeRegistration(container2, config, options) { + const { hasDependencies } = this.analyzeConstructor(config.constructor); + if (!hasDependencies && !config.autowireOptions && !config.parameterValues) { + this.createOptimizedFactory(container2, config, options); + return; + } + if (config.autowireOptions) { + this.createAutoWireFactory(container2, config, options); + return; + } + if (config.parameterValues) { + this.createParameterFactory(container2, config, options); + return; + } + if (hasDependencies) { + const className = config.constructor.name || "UnnamedClass"; + throw new Error(`Service "${className}" has constructor dependencies but no autowiring configuration. + +Solutions: + 1. \u2B50 Use the NovaDI transformer (recommended): + - Add "@novadi/core/unplugin" to your build config + - Transformer automatically generates .autoWire() for all dependencies + + 2. Add manual autowiring: + .autoWire({ map: { /* param: resolver */ } }) + + 3. Use a factory function: + .register((c) => new ${className}(...)) + +See docs: https://github.com/janus007/NovaDI#autowire`); + } + const factory = /* @__PURE__ */ __name(() => new config.constructor(), "factory"); + container2.bindFactory(config.token, factory, options); + } + applyRegistration(container2, config) { + const options = { lifetime: config.lifetime }; + switch (config.type) { + case "instance": + container2.bindValue(config.token, config.value); + break; + case "factory": + container2.bindFactory(config.token, config.factory, options); + break; + case "type": + this.applyTypeRegistration(container2, config, options); + break; + } + } +}; +__name(_Builder, "Builder"); +var Builder = _Builder; + +// node_modules/@novadi/core/dist/container.js +function isDisposable(obj) { + return obj && typeof obj.dispose === "function"; +} +__name(isDisposable, "isDisposable"); +var _ResolutionContext = class _ResolutionContext { + constructor() { + this.resolvingStack = /* @__PURE__ */ new Set(); + this.perRequestCache = /* @__PURE__ */ new Map(); + } + isResolving(token2) { + return this.resolvingStack.has(token2); + } + enterResolve(token2) { + this.resolvingStack.add(token2); + } + exitResolve(token2) { + this.resolvingStack.delete(token2); + this.path = void 0; + } + getPath() { + if (!this.path) { + this.path = Array.from(this.resolvingStack).map((t) => t.toString()); + } + return [...this.path]; + } + cachePerRequest(token2, instance) { + this.perRequestCache.set(token2, instance); + } + getPerRequest(token2) { + return this.perRequestCache.get(token2); + } + hasPerRequest(token2) { + return this.perRequestCache.has(token2); + } + /** + * Reset context for reuse in object pool + * Performance: Reusing contexts avoids heap allocations + */ + reset() { + this.resolvingStack.clear(); + this.perRequestCache.clear(); + this.path = void 0; + } +}; +__name(_ResolutionContext, "ResolutionContext"); +var ResolutionContext = _ResolutionContext; +var _ResolutionContextPool = class _ResolutionContextPool { + constructor() { + this.pool = []; + this.maxSize = 10; + } + acquire() { + const context = this.pool.pop(); + if (context) { + context.reset(); + return context; + } + return new ResolutionContext(); + } + release(context) { + if (this.pool.length < this.maxSize) { + this.pool.push(context); + } + } +}; +__name(_ResolutionContextPool, "ResolutionContextPool"); +var ResolutionContextPool = _ResolutionContextPool; +var _Container = class _Container { + constructor(parent) { + this.bindings = /* @__PURE__ */ new Map(); + this.singletonCache = /* @__PURE__ */ new Map(); + this.singletonOrder = []; + this.interfaceRegistry = /* @__PURE__ */ new Map(); + this.interfaceTokenCache = /* @__PURE__ */ new Map(); + this.fastTransientCache = /* @__PURE__ */ new Map(); + this.ultraFastSingletonCache = /* @__PURE__ */ new Map(); + this.parent = parent; + } + /** + * Bind a pre-created value to a token + */ + bindValue(token2, value) { + this.bindings.set(token2, { + type: "value", + lifetime: "singleton", + value, + constructor: void 0 + }); + this.invalidateBindingCache(); + } + /** + * Bind a factory function to a token + */ + bindFactory(token2, factory, options) { + this.bindings.set(token2, { + type: "factory", + lifetime: options?.lifetime || "transient", + factory, + dependencies: options?.dependencies, + constructor: void 0 + }); + this.invalidateBindingCache(); + } + /** + * Bind a class constructor to a token + */ + bindClass(token2, constructor, options) { + const binding = { + type: "class", + lifetime: options?.lifetime || "transient", + constructor, + dependencies: options?.dependencies + }; + this.bindings.set(token2, binding); + this.invalidateBindingCache(); + if (binding.lifetime === "transient" && (!binding.dependencies || binding.dependencies.length === 0)) { + this.fastTransientCache.set(token2, () => new constructor()); + } + } + /** + * Resolve a dependency synchronously + * Performance optimized with multiple fast paths + */ + resolve(token2) { + const cached = this.tryGetFromCaches(token2); + if (cached !== void 0) { + return cached; + } + if (this.currentContext) { + return this.resolveWithContext(token2, this.currentContext); + } + const context = _Container.contextPool.acquire(); + this.currentContext = context; + try { + return this.resolveWithContext(token2, context); + } finally { + this.currentContext = void 0; + _Container.contextPool.release(context); + } + } + /** + * SPECIALIZED: Ultra-fast singleton resolve (no safety checks) + * Use ONLY when you're 100% sure the token is a registered singleton + * @internal For performance-critical paths only + */ + resolveSingletonUnsafe(token2) { + return this.ultraFastSingletonCache.get(token2) ?? this.singletonCache.get(token2); + } + /** + * SPECIALIZED: Fast transient resolve for zero-dependency classes + * Skips all context creation and circular dependency checks + * @internal For performance-critical paths only + */ + resolveTransientSimple(token2) { + const factory = this.fastTransientCache.get(token2); + if (factory) { + return factory(); + } + return this.resolve(token2); + } + /** + * SPECIALIZED: Batch resolve multiple dependencies at once + * More efficient than multiple individual resolves + */ + resolveBatch(tokens) { + const wasResolving = !!this.currentContext; + const context = this.currentContext || _Container.contextPool.acquire(); + if (!wasResolving) { + this.currentContext = context; + } + try { + const results = tokens.map((token2) => { + const cached = this.tryGetFromCaches(token2); + if (cached !== void 0) + return cached; + return this.resolveWithContext(token2, context); + }); + return results; + } finally { + if (!wasResolving) { + this.currentContext = void 0; + _Container.contextPool.release(context); + } + } + } + /** + * Resolve a dependency asynchronously (supports async factories) + */ + async resolveAsync(token2) { + if (this.currentContext) { + return this.resolveAsyncWithContext(token2, this.currentContext); + } + const context = _Container.contextPool.acquire(); + this.currentContext = context; + try { + return await this.resolveAsyncWithContext(token2, context); + } finally { + this.currentContext = void 0; + _Container.contextPool.release(context); + } + } + /** + * Try to get instance from all cache levels + * Returns undefined if not cached + * @internal + */ + tryGetFromCaches(token2) { + const ultraFast = this.ultraFastSingletonCache.get(token2); + if (ultraFast !== void 0) { + return ultraFast; + } + if (this.singletonCache.has(token2)) { + const cached = this.singletonCache.get(token2); + this.ultraFastSingletonCache.set(token2, cached); + return cached; + } + const fastFactory = this.fastTransientCache.get(token2); + if (fastFactory) { + return fastFactory(); + } + return void 0; + } + /** + * Cache instance based on lifetime strategy + * @internal + */ + cacheInstance(token2, instance, lifetime, context) { + if (lifetime === "singleton") { + this.singletonCache.set(token2, instance); + this.singletonOrder.push(token2); + this.ultraFastSingletonCache.set(token2, instance); + } else if (lifetime === "per-request" && context) { + context.cachePerRequest(token2, instance); + } + } + /** + * Validate and get binding with circular dependency check + * Returns binding or throws error + * @internal + */ + validateAndGetBinding(token2, context) { + if (context.isResolving(token2)) { + throw new CircularDependencyError([...context.getPath(), token2.toString()]); + } + const binding = this.getBinding(token2); + if (!binding) { + throw new BindingNotFoundError(token2.toString(), context.getPath()); + } + return binding; + } + /** + * Instantiate from binding synchronously + * @internal + */ + instantiateBindingSync(binding, token2, context) { + switch (binding.type) { + case "value": + return binding.value; + case "factory": + const result = binding.factory(this); + if (result instanceof Promise) { + throw new Error(`Async factory detected for ${token2.toString()}. Use resolveAsync() instead.`); + } + return result; + case "class": + const deps = binding.dependencies || []; + const resolvedDeps = deps.map((dep) => this.resolveWithContext(dep, context)); + return new binding.constructor(...resolvedDeps); + case "inline-class": + return new binding.constructor(); + default: + throw new Error(`Unknown binding type: ${binding.type}`); + } + } + /** + * Instantiate from binding asynchronously + * @internal + */ + async instantiateBindingAsync(binding, context) { + switch (binding.type) { + case "value": + return binding.value; + case "factory": + return await Promise.resolve(binding.factory(this)); + case "class": + const deps = binding.dependencies || []; + const resolvedDeps = await Promise.all(deps.map((dep) => this.resolveAsyncWithContext(dep, context))); + return new binding.constructor(...resolvedDeps); + case "inline-class": + return new binding.constructor(); + default: + throw new Error(`Unknown binding type: ${binding.type}`); + } + } + /** + * Create a child container that inherits bindings from this container + */ + createChild() { + return new _Container(this); + } + /** + * Dispose all singleton instances in reverse registration order + */ + async dispose() { + const errors = []; + for (let i = this.singletonOrder.length - 1; i >= 0; i--) { + const token2 = this.singletonOrder[i]; + const instance = this.singletonCache.get(token2); + if (instance && isDisposable(instance)) { + try { + await instance.dispose(); + } catch (error) { + errors.push(error); + } + } + } + this.singletonCache.clear(); + this.singletonOrder.length = 0; + } + /** + * Create a fluent builder for registering dependencies + */ + builder() { + return new Builder(this); + } + /** + * Resolve a named service + */ + resolveNamed(name) { + const namedRegistrations = this.__namedRegistrations; + if (!namedRegistrations) { + throw new Error(`Named service "${name}" not found. No named registrations exist.`); + } + const config = namedRegistrations.get(name); + if (!config) { + throw new Error(`Named service "${name}" not found`); + } + return this.resolve(config.token); + } + /** + * Resolve a keyed service + */ + resolveKeyed(key) { + const keyedRegistrations = this.__keyedRegistrations; + if (!keyedRegistrations) { + throw new Error(`Keyed service not found. No keyed registrations exist.`); + } + const config = keyedRegistrations.get(key); + if (!config) { + const keyStr = typeof key === "symbol" ? key.toString() : `"${key}"`; + throw new Error(`Keyed service ${keyStr} not found`); + } + return this.resolve(config.token); + } + /** + * Resolve all registrations for a token + */ + resolveAll(token2) { + const multiRegistrations = this.__multiRegistrations; + if (!multiRegistrations) { + return []; + } + const tokens = multiRegistrations.get(token2); + if (!tokens || tokens.length === 0) { + return []; + } + return tokens.map((t) => this.resolve(t)); + } + /** + * Get registry information for debugging/visualization + * Returns array of binding information + */ + getRegistry() { + const registry = []; + this.bindings.forEach((binding, token2) => { + registry.push({ + token: token2.description || token2.symbol.toString(), + type: binding.type, + lifetime: binding.lifetime, + dependencies: binding.dependencies?.map((d) => d.description || d.symbol.toString()) + }); + }); + return registry; + } + /** + * Get or create a token for an interface type + * Uses a type name hash as key for the interface registry + */ + interfaceToken(typeName) { + const key = typeName || `Interface_${Math.random().toString(36).substr(2, 9)}`; + if (this.interfaceRegistry.has(key)) { + return this.interfaceRegistry.get(key); + } + if (this.parent) { + const parentToken = this.parent.interfaceToken(key); + return parentToken; + } + const token2 = Token(key); + this.interfaceRegistry.set(key, token2); + return token2; + } + /** + * Resolve a dependency by interface type without explicit token + */ + resolveType(typeName) { + const key = typeName || ""; + let token2 = this.interfaceTokenCache.get(key); + if (!token2) { + token2 = this.interfaceToken(typeName); + this.interfaceTokenCache.set(key, token2); + } + return this.resolve(token2); + } + /** + * Resolve a keyed interface + */ + resolveTypeKeyed(key, _typeName) { + return this.resolveKeyed(key); + } + /** + * Resolve all registrations for an interface type + */ + resolveTypeAll(typeName) { + const token2 = this.interfaceToken(typeName); + return this.resolveAll(token2); + } + /** + * Internal: Resolve with context for circular dependency detection + */ + resolveWithContext(token2, context) { + const binding = this.validateAndGetBinding(token2, context); + if (binding.lifetime === "per-request" && context.hasPerRequest(token2)) { + return context.getPerRequest(token2); + } + if (binding.lifetime === "singleton" && this.singletonCache.has(token2)) { + return this.singletonCache.get(token2); + } + context.enterResolve(token2); + try { + const instance = this.instantiateBindingSync(binding, token2, context); + this.cacheInstance(token2, instance, binding.lifetime, context); + return instance; + } finally { + context.exitResolve(token2); + } + } + /** + * Internal: Async resolve with context + */ + async resolveAsyncWithContext(token2, context) { + const binding = this.validateAndGetBinding(token2, context); + if (binding.lifetime === "per-request" && context.hasPerRequest(token2)) { + return context.getPerRequest(token2); + } + if (binding.lifetime === "singleton" && this.singletonCache.has(token2)) { + return this.singletonCache.get(token2); + } + context.enterResolve(token2); + try { + const instance = await this.instantiateBindingAsync(binding, context); + this.cacheInstance(token2, instance, binding.lifetime, context); + return instance; + } finally { + context.exitResolve(token2); + } + } + /** + * Get binding from this container or parent chain + * Performance optimized: Uses flat cache to avoid recursive parent lookups + */ + getBinding(token2) { + if (!this.bindingCache) { + this.buildBindingCache(); + } + return this.bindingCache.get(token2); + } + /** + * Build flat cache of all bindings including parent chain + * This converts O(n) parent chain traversal to O(1) lookup + */ + buildBindingCache() { + this.bindingCache = /* @__PURE__ */ new Map(); + let current = this; + while (current) { + current.bindings.forEach((binding, token2) => { + if (!this.bindingCache.has(token2)) { + this.bindingCache.set(token2, binding); + } + }); + current = current.parent; + } + } + /** + * Invalidate binding cache when new bindings are added + * Called by bindValue, bindFactory, bindClass + */ + invalidateBindingCache() { + this.bindingCache = void 0; + this.ultraFastSingletonCache.clear(); + } +}; +__name(_Container, "Container"); +var Container = _Container; +Container.contextPool = new ResolutionContextPool(); + +// src/features/date/DateRenderer.ts +var _DateRenderer = class _DateRenderer { + constructor(dateService) { + this.dateService = dateService; + this.type = "date"; + } + render(context) { + const dates = context.filter["date"] || []; + const resourceIds = context.filter["resource"] || []; + const dateGrouping = context.groupings?.find((g) => g.type === "date"); + const hideHeader = dateGrouping?.hideHeader === true; + const iterations = resourceIds.length || 1; + let columnCount = 0; + for (let r = 0; r < iterations; r++) { + const resourceId = resourceIds[r]; + for (const dateStr of dates) { + const date = this.dateService.parseISO(dateStr); + const segments = { date: dateStr }; + if (resourceId) + segments.resource = resourceId; + const columnKey = this.dateService.buildColumnKey(segments); + const header = document.createElement("swp-day-header"); + header.dataset.date = dateStr; + header.dataset.columnKey = columnKey; + if (resourceId) { + header.dataset.resourceId = resourceId; + } + if (hideHeader) { + header.dataset.hidden = "true"; + } + header.innerHTML = ` + ${this.dateService.getDayName(date, "short")} + ${date.getDate()} + `; + context.headerContainer.appendChild(header); + const column = document.createElement("swp-day-column"); + column.dataset.date = dateStr; + column.dataset.columnKey = columnKey; + if (resourceId) { + column.dataset.resourceId = resourceId; + } + column.innerHTML = ""; + context.columnContainer.appendChild(column); + columnCount++; + } + } + const container2 = context.columnContainer.closest("swp-calendar-container"); + if (container2) { + container2.style.setProperty("--grid-columns", String(columnCount)); + } + } +}; +__name(_DateRenderer, "DateRenderer"); +var DateRenderer = _DateRenderer; + +// src/core/DateService.ts +var import_dayjs = __toESM(require_dayjs_min(), 1); +var import_utc = __toESM(require_utc(), 1); +var import_timezone = __toESM(require_timezone(), 1); +var import_isoWeek = __toESM(require_isoWeek(), 1); +import_dayjs.default.extend(import_utc.default); +import_dayjs.default.extend(import_timezone.default); +import_dayjs.default.extend(import_isoWeek.default); +var _DateService = class _DateService { + constructor(config, baseDate) { + this.config = config; + this.timezone = config.timezone; + this.baseDate = baseDate ? (0, import_dayjs.default)(baseDate) : (0, import_dayjs.default)(); + } + /** + * Set a fixed base date (useful for demos with static mock data) + */ + setBaseDate(date) { + this.baseDate = (0, import_dayjs.default)(date); + } + /** + * Get the current base date (either fixed or today) + */ + getBaseDate() { + return this.baseDate.toDate(); + } + parseISO(isoString) { + return (0, import_dayjs.default)(isoString).toDate(); + } + getDayName(date, format = "short") { + return new Intl.DateTimeFormat(this.config.locale, { weekday: format }).format(date); + } + /** + * Get dates starting from a day offset + * @param dayOffset - Day offset from base date + * @param count - Number of consecutive days to return + * @returns Array of date strings in YYYY-MM-DD format + */ + getDatesFromOffset(dayOffset, count) { + const startDate = this.baseDate.add(dayOffset, "day"); + return Array.from({ length: count }, (_, i) => startDate.add(i, "day").format("YYYY-MM-DD")); + } + /** + * Get specific weekdays from the week containing the offset date + * @param dayOffset - Day offset from base date + * @param workDays - Array of ISO weekday numbers (1=Monday, 7=Sunday) + * @returns Array of date strings in YYYY-MM-DD format + */ + getWorkDaysFromOffset(dayOffset, workDays) { + const targetDate = this.baseDate.add(dayOffset, "day"); + const monday = targetDate.startOf("week").add(1, "day"); + return workDays.map((isoDay) => { + const daysFromMonday = isoDay === 7 ? 6 : isoDay - 1; + return monday.add(daysFromMonday, "day").format("YYYY-MM-DD"); + }); + } + // Legacy methods for backwards compatibility + getWeekDates(weekOffset = 0, days = 7) { + return this.getDatesFromOffset(weekOffset * 7, days); + } + getWorkWeekDates(weekOffset, workDays) { + return this.getWorkDaysFromOffset(weekOffset * 7, workDays); + } + // ============================================ + // FORMATTING + // ============================================ + formatTime(date, showSeconds = false) { + const pattern = showSeconds ? "HH:mm:ss" : "HH:mm"; + return (0, import_dayjs.default)(date).format(pattern); + } + formatTimeRange(start, end) { + return `${this.formatTime(start)} - ${this.formatTime(end)}`; + } + formatDate(date) { + return (0, import_dayjs.default)(date).format("YYYY-MM-DD"); + } + getDateKey(date) { + return this.formatDate(date); + } + // ============================================ + // COLUMN KEY + // ============================================ + /** + * Build a uniform columnKey from grouping segments + * Handles any combination of date, resource, team, etc. + * + * @example + * buildColumnKey({ date: '2025-12-09' }) → "2025-12-09" + * buildColumnKey({ date: '2025-12-09', resource: 'EMP001' }) → "2025-12-09:EMP001" + */ + buildColumnKey(segments) { + const date = segments.date; + const others = Object.entries(segments).filter(([k]) => k !== "date").sort(([a], [b]) => a.localeCompare(b)).map(([, v]) => v); + return date ? [date, ...others].join(":") : others.join(":"); + } + /** + * Parse a columnKey back into segments + * Assumes format: "date:resource:..." or just "date" + */ + parseColumnKey(columnKey) { + const parts = columnKey.split(":"); + return { + date: parts[0], + resource: parts[1] + }; + } + /** + * Extract dateKey from columnKey (first segment) + */ + getDateFromColumnKey(columnKey) { + return columnKey.split(":")[0]; + } + // ============================================ + // TIME CALCULATIONS + // ============================================ + timeToMinutes(timeString) { + const parts = timeString.split(":").map(Number); + const hours = parts[0] || 0; + const minutes = parts[1] || 0; + return hours * 60 + minutes; + } + minutesToTime(totalMinutes) { + const hours = Math.floor(totalMinutes / 60); + const minutes = totalMinutes % 60; + return (0, import_dayjs.default)().hour(hours).minute(minutes).format("HH:mm"); + } + getMinutesSinceMidnight(date) { + const d = (0, import_dayjs.default)(date); + return d.hour() * 60 + d.minute(); + } + // ============================================ + // UTC CONVERSIONS + // ============================================ + toUTC(localDate) { + return import_dayjs.default.tz(localDate, this.timezone).utc().toISOString(); + } + fromUTC(utcString) { + return import_dayjs.default.utc(utcString).tz(this.timezone).toDate(); + } + // ============================================ + // DATE CREATION + // ============================================ + createDateAtTime(baseDate, timeString) { + const totalMinutes = this.timeToMinutes(timeString); + const hours = Math.floor(totalMinutes / 60); + const minutes = totalMinutes % 60; + return (0, import_dayjs.default)(baseDate).startOf("day").hour(hours).minute(minutes).toDate(); + } + getISOWeekDay(date) { + return (0, import_dayjs.default)(date).isoWeekday(); + } +}; +__name(_DateService, "DateService"); +var DateService = _DateService; + +// src/core/BaseGroupingRenderer.ts +var _BaseGroupingRenderer = class _BaseGroupingRenderer { + /** + * Main render method - handles common logic + */ + async render(context) { + const allowedIds = context.filter[this.type] || []; + if (allowedIds.length === 0) + return; + const entities = await this.getEntities(allowedIds); + const dateCount = context.filter["date"]?.length || 1; + const childIds = context.childType ? context.filter[context.childType] || [] : []; + for (const entity of entities) { + const entityChildIds = context.parentChildMap?.[entity.id] || []; + const childCount = entityChildIds.filter((id) => childIds.includes(id)).length; + const colspan = childCount * dateCount; + const header = document.createElement(this.config.elementTag); + header.dataset[this.config.idAttribute] = entity.id; + header.style.setProperty(this.config.colspanVar, String(colspan)); + this.renderHeader(entity, header, context); + context.headerContainer.appendChild(header); + } + } + /** + * Override this method for custom header rendering + * Default: just sets textContent to display name + */ + renderHeader(entity, header, _context) { + header.textContent = this.getDisplayName(entity); + } + /** + * Helper to render a single entity header. + * Can be used by subclasses that override render() but want consistent header creation. + */ + createHeader(entity, context) { + const header = document.createElement(this.config.elementTag); + header.dataset[this.config.idAttribute] = entity.id; + this.renderHeader(entity, header, context); + return header; + } +}; +__name(_BaseGroupingRenderer, "BaseGroupingRenderer"); +var BaseGroupingRenderer = _BaseGroupingRenderer; + +// src/features/resource/ResourceRenderer.ts +var _ResourceRenderer = class _ResourceRenderer extends BaseGroupingRenderer { + constructor(resourceService) { + super(); + this.resourceService = resourceService; + this.type = "resource"; + this.config = { + elementTag: "swp-resource-header", + idAttribute: "resourceId", + colspanVar: "--resource-cols" + }; + } + getEntities(ids) { + return this.resourceService.getByIds(ids); + } + getDisplayName(entity) { + return entity.displayName; + } + /** + * Override render to handle: + * 1. Special ordering when parentChildMap exists (resources grouped by parent) + * 2. Different colspan calculation (just dateCount, not childCount * dateCount) + */ + async render(context) { + const resourceIds = context.filter["resource"] || []; + const dateCount = context.filter["date"]?.length || 1; + let orderedResourceIds; + if (context.parentChildMap) { + orderedResourceIds = []; + for (const childIds of Object.values(context.parentChildMap)) { + for (const childId of childIds) { + if (resourceIds.includes(childId)) { + orderedResourceIds.push(childId); + } + } + } + } else { + orderedResourceIds = resourceIds; + } + const resources = await this.getEntities(orderedResourceIds); + const resourceMap = new Map(resources.map((r) => [r.id, r])); + for (const resourceId of orderedResourceIds) { + const resource = resourceMap.get(resourceId); + if (!resource) + continue; + const header = this.createHeader(resource, context); + header.style.gridColumn = `span ${dateCount}`; + context.headerContainer.appendChild(header); + } + } +}; +__name(_ResourceRenderer, "ResourceRenderer"); +var ResourceRenderer = _ResourceRenderer; + +// src/features/team/TeamRenderer.ts +var _TeamRenderer = class _TeamRenderer extends BaseGroupingRenderer { + constructor(teamService) { + super(); + this.teamService = teamService; + this.type = "team"; + this.config = { + elementTag: "swp-team-header", + idAttribute: "teamId", + colspanVar: "--team-cols" + }; + } + getEntities(ids) { + return this.teamService.getByIds(ids); + } + getDisplayName(entity) { + return entity.name; + } +}; +__name(_TeamRenderer, "TeamRenderer"); +var TeamRenderer = _TeamRenderer; + +// src/features/department/DepartmentRenderer.ts +var _DepartmentRenderer = class _DepartmentRenderer extends BaseGroupingRenderer { + constructor(departmentService) { + super(); + this.departmentService = departmentService; + this.type = "department"; + this.config = { + elementTag: "swp-department-header", + idAttribute: "departmentId", + colspanVar: "--department-cols" + }; + } + getEntities(ids) { + return this.departmentService.getByIds(ids); + } + getDisplayName(entity) { + return entity.name; + } +}; +__name(_DepartmentRenderer, "DepartmentRenderer"); +var DepartmentRenderer = _DepartmentRenderer; + +// src/core/RenderBuilder.ts +function buildPipeline(renderers) { + return { + async run(context) { + for (const renderer of renderers) { + await renderer.render(context); + } + } + }; +} +__name(buildPipeline, "buildPipeline"); + +// src/core/FilterTemplate.ts +var _FilterTemplate = class _FilterTemplate { + constructor(dateService, entityResolver) { + this.dateService = dateService; + this.entityResolver = entityResolver; + this.fields = []; + } + /** + * Tilføj felt til template + * @param idProperty - Property-navn (bruges på både event og column.dataset) + * @param derivedFrom - Hvis feltet udledes fra anden property (f.eks. date fra start) + */ + addField(idProperty, derivedFrom) { + this.fields.push({ idProperty, derivedFrom }); + return this; + } + /** + * Parse dot-notation string into components + * @example 'resource.teamId' → { entityType: 'resource', property: 'teamId', foreignKey: 'resourceId' } + */ + parseDotNotation(idProperty) { + if (!idProperty.includes(".")) + return null; + const [entityType, property] = idProperty.split("."); + return { + entityType, + property, + foreignKey: entityType + "Id" + // Convention: resource → resourceId + }; + } + /** + * Get dataset key for column lookup + * For dot-notation 'resource.teamId', we look for 'teamId' in dataset + */ + getDatasetKey(idProperty) { + const dotNotation = this.parseDotNotation(idProperty); + if (dotNotation) { + return dotNotation.property; + } + return idProperty; + } + /** + * Byg nøgle fra kolonne + * Læser værdier fra column.dataset[idProperty] + * For dot-notation, uses the property part (resource.teamId → teamId) + */ + buildKeyFromColumn(column) { + return this.fields.map((f) => { + const key = this.getDatasetKey(f.idProperty); + return column.dataset[key] || ""; + }).join(":"); + } + /** + * Byg nøgle fra event + * Læser værdier fra event[idProperty] eller udleder fra derivedFrom + * For dot-notation, resolves via EntityResolver + */ + buildKeyFromEvent(event) { + const eventRecord = event; + return this.fields.map((f) => { + const dotNotation = this.parseDotNotation(f.idProperty); + if (dotNotation) { + return this.resolveDotNotation(eventRecord, dotNotation); + } + if (f.derivedFrom) { + const sourceValue = eventRecord[f.derivedFrom]; + if (sourceValue instanceof Date) { + return this.dateService.getDateKey(sourceValue); + } + return String(sourceValue || ""); + } + return String(eventRecord[f.idProperty] || ""); + }).join(":"); + } + /** + * Resolve dot-notation reference via EntityResolver + */ + resolveDotNotation(eventRecord, dotNotation) { + if (!this.entityResolver) { + console.warn(`FilterTemplate: EntityResolver required for dot-notation '${dotNotation.entityType}.${dotNotation.property}'`); + return ""; + } + const foreignId = eventRecord[dotNotation.foreignKey]; + if (!foreignId) + return ""; + const entity = this.entityResolver.resolve(dotNotation.entityType, String(foreignId)); + if (!entity) + return ""; + return String(entity[dotNotation.property] || ""); + } + /** + * Match event mod kolonne + */ + matches(event, column) { + return this.buildKeyFromEvent(event) === this.buildKeyFromColumn(column); + } +}; +__name(_FilterTemplate, "FilterTemplate"); +var FilterTemplate = _FilterTemplate; + +// src/core/CalendarOrchestrator.ts +var _CalendarOrchestrator = class _CalendarOrchestrator { + constructor(allRenderers, eventRenderer, scheduleRenderer, headerDrawerRenderer, dateService, entityServices) { + this.allRenderers = allRenderers; + this.eventRenderer = eventRenderer; + this.scheduleRenderer = scheduleRenderer; + this.headerDrawerRenderer = headerDrawerRenderer; + this.dateService = dateService; + this.entityServices = entityServices; + } + async render(viewConfig, container2) { + const headerContainer = container2.querySelector("swp-calendar-header"); + const columnContainer = container2.querySelector("swp-day-columns"); + if (!headerContainer || !columnContainer) { + throw new Error("Missing swp-calendar-header or swp-day-columns"); + } + const filter = {}; + for (const grouping of viewConfig.groupings) { + filter[grouping.type] = grouping.values; + } + const filterTemplate = new FilterTemplate(this.dateService); + for (const grouping of viewConfig.groupings) { + if (grouping.idProperty) { + filterTemplate.addField(grouping.idProperty, grouping.derivedFrom); + } + } + const { parentChildMap, childType } = await this.resolveBelongsTo(viewConfig.groupings, filter); + const context = { headerContainer, columnContainer, filter, groupings: viewConfig.groupings, parentChildMap, childType }; + headerContainer.innerHTML = ""; + columnContainer.innerHTML = ""; + const levels = viewConfig.groupings.map((g) => g.type).join(" "); + headerContainer.dataset.levels = levels; + const activeRenderers = this.selectRenderers(viewConfig); + const pipeline = buildPipeline(activeRenderers); + await pipeline.run(context); + await this.scheduleRenderer.render(container2, filter); + await this.eventRenderer.render(container2, filter, filterTemplate); + await this.headerDrawerRenderer.render(container2, filter, filterTemplate); + } + selectRenderers(viewConfig) { + const types = viewConfig.groupings.map((g) => g.type); + return types.map((type) => this.allRenderers.find((r) => r.type === type)).filter((r) => r !== void 0); + } + /** + * Resolve belongsTo relations to build parent-child map + * e.g., belongsTo: 'team.resourceIds' → { team1: ['EMP001', 'EMP002'], team2: [...] } + * Also returns the childType (the grouping type that has belongsTo) + */ + async resolveBelongsTo(groupings, filter) { + const childGrouping = groupings.find((g) => g.belongsTo); + if (!childGrouping?.belongsTo) + return {}; + const [entityType, property] = childGrouping.belongsTo.split("."); + if (!entityType || !property) + return {}; + const parentIds = filter[entityType] || []; + if (parentIds.length === 0) + return {}; + const service = this.entityServices.find((s) => s.entityType.toLowerCase() === entityType); + if (!service) + return {}; + const allEntities = await service.getAll(); + const entities = allEntities.filter((e) => parentIds.includes(e.id)); + const map = {}; + for (const entity of entities) { + const entityRecord = entity; + const children = entityRecord[property] || []; + map[entityRecord.id] = children; + } + return { parentChildMap: map, childType: childGrouping.type }; + } +}; +__name(_CalendarOrchestrator, "CalendarOrchestrator"); +var CalendarOrchestrator = _CalendarOrchestrator; + +// src/core/NavigationAnimator.ts +var _NavigationAnimator = class _NavigationAnimator { + constructor(headerTrack, contentTrack, headerDrawer) { + this.headerTrack = headerTrack; + this.contentTrack = contentTrack; + this.headerDrawer = headerDrawer; + } + async slide(direction, renderFn) { + const out = direction === "left" ? "-100%" : "100%"; + const into = direction === "left" ? "100%" : "-100%"; + await this.animateOut(out); + await renderFn(); + await this.animateIn(into); + } + async animateOut(translate) { + const animations = [ + this.headerTrack.animate([{ transform: "translateX(0)" }, { transform: `translateX(${translate})` }], { duration: 200, easing: "ease-in" }).finished, + this.contentTrack.animate([{ transform: "translateX(0)" }, { transform: `translateX(${translate})` }], { duration: 200, easing: "ease-in" }).finished + ]; + if (this.headerDrawer) { + animations.push(this.headerDrawer.animate([{ transform: "translateX(0)" }, { transform: `translateX(${translate})` }], { duration: 200, easing: "ease-in" }).finished); + } + await Promise.all(animations); + } + async animateIn(translate) { + const animations = [ + this.headerTrack.animate([{ transform: `translateX(${translate})` }, { transform: "translateX(0)" }], { duration: 200, easing: "ease-out" }).finished, + this.contentTrack.animate([{ transform: `translateX(${translate})` }, { transform: "translateX(0)" }], { duration: 200, easing: "ease-out" }).finished + ]; + if (this.headerDrawer) { + animations.push(this.headerDrawer.animate([{ transform: `translateX(${translate})` }, { transform: "translateX(0)" }], { duration: 200, easing: "ease-out" }).finished); + } + await Promise.all(animations); + } +}; +__name(_NavigationAnimator, "NavigationAnimator"); +var NavigationAnimator = _NavigationAnimator; + +// src/core/CalendarEvents.ts +var CalendarEvents = { + // Command events (host → calendar) + CMD_NAVIGATE_PREV: "calendar:cmd:navigate:prev", + CMD_NAVIGATE_NEXT: "calendar:cmd:navigate:next", + CMD_DRAWER_TOGGLE: "calendar:cmd:drawer:toggle", + CMD_RENDER: "calendar:cmd:render", + CMD_WORKWEEK_CHANGE: "calendar:cmd:workweek:change", + CMD_VIEW_UPDATE: "calendar:cmd:view:update" +}; + +// src/core/CalendarApp.ts +var _CalendarApp = class _CalendarApp { + constructor(orchestrator, timeAxisRenderer, dateService, scrollManager, headerDrawerManager, dragDropManager, edgeScrollManager, resizeManager, headerDrawerRenderer, eventPersistenceManager, settingsService, viewConfigService, eventBus) { + this.orchestrator = orchestrator; + this.timeAxisRenderer = timeAxisRenderer; + this.dateService = dateService; + this.scrollManager = scrollManager; + this.headerDrawerManager = headerDrawerManager; + this.dragDropManager = dragDropManager; + this.edgeScrollManager = edgeScrollManager; + this.resizeManager = resizeManager; + this.headerDrawerRenderer = headerDrawerRenderer; + this.eventPersistenceManager = eventPersistenceManager; + this.settingsService = settingsService; + this.viewConfigService = viewConfigService; + this.eventBus = eventBus; + this.dayOffset = 0; + this.currentViewId = "simple"; + this.workweekPreset = null; + this.groupingOverrides = /* @__PURE__ */ new Map(); + } + async init(container2) { + this.container = container2; + const gridSettings = await this.settingsService.getGridSettings(); + if (!gridSettings) { + throw new Error("GridSettings not found"); + } + this.workweekPreset = await this.settingsService.getDefaultWorkweekPreset(); + this.animator = new NavigationAnimator(container2.querySelector("swp-header-track"), container2.querySelector("swp-content-track"), container2.querySelector("swp-header-drawer")); + this.timeAxisRenderer.render(container2.querySelector("#time-axis"), gridSettings.dayStartHour, gridSettings.dayEndHour); + this.scrollManager.init(container2); + this.headerDrawerManager.init(container2); + this.dragDropManager.init(container2); + this.resizeManager.init(container2); + const scrollableContent = container2.querySelector("swp-scrollable-content"); + this.edgeScrollManager.init(scrollableContent); + this.setupEventListeners(); + this.emitStatus("ready"); + } + setupEventListeners() { + this.eventBus.on(CalendarEvents.CMD_NAVIGATE_PREV, () => { + this.handleNavigatePrev(); + }); + this.eventBus.on(CalendarEvents.CMD_NAVIGATE_NEXT, () => { + this.handleNavigateNext(); + }); + this.eventBus.on(CalendarEvents.CMD_DRAWER_TOGGLE, () => { + this.headerDrawerManager.toggle(); + }); + this.eventBus.on(CalendarEvents.CMD_RENDER, (e) => { + const { viewId } = e.detail; + this.handleRenderCommand(viewId); + }); + this.eventBus.on(CalendarEvents.CMD_WORKWEEK_CHANGE, (e) => { + const { presetId } = e.detail; + this.handleWorkweekChange(presetId); + }); + this.eventBus.on(CalendarEvents.CMD_VIEW_UPDATE, (e) => { + const { type, values } = e.detail; + this.handleViewUpdate(type, values); + }); + } + async handleRenderCommand(viewId) { + this.currentViewId = viewId; + await this.render(); + this.emitStatus("rendered", { viewId }); + } + async handleNavigatePrev() { + const step = this.workweekPreset?.periodDays ?? 7; + this.dayOffset -= step; + await this.animator.slide("right", () => this.render()); + this.emitStatus("rendered", { viewId: this.currentViewId }); + } + async handleNavigateNext() { + const step = this.workweekPreset?.periodDays ?? 7; + this.dayOffset += step; + await this.animator.slide("left", () => this.render()); + this.emitStatus("rendered", { viewId: this.currentViewId }); + } + async handleWorkweekChange(presetId) { + const preset = await this.settingsService.getWorkweekPreset(presetId); + if (preset) { + this.workweekPreset = preset; + await this.render(); + this.emitStatus("rendered", { viewId: this.currentViewId }); + } + } + async handleViewUpdate(type, values) { + this.groupingOverrides.set(type, values); + await this.render(); + this.emitStatus("rendered", { viewId: this.currentViewId }); + } + async render() { + const storedConfig = await this.viewConfigService.getById(this.currentViewId); + if (!storedConfig) { + this.emitStatus("error", { message: `ViewConfig not found: ${this.currentViewId}` }); + return; + } + const workDays = this.workweekPreset?.workDays || [1, 2, 3, 4, 5]; + const periodDays = this.workweekPreset?.periodDays ?? 7; + const dates = periodDays === 1 ? this.dateService.getDatesFromOffset(this.dayOffset, workDays.length) : this.dateService.getWorkDaysFromOffset(this.dayOffset, workDays); + const viewConfig = { + ...storedConfig, + groupings: storedConfig.groupings.map((g) => { + if (g.type === "date") { + return { ...g, values: dates }; + } + const override = this.groupingOverrides.get(g.type); + if (override) { + return { ...g, values: override }; + } + return g; + }) + }; + await this.orchestrator.render(viewConfig, this.container); + } + emitStatus(status, detail) { + this.container.dispatchEvent(new CustomEvent(`calendar:status:${status}`, { + detail, + bubbles: true + })); + } +}; +__name(_CalendarApp, "CalendarApp"); +var CalendarApp = _CalendarApp; + +// src/features/timeaxis/TimeAxisRenderer.ts +var _TimeAxisRenderer = class _TimeAxisRenderer { + render(container2, startHour = 6, endHour = 20) { + container2.innerHTML = ""; + for (let hour = startHour; hour <= endHour; hour++) { + const marker = document.createElement("swp-hour-marker"); + marker.textContent = `${hour.toString().padStart(2, "0")}:00`; + container2.appendChild(marker); + } + } +}; +__name(_TimeAxisRenderer, "TimeAxisRenderer"); +var TimeAxisRenderer = _TimeAxisRenderer; + +// src/core/ScrollManager.ts +var _ScrollManager = class _ScrollManager { + init(container2) { + this.scrollableContent = container2.querySelector("swp-scrollable-content"); + this.timeAxisContent = container2.querySelector("swp-time-axis-content"); + this.calendarHeader = container2.querySelector("swp-calendar-header"); + this.headerDrawer = container2.querySelector("swp-header-drawer"); + this.headerViewport = container2.querySelector("swp-header-viewport"); + this.headerSpacer = container2.querySelector("swp-header-spacer"); + this.scrollableContent.addEventListener("scroll", () => this.onScroll()); + this.resizeObserver = new ResizeObserver(() => this.syncHeaderSpacerHeight()); + this.resizeObserver.observe(this.headerViewport); + this.syncHeaderSpacerHeight(); + } + syncHeaderSpacerHeight() { + const computedHeight = getComputedStyle(this.headerViewport).height; + this.headerSpacer.style.height = computedHeight; + } + onScroll() { + const { scrollTop, scrollLeft } = this.scrollableContent; + this.timeAxisContent.style.transform = `translateY(-${scrollTop}px)`; + this.calendarHeader.style.transform = `translateX(-${scrollLeft}px)`; + this.headerDrawer.style.transform = `translateX(-${scrollLeft}px)`; + } +}; +__name(_ScrollManager, "ScrollManager"); +var ScrollManager = _ScrollManager; + +// src/core/HeaderDrawerManager.ts +var _HeaderDrawerManager = class _HeaderDrawerManager { + constructor() { + this.expanded = false; + this.currentRows = 0; + this.rowHeight = 25; + this.duration = 200; + } + init(container2) { + this.drawer = container2.querySelector("swp-header-drawer"); + if (!this.drawer) + console.error("HeaderDrawerManager: swp-header-drawer not found"); + } + toggle() { + this.expanded ? this.collapse() : this.expand(); + } + /** + * Expand drawer to single row (legacy support) + */ + expand() { + this.expandToRows(1); + } + /** + * Expand drawer to fit specified number of rows + */ + expandToRows(rowCount) { + const targetHeight = rowCount * this.rowHeight; + const currentHeight = this.expanded ? this.currentRows * this.rowHeight : 0; + if (this.expanded && this.currentRows === rowCount) + return; + this.currentRows = rowCount; + this.expanded = true; + this.animate(currentHeight, targetHeight); + } + collapse() { + if (!this.expanded) + return; + const currentHeight = this.currentRows * this.rowHeight; + this.expanded = false; + this.currentRows = 0; + this.animate(currentHeight, 0); + } + animate(from, to) { + const keyframes = [ + { height: `${from}px` }, + { height: `${to}px` } + ]; + const options = { + duration: this.duration, + easing: "ease", + fill: "forwards" + }; + this.drawer.animate(keyframes, options); + } + isExpanded() { + return this.expanded; + } + getRowCount() { + return this.currentRows; + } +}; +__name(_HeaderDrawerManager, "HeaderDrawerManager"); +var HeaderDrawerManager = _HeaderDrawerManager; + +// src/demo/MockStores.ts +var _MockTeamStore = class _MockTeamStore { + constructor() { + this.type = "team"; + this.teams = [ + { id: "alpha", name: "Team Alpha" }, + { id: "beta", name: "Team Beta" } + ]; + } + getByIds(ids) { + return this.teams.filter((t) => ids.includes(t.id)); + } +}; +__name(_MockTeamStore, "MockTeamStore"); +var MockTeamStore = _MockTeamStore; +var _MockResourceStore = class _MockResourceStore { + constructor() { + this.type = "resource"; + this.resources = [ + { id: "alice", name: "Alice", teamId: "alpha" }, + { id: "bob", name: "Bob", teamId: "alpha" }, + { id: "carol", name: "Carol", teamId: "beta" }, + { id: "dave", name: "Dave", teamId: "beta" } + ]; + } + getByIds(ids) { + return this.resources.filter((r) => ids.includes(r.id)); + } +}; +__name(_MockResourceStore, "MockResourceStore"); +var MockResourceStore = _MockResourceStore; + +// src/demo/DemoApp.ts +var _DemoApp = class _DemoApp { + constructor(indexedDBContext, dataSeeder, auditService, calendarApp, dateService, resourceService, eventBus) { + this.indexedDBContext = indexedDBContext; + this.dataSeeder = dataSeeder; + this.auditService = auditService; + this.calendarApp = calendarApp; + this.dateService = dateService; + this.resourceService = resourceService; + this.eventBus = eventBus; + this.currentView = "simple"; + } + async init() { + this.dateService.setBaseDate(/* @__PURE__ */ new Date("2025-12-08")); + await this.indexedDBContext.initialize(); + console.log("[DemoApp] IndexedDB initialized"); + await this.dataSeeder.seedIfEmpty(); + console.log("[DemoApp] Data seeding complete"); + this.container = document.querySelector("swp-calendar-container"); + await this.calendarApp.init(this.container); + console.log("[DemoApp] CalendarApp initialized"); + this.setupNavigation(); + this.setupDrawerToggle(); + this.setupViewSwitching(); + this.setupWorkweekSelector(); + await this.setupResourceSelector(); + this.setupStatusListeners(); + this.eventBus.emit(CalendarEvents.CMD_RENDER, { viewId: this.currentView }); + } + setupNavigation() { + document.getElementById("btn-prev").onclick = () => { + this.eventBus.emit(CalendarEvents.CMD_NAVIGATE_PREV); + }; + document.getElementById("btn-next").onclick = () => { + this.eventBus.emit(CalendarEvents.CMD_NAVIGATE_NEXT); + }; + } + setupViewSwitching() { + const chips = document.querySelectorAll(".view-chip"); + chips.forEach((chip) => { + chip.addEventListener("click", () => { + chips.forEach((c) => c.classList.remove("active")); + chip.classList.add("active"); + const view = chip.dataset.view; + if (view) { + this.currentView = view; + this.updateSelectorVisibility(); + this.eventBus.emit(CalendarEvents.CMD_RENDER, { viewId: view }); + } + }); + }); + } + updateSelectorVisibility() { + const selector = document.querySelector("swp-resource-selector"); + const showSelector = this.currentView === "picker" || this.currentView === "day"; + selector?.classList.toggle("hidden", !showSelector); + } + setupDrawerToggle() { + document.getElementById("btn-drawer").onclick = () => { + this.eventBus.emit(CalendarEvents.CMD_DRAWER_TOGGLE); + }; + } + setupWorkweekSelector() { + const workweekSelect = document.getElementById("workweek-select"); + workweekSelect?.addEventListener("change", () => { + const presetId = workweekSelect.value; + this.eventBus.emit(CalendarEvents.CMD_WORKWEEK_CHANGE, { presetId }); + }); + } + async setupResourceSelector() { + const resources = await this.resourceService.getAll(); + const container2 = document.querySelector(".resource-checkboxes"); + if (!container2) + return; + container2.innerHTML = ""; + resources.forEach((r) => { + const label = document.createElement("label"); + label.innerHTML = ` + + ${r.displayName} + `; + container2.appendChild(label); + }); + container2.addEventListener("change", () => { + const checked = container2.querySelectorAll("input:checked"); + const values = Array.from(checked).map((cb) => cb.value); + this.eventBus.emit(CalendarEvents.CMD_VIEW_UPDATE, { type: "resource", values }); + }); + } + setupStatusListeners() { + this.container.addEventListener("calendar:status:ready", () => { + console.log("[DemoApp] Calendar ready"); + }); + this.container.addEventListener("calendar:status:rendered", (e) => { + console.log("[DemoApp] Calendar rendered:", e.detail.viewId); + }); + this.container.addEventListener("calendar:status:error", (e) => { + console.error("[DemoApp] Calendar error:", e.detail.message); + }); + } +}; +__name(_DemoApp, "DemoApp"); +var DemoApp = _DemoApp; + +// src/core/EventBus.ts +var _EventBus = class _EventBus { + constructor() { + this.eventLog = []; + this.debug = false; + this.listeners = /* @__PURE__ */ new Set(); + this.logConfig = { + calendar: true, + grid: true, + event: true, + scroll: true, + navigation: true, + view: true, + default: true + }; + } + /** + * Subscribe to an event via DOM addEventListener + */ + on(eventType, handler, options) { + document.addEventListener(eventType, handler, options); + this.listeners.add({ eventType, handler, options }); + return () => this.off(eventType, handler); + } + /** + * Subscribe to an event once + */ + once(eventType, handler) { + return this.on(eventType, handler, { once: true }); + } + /** + * Unsubscribe from an event + */ + off(eventType, handler) { + document.removeEventListener(eventType, handler); + for (const listener of this.listeners) { + if (listener.eventType === eventType && listener.handler === handler) { + this.listeners.delete(listener); + break; + } + } + } + /** + * Emit an event via DOM CustomEvent + */ + emit(eventType, detail = {}) { + if (!eventType) { + return false; + } + const event = new CustomEvent(eventType, { + detail: detail ?? {}, + bubbles: true, + cancelable: true + }); + if (this.debug) { + this.logEventWithGrouping(eventType, detail); + } + this.eventLog.push({ + type: eventType, + detail: detail ?? {}, + timestamp: Date.now() + }); + return !document.dispatchEvent(event); + } + /** + * Log event with console grouping + */ + logEventWithGrouping(eventType, _detail) { + const category = this.extractCategory(eventType); + if (!this.logConfig[category]) { + return; + } + this.getCategoryStyle(category); + } + /** + * Extract category from event type + */ + extractCategory(eventType) { + if (!eventType) { + return "unknown"; + } + if (eventType.includes(":")) { + return eventType.split(":")[0]; + } + const lowerType = eventType.toLowerCase(); + if (lowerType.includes("grid") || lowerType.includes("rendered")) + return "grid"; + if (lowerType.includes("event") || lowerType.includes("sync")) + return "event"; + if (lowerType.includes("scroll")) + return "scroll"; + if (lowerType.includes("nav") || lowerType.includes("date")) + return "navigation"; + if (lowerType.includes("view")) + return "view"; + return "default"; + } + /** + * Get styling for different categories + */ + getCategoryStyle(category) { + const styles = { + calendar: { emoji: "\u{1F4C5}", color: "#2196F3" }, + grid: { emoji: "\u{1F4CA}", color: "#4CAF50" }, + event: { emoji: "\u{1F4CC}", color: "#FF9800" }, + scroll: { emoji: "\u{1F4DC}", color: "#9C27B0" }, + navigation: { emoji: "\u{1F9ED}", color: "#F44336" }, + view: { emoji: "\u{1F441}", color: "#00BCD4" }, + default: { emoji: "\u{1F4E2}", color: "#607D8B" } + }; + return styles[category] || styles.default; + } + /** + * Configure logging for specific categories + */ + setLogConfig(config) { + this.logConfig = { ...this.logConfig, ...config }; + } + /** + * Get current log configuration + */ + getLogConfig() { + return { ...this.logConfig }; + } + /** + * Get event history + */ + getEventLog(eventType) { + if (eventType) { + return this.eventLog.filter((e) => e.type === eventType); + } + return this.eventLog; + } + /** + * Enable/disable debug mode + */ + setDebug(enabled) { + this.debug = enabled; + } +}; +__name(_EventBus, "EventBus"); +var EventBus = _EventBus; + +// src/storage/IndexedDBContext.ts +var _IndexedDBContext = class _IndexedDBContext { + constructor(stores) { + this.db = null; + this.initialized = false; + this.stores = stores; + } + /** + * Initialize and open the database + */ + async initialize() { + return new Promise((resolve, reject) => { + const request = indexedDB.open(_IndexedDBContext.DB_NAME, _IndexedDBContext.DB_VERSION); + request.onerror = () => { + reject(new Error(`Failed to open IndexedDB: ${request.error}`)); + }; + request.onsuccess = () => { + this.db = request.result; + this.initialized = true; + resolve(); + }; + request.onupgradeneeded = (event) => { + const db = event.target.result; + this.stores.forEach((store) => { + if (!db.objectStoreNames.contains(store.storeName)) { + store.create(db); + } + }); + }; + }); + } + /** + * Check if database is initialized + */ + isInitialized() { + return this.initialized; + } + /** + * Get IDBDatabase instance + */ + getDatabase() { + if (!this.db) { + throw new Error("IndexedDB not initialized. Call initialize() first."); + } + return this.db; + } + /** + * Close database connection + */ + close() { + if (this.db) { + this.db.close(); + this.db = null; + this.initialized = false; + } + } + /** + * Delete entire database (for testing/reset) + */ + static async deleteDatabase() { + return new Promise((resolve, reject) => { + const request = indexedDB.deleteDatabase(_IndexedDBContext.DB_NAME); + request.onsuccess = () => resolve(); + request.onerror = () => reject(new Error(`Failed to delete database: ${request.error}`)); + }); + } +}; +__name(_IndexedDBContext, "IndexedDBContext"); +var IndexedDBContext = _IndexedDBContext; +IndexedDBContext.DB_NAME = "CalendarDB"; +IndexedDBContext.DB_VERSION = 4; + +// src/storage/events/EventStore.ts +var _EventStore = class _EventStore { + constructor() { + this.storeName = _EventStore.STORE_NAME; + } + /** + * Create the events ObjectStore with indexes + */ + create(db) { + const store = db.createObjectStore(_EventStore.STORE_NAME, { keyPath: "id" }); + store.createIndex("start", "start", { unique: false }); + store.createIndex("end", "end", { unique: false }); + store.createIndex("syncStatus", "syncStatus", { unique: false }); + store.createIndex("resourceId", "resourceId", { unique: false }); + store.createIndex("customerId", "customerId", { unique: false }); + store.createIndex("bookingId", "bookingId", { unique: false }); + store.createIndex("startEnd", ["start", "end"], { unique: false }); + } +}; +__name(_EventStore, "EventStore"); +var EventStore = _EventStore; +EventStore.STORE_NAME = "events"; + +// src/storage/events/EventSerialization.ts +var _EventSerialization = class _EventSerialization { + /** + * Serialize event for IndexedDB storage + */ + static serialize(event) { + return { + ...event, + start: event.start instanceof Date ? event.start.toISOString() : event.start, + end: event.end instanceof Date ? event.end.toISOString() : event.end + }; + } + /** + * Deserialize event from IndexedDB storage + */ + static deserialize(data) { + return { + ...data, + start: typeof data.start === "string" ? new Date(data.start) : data.start, + end: typeof data.end === "string" ? new Date(data.end) : data.end + }; + } +}; +__name(_EventSerialization, "EventSerialization"); +var EventSerialization = _EventSerialization; + +// src/storage/SyncPlugin.ts +var _SyncPlugin = class _SyncPlugin { + constructor(service) { + this.service = service; + } + /** + * Mark entity as successfully synced + */ + async markAsSynced(id) { + const entity = await this.service.get(id); + if (entity) { + entity.syncStatus = "synced"; + await this.service.save(entity); + } + } + /** + * Mark entity as sync error + */ + async markAsError(id) { + const entity = await this.service.get(id); + if (entity) { + entity.syncStatus = "error"; + await this.service.save(entity); + } + } + /** + * Get current sync status for an entity + */ + async getSyncStatus(id) { + const entity = await this.service.get(id); + return entity ? entity.syncStatus : null; + } + /** + * Get entities by sync status using IndexedDB index + */ + async getBySyncStatus(syncStatus) { + return new Promise((resolve, reject) => { + const transaction = this.service.db.transaction([this.service.storeName], "readonly"); + const store = transaction.objectStore(this.service.storeName); + const index = store.index("syncStatus"); + const request = index.getAll(syncStatus); + request.onsuccess = () => { + const data = request.result; + const entities = data.map((item) => this.service.deserialize(item)); + resolve(entities); + }; + request.onerror = () => { + reject(new Error(`Failed to get by sync status ${syncStatus}: ${request.error}`)); + }; + }); + } +}; +__name(_SyncPlugin, "SyncPlugin"); +var SyncPlugin = _SyncPlugin; + +// src/constants/CoreEvents.ts +var CoreEvents = { + // Lifecycle events + INITIALIZED: "core:initialized", + READY: "core:ready", + DESTROYED: "core:destroyed", + // View events + VIEW_CHANGED: "view:changed", + VIEW_RENDERED: "view:rendered", + // Navigation events + DATE_CHANGED: "nav:date-changed", + NAVIGATION_COMPLETED: "nav:navigation-completed", + // Data events + DATA_LOADING: "data:loading", + DATA_LOADED: "data:loaded", + DATA_ERROR: "data:error", + // Grid events + GRID_RENDERED: "grid:rendered", + GRID_CLICKED: "grid:clicked", + // Event management + EVENT_CREATED: "event:created", + EVENT_UPDATED: "event:updated", + EVENT_DELETED: "event:deleted", + EVENT_SELECTED: "event:selected", + // Event drag-drop + EVENT_DRAG_START: "event:drag-start", + EVENT_DRAG_MOVE: "event:drag-move", + EVENT_DRAG_END: "event:drag-end", + EVENT_DRAG_CANCEL: "event:drag-cancel", + EVENT_DRAG_COLUMN_CHANGE: "event:drag-column-change", + // Header drag (timed → header conversion) + EVENT_DRAG_ENTER_HEADER: "event:drag-enter-header", + EVENT_DRAG_MOVE_HEADER: "event:drag-move-header", + EVENT_DRAG_LEAVE_HEADER: "event:drag-leave-header", + // Event resize + EVENT_RESIZE_START: "event:resize-start", + EVENT_RESIZE_END: "event:resize-end", + // Edge scroll + EDGE_SCROLL_TICK: "edge-scroll:tick", + EDGE_SCROLL_STARTED: "edge-scroll:started", + EDGE_SCROLL_STOPPED: "edge-scroll:stopped", + // System events + ERROR: "system:error", + // Sync events + SYNC_STARTED: "sync:started", + SYNC_COMPLETED: "sync:completed", + SYNC_FAILED: "sync:failed", + // Entity events - for audit and sync + ENTITY_SAVED: "entity:saved", + ENTITY_DELETED: "entity:deleted", + // Audit events + AUDIT_LOGGED: "audit:logged", + // Rendering events + EVENTS_RENDERED: "events:rendered" +}; + +// node_modules/json-diff-ts/dist/index.js +function arrayDifference(first, second) { + const secondSet = new Set(second); + return first.filter((item) => !secondSet.has(item)); +} +__name(arrayDifference, "arrayDifference"); +function arrayIntersection(first, second) { + const secondSet = new Set(second); + return first.filter((item) => secondSet.has(item)); +} +__name(arrayIntersection, "arrayIntersection"); +function keyBy(arr, getKey2) { + const result = {}; + for (const item of arr) { + result[String(getKey2(item))] = item; + } + return result; +} +__name(keyBy, "keyBy"); +function diff(oldObj, newObj, options = {}) { + let { embeddedObjKeys } = options; + const { keysToSkip, treatTypeChangeAsReplace } = options; + if (embeddedObjKeys instanceof Map) { + embeddedObjKeys = new Map( + Array.from(embeddedObjKeys.entries()).map(([key, value]) => [ + key instanceof RegExp ? key : key.replace(/^\./, ""), + value + ]) + ); + } else if (embeddedObjKeys) { + embeddedObjKeys = Object.fromEntries( + Object.entries(embeddedObjKeys).map(([key, value]) => [key.replace(/^\./, ""), value]) + ); + } + return compare(oldObj, newObj, [], [], { + embeddedObjKeys, + keysToSkip: keysToSkip ?? [], + treatTypeChangeAsReplace: treatTypeChangeAsReplace ?? true + }); +} +__name(diff, "diff"); +var getTypeOfObj = /* @__PURE__ */ __name((obj) => { + if (typeof obj === "undefined") { + return "undefined"; + } + if (obj === null) { + return null; + } + return Object.prototype.toString.call(obj).match(/^\[object\s(.*)\]$/)[1]; +}, "getTypeOfObj"); +var getKey = /* @__PURE__ */ __name((path) => { + const left = path[path.length - 1]; + return left != null ? left : "$root"; +}, "getKey"); +var compare = /* @__PURE__ */ __name((oldObj, newObj, path, keyPath, options) => { + let changes = []; + const currentPath = keyPath.join("."); + if (options.keysToSkip?.some((skipPath) => { + if (currentPath === skipPath) { + return true; + } + if (skipPath.includes(".") && skipPath.startsWith(currentPath + ".")) { + return false; + } + if (skipPath.includes(".")) { + const skipParts = skipPath.split("."); + const currentParts = currentPath.split("."); + if (currentParts.length >= skipParts.length) { + for (let i = 0; i < skipParts.length; i++) { + if (skipParts[i] !== currentParts[i]) { + return false; + } + } + return true; + } + } + return false; + })) { + return changes; + } + const typeOfOldObj = getTypeOfObj(oldObj); + const typeOfNewObj = getTypeOfObj(newObj); + if (options.treatTypeChangeAsReplace && typeOfOldObj !== typeOfNewObj) { + if (typeOfOldObj !== "undefined") { + changes.push({ type: "REMOVE", key: getKey(path), value: oldObj }); + } + if (typeOfNewObj !== "undefined") { + changes.push({ type: "ADD", key: getKey(path), value: newObj }); + } + return changes; + } + if (typeOfNewObj === "undefined" && typeOfOldObj !== "undefined") { + changes.push({ type: "REMOVE", key: getKey(path), value: oldObj }); + return changes; + } + if (typeOfNewObj === "Object" && typeOfOldObj === "Array") { + changes.push({ type: "UPDATE", key: getKey(path), value: newObj, oldValue: oldObj }); + return changes; + } + if (typeOfNewObj === null) { + if (typeOfOldObj !== null) { + changes.push({ type: "UPDATE", key: getKey(path), value: newObj, oldValue: oldObj }); + } + return changes; + } + switch (typeOfOldObj) { + case "Date": + if (typeOfNewObj === "Date") { + changes = changes.concat( + comparePrimitives(oldObj.getTime(), newObj.getTime(), path).map((x) => ({ + ...x, + value: new Date(x.value), + oldValue: new Date(x.oldValue) + })) + ); + } else { + changes = changes.concat(comparePrimitives(oldObj, newObj, path)); + } + break; + case "Object": { + const diffs = compareObject(oldObj, newObj, path, keyPath, false, options); + if (diffs.length) { + if (path.length) { + changes.push({ + type: "UPDATE", + key: getKey(path), + changes: diffs + }); + } else { + changes = changes.concat(diffs); + } + } + break; + } + case "Array": + changes = changes.concat(compareArray(oldObj, newObj, path, keyPath, options)); + break; + case "Function": + break; + default: + changes = changes.concat(comparePrimitives(oldObj, newObj, path)); + } + return changes; +}, "compare"); +var compareObject = /* @__PURE__ */ __name((oldObj, newObj, path, keyPath, skipPath = false, options = {}) => { + let k; + let newKeyPath; + let newPath; + if (skipPath == null) { + skipPath = false; + } + let changes = []; + const oldObjKeys = Object.keys(oldObj); + const newObjKeys = Object.keys(newObj); + const intersectionKeys = arrayIntersection(oldObjKeys, newObjKeys); + for (k of intersectionKeys) { + newPath = path.concat([k]); + newKeyPath = skipPath ? keyPath : keyPath.concat([k]); + const diffs = compare(oldObj[k], newObj[k], newPath, newKeyPath, options); + if (diffs.length) { + changes = changes.concat(diffs); + } + } + const addedKeys = arrayDifference(newObjKeys, oldObjKeys); + for (k of addedKeys) { + newPath = path.concat([k]); + newKeyPath = skipPath ? keyPath : keyPath.concat([k]); + const currentPath = newKeyPath.join("."); + if (options.keysToSkip?.some((skipPath2) => currentPath === skipPath2 || currentPath.startsWith(skipPath2 + "."))) { + continue; + } + changes.push({ + type: "ADD", + key: getKey(newPath), + value: newObj[k] + }); + } + const deletedKeys = arrayDifference(oldObjKeys, newObjKeys); + for (k of deletedKeys) { + newPath = path.concat([k]); + newKeyPath = skipPath ? keyPath : keyPath.concat([k]); + const currentPath = newKeyPath.join("."); + if (options.keysToSkip?.some((skipPath2) => currentPath === skipPath2 || currentPath.startsWith(skipPath2 + "."))) { + continue; + } + changes.push({ + type: "REMOVE", + key: getKey(newPath), + value: oldObj[k] + }); + } + return changes; +}, "compareObject"); +var compareArray = /* @__PURE__ */ __name((oldObj, newObj, path, keyPath, options) => { + if (getTypeOfObj(newObj) !== "Array") { + return [{ type: "UPDATE", key: getKey(path), value: newObj, oldValue: oldObj }]; + } + const left = getObjectKey(options.embeddedObjKeys, keyPath); + const uniqKey = left != null ? left : "$index"; + const indexedOldObj = convertArrayToObj(oldObj, uniqKey); + const indexedNewObj = convertArrayToObj(newObj, uniqKey); + const diffs = compareObject(indexedOldObj, indexedNewObj, path, keyPath, true, options); + if (diffs.length) { + return [ + { + type: "UPDATE", + key: getKey(path), + embeddedKey: typeof uniqKey === "function" && uniqKey.length === 2 ? uniqKey(newObj[0], true) : uniqKey, + changes: diffs + } + ]; + } else { + return []; + } +}, "compareArray"); +var getObjectKey = /* @__PURE__ */ __name((embeddedObjKeys, keyPath) => { + if (embeddedObjKeys != null) { + const path = keyPath.join("."); + if (embeddedObjKeys instanceof Map) { + for (const [key2, value] of embeddedObjKeys.entries()) { + if (key2 instanceof RegExp) { + if (path.match(key2)) { + return value; + } + } else if (path === key2) { + return value; + } + } + } + const key = embeddedObjKeys[path]; + if (key != null) { + return key; + } + } + return void 0; +}, "getObjectKey"); +var convertArrayToObj = /* @__PURE__ */ __name((arr, uniqKey) => { + let obj = {}; + if (uniqKey === "$value") { + arr.forEach((value) => { + obj[value] = value; + }); + } else if (uniqKey !== "$index") { + const keyFunction = typeof uniqKey === "string" ? (item) => item[uniqKey] : uniqKey; + obj = keyBy(arr, keyFunction); + } else { + for (let i = 0; i < arr.length; i++) { + const value = arr[i]; + obj[i] = value; + } + } + return obj; +}, "convertArrayToObj"); +var comparePrimitives = /* @__PURE__ */ __name((oldObj, newObj, path) => { + const changes = []; + if (oldObj !== newObj) { + changes.push({ + type: "UPDATE", + key: getKey(path), + value: newObj, + oldValue: oldObj + }); + } + return changes; +}, "comparePrimitives"); + +// src/storage/BaseEntityService.ts +var _BaseEntityService = class _BaseEntityService { + constructor(context, eventBus) { + this.context = context; + this.eventBus = eventBus; + this.syncPlugin = new SyncPlugin(this); + } + get db() { + return this.context.getDatabase(); + } + /** + * Serialize entity before storing in IndexedDB + */ + serialize(entity) { + return entity; + } + /** + * Deserialize data from IndexedDB back to entity + */ + deserialize(data) { + return data; + } + /** + * Get a single entity by ID + */ + async get(id) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const request = store.get(id); + request.onsuccess = () => { + const data = request.result; + resolve(data ? this.deserialize(data) : null); + }; + request.onerror = () => { + reject(new Error(`Failed to get ${this.entityType} ${id}: ${request.error}`)); + }; + }); + } + /** + * Get all entities + */ + async getAll() { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const request = store.getAll(); + request.onsuccess = () => { + const data = request.result; + const entities = data.map((item) => this.deserialize(item)); + resolve(entities); + }; + request.onerror = () => { + reject(new Error(`Failed to get all ${this.entityType}s: ${request.error}`)); + }; + }); + } + /** + * Save an entity (create or update) + * Emits ENTITY_SAVED event with operation type and changes (diff for updates) + * @param entity - Entity to save + * @param silent - If true, skip event emission (used for seeding) + */ + async save(entity, silent = false) { + const entityId = entity.id; + const existingEntity = await this.get(entityId); + const isCreate = existingEntity === null; + let changes; + if (isCreate) { + changes = entity; + } else { + const existingSerialized = this.serialize(existingEntity); + const newSerialized = this.serialize(entity); + changes = diff(existingSerialized, newSerialized); + } + const serialized = this.serialize(entity); + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readwrite"); + const store = transaction.objectStore(this.storeName); + const request = store.put(serialized); + request.onsuccess = () => { + if (!silent) { + const payload = { + entityType: this.entityType, + entityId, + operation: isCreate ? "create" : "update", + changes, + timestamp: Date.now() + }; + this.eventBus.emit(CoreEvents.ENTITY_SAVED, payload); + } + resolve(); + }; + request.onerror = () => { + reject(new Error(`Failed to save ${this.entityType} ${entityId}: ${request.error}`)); + }; + }); + } + /** + * Delete an entity + * Emits ENTITY_DELETED event + */ + async delete(id) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readwrite"); + const store = transaction.objectStore(this.storeName); + const request = store.delete(id); + request.onsuccess = () => { + const payload = { + entityType: this.entityType, + entityId: id, + operation: "delete", + timestamp: Date.now() + }; + this.eventBus.emit(CoreEvents.ENTITY_DELETED, payload); + resolve(); + }; + request.onerror = () => { + reject(new Error(`Failed to delete ${this.entityType} ${id}: ${request.error}`)); + }; + }); + } + // Sync methods - delegate to SyncPlugin + async markAsSynced(id) { + return this.syncPlugin.markAsSynced(id); + } + async markAsError(id) { + return this.syncPlugin.markAsError(id); + } + async getSyncStatus(id) { + return this.syncPlugin.getSyncStatus(id); + } + async getBySyncStatus(syncStatus) { + return this.syncPlugin.getBySyncStatus(syncStatus); + } +}; +__name(_BaseEntityService, "BaseEntityService"); +var BaseEntityService = _BaseEntityService; + +// src/storage/events/EventService.ts +var _EventService = class _EventService extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = EventStore.STORE_NAME; + this.entityType = "Event"; + } + serialize(event) { + return EventSerialization.serialize(event); + } + deserialize(data) { + return EventSerialization.deserialize(data); + } + /** + * Get events within a date range + */ + async getByDateRange(start, end) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const index = store.index("start"); + const range = IDBKeyRange.lowerBound(start.toISOString()); + const request = index.getAll(range); + request.onsuccess = () => { + const data = request.result; + const events = data.map((item) => this.deserialize(item)).filter((event) => event.start <= end); + resolve(events); + }; + request.onerror = () => { + reject(new Error(`Failed to get events by date range: ${request.error}`)); + }; + }); + } + /** + * Get events for a specific resource + */ + async getByResource(resourceId) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const index = store.index("resourceId"); + const request = index.getAll(resourceId); + request.onsuccess = () => { + const data = request.result; + const events = data.map((item) => this.deserialize(item)); + resolve(events); + }; + request.onerror = () => { + reject(new Error(`Failed to get events for resource ${resourceId}: ${request.error}`)); + }; + }); + } + /** + * Get events for a resource within a date range + */ + async getByResourceAndDateRange(resourceId, start, end) { + const resourceEvents = await this.getByResource(resourceId); + return resourceEvents.filter((event) => event.start >= start && event.start <= end); + } +}; +__name(_EventService, "EventService"); +var EventService = _EventService; + +// src/storage/resources/ResourceStore.ts +var _ResourceStore = class _ResourceStore { + constructor() { + this.storeName = _ResourceStore.STORE_NAME; + } + create(db) { + const store = db.createObjectStore(_ResourceStore.STORE_NAME, { keyPath: "id" }); + store.createIndex("type", "type", { unique: false }); + store.createIndex("syncStatus", "syncStatus", { unique: false }); + store.createIndex("isActive", "isActive", { unique: false }); + } +}; +__name(_ResourceStore, "ResourceStore"); +var ResourceStore = _ResourceStore; +ResourceStore.STORE_NAME = "resources"; + +// src/storage/resources/ResourceService.ts +var _ResourceService = class _ResourceService extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = ResourceStore.STORE_NAME; + this.entityType = "Resource"; + } + /** + * Get all active resources + */ + async getActive() { + const all = await this.getAll(); + return all.filter((r) => r.isActive !== false); + } + /** + * Get resources by IDs + */ + async getByIds(ids) { + if (ids.length === 0) + return []; + const results = await Promise.all(ids.map((id) => this.get(id))); + return results.filter((r) => r !== null); + } + /** + * Get resources by type + */ + async getByType(type) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const index = store.index("type"); + const request = index.getAll(type); + request.onsuccess = () => { + const data = request.result; + resolve(data); + }; + request.onerror = () => { + reject(new Error(`Failed to get resources by type ${type}: ${request.error}`)); + }; + }); + } +}; +__name(_ResourceService, "ResourceService"); +var ResourceService = _ResourceService; + +// src/storage/bookings/BookingStore.ts +var _BookingStore = class _BookingStore { + constructor() { + this.storeName = _BookingStore.STORE_NAME; + } + create(db) { + const store = db.createObjectStore(_BookingStore.STORE_NAME, { keyPath: "id" }); + store.createIndex("customerId", "customerId", { unique: false }); + store.createIndex("status", "status", { unique: false }); + store.createIndex("syncStatus", "syncStatus", { unique: false }); + store.createIndex("createdAt", "createdAt", { unique: false }); + } +}; +__name(_BookingStore, "BookingStore"); +var BookingStore = _BookingStore; +BookingStore.STORE_NAME = "bookings"; + +// src/storage/bookings/BookingService.ts +var _BookingService = class _BookingService extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = BookingStore.STORE_NAME; + this.entityType = "Booking"; + } + serialize(booking) { + return { + ...booking, + createdAt: booking.createdAt.toISOString() + }; + } + deserialize(data) { + const raw = data; + return { + ...raw, + createdAt: new Date(raw.createdAt) + }; + } + /** + * Get bookings for a customer + */ + async getByCustomer(customerId) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const index = store.index("customerId"); + const request = index.getAll(customerId); + request.onsuccess = () => { + const data = request.result; + const bookings = data.map((item) => this.deserialize(item)); + resolve(bookings); + }; + request.onerror = () => { + reject(new Error(`Failed to get bookings for customer ${customerId}: ${request.error}`)); + }; + }); + } + /** + * Get bookings by status + */ + async getByStatus(status) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const index = store.index("status"); + const request = index.getAll(status); + request.onsuccess = () => { + const data = request.result; + const bookings = data.map((item) => this.deserialize(item)); + resolve(bookings); + }; + request.onerror = () => { + reject(new Error(`Failed to get bookings with status ${status}: ${request.error}`)); + }; + }); + } +}; +__name(_BookingService, "BookingService"); +var BookingService = _BookingService; + +// src/storage/customers/CustomerStore.ts +var _CustomerStore = class _CustomerStore { + constructor() { + this.storeName = _CustomerStore.STORE_NAME; + } + create(db) { + const store = db.createObjectStore(_CustomerStore.STORE_NAME, { keyPath: "id" }); + store.createIndex("name", "name", { unique: false }); + store.createIndex("phone", "phone", { unique: false }); + store.createIndex("syncStatus", "syncStatus", { unique: false }); + } +}; +__name(_CustomerStore, "CustomerStore"); +var CustomerStore = _CustomerStore; +CustomerStore.STORE_NAME = "customers"; + +// src/storage/customers/CustomerService.ts +var _CustomerService = class _CustomerService extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = CustomerStore.STORE_NAME; + this.entityType = "Customer"; + } + /** + * Search customers by name (case-insensitive contains) + */ + async searchByName(query) { + const all = await this.getAll(); + const lowerQuery = query.toLowerCase(); + return all.filter((c) => c.name.toLowerCase().includes(lowerQuery)); + } + /** + * Find customer by phone + */ + async getByPhone(phone) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const index = store.index("phone"); + const request = index.get(phone); + request.onsuccess = () => { + const data = request.result; + resolve(data ? data : null); + }; + request.onerror = () => { + reject(new Error(`Failed to find customer by phone ${phone}: ${request.error}`)); + }; + }); + } +}; +__name(_CustomerService, "CustomerService"); +var CustomerService = _CustomerService; + +// src/storage/teams/TeamStore.ts +var _TeamStore = class _TeamStore { + constructor() { + this.storeName = _TeamStore.STORE_NAME; + } + create(db) { + db.createObjectStore(_TeamStore.STORE_NAME, { keyPath: "id" }); + } +}; +__name(_TeamStore, "TeamStore"); +var TeamStore = _TeamStore; +TeamStore.STORE_NAME = "teams"; + +// src/storage/teams/TeamService.ts +var _TeamService = class _TeamService extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = TeamStore.STORE_NAME; + this.entityType = "Team"; + } + /** + * Get teams by IDs + */ + async getByIds(ids) { + if (ids.length === 0) + return []; + const results = await Promise.all(ids.map((id) => this.get(id))); + return results.filter((t) => t !== null); + } + /** + * Build reverse lookup: resourceId → teamId + */ + async buildResourceToTeamMap() { + const teams = await this.getAll(); + const map = {}; + for (const team of teams) { + for (const resourceId of team.resourceIds) { + map[resourceId] = team.id; + } + } + return map; + } +}; +__name(_TeamService, "TeamService"); +var TeamService = _TeamService; + +// src/storage/departments/DepartmentStore.ts +var _DepartmentStore = class _DepartmentStore { + constructor() { + this.storeName = _DepartmentStore.STORE_NAME; + } + create(db) { + db.createObjectStore(_DepartmentStore.STORE_NAME, { keyPath: "id" }); + } +}; +__name(_DepartmentStore, "DepartmentStore"); +var DepartmentStore = _DepartmentStore; +DepartmentStore.STORE_NAME = "departments"; + +// src/storage/departments/DepartmentService.ts +var _DepartmentService = class _DepartmentService extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = DepartmentStore.STORE_NAME; + this.entityType = "Department"; + } + /** + * Get departments by IDs + */ + async getByIds(ids) { + if (ids.length === 0) + return []; + const results = await Promise.all(ids.map((id) => this.get(id))); + return results.filter((d) => d !== null); + } +}; +__name(_DepartmentService, "DepartmentService"); +var DepartmentService = _DepartmentService; + +// src/storage/settings/SettingsStore.ts +var _SettingsStore = class _SettingsStore { + constructor() { + this.storeName = _SettingsStore.STORE_NAME; + } + create(db) { + db.createObjectStore(_SettingsStore.STORE_NAME, { keyPath: "id" }); + } +}; +__name(_SettingsStore, "SettingsStore"); +var SettingsStore = _SettingsStore; +SettingsStore.STORE_NAME = "settings"; + +// src/types/SettingsTypes.ts +var SettingsIds = { + WORKWEEK: "workweek", + GRID: "grid", + TIME_FORMAT: "timeFormat", + VIEWS: "views" +}; + +// src/storage/settings/SettingsService.ts +var _SettingsService = class _SettingsService extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = SettingsStore.STORE_NAME; + this.entityType = "Settings"; + } + /** + * Get workweek settings + */ + async getWorkweekSettings() { + return this.get(SettingsIds.WORKWEEK); + } + /** + * Get grid settings + */ + async getGridSettings() { + return this.get(SettingsIds.GRID); + } + /** + * Get time format settings + */ + async getTimeFormatSettings() { + return this.get(SettingsIds.TIME_FORMAT); + } + /** + * Get view settings + */ + async getViewSettings() { + return this.get(SettingsIds.VIEWS); + } + /** + * Get workweek preset by ID + */ + async getWorkweekPreset(presetId) { + const settings = await this.getWorkweekSettings(); + if (!settings) + return null; + return settings.presets[presetId] || null; + } + /** + * Get the default workweek preset + */ + async getDefaultWorkweekPreset() { + const settings = await this.getWorkweekSettings(); + if (!settings) + return null; + return settings.presets[settings.defaultPreset] || null; + } + /** + * Get all available workweek presets + */ + async getWorkweekPresets() { + const settings = await this.getWorkweekSettings(); + if (!settings) + return []; + return Object.values(settings.presets); + } +}; +__name(_SettingsService, "SettingsService"); +var SettingsService = _SettingsService; + +// src/storage/viewconfigs/ViewConfigStore.ts +var _ViewConfigStore = class _ViewConfigStore { + constructor() { + this.storeName = _ViewConfigStore.STORE_NAME; + } + create(db) { + db.createObjectStore(_ViewConfigStore.STORE_NAME, { keyPath: "id" }); + } +}; +__name(_ViewConfigStore, "ViewConfigStore"); +var ViewConfigStore = _ViewConfigStore; +ViewConfigStore.STORE_NAME = "viewconfigs"; + +// src/storage/viewconfigs/ViewConfigService.ts +var _ViewConfigService = class _ViewConfigService extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = ViewConfigStore.STORE_NAME; + this.entityType = "ViewConfig"; + } + async getById(id) { + return this.get(id); + } +}; +__name(_ViewConfigService, "ViewConfigService"); +var ViewConfigService = _ViewConfigService; + +// src/storage/audit/AuditStore.ts +var _AuditStore = class _AuditStore { + constructor() { + this.storeName = "audit"; + } + create(db) { + const store = db.createObjectStore(this.storeName, { keyPath: "id" }); + store.createIndex("syncStatus", "syncStatus", { unique: false }); + store.createIndex("synced", "synced", { unique: false }); + store.createIndex("entityId", "entityId", { unique: false }); + store.createIndex("timestamp", "timestamp", { unique: false }); + } +}; +__name(_AuditStore, "AuditStore"); +var AuditStore = _AuditStore; + +// src/storage/audit/AuditService.ts +var _AuditService = class _AuditService extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = "audit"; + this.entityType = "Audit"; + this.setupEventListeners(); + } + /** + * Setup listeners for ENTITY_SAVED and ENTITY_DELETED events + */ + setupEventListeners() { + this.eventBus.on(CoreEvents.ENTITY_SAVED, (event) => { + const detail = event.detail; + this.handleEntitySaved(detail); + }); + this.eventBus.on(CoreEvents.ENTITY_DELETED, (event) => { + const detail = event.detail; + this.handleEntityDeleted(detail); + }); + } + /** + * Handle ENTITY_SAVED event - create audit entry + */ + async handleEntitySaved(payload) { + if (payload.entityType === "Audit") + return; + const auditEntry = { + id: crypto.randomUUID(), + entityType: payload.entityType, + entityId: payload.entityId, + operation: payload.operation, + userId: _AuditService.DEFAULT_USER_ID, + timestamp: payload.timestamp, + changes: payload.changes, + synced: false, + syncStatus: "pending" + }; + await this.save(auditEntry); + } + /** + * Handle ENTITY_DELETED event - create audit entry + */ + async handleEntityDeleted(payload) { + if (payload.entityType === "Audit") + return; + const auditEntry = { + id: crypto.randomUUID(), + entityType: payload.entityType, + entityId: payload.entityId, + operation: "delete", + userId: _AuditService.DEFAULT_USER_ID, + timestamp: payload.timestamp, + changes: { id: payload.entityId }, + // For delete, just store the ID + synced: false, + syncStatus: "pending" + }; + await this.save(auditEntry); + } + /** + * Override save to NOT trigger ENTITY_SAVED event + * Instead, emits AUDIT_LOGGED for SyncManager to listen + * + * This prevents infinite loops: + * - BaseEntityService.save() emits ENTITY_SAVED + * - AuditService listens to ENTITY_SAVED and creates audit + * - If AuditService.save() also emitted ENTITY_SAVED, it would loop + */ + async save(entity) { + const serialized = this.serialize(entity); + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readwrite"); + const store = transaction.objectStore(this.storeName); + const request = store.put(serialized); + request.onsuccess = () => { + const payload = { + auditId: entity.id, + entityType: entity.entityType, + entityId: entity.entityId, + operation: entity.operation, + timestamp: entity.timestamp + }; + this.eventBus.emit(CoreEvents.AUDIT_LOGGED, payload); + resolve(); + }; + request.onerror = () => { + reject(new Error(`Failed to save audit entry ${entity.id}: ${request.error}`)); + }; + }); + } + /** + * Override delete to NOT trigger ENTITY_DELETED event + * Audit entries should never be deleted (compliance requirement) + */ + async delete(_id) { + throw new Error("Audit entries cannot be deleted (compliance requirement)"); + } + /** + * Get pending audit entries (for sync) + */ + async getPendingAudits() { + return this.getBySyncStatus("pending"); + } + /** + * Get audit entries for a specific entity + */ + async getByEntityId(entityId) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const index = store.index("entityId"); + const request = index.getAll(entityId); + request.onsuccess = () => { + const entries = request.result; + resolve(entries); + }; + request.onerror = () => { + reject(new Error(`Failed to get audit entries for entity ${entityId}: ${request.error}`)); + }; + }); + } +}; +__name(_AuditService, "AuditService"); +var AuditService = _AuditService; +AuditService.DEFAULT_USER_ID = "00000000-0000-0000-0000-000000000001"; + +// src/repositories/MockEventRepository.ts +var _MockEventRepository = class _MockEventRepository { + constructor() { + this.entityType = "Event"; + this.dataUrl = "data/mock-events.json"; + } + /** + * Fetch all events from mock JSON file + */ + async fetchAll() { + try { + const response = await fetch(this.dataUrl); + if (!response.ok) { + throw new Error(`Failed to load mock events: ${response.status} ${response.statusText}`); + } + const rawData = await response.json(); + return this.processCalendarData(rawData); + } catch (error) { + console.error("Failed to load event data:", error); + throw error; + } + } + async sendCreate(_event) { + throw new Error("MockEventRepository does not support sendCreate. Mock data is read-only."); + } + async sendUpdate(_id, _updates) { + throw new Error("MockEventRepository does not support sendUpdate. Mock data is read-only."); + } + async sendDelete(_id) { + throw new Error("MockEventRepository does not support sendDelete. Mock data is read-only."); + } + processCalendarData(data) { + return data.map((event) => { + if (event.type === "customer") { + if (!event.bookingId) + console.warn(`Customer event ${event.id} missing bookingId`); + if (!event.resourceId) + console.warn(`Customer event ${event.id} missing resourceId`); + if (!event.customerId) + console.warn(`Customer event ${event.id} missing customerId`); + } + return { + id: event.id, + title: event.title, + description: event.description, + start: new Date(event.start), + end: new Date(event.end), + type: event.type, + allDay: event.allDay || false, + bookingId: event.bookingId, + resourceId: event.resourceId, + customerId: event.customerId, + recurringId: event.recurringId, + metadata: event.metadata, + syncStatus: "synced" + }; + }); + } +}; +__name(_MockEventRepository, "MockEventRepository"); +var MockEventRepository = _MockEventRepository; + +// src/repositories/MockResourceRepository.ts +var _MockResourceRepository = class _MockResourceRepository { + constructor() { + this.entityType = "Resource"; + this.dataUrl = "data/mock-resources.json"; + } + async fetchAll() { + try { + const response = await fetch(this.dataUrl); + if (!response.ok) { + throw new Error(`Failed to load mock resources: ${response.status} ${response.statusText}`); + } + const rawData = await response.json(); + return this.processResourceData(rawData); + } catch (error) { + console.error("Failed to load resource data:", error); + throw error; + } + } + async sendCreate(_resource) { + throw new Error("MockResourceRepository does not support sendCreate. Mock data is read-only."); + } + async sendUpdate(_id, _updates) { + throw new Error("MockResourceRepository does not support sendUpdate. Mock data is read-only."); + } + async sendDelete(_id) { + throw new Error("MockResourceRepository does not support sendDelete. Mock data is read-only."); + } + processResourceData(data) { + return data.map((resource) => ({ + id: resource.id, + name: resource.name, + displayName: resource.displayName, + type: resource.type, + avatarUrl: resource.avatarUrl, + color: resource.color, + isActive: resource.isActive, + defaultSchedule: resource.defaultSchedule, + metadata: resource.metadata, + syncStatus: "synced" + })); + } +}; +__name(_MockResourceRepository, "MockResourceRepository"); +var MockResourceRepository = _MockResourceRepository; + +// src/repositories/MockBookingRepository.ts +var _MockBookingRepository = class _MockBookingRepository { + constructor() { + this.entityType = "Booking"; + this.dataUrl = "data/mock-bookings.json"; + } + async fetchAll() { + try { + const response = await fetch(this.dataUrl); + if (!response.ok) { + throw new Error(`Failed to load mock bookings: ${response.status} ${response.statusText}`); + } + const rawData = await response.json(); + return this.processBookingData(rawData); + } catch (error) { + console.error("Failed to load booking data:", error); + throw error; + } + } + async sendCreate(_booking) { + throw new Error("MockBookingRepository does not support sendCreate. Mock data is read-only."); + } + async sendUpdate(_id, _updates) { + throw new Error("MockBookingRepository does not support sendUpdate. Mock data is read-only."); + } + async sendDelete(_id) { + throw new Error("MockBookingRepository does not support sendDelete. Mock data is read-only."); + } + processBookingData(data) { + return data.map((booking) => ({ + id: booking.id, + customerId: booking.customerId, + status: booking.status, + createdAt: new Date(booking.createdAt), + services: booking.services, + totalPrice: booking.totalPrice, + tags: booking.tags, + notes: booking.notes, + syncStatus: "synced" + })); + } +}; +__name(_MockBookingRepository, "MockBookingRepository"); +var MockBookingRepository = _MockBookingRepository; + +// src/repositories/MockCustomerRepository.ts +var _MockCustomerRepository = class _MockCustomerRepository { + constructor() { + this.entityType = "Customer"; + this.dataUrl = "data/mock-customers.json"; + } + async fetchAll() { + try { + const response = await fetch(this.dataUrl); + if (!response.ok) { + throw new Error(`Failed to load mock customers: ${response.status} ${response.statusText}`); + } + const rawData = await response.json(); + return this.processCustomerData(rawData); + } catch (error) { + console.error("Failed to load customer data:", error); + throw error; + } + } + async sendCreate(_customer) { + throw new Error("MockCustomerRepository does not support sendCreate. Mock data is read-only."); + } + async sendUpdate(_id, _updates) { + throw new Error("MockCustomerRepository does not support sendUpdate. Mock data is read-only."); + } + async sendDelete(_id) { + throw new Error("MockCustomerRepository does not support sendDelete. Mock data is read-only."); + } + processCustomerData(data) { + return data.map((customer) => ({ + id: customer.id, + name: customer.name, + phone: customer.phone, + email: customer.email, + metadata: customer.metadata, + syncStatus: "synced" + })); + } +}; +__name(_MockCustomerRepository, "MockCustomerRepository"); +var MockCustomerRepository = _MockCustomerRepository; + +// src/repositories/MockAuditRepository.ts +var _MockAuditRepository = class _MockAuditRepository { + constructor() { + this.entityType = "Audit"; + } + async sendCreate(entity) { + await new Promise((resolve) => setTimeout(resolve, 100)); + console.log("MockAuditRepository: Audit entry synced to backend:", { + id: entity.id, + entityType: entity.entityType, + entityId: entity.entityId, + operation: entity.operation, + timestamp: new Date(entity.timestamp).toISOString() + }); + return entity; + } + async sendUpdate(_id, _entity) { + throw new Error("Audit entries cannot be updated"); + } + async sendDelete(_id) { + throw new Error("Audit entries cannot be deleted"); + } + async fetchAll() { + return []; + } + async fetchById(_id) { + return null; + } +}; +__name(_MockAuditRepository, "MockAuditRepository"); +var MockAuditRepository = _MockAuditRepository; + +// src/repositories/MockTeamRepository.ts +var _MockTeamRepository = class _MockTeamRepository { + constructor() { + this.entityType = "Team"; + this.dataUrl = "data/mock-teams.json"; + } + async fetchAll() { + try { + const response = await fetch(this.dataUrl); + if (!response.ok) { + throw new Error(`Failed to load mock teams: ${response.status} ${response.statusText}`); + } + const rawData = await response.json(); + return this.processTeamData(rawData); + } catch (error) { + console.error("Failed to load team data:", error); + throw error; + } + } + async sendCreate(_team) { + throw new Error("MockTeamRepository does not support sendCreate. Mock data is read-only."); + } + async sendUpdate(_id, _updates) { + throw new Error("MockTeamRepository does not support sendUpdate. Mock data is read-only."); + } + async sendDelete(_id) { + throw new Error("MockTeamRepository does not support sendDelete. Mock data is read-only."); + } + processTeamData(data) { + return data.map((team) => ({ + id: team.id, + name: team.name, + resourceIds: team.resourceIds, + syncStatus: "synced" + })); + } +}; +__name(_MockTeamRepository, "MockTeamRepository"); +var MockTeamRepository = _MockTeamRepository; + +// src/repositories/MockDepartmentRepository.ts +var _MockDepartmentRepository = class _MockDepartmentRepository { + constructor() { + this.entityType = "Department"; + this.dataUrl = "data/mock-departments.json"; + } + async fetchAll() { + try { + const response = await fetch(this.dataUrl); + if (!response.ok) { + throw new Error(`Failed to load mock departments: ${response.status} ${response.statusText}`); + } + const rawData = await response.json(); + return this.processDepartmentData(rawData); + } catch (error) { + console.error("Failed to load department data:", error); + throw error; + } + } + async sendCreate(_department) { + throw new Error("MockDepartmentRepository does not support sendCreate. Mock data is read-only."); + } + async sendUpdate(_id, _updates) { + throw new Error("MockDepartmentRepository does not support sendUpdate. Mock data is read-only."); + } + async sendDelete(_id) { + throw new Error("MockDepartmentRepository does not support sendDelete. Mock data is read-only."); + } + processDepartmentData(data) { + return data.map((dept) => ({ + id: dept.id, + name: dept.name, + resourceIds: dept.resourceIds, + syncStatus: "synced" + })); + } +}; +__name(_MockDepartmentRepository, "MockDepartmentRepository"); +var MockDepartmentRepository = _MockDepartmentRepository; + +// src/repositories/MockSettingsRepository.ts +var _MockSettingsRepository = class _MockSettingsRepository { + constructor() { + this.entityType = "Settings"; + this.dataUrl = "data/tenant-settings.json"; + } + async fetchAll() { + try { + const response = await fetch(this.dataUrl); + if (!response.ok) { + throw new Error(`Failed to load tenant settings: ${response.status} ${response.statusText}`); + } + const settings = await response.json(); + return settings.map((s) => ({ + ...s, + syncStatus: s.syncStatus || "synced" + })); + } catch (error) { + console.error("Failed to load tenant settings:", error); + throw error; + } + } + async sendCreate(_settings) { + throw new Error("MockSettingsRepository does not support sendCreate. Mock data is read-only."); + } + async sendUpdate(_id, _updates) { + throw new Error("MockSettingsRepository does not support sendUpdate. Mock data is read-only."); + } + async sendDelete(_id) { + throw new Error("MockSettingsRepository does not support sendDelete. Mock data is read-only."); + } +}; +__name(_MockSettingsRepository, "MockSettingsRepository"); +var MockSettingsRepository = _MockSettingsRepository; + +// src/repositories/MockViewConfigRepository.ts +var _MockViewConfigRepository = class _MockViewConfigRepository { + constructor() { + this.entityType = "ViewConfig"; + this.dataUrl = "data/viewconfigs.json"; + } + async fetchAll() { + try { + const response = await fetch(this.dataUrl); + if (!response.ok) { + throw new Error(`Failed to load viewconfigs: ${response.status} ${response.statusText}`); + } + const rawData = await response.json(); + const configs = rawData.map((config) => ({ + ...config, + syncStatus: config.syncStatus || "synced" + })); + return configs; + } catch (error) { + console.error("Failed to load viewconfigs:", error); + throw error; + } + } + async sendCreate(_config) { + throw new Error("MockViewConfigRepository does not support sendCreate. Mock data is read-only."); + } + async sendUpdate(_id, _updates) { + throw new Error("MockViewConfigRepository does not support sendUpdate. Mock data is read-only."); + } + async sendDelete(_id) { + throw new Error("MockViewConfigRepository does not support sendDelete. Mock data is read-only."); + } +}; +__name(_MockViewConfigRepository, "MockViewConfigRepository"); +var MockViewConfigRepository = _MockViewConfigRepository; + +// src/workers/DataSeeder.ts +var _DataSeeder = class _DataSeeder { + constructor(services, repositories) { + this.services = services; + this.repositories = repositories; + } + /** + * Seed all entity stores if they are empty + */ + async seedIfEmpty() { + console.log("[DataSeeder] Checking if database needs seeding..."); + try { + for (const service of this.services) { + const repository = this.repositories.find((repo) => repo.entityType === service.entityType); + if (!repository) { + console.warn(`[DataSeeder] No repository found for entity type: ${service.entityType}, skipping`); + continue; + } + await this.seedEntity(service.entityType, service, repository); + } + console.log("[DataSeeder] Seeding complete"); + } catch (error) { + console.error("[DataSeeder] Seeding failed:", error); + throw error; + } + } + async seedEntity(entityType, service, repository) { + const existing = await service.getAll(); + if (existing.length > 0) { + console.log(`[DataSeeder] ${entityType} store already has ${existing.length} items, skipping seed`); + return; + } + console.log(`[DataSeeder] ${entityType} store is empty, fetching from repository...`); + const data = await repository.fetchAll(); + console.log(`[DataSeeder] Fetched ${data.length} ${entityType} items, saving to IndexedDB...`); + for (const entity of data) { + await service.save(entity, true); + } + console.log(`[DataSeeder] ${entityType} seeding complete (${data.length} items saved)`); + } +}; +__name(_DataSeeder, "DataSeeder"); +var DataSeeder = _DataSeeder; + +// src/utils/PositionUtils.ts +function calculateEventPosition(start, end, config) { + const startMinutes = start.getHours() * 60 + start.getMinutes(); + const endMinutes = end.getHours() * 60 + end.getMinutes(); + const dayStartMinutes = config.dayStartHour * 60; + const minuteHeight = config.hourHeight / 60; + const top = (startMinutes - dayStartMinutes) * minuteHeight; + const height = (endMinutes - startMinutes) * minuteHeight; + return { top, height }; +} +__name(calculateEventPosition, "calculateEventPosition"); +function minutesToPixels(minutes, config) { + return minutes / 60 * config.hourHeight; +} +__name(minutesToPixels, "minutesToPixels"); +function pixelsToMinutes(pixels, config) { + return pixels / config.hourHeight * 60; +} +__name(pixelsToMinutes, "pixelsToMinutes"); +function snapToGrid(pixels, config) { + const snapPixels = minutesToPixels(config.snapInterval, config); + return Math.round(pixels / snapPixels) * snapPixels; +} +__name(snapToGrid, "snapToGrid"); + +// src/features/event/EventLayoutEngine.ts +function eventsOverlap(a, b) { + return a.start < b.end && a.end > b.start; +} +__name(eventsOverlap, "eventsOverlap"); +function eventsWithinThreshold(a, b, thresholdMinutes) { + const thresholdMs = thresholdMinutes * 60 * 1e3; + const startToStartDiff = Math.abs(a.start.getTime() - b.start.getTime()); + if (startToStartDiff <= thresholdMs) + return true; + const bStartsBeforeAEnds = a.end.getTime() - b.start.getTime(); + if (bStartsBeforeAEnds > 0 && bStartsBeforeAEnds <= thresholdMs) + return true; + const aStartsBeforeBEnds = b.end.getTime() - a.start.getTime(); + if (aStartsBeforeBEnds > 0 && aStartsBeforeBEnds <= thresholdMs) + return true; + return false; +} +__name(eventsWithinThreshold, "eventsWithinThreshold"); +function findOverlapGroups(events) { + if (events.length === 0) + return []; + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + const used = /* @__PURE__ */ new Set(); + const groups = []; + for (const event of sorted) { + if (used.has(event.id)) + continue; + const group = [event]; + used.add(event.id); + let expanded = true; + while (expanded) { + expanded = false; + for (const candidate of sorted) { + if (used.has(candidate.id)) + continue; + const connects = group.some((member) => eventsOverlap(member, candidate)); + if (connects) { + group.push(candidate); + used.add(candidate.id); + expanded = true; + } + } + } + groups.push(group); + } + return groups; +} +__name(findOverlapGroups, "findOverlapGroups"); +function findGridCandidates(events, thresholdMinutes) { + if (events.length === 0) + return []; + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + const used = /* @__PURE__ */ new Set(); + const groups = []; + for (const event of sorted) { + if (used.has(event.id)) + continue; + const group = [event]; + used.add(event.id); + let expanded = true; + while (expanded) { + expanded = false; + for (const candidate of sorted) { + if (used.has(candidate.id)) + continue; + const connects = group.some((member) => eventsWithinThreshold(member, candidate, thresholdMinutes)); + if (connects) { + group.push(candidate); + used.add(candidate.id); + expanded = true; + } + } + } + groups.push(group); + } + return groups; +} +__name(findGridCandidates, "findGridCandidates"); +function calculateStackLevels(events) { + const levels = /* @__PURE__ */ new Map(); + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + for (const event of sorted) { + let maxOverlappingLevel = -1; + for (const [id, level] of levels) { + const other = events.find((e) => e.id === id); + if (other && eventsOverlap(event, other)) { + maxOverlappingLevel = Math.max(maxOverlappingLevel, level); + } + } + levels.set(event.id, maxOverlappingLevel + 1); + } + return levels; +} +__name(calculateStackLevels, "calculateStackLevels"); +function allocateColumns(events) { + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + const columns = []; + for (const event of sorted) { + let placed = false; + for (const column of columns) { + const canFit = !column.some((e) => eventsOverlap(event, e)); + if (canFit) { + column.push(event); + placed = true; + break; + } + } + if (!placed) { + columns.push([event]); + } + } + return columns; +} +__name(allocateColumns, "allocateColumns"); +function calculateColumnLayout(events, config) { + const thresholdMinutes = config.gridStartThresholdMinutes ?? 10; + const result = { + grids: [], + stacked: [] + }; + if (events.length === 0) + return result; + const overlapGroups = findOverlapGroups(events); + for (const overlapGroup of overlapGroups) { + if (overlapGroup.length === 1) { + result.stacked.push({ + event: overlapGroup[0], + stackLevel: 0 + }); + continue; + } + const gridSubgroups = findGridCandidates(overlapGroup, thresholdMinutes); + const largestGridCandidate = gridSubgroups.reduce((max, g) => g.length > max.length ? g : max, gridSubgroups[0]); + if (largestGridCandidate.length === overlapGroup.length) { + const columns = allocateColumns(overlapGroup); + const earliest = overlapGroup.reduce((min, e) => e.start < min.start ? e : min, overlapGroup[0]); + const position = calculateEventPosition(earliest.start, earliest.end, config); + result.grids.push({ + events: overlapGroup, + columns, + stackLevel: 0, + position: { top: position.top } + }); + } else { + const levels = calculateStackLevels(overlapGroup); + for (const event of overlapGroup) { + result.stacked.push({ + event, + stackLevel: levels.get(event.id) ?? 0 + }); + } + } + } + return result; +} +__name(calculateColumnLayout, "calculateColumnLayout"); + +// src/features/event/EventRenderer.ts +var _EventRenderer = class _EventRenderer { + constructor(eventService, dateService, gridConfig, eventBus) { + this.eventService = eventService; + this.dateService = dateService; + this.gridConfig = gridConfig; + this.eventBus = eventBus; + this.container = null; + this.setupListeners(); + } + /** + * Setup listeners for drag-drop and update events + */ + setupListeners() { + this.eventBus.on(CoreEvents.EVENT_DRAG_COLUMN_CHANGE, (e) => { + const payload = e.detail; + this.handleColumnChange(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_MOVE, (e) => { + const payload = e.detail; + this.updateDragTimestamp(payload); + }); + this.eventBus.on(CoreEvents.EVENT_UPDATED, (e) => { + const payload = e.detail; + this.handleEventUpdated(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_END, (e) => { + const payload = e.detail; + this.handleDragEnd(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_LEAVE_HEADER, (e) => { + const payload = e.detail; + this.handleDragLeaveHeader(payload); + }); + } + /** + * Handle EVENT_DRAG_END - remove element if dropped in header + */ + handleDragEnd(payload) { + if (payload.target === "header") { + const element = this.container?.querySelector(`swp-content-viewport swp-event[data-event-id="${payload.swpEvent.eventId}"]`); + element?.remove(); + } + } + /** + * Handle header item leaving header - create swp-event in grid + */ + handleDragLeaveHeader(payload) { + if (payload.source !== "header") + return; + if (!payload.targetColumn || !payload.start || !payload.end) + return; + if (payload.element) { + payload.element.classList.add("drag-ghost"); + payload.element.style.opacity = "0.3"; + payload.element.style.pointerEvents = "none"; + } + const event = { + id: payload.eventId, + title: payload.title || "", + description: "", + start: payload.start, + end: payload.end, + type: "customer", + allDay: false, + syncStatus: "pending" + }; + const element = this.createEventElement(event); + let eventsLayer = payload.targetColumn.querySelector("swp-events-layer"); + if (!eventsLayer) { + eventsLayer = document.createElement("swp-events-layer"); + payload.targetColumn.appendChild(eventsLayer); + } + eventsLayer.appendChild(element); + element.classList.add("dragging"); + } + /** + * Handle EVENT_UPDATED - re-render affected columns + */ + async handleEventUpdated(payload) { + if (payload.sourceColumnKey !== payload.targetColumnKey) { + await this.rerenderColumn(payload.sourceColumnKey); + } + await this.rerenderColumn(payload.targetColumnKey); + } + /** + * Re-render a single column with fresh data from IndexedDB + */ + async rerenderColumn(columnKey) { + const column = this.findColumn(columnKey); + if (!column) + return; + const date = column.dataset.date; + const resourceId = column.dataset.resourceId; + if (!date) + return; + const startDate = new Date(date); + const endDate = new Date(date); + endDate.setHours(23, 59, 59, 999); + const events = resourceId ? await this.eventService.getByResourceAndDateRange(resourceId, startDate, endDate) : await this.eventService.getByDateRange(startDate, endDate); + const timedEvents = events.filter((event) => !event.allDay && this.dateService.getDateKey(event.start) === date); + let eventsLayer = column.querySelector("swp-events-layer"); + if (!eventsLayer) { + eventsLayer = document.createElement("swp-events-layer"); + column.appendChild(eventsLayer); + } + eventsLayer.innerHTML = ""; + const layout = calculateColumnLayout(timedEvents, this.gridConfig); + layout.grids.forEach((grid) => { + const groupEl = this.renderGridGroup(grid); + eventsLayer.appendChild(groupEl); + }); + layout.stacked.forEach((item) => { + const eventEl = this.renderStackedEvent(item.event, item.stackLevel); + eventsLayer.appendChild(eventEl); + }); + } + /** + * Find a column element by columnKey + */ + findColumn(columnKey) { + if (!this.container) + return null; + return this.container.querySelector(`swp-day-column[data-column-key="${columnKey}"]`); + } + /** + * Handle event moving to a new column during drag + */ + handleColumnChange(payload) { + const eventsLayer = payload.newColumn.querySelector("swp-events-layer"); + if (!eventsLayer) + return; + eventsLayer.appendChild(payload.element); + payload.element.style.top = `${payload.currentY}px`; + } + /** + * Update timestamp display during drag (snapped to grid) + */ + updateDragTimestamp(payload) { + const timeEl = payload.element.querySelector("swp-event-time"); + if (!timeEl) + return; + const snappedY = snapToGrid(payload.currentY, this.gridConfig); + const minutesFromGridStart = pixelsToMinutes(snappedY, this.gridConfig); + const startMinutes = this.gridConfig.dayStartHour * 60 + minutesFromGridStart; + const height = parseFloat(payload.element.style.height) || this.gridConfig.hourHeight; + const durationMinutes = pixelsToMinutes(height, this.gridConfig); + const start = this.minutesToDate(startMinutes); + const end = this.minutesToDate(startMinutes + durationMinutes); + timeEl.textContent = this.dateService.formatTimeRange(start, end); + } + /** + * Convert minutes since midnight to a Date object (today) + */ + minutesToDate(minutes) { + const date = /* @__PURE__ */ new Date(); + date.setHours(Math.floor(minutes / 60) % 24, minutes % 60, 0, 0); + return date; + } + /** + * Render events for visible dates into day columns + * @param container - Calendar container element + * @param filter - Filter with 'date' and optionally 'resource' arrays + * @param filterTemplate - Template for matching events to columns + */ + async render(container2, filter, filterTemplate) { + this.container = container2; + const visibleDates = filter["date"] || []; + if (visibleDates.length === 0) + return; + const startDate = new Date(visibleDates[0]); + const endDate = new Date(visibleDates[visibleDates.length - 1]); + endDate.setHours(23, 59, 59, 999); + const events = await this.eventService.getByDateRange(startDate, endDate); + const dayColumns = container2.querySelector("swp-day-columns"); + if (!dayColumns) + return; + const columns = dayColumns.querySelectorAll("swp-day-column"); + columns.forEach((column) => { + const columnEl = column; + const columnEvents = events.filter((event) => filterTemplate.matches(event, columnEl)); + let eventsLayer = column.querySelector("swp-events-layer"); + if (!eventsLayer) { + eventsLayer = document.createElement("swp-events-layer"); + column.appendChild(eventsLayer); + } + eventsLayer.innerHTML = ""; + const timedEvents = columnEvents.filter((event) => !event.allDay); + const layout = calculateColumnLayout(timedEvents, this.gridConfig); + layout.grids.forEach((grid) => { + const groupEl = this.renderGridGroup(grid); + eventsLayer.appendChild(groupEl); + }); + layout.stacked.forEach((item) => { + const eventEl = this.renderStackedEvent(item.event, item.stackLevel); + eventsLayer.appendChild(eventEl); + }); + }); + } + /** + * Create a single event element + * + * CLEAN approach: + * - Only data-id for lookup + * - Visible content in innerHTML only + */ + createEventElement(event) { + const element = document.createElement("swp-event"); + element.dataset.eventId = event.id; + if (event.resourceId) { + element.dataset.resourceId = event.resourceId; + } + const position = calculateEventPosition(event.start, event.end, this.gridConfig); + element.style.top = `${position.top}px`; + element.style.height = `${position.height}px`; + const colorClass = this.getColorClass(event); + if (colorClass) { + element.classList.add(colorClass); + } + element.innerHTML = ` + ${this.dateService.formatTimeRange(event.start, event.end)} + ${this.escapeHtml(event.title)} + ${event.description ? `${this.escapeHtml(event.description)}` : ""} + `; + return element; + } + /** + * Get color class based on metadata.color or event type + */ + getColorClass(event) { + if (event.metadata?.color) { + return `is-${event.metadata.color}`; + } + const typeColors = { + "customer": "is-blue", + "vacation": "is-green", + "break": "is-amber", + "meeting": "is-purple", + "blocked": "is-red" + }; + return typeColors[event.type] || "is-blue"; + } + /** + * Escape HTML to prevent XSS + */ + escapeHtml(text) { + const div = document.createElement("div"); + div.textContent = text; + return div.innerHTML; + } + /** + * Render a GRID group with side-by-side columns + * Used when multiple events start at the same time + */ + renderGridGroup(layout) { + const group = document.createElement("swp-event-group"); + group.classList.add(`cols-${layout.columns.length}`); + group.style.top = `${layout.position.top}px`; + if (layout.stackLevel > 0) { + group.style.marginLeft = `${layout.stackLevel * 15}px`; + group.style.zIndex = `${100 + layout.stackLevel}`; + } + let maxBottom = 0; + for (const event of layout.events) { + const pos = calculateEventPosition(event.start, event.end, this.gridConfig); + const eventBottom = pos.top + pos.height; + if (eventBottom > maxBottom) + maxBottom = eventBottom; + } + const groupHeight = maxBottom - layout.position.top; + group.style.height = `${groupHeight}px`; + layout.columns.forEach((columnEvents) => { + const wrapper = document.createElement("div"); + wrapper.style.position = "relative"; + columnEvents.forEach((event) => { + const eventEl = this.createEventElement(event); + const pos = calculateEventPosition(event.start, event.end, this.gridConfig); + eventEl.style.top = `${pos.top - layout.position.top}px`; + eventEl.style.position = "absolute"; + eventEl.style.left = "0"; + eventEl.style.right = "0"; + wrapper.appendChild(eventEl); + }); + group.appendChild(wrapper); + }); + return group; + } + /** + * Render a STACKED event with margin-left offset + * Used for overlapping events that don't start at the same time + */ + renderStackedEvent(event, stackLevel) { + const element = this.createEventElement(event); + element.dataset.stackLink = JSON.stringify({ stackLevel }); + if (stackLevel > 0) { + element.style.marginLeft = `${stackLevel * 15}px`; + element.style.zIndex = `${100 + stackLevel}`; + } + return element; + } +}; +__name(_EventRenderer, "EventRenderer"); +var EventRenderer = _EventRenderer; + +// src/features/schedule/ScheduleRenderer.ts +var _ScheduleRenderer = class _ScheduleRenderer { + constructor(scheduleService, dateService, gridConfig) { + this.scheduleService = scheduleService; + this.dateService = dateService; + this.gridConfig = gridConfig; + } + /** + * Render unavailable zones for visible columns + * @param container - Calendar container element + * @param filter - Filter with 'date' and 'resource' arrays + */ + async render(container2, filter) { + const dates = filter["date"] || []; + const resourceIds = filter["resource"] || []; + if (dates.length === 0) + return; + const dayColumns = container2.querySelector("swp-day-columns"); + if (!dayColumns) + return; + const columns = dayColumns.querySelectorAll("swp-day-column"); + for (const column of columns) { + const date = column.dataset.date; + const resourceId = column.dataset.resourceId; + if (!date || !resourceId) + continue; + let unavailableLayer = column.querySelector("swp-unavailable-layer"); + if (!unavailableLayer) { + unavailableLayer = document.createElement("swp-unavailable-layer"); + column.insertBefore(unavailableLayer, column.firstChild); + } + unavailableLayer.innerHTML = ""; + const schedule = await this.scheduleService.getScheduleForDate(resourceId, date); + this.renderUnavailableZones(unavailableLayer, schedule); + } + } + /** + * Render unavailable time zones based on schedule + */ + renderUnavailableZones(layer, schedule) { + const dayStartMinutes = this.gridConfig.dayStartHour * 60; + const dayEndMinutes = this.gridConfig.dayEndHour * 60; + const minuteHeight = this.gridConfig.hourHeight / 60; + if (schedule === null) { + const zone = this.createUnavailableZone(0, (dayEndMinutes - dayStartMinutes) * minuteHeight); + layer.appendChild(zone); + return; + } + const workStartMinutes = this.dateService.timeToMinutes(schedule.start); + const workEndMinutes = this.dateService.timeToMinutes(schedule.end); + if (workStartMinutes > dayStartMinutes) { + const top = 0; + const height = (workStartMinutes - dayStartMinutes) * minuteHeight; + const zone = this.createUnavailableZone(top, height); + layer.appendChild(zone); + } + if (workEndMinutes < dayEndMinutes) { + const top = (workEndMinutes - dayStartMinutes) * minuteHeight; + const height = (dayEndMinutes - workEndMinutes) * minuteHeight; + const zone = this.createUnavailableZone(top, height); + layer.appendChild(zone); + } + } + /** + * Create an unavailable zone element + */ + createUnavailableZone(top, height) { + const zone = document.createElement("swp-unavailable-zone"); + zone.style.top = `${top}px`; + zone.style.height = `${height}px`; + return zone; + } +}; +__name(_ScheduleRenderer, "ScheduleRenderer"); +var ScheduleRenderer = _ScheduleRenderer; + +// src/features/headerdrawer/HeaderDrawerRenderer.ts +var _HeaderDrawerRenderer = class _HeaderDrawerRenderer { + constructor(eventBus, gridConfig, headerDrawerManager, eventService, dateService) { + this.eventBus = eventBus; + this.gridConfig = gridConfig; + this.headerDrawerManager = headerDrawerManager; + this.eventService = eventService; + this.dateService = dateService; + this.currentItem = null; + this.container = null; + this.sourceElement = null; + this.wasExpandedBeforeDrag = false; + this.filterTemplate = null; + this.setupListeners(); + } + /** + * Render allDay events into the header drawer with row stacking + * @param filterTemplate - Template for matching events to columns + */ + async render(container2, filter, filterTemplate) { + this.filterTemplate = filterTemplate; + const drawer = container2.querySelector("swp-header-drawer"); + if (!drawer) + return; + const visibleDates = filter["date"] || []; + if (visibleDates.length === 0) + return; + const visibleColumnKeys = this.getVisibleColumnKeysFromDOM(); + if (visibleColumnKeys.length === 0) + return; + const startDate = new Date(visibleDates[0]); + const endDate = new Date(visibleDates[visibleDates.length - 1]); + endDate.setHours(23, 59, 59, 999); + const events = await this.eventService.getByDateRange(startDate, endDate); + const allDayEvents = events.filter((event) => event.allDay !== false); + drawer.innerHTML = ""; + if (allDayEvents.length === 0) + return; + const layouts = this.calculateLayout(allDayEvents, visibleColumnKeys); + const rowCount = Math.max(1, ...layouts.map((l) => l.row)); + layouts.forEach((layout) => { + const item = this.createHeaderItem(layout); + drawer.appendChild(item); + }); + this.headerDrawerManager.expandToRows(rowCount); + } + /** + * Create a header item element from layout + */ + createHeaderItem(layout) { + const { event, columnKey, row, colStart, colEnd } = layout; + const item = document.createElement("swp-header-item"); + item.dataset.eventId = event.id; + item.dataset.itemType = "event"; + item.dataset.start = event.start.toISOString(); + item.dataset.end = event.end.toISOString(); + item.dataset.columnKey = columnKey; + item.textContent = event.title; + const colorClass = this.getColorClass(event); + if (colorClass) + item.classList.add(colorClass); + item.style.gridArea = `${row} / ${colStart} / ${row + 1} / ${colEnd}`; + return item; + } + /** + * Calculate layout for all events with row stacking + * Uses track-based algorithm to find available rows for overlapping events + */ + calculateLayout(events, visibleColumnKeys) { + const tracks = [new Array(visibleColumnKeys.length).fill(false)]; + const layouts = []; + for (const event of events) { + const columnKey = this.buildColumnKeyFromEvent(event); + const startCol = visibleColumnKeys.indexOf(columnKey); + const endColumnKey = this.buildColumnKeyFromEvent(event, event.end); + const endCol = visibleColumnKeys.indexOf(endColumnKey); + if (startCol === -1 && endCol === -1) + continue; + const colStart = Math.max(0, startCol); + const colEnd = (endCol !== -1 ? endCol : visibleColumnKeys.length - 1) + 1; + const row = this.findAvailableRow(tracks, colStart, colEnd); + for (let c = colStart; c < colEnd; c++) { + tracks[row][c] = true; + } + layouts.push({ event, columnKey, row: row + 1, colStart: colStart + 1, colEnd: colEnd + 1 }); + } + return layouts; + } + /** + * Build columnKey from event using FilterTemplate + * Uses the same template that columns use for matching + */ + buildColumnKeyFromEvent(event, date) { + if (!this.filterTemplate) { + const dateStr = this.dateService.getDateKey(date || event.start); + return dateStr; + } + if (date && date.getTime() !== event.start.getTime()) { + const tempEvent = { ...event, start: date }; + return this.filterTemplate.buildKeyFromEvent(tempEvent); + } + return this.filterTemplate.buildKeyFromEvent(event); + } + /** + * Find available row for event spanning columns [colStart, colEnd) + */ + findAvailableRow(tracks, colStart, colEnd) { + for (let row = 0; row < tracks.length; row++) { + let available = true; + for (let c = colStart; c < colEnd; c++) { + if (tracks[row][c]) { + available = false; + break; + } + } + if (available) + return row; + } + tracks.push(new Array(tracks[0].length).fill(false)); + return tracks.length - 1; + } + /** + * Get color class based on event metadata or type + */ + getColorClass(event) { + if (event.metadata?.color) { + return `is-${event.metadata.color}`; + } + const typeColors = { + "customer": "is-blue", + "vacation": "is-green", + "break": "is-amber", + "meeting": "is-purple", + "blocked": "is-red" + }; + return typeColors[event.type] || "is-blue"; + } + /** + * Setup event listeners for drag events + */ + setupListeners() { + this.eventBus.on(CoreEvents.EVENT_DRAG_ENTER_HEADER, (e) => { + const payload = e.detail; + this.handleDragEnter(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_MOVE_HEADER, (e) => { + const payload = e.detail; + this.handleDragMove(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_LEAVE_HEADER, (e) => { + const payload = e.detail; + this.handleDragLeave(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_END, (e) => { + const payload = e.detail; + this.handleDragEnd(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_CANCEL, () => { + this.cleanup(); + }); + } + /** + * Handle drag entering header zone - create preview item + */ + handleDragEnter(payload) { + this.container = document.querySelector("swp-header-drawer"); + if (!this.container) + return; + this.wasExpandedBeforeDrag = this.headerDrawerManager.isExpanded(); + if (!this.wasExpandedBeforeDrag) { + this.headerDrawerManager.expandToRows(1); + } + this.sourceElement = payload.element; + const item = document.createElement("swp-header-item"); + item.dataset.eventId = payload.eventId; + item.dataset.itemType = payload.itemType; + item.dataset.duration = String(payload.duration); + item.dataset.columnKey = payload.sourceColumnKey; + item.textContent = payload.title; + if (payload.colorClass) { + item.classList.add(payload.colorClass); + } + item.classList.add("dragging"); + const col = payload.sourceColumnIndex + 1; + const endCol = col + payload.duration; + item.style.gridArea = `1 / ${col} / 2 / ${endCol}`; + this.container.appendChild(item); + this.currentItem = item; + payload.element.style.visibility = "hidden"; + } + /** + * Handle drag moving within header - update column position + */ + handleDragMove(payload) { + if (!this.currentItem) + return; + const col = payload.columnIndex + 1; + const duration = parseInt(this.currentItem.dataset.duration || "1", 10); + const endCol = col + duration; + this.currentItem.style.gridArea = `1 / ${col} / 2 / ${endCol}`; + this.currentItem.dataset.columnKey = payload.columnKey; + } + /** + * Handle drag leaving header - cleanup for grid→header drag only + */ + handleDragLeave(payload) { + if (payload.source === "grid") { + this.cleanup(); + } + } + /** + * Handle drag end - finalize based on drop target + */ + handleDragEnd(payload) { + if (payload.target === "header") { + if (this.currentItem) { + this.currentItem.classList.remove("dragging"); + this.recalculateDrawerLayout(); + this.currentItem = null; + this.sourceElement = null; + } + } else { + const ghost = document.querySelector(`swp-header-item.drag-ghost[data-event-id="${payload.swpEvent.eventId}"]`); + ghost?.remove(); + this.recalculateDrawerLayout(); + } + } + /** + * Recalculate layout for all items currently in the drawer + * Called after drop to reposition items and adjust height + */ + recalculateDrawerLayout() { + const drawer = document.querySelector("swp-header-drawer"); + if (!drawer) + return; + const items = Array.from(drawer.querySelectorAll("swp-header-item")); + if (items.length === 0) + return; + const visibleColumnKeys = this.getVisibleColumnKeysFromDOM(); + if (visibleColumnKeys.length === 0) + return; + const itemData = items.map((item) => ({ + element: item, + columnKey: item.dataset.columnKey || "", + duration: parseInt(item.dataset.duration || "1", 10) + })); + const tracks = [new Array(visibleColumnKeys.length).fill(false)]; + for (const item of itemData) { + const startCol = visibleColumnKeys.indexOf(item.columnKey); + if (startCol === -1) + continue; + const colStart = startCol; + const colEnd = Math.min(startCol + item.duration, visibleColumnKeys.length); + const row = this.findAvailableRow(tracks, colStart, colEnd); + for (let c = colStart; c < colEnd; c++) { + tracks[row][c] = true; + } + item.element.style.gridArea = `${row + 1} / ${colStart + 1} / ${row + 2} / ${colEnd + 1}`; + } + const rowCount = tracks.length; + this.headerDrawerManager.expandToRows(rowCount); + } + /** + * Get visible column keys from DOM (preserves order for multi-resource views) + * Uses filterTemplate.buildKeyFromColumn() for consistent key format with events + */ + getVisibleColumnKeysFromDOM() { + if (!this.filterTemplate) + return []; + const columns = document.querySelectorAll("swp-day-column"); + const columnKeys = []; + columns.forEach((col) => { + const columnKey = this.filterTemplate.buildKeyFromColumn(col); + if (columnKey) + columnKeys.push(columnKey); + }); + return columnKeys; + } + /** + * Cleanup preview item and restore source visibility + */ + cleanup() { + this.currentItem?.remove(); + this.currentItem = null; + if (this.sourceElement) { + this.sourceElement.style.visibility = ""; + this.sourceElement = null; + } + if (!this.wasExpandedBeforeDrag) { + this.headerDrawerManager.collapse(); + } + } +}; +__name(_HeaderDrawerRenderer, "HeaderDrawerRenderer"); +var HeaderDrawerRenderer = _HeaderDrawerRenderer; + +// src/storage/schedules/ScheduleOverrideStore.ts +var _ScheduleOverrideStore = class _ScheduleOverrideStore { + constructor() { + this.storeName = _ScheduleOverrideStore.STORE_NAME; + } + create(db) { + const store = db.createObjectStore(_ScheduleOverrideStore.STORE_NAME, { keyPath: "id" }); + store.createIndex("resourceId", "resourceId", { unique: false }); + store.createIndex("date", "date", { unique: false }); + store.createIndex("resourceId_date", ["resourceId", "date"], { unique: true }); + store.createIndex("syncStatus", "syncStatus", { unique: false }); + } +}; +__name(_ScheduleOverrideStore, "ScheduleOverrideStore"); +var ScheduleOverrideStore = _ScheduleOverrideStore; +ScheduleOverrideStore.STORE_NAME = "scheduleOverrides"; + +// src/storage/schedules/ScheduleOverrideService.ts +var _ScheduleOverrideService = class _ScheduleOverrideService { + constructor(context) { + this.context = context; + } + get db() { + return this.context.getDatabase(); + } + /** + * Get override for a specific resource and date + */ + async getOverride(resourceId, date) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([ScheduleOverrideStore.STORE_NAME], "readonly"); + const store = transaction.objectStore(ScheduleOverrideStore.STORE_NAME); + const index = store.index("resourceId_date"); + const request = index.get([resourceId, date]); + request.onsuccess = () => { + resolve(request.result || null); + }; + request.onerror = () => { + reject(new Error(`Failed to get override for ${resourceId} on ${date}: ${request.error}`)); + }; + }); + } + /** + * Get all overrides for a resource + */ + async getByResource(resourceId) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([ScheduleOverrideStore.STORE_NAME], "readonly"); + const store = transaction.objectStore(ScheduleOverrideStore.STORE_NAME); + const index = store.index("resourceId"); + const request = index.getAll(resourceId); + request.onsuccess = () => { + resolve(request.result || []); + }; + request.onerror = () => { + reject(new Error(`Failed to get overrides for ${resourceId}: ${request.error}`)); + }; + }); + } + /** + * Get overrides for a date range + */ + async getByDateRange(resourceId, startDate, endDate) { + const all = await this.getByResource(resourceId); + return all.filter((o) => o.date >= startDate && o.date <= endDate); + } + /** + * Save an override + */ + async save(override) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([ScheduleOverrideStore.STORE_NAME], "readwrite"); + const store = transaction.objectStore(ScheduleOverrideStore.STORE_NAME); + const request = store.put(override); + request.onsuccess = () => resolve(); + request.onerror = () => { + reject(new Error(`Failed to save override ${override.id}: ${request.error}`)); + }; + }); + } + /** + * Delete an override + */ + async delete(id) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([ScheduleOverrideStore.STORE_NAME], "readwrite"); + const store = transaction.objectStore(ScheduleOverrideStore.STORE_NAME); + const request = store.delete(id); + request.onsuccess = () => resolve(); + request.onerror = () => { + reject(new Error(`Failed to delete override ${id}: ${request.error}`)); + }; + }); + } +}; +__name(_ScheduleOverrideService, "ScheduleOverrideService"); +var ScheduleOverrideService = _ScheduleOverrideService; + +// src/storage/schedules/ResourceScheduleService.ts +var _ResourceScheduleService = class _ResourceScheduleService { + constructor(resourceService, overrideService, dateService) { + this.resourceService = resourceService; + this.overrideService = overrideService; + this.dateService = dateService; + } + /** + * Get effective schedule for a resource on a specific date + * + * @param resourceId - Resource ID + * @param date - Date string "YYYY-MM-DD" + * @returns ITimeSlot or null (fri/closed) + */ + async getScheduleForDate(resourceId, date) { + const override = await this.overrideService.getOverride(resourceId, date); + if (override) { + return override.schedule; + } + const resource = await this.resourceService.get(resourceId); + if (!resource || !resource.defaultSchedule) { + return null; + } + const weekDay = this.dateService.getISOWeekDay(date); + return resource.defaultSchedule[weekDay] || null; + } + /** + * Get schedules for multiple dates + * + * @param resourceId - Resource ID + * @param dates - Array of date strings "YYYY-MM-DD" + * @returns Map of date -> ITimeSlot | null + */ + async getSchedulesForDates(resourceId, dates) { + const result = /* @__PURE__ */ new Map(); + const resource = await this.resourceService.get(resourceId); + const overrides = dates.length > 0 ? await this.overrideService.getByDateRange(resourceId, dates[0], dates[dates.length - 1]) : []; + const overrideMap = new Map(overrides.map((o) => [o.date, o.schedule])); + for (const date of dates) { + if (overrideMap.has(date)) { + result.set(date, overrideMap.get(date)); + continue; + } + if (resource?.defaultSchedule) { + const weekDay = this.dateService.getISOWeekDay(date); + result.set(date, resource.defaultSchedule[weekDay] || null); + } else { + result.set(date, null); + } + } + return result; + } +}; +__name(_ResourceScheduleService, "ResourceScheduleService"); +var ResourceScheduleService = _ResourceScheduleService; + +// src/types/SwpEvent.ts +var _SwpEvent = class _SwpEvent { + constructor(element, columnKey, start, end) { + this.element = element; + this.columnKey = columnKey; + this._start = start; + this._end = end; + } + /** Event ID from element.dataset.eventId */ + get eventId() { + return this.element.dataset.eventId || ""; + } + get start() { + return this._start; + } + get end() { + return this._end; + } + /** Duration in minutes */ + get durationMinutes() { + return (this._end.getTime() - this._start.getTime()) / (1e3 * 60); + } + /** Duration in milliseconds */ + get durationMs() { + return this._end.getTime() - this._start.getTime(); + } + /** + * Factory: Create SwpEvent from element + columnKey + * Reads top/height from element.style to calculate start/end + * @param columnKey - Opaque column identifier (do NOT parse - use only for matching) + * @param date - Date string (YYYY-MM-DD) for time calculations + */ + static fromElement(element, columnKey, date, gridConfig) { + const topPixels = parseFloat(element.style.top) || 0; + const heightPixels = parseFloat(element.style.height) || 0; + const startMinutesFromGrid = topPixels / gridConfig.hourHeight * 60; + const totalMinutes = gridConfig.dayStartHour * 60 + startMinutesFromGrid; + const start = new Date(date); + start.setHours(Math.floor(totalMinutes / 60), totalMinutes % 60, 0, 0); + const durationMinutes = heightPixels / gridConfig.hourHeight * 60; + const end = new Date(start.getTime() + durationMinutes * 60 * 1e3); + return new _SwpEvent(element, columnKey, start, end); + } +}; +__name(_SwpEvent, "SwpEvent"); +var SwpEvent = _SwpEvent; + +// src/managers/DragDropManager.ts +var _DragDropManager = class _DragDropManager { + constructor(eventBus, gridConfig) { + this.eventBus = eventBus; + this.gridConfig = gridConfig; + this.dragState = null; + this.mouseDownPosition = null; + this.pendingElement = null; + this.pendingMouseOffset = null; + this.container = null; + this.inHeader = false; + this.DRAG_THRESHOLD = 5; + this.INTERPOLATION_FACTOR = 0.3; + this.handlePointerDown = (e) => { + const target = e.target; + if (target.closest("swp-resize-handle")) + return; + const eventElement = target.closest("swp-event"); + const headerItem = target.closest("swp-header-item"); + const draggable = eventElement || headerItem; + if (!draggable) + return; + this.mouseDownPosition = { x: e.clientX, y: e.clientY }; + this.pendingElement = draggable; + const rect = draggable.getBoundingClientRect(); + this.pendingMouseOffset = { + x: e.clientX - rect.left, + y: e.clientY - rect.top + }; + draggable.setPointerCapture(e.pointerId); + }; + this.handlePointerMove = (e) => { + if (!this.mouseDownPosition || !this.pendingElement) { + if (this.dragState) { + this.updateDragTarget(e); + } + return; + } + const deltaX = Math.abs(e.clientX - this.mouseDownPosition.x); + const deltaY = Math.abs(e.clientY - this.mouseDownPosition.y); + const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + if (distance < this.DRAG_THRESHOLD) + return; + this.initializeDrag(this.pendingElement, this.pendingMouseOffset, e); + this.mouseDownPosition = null; + this.pendingElement = null; + this.pendingMouseOffset = null; + }; + this.handlePointerUp = (_e) => { + this.mouseDownPosition = null; + this.pendingElement = null; + this.pendingMouseOffset = null; + if (!this.dragState) + return; + cancelAnimationFrame(this.dragState.animationId); + if (this.dragState.dragSource === "header") { + this.handleHeaderItemDragEnd(); + } else { + this.handleGridEventDragEnd(); + } + this.dragState.element.classList.remove("dragging"); + this.dragState = null; + this.inHeader = false; + }; + this.animateDrag = () => { + if (!this.dragState) + return; + const diff2 = this.dragState.targetY - this.dragState.currentY; + if (Math.abs(diff2) <= 0.5) { + this.dragState.animationId = 0; + return; + } + this.dragState.currentY += diff2 * this.INTERPOLATION_FACTOR; + this.dragState.element.style.top = `${this.dragState.currentY}px`; + if (this.dragState.columnElement) { + const payload = { + eventId: this.dragState.eventId, + element: this.dragState.element, + currentY: this.dragState.currentY, + columnElement: this.dragState.columnElement + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_MOVE, payload); + } + this.dragState.animationId = requestAnimationFrame(this.animateDrag); + }; + this.setupScrollListener(); + } + setupScrollListener() { + this.eventBus.on(CoreEvents.EDGE_SCROLL_TICK, (e) => { + if (!this.dragState) + return; + const { scrollDelta } = e.detail; + this.dragState.targetY += scrollDelta; + this.dragState.currentY += scrollDelta; + this.dragState.element.style.top = `${this.dragState.currentY}px`; + }); + } + /** + * Initialize drag-drop on a container element + */ + init(container2) { + this.container = container2; + container2.addEventListener("pointerdown", this.handlePointerDown); + document.addEventListener("pointermove", this.handlePointerMove); + document.addEventListener("pointerup", this.handlePointerUp); + } + /** + * Handle drag end for header items + */ + handleHeaderItemDragEnd() { + if (!this.dragState) + return; + if (!this.inHeader && this.dragState.currentColumn) { + const gridEvent = this.dragState.currentColumn.querySelector(`swp-event[data-event-id="${this.dragState.eventId}"]`); + if (gridEvent) { + const columnKey = this.dragState.currentColumn.dataset.columnKey || ""; + const date = this.dragState.currentColumn.dataset.date || ""; + const swpEvent = SwpEvent.fromElement(gridEvent, columnKey, date, this.gridConfig); + const payload = { + swpEvent, + sourceColumnKey: this.dragState.sourceColumnKey, + target: "grid" + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_END, payload); + } + } + } + /** + * Handle drag end for grid events + */ + handleGridEventDragEnd() { + if (!this.dragState || !this.dragState.columnElement) + return; + const snappedY = snapToGrid(this.dragState.currentY, this.gridConfig); + this.dragState.element.style.top = `${snappedY}px`; + this.dragState.ghostElement?.remove(); + const columnKey = this.dragState.columnElement.dataset.columnKey || ""; + const date = this.dragState.columnElement.dataset.date || ""; + const swpEvent = SwpEvent.fromElement(this.dragState.element, columnKey, date, this.gridConfig); + const payload = { + swpEvent, + sourceColumnKey: this.dragState.sourceColumnKey, + target: this.inHeader ? "header" : "grid" + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_END, payload); + } + initializeDrag(element, mouseOffset, e) { + const eventId = element.dataset.eventId || ""; + const isHeaderItem = element.tagName.toLowerCase() === "swp-header-item"; + const columnElement = element.closest("swp-day-column"); + if (!isHeaderItem && !columnElement) + return; + if (isHeaderItem) { + this.initializeHeaderItemDrag(element, mouseOffset, eventId); + } else { + this.initializeGridEventDrag(element, mouseOffset, e, columnElement, eventId); + } + } + /** + * Initialize drag for a header item (allDay event) + */ + initializeHeaderItemDrag(element, mouseOffset, eventId) { + element.classList.add("dragging"); + this.dragState = { + eventId, + element, + ghostElement: null, + // No ghost for header items + startY: 0, + mouseOffset, + columnElement: null, + currentColumn: null, + targetY: 0, + currentY: 0, + animationId: 0, + sourceColumnKey: "", + // Will be set from header item data + dragSource: "header" + }; + this.inHeader = true; + } + /** + * Initialize drag for a grid event + */ + initializeGridEventDrag(element, mouseOffset, e, columnElement, eventId) { + const elementRect = element.getBoundingClientRect(); + const columnRect = columnElement.getBoundingClientRect(); + const startY = elementRect.top - columnRect.top; + const group = element.closest("swp-event-group"); + if (group) { + const eventsLayer = columnElement.querySelector("swp-events-layer"); + if (eventsLayer) { + eventsLayer.appendChild(element); + } + } + element.style.position = "absolute"; + element.style.top = `${startY}px`; + element.style.left = "2px"; + element.style.right = "2px"; + element.style.marginLeft = "0"; + const ghostElement = element.cloneNode(true); + ghostElement.classList.add("drag-ghost"); + ghostElement.style.opacity = "0.3"; + ghostElement.style.pointerEvents = "none"; + element.parentNode?.insertBefore(ghostElement, element); + element.classList.add("dragging"); + const targetY = e.clientY - columnRect.top - mouseOffset.y; + this.dragState = { + eventId, + element, + ghostElement, + startY, + mouseOffset, + columnElement, + currentColumn: columnElement, + targetY: Math.max(0, targetY), + currentY: startY, + animationId: 0, + sourceColumnKey: columnElement.dataset.columnKey || "", + dragSource: "grid" + }; + const payload = { + eventId, + element, + ghostElement, + startY, + mouseOffset, + columnElement + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_START, payload); + this.animateDrag(); + } + updateDragTarget(e) { + if (!this.dragState) + return; + this.checkHeaderZone(e); + if (this.inHeader) + return; + const columnAtPoint = this.getColumnAtPoint(e.clientX); + if (this.dragState.dragSource === "header" && columnAtPoint && !this.dragState.currentColumn) { + this.dragState.currentColumn = columnAtPoint; + this.dragState.columnElement = columnAtPoint; + } + if (columnAtPoint && columnAtPoint !== this.dragState.currentColumn && this.dragState.currentColumn) { + const payload = { + eventId: this.dragState.eventId, + element: this.dragState.element, + previousColumn: this.dragState.currentColumn, + newColumn: columnAtPoint, + currentY: this.dragState.currentY + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_COLUMN_CHANGE, payload); + this.dragState.currentColumn = columnAtPoint; + this.dragState.columnElement = columnAtPoint; + } + if (!this.dragState.columnElement) + return; + const columnRect = this.dragState.columnElement.getBoundingClientRect(); + const targetY = e.clientY - columnRect.top - this.dragState.mouseOffset.y; + this.dragState.targetY = Math.max(0, targetY); + if (!this.dragState.animationId) { + this.animateDrag(); + } + } + /** + * Check if pointer is in header zone and emit appropriate events + */ + checkHeaderZone(e) { + if (!this.dragState) + return; + const headerViewport = document.querySelector("swp-header-viewport"); + if (!headerViewport) + return; + const rect = headerViewport.getBoundingClientRect(); + const isInHeader = e.clientY < rect.bottom; + if (isInHeader && !this.inHeader) { + this.inHeader = true; + if (this.dragState.dragSource === "grid" && this.dragState.columnElement) { + const payload = { + eventId: this.dragState.eventId, + element: this.dragState.element, + sourceColumnIndex: this.getColumnIndex(this.dragState.columnElement), + sourceColumnKey: this.dragState.columnElement.dataset.columnKey || "", + title: this.dragState.element.querySelector("swp-event-title")?.textContent || "", + colorClass: [...this.dragState.element.classList].find((c) => c.startsWith("is-")), + itemType: "event", + duration: 1 + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_ENTER_HEADER, payload); + } + } else if (!isInHeader && this.inHeader) { + this.inHeader = false; + const targetColumn = this.getColumnAtPoint(e.clientX); + if (this.dragState.dragSource === "header") { + const payload = { + eventId: this.dragState.eventId, + source: "header", + element: this.dragState.element, + targetColumn: targetColumn || void 0, + start: this.dragState.element.dataset.start ? new Date(this.dragState.element.dataset.start) : void 0, + end: this.dragState.element.dataset.end ? new Date(this.dragState.element.dataset.end) : void 0, + title: this.dragState.element.textContent || "", + colorClass: [...this.dragState.element.classList].find((c) => c.startsWith("is-")) + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_LEAVE_HEADER, payload); + if (targetColumn) { + const newElement = targetColumn.querySelector(`swp-event[data-event-id="${this.dragState.eventId}"]`); + if (newElement) { + this.dragState.element = newElement; + this.dragState.columnElement = targetColumn; + this.dragState.currentColumn = targetColumn; + this.animateDrag(); + } + } + } else { + const payload = { + eventId: this.dragState.eventId, + source: "grid" + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_LEAVE_HEADER, payload); + } + } else if (isInHeader) { + const column = this.getColumnAtX(e.clientX); + if (column) { + const payload = { + eventId: this.dragState.eventId, + columnIndex: this.getColumnIndex(column), + columnKey: column.dataset.columnKey || "" + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_MOVE_HEADER, payload); + } + } + } + /** + * Get column index (0-based) for a column element + */ + getColumnIndex(column) { + if (!this.container || !column) + return 0; + const columns = Array.from(this.container.querySelectorAll("swp-day-column")); + return columns.indexOf(column); + } + /** + * Get column at X coordinate (alias for getColumnAtPoint) + */ + getColumnAtX(clientX) { + return this.getColumnAtPoint(clientX); + } + /** + * Find column element at given X coordinate + */ + getColumnAtPoint(clientX) { + if (!this.container) + return null; + const columns = this.container.querySelectorAll("swp-day-column"); + for (const col of columns) { + const rect = col.getBoundingClientRect(); + if (clientX >= rect.left && clientX <= rect.right) { + return col; + } + } + return null; + } + /** + * Cancel drag and animate back to start position + */ + cancelDrag() { + if (!this.dragState) + return; + cancelAnimationFrame(this.dragState.animationId); + const { element, ghostElement, startY, eventId } = this.dragState; + element.style.transition = "top 200ms ease-out"; + element.style.top = `${startY}px`; + setTimeout(() => { + ghostElement?.remove(); + element.style.transition = ""; + element.classList.remove("dragging"); + }, 200); + const payload = { + eventId, + element, + startY + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_CANCEL, payload); + this.dragState = null; + this.inHeader = false; + } +}; +__name(_DragDropManager, "DragDropManager"); +var DragDropManager = _DragDropManager; + +// src/managers/EdgeScrollManager.ts +var _EdgeScrollManager = class _EdgeScrollManager { + constructor(eventBus) { + this.eventBus = eventBus; + this.scrollableContent = null; + this.timeGrid = null; + this.draggedElement = null; + this.scrollRAF = null; + this.mouseY = 0; + this.isDragging = false; + this.isScrolling = false; + this.lastTs = 0; + this.rect = null; + this.initialScrollTop = 0; + this.OUTER_ZONE = 100; + this.INNER_ZONE = 50; + this.SLOW_SPEED = 140; + this.FAST_SPEED = 640; + this.trackMouse = (e) => { + if (this.isDragging) { + this.mouseY = e.clientY; + } + }; + this.scrollTick = (ts) => { + if (!this.isDragging || !this.scrollableContent) + return; + const dt = this.lastTs ? (ts - this.lastTs) / 1e3 : 0; + this.lastTs = ts; + this.rect ?? (this.rect = this.scrollableContent.getBoundingClientRect()); + const velocity = this.calculateVelocity(); + if (velocity !== 0 && !this.isAtBoundary(velocity)) { + const scrollDelta = velocity * dt; + this.scrollableContent.scrollTop += scrollDelta; + this.rect = null; + this.eventBus.emit(CoreEvents.EDGE_SCROLL_TICK, { scrollDelta }); + this.setScrollingState(true); + } else { + this.setScrollingState(false); + } + this.scrollRAF = requestAnimationFrame(this.scrollTick); + }; + this.subscribeToEvents(); + document.addEventListener("pointermove", this.trackMouse); + } + init(scrollableContent) { + this.scrollableContent = scrollableContent; + this.timeGrid = scrollableContent.querySelector("swp-time-grid"); + this.scrollableContent.style.scrollBehavior = "auto"; + } + subscribeToEvents() { + this.eventBus.on(CoreEvents.EVENT_DRAG_START, (event) => { + const payload = event.detail; + this.draggedElement = payload.element; + this.startDrag(); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_END, () => this.stopDrag()); + this.eventBus.on(CoreEvents.EVENT_DRAG_CANCEL, () => this.stopDrag()); + } + startDrag() { + this.isDragging = true; + this.isScrolling = false; + this.lastTs = 0; + this.initialScrollTop = this.scrollableContent?.scrollTop ?? 0; + if (this.scrollRAF === null) { + this.scrollRAF = requestAnimationFrame(this.scrollTick); + } + } + stopDrag() { + this.isDragging = false; + this.setScrollingState(false); + if (this.scrollRAF !== null) { + cancelAnimationFrame(this.scrollRAF); + this.scrollRAF = null; + } + this.rect = null; + this.lastTs = 0; + this.initialScrollTop = 0; + } + calculateVelocity() { + if (!this.rect) + return 0; + const distTop = this.mouseY - this.rect.top; + const distBot = this.rect.bottom - this.mouseY; + if (distTop < this.INNER_ZONE) + return -this.FAST_SPEED; + if (distTop < this.OUTER_ZONE) + return -this.SLOW_SPEED; + if (distBot < this.INNER_ZONE) + return this.FAST_SPEED; + if (distBot < this.OUTER_ZONE) + return this.SLOW_SPEED; + return 0; + } + isAtBoundary(velocity) { + if (!this.scrollableContent || !this.timeGrid || !this.draggedElement) + return false; + const atTop = this.scrollableContent.scrollTop <= 0 && velocity < 0; + const atBottom = velocity > 0 && this.draggedElement.getBoundingClientRect().bottom >= this.timeGrid.getBoundingClientRect().bottom; + return atTop || atBottom; + } + setScrollingState(scrolling) { + if (this.isScrolling === scrolling) + return; + this.isScrolling = scrolling; + if (scrolling) { + this.eventBus.emit(CoreEvents.EDGE_SCROLL_STARTED, {}); + } else { + this.initialScrollTop = this.scrollableContent?.scrollTop ?? 0; + this.eventBus.emit(CoreEvents.EDGE_SCROLL_STOPPED, {}); + } + } +}; +__name(_EdgeScrollManager, "EdgeScrollManager"); +var EdgeScrollManager = _EdgeScrollManager; + +// src/managers/ResizeManager.ts +var _ResizeManager = class _ResizeManager { + constructor(eventBus, gridConfig, dateService) { + this.eventBus = eventBus; + this.gridConfig = gridConfig; + this.dateService = dateService; + this.container = null; + this.resizeState = null; + this.Z_INDEX_RESIZING = "1000"; + this.ANIMATION_SPEED = 0.35; + this.MIN_HEIGHT_MINUTES = 15; + this.handleMouseOver = (e) => { + const target = e.target; + const eventElement = target.closest("swp-event"); + if (!eventElement || this.resizeState) + return; + if (!eventElement.querySelector(":scope > swp-resize-handle")) { + const handle = this.createResizeHandle(); + eventElement.appendChild(handle); + } + }; + this.handlePointerDown = (e) => { + const handle = e.target.closest("swp-resize-handle"); + if (!handle) + return; + const element = handle.parentElement; + if (!element) + return; + const eventId = element.dataset.eventId || ""; + const startHeight = element.offsetHeight; + const startDurationMinutes = pixelsToMinutes(startHeight, this.gridConfig); + const container2 = element.closest("swp-event-group") ?? element; + const prevZIndex = container2.style.zIndex; + this.resizeState = { + eventId, + element, + handleElement: handle, + startY: e.clientY, + startHeight, + startDurationMinutes, + pointerId: e.pointerId, + prevZIndex, + // Animation state + currentHeight: startHeight, + targetHeight: startHeight, + animationId: null + }; + container2.style.zIndex = this.Z_INDEX_RESIZING; + try { + handle.setPointerCapture(e.pointerId); + } catch (err) { + console.warn("Pointer capture failed:", err); + } + document.documentElement.classList.add("swp--resizing"); + this.eventBus.emit(CoreEvents.EVENT_RESIZE_START, { + eventId, + element, + startHeight + }); + e.preventDefault(); + }; + this.handlePointerMove = (e) => { + if (!this.resizeState) + return; + const deltaY = e.clientY - this.resizeState.startY; + const minHeight = this.MIN_HEIGHT_MINUTES / 60 * this.gridConfig.hourHeight; + const newHeight = Math.max(minHeight, this.resizeState.startHeight + deltaY); + this.resizeState.targetHeight = newHeight; + if (this.resizeState.animationId === null) { + this.animateHeight(); + } + }; + this.animateHeight = () => { + if (!this.resizeState) + return; + const diff2 = this.resizeState.targetHeight - this.resizeState.currentHeight; + if (Math.abs(diff2) < 0.5) { + this.resizeState.animationId = null; + return; + } + this.resizeState.currentHeight += diff2 * this.ANIMATION_SPEED; + this.resizeState.element.style.height = `${this.resizeState.currentHeight}px`; + this.updateTimestampDisplay(); + this.resizeState.animationId = requestAnimationFrame(this.animateHeight); + }; + this.handlePointerUp = (e) => { + if (!this.resizeState) + return; + if (this.resizeState.animationId !== null) { + cancelAnimationFrame(this.resizeState.animationId); + } + try { + this.resizeState.handleElement.releasePointerCapture(e.pointerId); + } catch (err) { + console.warn("Pointer release failed:", err); + } + this.snapToGridFinal(); + this.updateTimestampDisplay(); + const container2 = this.resizeState.element.closest("swp-event-group") ?? this.resizeState.element; + container2.style.zIndex = this.resizeState.prevZIndex; + document.documentElement.classList.remove("swp--resizing"); + const column = this.resizeState.element.closest("swp-day-column"); + const columnKey = column?.dataset.columnKey || ""; + const date = column?.dataset.date || ""; + const swpEvent = SwpEvent.fromElement(this.resizeState.element, columnKey, date, this.gridConfig); + this.eventBus.emit(CoreEvents.EVENT_RESIZE_END, { + swpEvent + }); + this.resizeState = null; + }; + } + /** + * Initialize resize functionality on container + */ + init(container2) { + this.container = container2; + container2.addEventListener("mouseover", this.handleMouseOver, true); + document.addEventListener("pointerdown", this.handlePointerDown, true); + document.addEventListener("pointermove", this.handlePointerMove, true); + document.addEventListener("pointerup", this.handlePointerUp, true); + } + /** + * Create resize handle element + */ + createResizeHandle() { + const handle = document.createElement("swp-resize-handle"); + handle.setAttribute("aria-label", "Resize event"); + handle.setAttribute("role", "separator"); + return handle; + } + /** + * Update timestamp display with snapped end time + */ + updateTimestampDisplay() { + if (!this.resizeState) + return; + const timeEl = this.resizeState.element.querySelector("swp-event-time"); + if (!timeEl) + return; + const top = parseFloat(this.resizeState.element.style.top) || 0; + const startMinutesFromGrid = pixelsToMinutes(top, this.gridConfig); + const startMinutes = this.gridConfig.dayStartHour * 60 + startMinutesFromGrid; + const snappedHeight = snapToGrid(this.resizeState.currentHeight, this.gridConfig); + const durationMinutes = pixelsToMinutes(snappedHeight, this.gridConfig); + const endMinutes = startMinutes + durationMinutes; + const start = this.minutesToDate(startMinutes); + const end = this.minutesToDate(endMinutes); + timeEl.textContent = this.dateService.formatTimeRange(start, end); + } + /** + * Convert minutes since midnight to Date + */ + minutesToDate(minutes) { + const date = /* @__PURE__ */ new Date(); + date.setHours(Math.floor(minutes / 60) % 24, minutes % 60, 0, 0); + return date; + } + /** + * Snap final height to grid interval + */ + snapToGridFinal() { + if (!this.resizeState) + return; + const currentHeight = this.resizeState.element.offsetHeight; + const snappedHeight = snapToGrid(currentHeight, this.gridConfig); + const minHeight = minutesToPixels(this.MIN_HEIGHT_MINUTES, this.gridConfig); + const finalHeight = Math.max(minHeight, snappedHeight); + this.resizeState.element.style.height = `${finalHeight}px`; + this.resizeState.currentHeight = finalHeight; + } +}; +__name(_ResizeManager, "ResizeManager"); +var ResizeManager = _ResizeManager; + +// src/managers/EventPersistenceManager.ts +var _EventPersistenceManager = class _EventPersistenceManager { + constructor(eventService, eventBus, dateService) { + this.eventService = eventService; + this.eventBus = eventBus; + this.dateService = dateService; + this.handleDragEnd = async (e) => { + const payload = e.detail; + const { swpEvent } = payload; + const event = await this.eventService.get(swpEvent.eventId); + if (!event) { + console.warn(`EventPersistenceManager: Event ${swpEvent.eventId} not found`); + return; + } + const { resource } = this.dateService.parseColumnKey(swpEvent.columnKey); + const updatedEvent = { + ...event, + start: swpEvent.start, + end: swpEvent.end, + resourceId: resource ?? event.resourceId, + allDay: payload.target === "header", + syncStatus: "pending" + }; + await this.eventService.save(updatedEvent); + const updatePayload = { + eventId: updatedEvent.id, + sourceColumnKey: payload.sourceColumnKey, + targetColumnKey: swpEvent.columnKey + }; + this.eventBus.emit(CoreEvents.EVENT_UPDATED, updatePayload); + }; + this.handleResizeEnd = async (e) => { + const payload = e.detail; + const { swpEvent } = payload; + const event = await this.eventService.get(swpEvent.eventId); + if (!event) { + console.warn(`EventPersistenceManager: Event ${swpEvent.eventId} not found`); + return; + } + const updatedEvent = { + ...event, + end: swpEvent.end, + syncStatus: "pending" + }; + await this.eventService.save(updatedEvent); + const updatePayload = { + eventId: updatedEvent.id, + sourceColumnKey: swpEvent.columnKey, + targetColumnKey: swpEvent.columnKey + }; + this.eventBus.emit(CoreEvents.EVENT_UPDATED, updatePayload); + }; + this.setupListeners(); + } + setupListeners() { + this.eventBus.on(CoreEvents.EVENT_DRAG_END, this.handleDragEnd); + this.eventBus.on(CoreEvents.EVENT_RESIZE_END, this.handleResizeEnd); + } +}; +__name(_EventPersistenceManager, "EventPersistenceManager"); +var EventPersistenceManager = _EventPersistenceManager; + +// src/CompositionRoot.ts +var defaultTimeFormatConfig = { + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + use24HourFormat: true, + locale: "da-DK", + dateFormat: "locale", + showSeconds: false +}; +var defaultGridConfig = { + hourHeight: 64, + dayStartHour: 6, + dayEndHour: 18, + snapInterval: 15, + gridStartThresholdMinutes: 30 +}; +function createContainer() { + const container2 = new Container(); + const builder = container2.builder(); + builder.registerInstance(defaultTimeFormatConfig).as("ITimeFormatConfig"); + builder.registerInstance(defaultGridConfig).as("IGridConfig"); + builder.registerType(EventBus).as("EventBus"); + builder.registerType(EventBus).as("IEventBus"); + builder.registerType(DateService).as("DateService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("ITimeFormatConfig"), + void 0 + ] + }); + builder.registerType(IndexedDBContext).as("IndexedDBContext").autoWire({ + mapResolvers: [ + (c) => c.resolveTypeAll("IStore") + ] + }); + builder.registerType(EventStore).as("IStore"); + builder.registerType(ResourceStore).as("IStore"); + builder.registerType(BookingStore).as("IStore"); + builder.registerType(CustomerStore).as("IStore"); + builder.registerType(TeamStore).as("IStore"); + builder.registerType(DepartmentStore).as("IStore"); + builder.registerType(ScheduleOverrideStore).as("IStore"); + builder.registerType(AuditStore).as("IStore"); + builder.registerType(SettingsStore).as("IStore"); + builder.registerType(ViewConfigStore).as("IStore"); + builder.registerType(EventService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(EventService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(EventService).as("EventService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ResourceService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ResourceService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ResourceService).as("ResourceService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(BookingService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(BookingService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(BookingService).as("BookingService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(CustomerService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(CustomerService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(CustomerService).as("CustomerService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(TeamService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(TeamService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(TeamService).as("TeamService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(DepartmentService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(DepartmentService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(DepartmentService).as("DepartmentService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(SettingsService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(SettingsService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(SettingsService).as("SettingsService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ViewConfigService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ViewConfigService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ViewConfigService).as("ViewConfigService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(MockEventRepository).as("IApiRepository"); + builder.registerType(MockEventRepository).as("IApiRepository"); + builder.registerType(MockResourceRepository).as("IApiRepository"); + builder.registerType(MockResourceRepository).as("IApiRepository"); + builder.registerType(MockBookingRepository).as("IApiRepository"); + builder.registerType(MockBookingRepository).as("IApiRepository"); + builder.registerType(MockCustomerRepository).as("IApiRepository"); + builder.registerType(MockCustomerRepository).as("IApiRepository"); + builder.registerType(MockAuditRepository).as("IApiRepository"); + builder.registerType(MockAuditRepository).as("IApiRepository"); + builder.registerType(MockTeamRepository).as("IApiRepository"); + builder.registerType(MockTeamRepository).as("IApiRepository"); + builder.registerType(MockDepartmentRepository).as("IApiRepository"); + builder.registerType(MockDepartmentRepository).as("IApiRepository"); + builder.registerType(MockSettingsRepository).as("IApiRepository"); + builder.registerType(MockSettingsRepository).as("IApiRepository"); + builder.registerType(MockViewConfigRepository).as("IApiRepository"); + builder.registerType(MockViewConfigRepository).as("IApiRepository"); + builder.registerType(AuditService).as("AuditService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(DataSeeder).as("DataSeeder").autoWire({ + mapResolvers: [ + (c) => c.resolveTypeAll("IEntityService"), + (c) => c.resolveTypeAll("IApiRepository") + ] + }); + builder.registerType(ScheduleOverrideService).as("ScheduleOverrideService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext") + ] + }); + builder.registerType(ResourceScheduleService).as("ResourceScheduleService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("ResourceService"), + (c) => c.resolveType("ScheduleOverrideService"), + (c) => c.resolveType("DateService") + ] + }); + builder.registerType(EventRenderer).as("EventRenderer").autoWire({ + mapResolvers: [ + (c) => c.resolveType("EventService"), + (c) => c.resolveType("DateService"), + (c) => c.resolveType("IGridConfig"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ScheduleRenderer).as("ScheduleRenderer").autoWire({ + mapResolvers: [ + (c) => c.resolveType("ResourceScheduleService"), + (c) => c.resolveType("DateService"), + (c) => c.resolveType("IGridConfig") + ] + }); + builder.registerType(HeaderDrawerRenderer).as("HeaderDrawerRenderer").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IEventBus"), + (c) => c.resolveType("IGridConfig"), + (c) => c.resolveType("HeaderDrawerManager"), + (c) => c.resolveType("EventService"), + (c) => c.resolveType("DateService") + ] + }); + builder.registerType(DateRenderer).as("IRenderer").autoWire({ + mapResolvers: [ + (c) => c.resolveType("DateService") + ] + }); + builder.registerType(ResourceRenderer).as("IRenderer").autoWire({ + mapResolvers: [ + (c) => c.resolveType("ResourceService") + ] + }); + builder.registerType(TeamRenderer).as("IRenderer").autoWire({ + mapResolvers: [ + (c) => c.resolveType("TeamService") + ] + }); + builder.registerType(DepartmentRenderer).as("IRenderer").autoWire({ + mapResolvers: [ + (c) => c.resolveType("DepartmentService") + ] + }); + builder.registerType(MockTeamStore).as("IGroupingStore"); + builder.registerType(MockResourceStore).as("IGroupingStore"); + builder.registerType(CalendarOrchestrator).as("CalendarOrchestrator").autoWire({ + mapResolvers: [ + (c) => c.resolveTypeAll("IRenderer"), + (c) => c.resolveType("EventRenderer"), + (c) => c.resolveType("ScheduleRenderer"), + (c) => c.resolveType("HeaderDrawerRenderer"), + (c) => c.resolveType("DateService"), + (c) => c.resolveTypeAll("IEntityService") + ] + }); + builder.registerType(TimeAxisRenderer).as("TimeAxisRenderer"); + builder.registerType(ScrollManager).as("ScrollManager"); + builder.registerType(HeaderDrawerManager).as("HeaderDrawerManager"); + builder.registerType(DragDropManager).as("DragDropManager").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IEventBus"), + (c) => c.resolveType("IGridConfig") + ] + }); + builder.registerType(EdgeScrollManager).as("EdgeScrollManager").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ResizeManager).as("ResizeManager").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IEventBus"), + (c) => c.resolveType("IGridConfig"), + (c) => c.resolveType("DateService") + ] + }); + builder.registerType(EventPersistenceManager).as("EventPersistenceManager").autoWire({ + mapResolvers: [ + (c) => c.resolveType("EventService"), + (c) => c.resolveType("IEventBus"), + (c) => c.resolveType("DateService") + ] + }); + builder.registerType(CalendarApp).as("CalendarApp").autoWire({ + mapResolvers: [ + (c) => c.resolveType("CalendarOrchestrator"), + (c) => c.resolveType("TimeAxisRenderer"), + (c) => c.resolveType("DateService"), + (c) => c.resolveType("ScrollManager"), + (c) => c.resolveType("HeaderDrawerManager"), + (c) => c.resolveType("DragDropManager"), + (c) => c.resolveType("EdgeScrollManager"), + (c) => c.resolveType("ResizeManager"), + (c) => c.resolveType("HeaderDrawerRenderer"), + (c) => c.resolveType("EventPersistenceManager"), + (c) => c.resolveType("SettingsService"), + (c) => c.resolveType("ViewConfigService"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(DemoApp).as("DemoApp").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("DataSeeder"), + (c) => c.resolveType("AuditService"), + (c) => c.resolveType("CalendarApp"), + (c) => c.resolveType("DateService"), + (c) => c.resolveType("ResourceService"), + (c) => c.resolveType("IEventBus") + ] + }); + return builder.build(); +} +__name(createContainer, "createContainer"); + +// src/demo/index.ts +var container = createContainer(); +container.resolveType("DemoApp").init().catch(console.error); +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vbm9kZV9tb2R1bGVzL2RheWpzL2RheWpzLm1pbi5qcyIsICIuLi8uLi9ub2RlX21vZHVsZXMvZGF5anMvcGx1Z2luL3V0Yy5qcyIsICIuLi8uLi9ub2RlX21vZHVsZXMvZGF5anMvcGx1Z2luL3RpbWV6b25lLmpzIiwgIi4uLy4uL25vZGVfbW9kdWxlcy9kYXlqcy9wbHVnaW4vaXNvV2Vlay5qcyIsICIuLi8uLi9ub2RlX21vZHVsZXMvQG5vdmFkaS9jb3JlL2Rpc3QvdG9rZW4uanMiLCAiLi4vLi4vbm9kZV9tb2R1bGVzL0Bub3ZhZGkvY29yZS9kaXN0L2Vycm9ycy5qcyIsICIuLi8uLi9ub2RlX21vZHVsZXMvQG5vdmFkaS9jb3JlL2Rpc3QvYXV0b3dpcmUuanMiLCAiLi4vLi4vbm9kZV9tb2R1bGVzL0Bub3ZhZGkvY29yZS9kaXN0L2J1aWxkZXIuanMiLCAiLi4vLi4vbm9kZV9tb2R1bGVzL0Bub3ZhZGkvY29yZS9kaXN0L2NvbnRhaW5lci5qcyIsICIuLi8uLi9zcmMvZmVhdHVyZXMvZGF0ZS9EYXRlUmVuZGVyZXIudHMiLCAiLi4vLi4vc3JjL2NvcmUvRGF0ZVNlcnZpY2UudHMiLCAiLi4vLi4vc3JjL2NvcmUvQmFzZUdyb3VwaW5nUmVuZGVyZXIudHMiLCAiLi4vLi4vc3JjL2ZlYXR1cmVzL3Jlc291cmNlL1Jlc291cmNlUmVuZGVyZXIudHMiLCAiLi4vLi4vc3JjL2ZlYXR1cmVzL3RlYW0vVGVhbVJlbmRlcmVyLnRzIiwgIi4uLy4uL3NyYy9mZWF0dXJlcy9kZXBhcnRtZW50L0RlcGFydG1lbnRSZW5kZXJlci50cyIsICIuLi8uLi9zcmMvY29yZS9SZW5kZXJCdWlsZGVyLnRzIiwgIi4uLy4uL3NyYy9jb3JlL0ZpbHRlclRlbXBsYXRlLnRzIiwgIi4uLy4uL3NyYy9jb3JlL0NhbGVuZGFyT3JjaGVzdHJhdG9yLnRzIiwgIi4uLy4uL3NyYy9jb3JlL05hdmlnYXRpb25BbmltYXRvci50cyIsICIuLi8uLi9zcmMvY29yZS9DYWxlbmRhckV2ZW50cy50cyIsICIuLi8uLi9zcmMvY29yZS9DYWxlbmRhckFwcC50cyIsICIuLi8uLi9zcmMvZmVhdHVyZXMvdGltZWF4aXMvVGltZUF4aXNSZW5kZXJlci50cyIsICIuLi8uLi9zcmMvY29yZS9TY3JvbGxNYW5hZ2VyLnRzIiwgIi4uLy4uL3NyYy9jb3JlL0hlYWRlckRyYXdlck1hbmFnZXIudHMiLCAiLi4vLi4vc3JjL2RlbW8vTW9ja1N0b3Jlcy50cyIsICIuLi8uLi9zcmMvZGVtby9EZW1vQXBwLnRzIiwgIi4uLy4uL3NyYy9jb3JlL0V2ZW50QnVzLnRzIiwgIi4uLy4uL3NyYy9zdG9yYWdlL0luZGV4ZWREQkNvbnRleHQudHMiLCAiLi4vLi4vc3JjL3N0b3JhZ2UvZXZlbnRzL0V2ZW50U3RvcmUudHMiLCAiLi4vLi4vc3JjL3N0b3JhZ2UvZXZlbnRzL0V2ZW50U2VyaWFsaXphdGlvbi50cyIsICIuLi8uLi9zcmMvc3RvcmFnZS9TeW5jUGx1Z2luLnRzIiwgIi4uLy4uL3NyYy9jb25zdGFudHMvQ29yZUV2ZW50cy50cyIsICIuLi8uLi9ub2RlX21vZHVsZXMvanNvbi1kaWZmLXRzL3NyYy9oZWxwZXJzLnRzIiwgIi4uLy4uL25vZGVfbW9kdWxlcy9qc29uLWRpZmYtdHMvc3JjL2pzb25EaWZmLnRzIiwgIi4uLy4uL25vZGVfbW9kdWxlcy9qc29uLWRpZmYtdHMvc3JjL2pzb25Db21wYXJlLnRzIiwgIi4uLy4uL3NyYy9zdG9yYWdlL0Jhc2VFbnRpdHlTZXJ2aWNlLnRzIiwgIi4uLy4uL3NyYy9zdG9yYWdlL2V2ZW50cy9FdmVudFNlcnZpY2UudHMiLCAiLi4vLi4vc3JjL3N0b3JhZ2UvcmVzb3VyY2VzL1Jlc291cmNlU3RvcmUudHMiLCAiLi4vLi4vc3JjL3N0b3JhZ2UvcmVzb3VyY2VzL1Jlc291cmNlU2VydmljZS50cyIsICIuLi8uLi9zcmMvc3RvcmFnZS9ib29raW5ncy9Cb29raW5nU3RvcmUudHMiLCAiLi4vLi4vc3JjL3N0b3JhZ2UvYm9va2luZ3MvQm9va2luZ1NlcnZpY2UudHMiLCAiLi4vLi4vc3JjL3N0b3JhZ2UvY3VzdG9tZXJzL0N1c3RvbWVyU3RvcmUudHMiLCAiLi4vLi4vc3JjL3N0b3JhZ2UvY3VzdG9tZXJzL0N1c3RvbWVyU2VydmljZS50cyIsICIuLi8uLi9zcmMvc3RvcmFnZS90ZWFtcy9UZWFtU3RvcmUudHMiLCAiLi4vLi4vc3JjL3N0b3JhZ2UvdGVhbXMvVGVhbVNlcnZpY2UudHMiLCAiLi4vLi4vc3JjL3N0b3JhZ2UvZGVwYXJ0bWVudHMvRGVwYXJ0bWVudFN0b3JlLnRzIiwgIi4uLy4uL3NyYy9zdG9yYWdlL2RlcGFydG1lbnRzL0RlcGFydG1lbnRTZXJ2aWNlLnRzIiwgIi4uLy4uL3NyYy9zdG9yYWdlL3NldHRpbmdzL1NldHRpbmdzU3RvcmUudHMiLCAiLi4vLi4vc3JjL3R5cGVzL1NldHRpbmdzVHlwZXMudHMiLCAiLi4vLi4vc3JjL3N0b3JhZ2Uvc2V0dGluZ3MvU2V0dGluZ3NTZXJ2aWNlLnRzIiwgIi4uLy4uL3NyYy9zdG9yYWdlL3ZpZXdjb25maWdzL1ZpZXdDb25maWdTdG9yZS50cyIsICIuLi8uLi9zcmMvc3RvcmFnZS92aWV3Y29uZmlncy9WaWV3Q29uZmlnU2VydmljZS50cyIsICIuLi8uLi9zcmMvc3RvcmFnZS9hdWRpdC9BdWRpdFN0b3JlLnRzIiwgIi4uLy4uL3NyYy9zdG9yYWdlL2F1ZGl0L0F1ZGl0U2VydmljZS50cyIsICIuLi8uLi9zcmMvcmVwb3NpdG9yaWVzL01vY2tFdmVudFJlcG9zaXRvcnkudHMiLCAiLi4vLi4vc3JjL3JlcG9zaXRvcmllcy9Nb2NrUmVzb3VyY2VSZXBvc2l0b3J5LnRzIiwgIi4uLy4uL3NyYy9yZXBvc2l0b3JpZXMvTW9ja0Jvb2tpbmdSZXBvc2l0b3J5LnRzIiwgIi4uLy4uL3NyYy9yZXBvc2l0b3JpZXMvTW9ja0N1c3RvbWVyUmVwb3NpdG9yeS50cyIsICIuLi8uLi9zcmMvcmVwb3NpdG9yaWVzL01vY2tBdWRpdFJlcG9zaXRvcnkudHMiLCAiLi4vLi4vc3JjL3JlcG9zaXRvcmllcy9Nb2NrVGVhbVJlcG9zaXRvcnkudHMiLCAiLi4vLi4vc3JjL3JlcG9zaXRvcmllcy9Nb2NrRGVwYXJ0bWVudFJlcG9zaXRvcnkudHMiLCAiLi4vLi4vc3JjL3JlcG9zaXRvcmllcy9Nb2NrU2V0dGluZ3NSZXBvc2l0b3J5LnRzIiwgIi4uLy4uL3NyYy9yZXBvc2l0b3JpZXMvTW9ja1ZpZXdDb25maWdSZXBvc2l0b3J5LnRzIiwgIi4uLy4uL3NyYy93b3JrZXJzL0RhdGFTZWVkZXIudHMiLCAiLi4vLi4vc3JjL3V0aWxzL1Bvc2l0aW9uVXRpbHMudHMiLCAiLi4vLi4vc3JjL2ZlYXR1cmVzL2V2ZW50L0V2ZW50TGF5b3V0RW5naW5lLnRzIiwgIi4uLy4uL3NyYy9mZWF0dXJlcy9ldmVudC9FdmVudFJlbmRlcmVyLnRzIiwgIi4uLy4uL3NyYy9mZWF0dXJlcy9zY2hlZHVsZS9TY2hlZHVsZVJlbmRlcmVyLnRzIiwgIi4uLy4uL3NyYy9mZWF0dXJlcy9oZWFkZXJkcmF3ZXIvSGVhZGVyRHJhd2VyUmVuZGVyZXIudHMiLCAiLi4vLi4vc3JjL3N0b3JhZ2Uvc2NoZWR1bGVzL1NjaGVkdWxlT3ZlcnJpZGVTdG9yZS50cyIsICIuLi8uLi9zcmMvc3RvcmFnZS9zY2hlZHVsZXMvU2NoZWR1bGVPdmVycmlkZVNlcnZpY2UudHMiLCAiLi4vLi4vc3JjL3N0b3JhZ2Uvc2NoZWR1bGVzL1Jlc291cmNlU2NoZWR1bGVTZXJ2aWNlLnRzIiwgIi4uLy4uL3NyYy90eXBlcy9Td3BFdmVudC50cyIsICIuLi8uLi9zcmMvbWFuYWdlcnMvRHJhZ0Ryb3BNYW5hZ2VyLnRzIiwgIi4uLy4uL3NyYy9tYW5hZ2Vycy9FZGdlU2Nyb2xsTWFuYWdlci50cyIsICIuLi8uLi9zcmMvbWFuYWdlcnMvUmVzaXplTWFuYWdlci50cyIsICIuLi8uLi9zcmMvbWFuYWdlcnMvRXZlbnRQZXJzaXN0ZW5jZU1hbmFnZXIudHMiLCAiLi4vLi4vc3JjL0NvbXBvc2l0aW9uUm9vdC50cyIsICIuLi8uLi9zcmMvZGVtby9pbmRleC50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiIWZ1bmN0aW9uKHQsZSl7XCJvYmplY3RcIj09dHlwZW9mIGV4cG9ydHMmJlwidW5kZWZpbmVkXCIhPXR5cGVvZiBtb2R1bGU/bW9kdWxlLmV4cG9ydHM9ZSgpOlwiZnVuY3Rpb25cIj09dHlwZW9mIGRlZmluZSYmZGVmaW5lLmFtZD9kZWZpbmUoZSk6KHQ9XCJ1bmRlZmluZWRcIiE9dHlwZW9mIGdsb2JhbFRoaXM/Z2xvYmFsVGhpczp0fHxzZWxmKS5kYXlqcz1lKCl9KHRoaXMsKGZ1bmN0aW9uKCl7XCJ1c2Ugc3RyaWN0XCI7dmFyIHQ9MWUzLGU9NmU0LG49MzZlNSxyPVwibWlsbGlzZWNvbmRcIixpPVwic2Vjb25kXCIscz1cIm1pbnV0ZVwiLHU9XCJob3VyXCIsYT1cImRheVwiLG89XCJ3ZWVrXCIsYz1cIm1vbnRoXCIsZj1cInF1YXJ0ZXJcIixoPVwieWVhclwiLGQ9XCJkYXRlXCIsbD1cIkludmFsaWQgRGF0ZVwiLCQ9L14oXFxkezR9KVstL10/KFxcZHsxLDJ9KT9bLS9dPyhcXGR7MCwyfSlbVHRcXHNdKihcXGR7MSwyfSk/Oj8oXFxkezEsMn0pPzo/KFxcZHsxLDJ9KT9bLjpdPyhcXGQrKT8kLyx5PS9cXFsoW15cXF1dKyldfFl7MSw0fXxNezEsNH18RHsxLDJ9fGR7MSw0fXxIezEsMn18aHsxLDJ9fGF8QXxtezEsMn18c3sxLDJ9fFp7MSwyfXxTU1MvZyxNPXtuYW1lOlwiZW5cIix3ZWVrZGF5czpcIlN1bmRheV9Nb25kYXlfVHVlc2RheV9XZWRuZXNkYXlfVGh1cnNkYXlfRnJpZGF5X1NhdHVyZGF5XCIuc3BsaXQoXCJfXCIpLG1vbnRoczpcIkphbnVhcnlfRmVicnVhcnlfTWFyY2hfQXByaWxfTWF5X0p1bmVfSnVseV9BdWd1c3RfU2VwdGVtYmVyX09jdG9iZXJfTm92ZW1iZXJfRGVjZW1iZXJcIi5zcGxpdChcIl9cIiksb3JkaW5hbDpmdW5jdGlvbih0KXt2YXIgZT1bXCJ0aFwiLFwic3RcIixcIm5kXCIsXCJyZFwiXSxuPXQlMTAwO3JldHVyblwiW1wiK3QrKGVbKG4tMjApJTEwXXx8ZVtuXXx8ZVswXSkrXCJdXCJ9fSxtPWZ1bmN0aW9uKHQsZSxuKXt2YXIgcj1TdHJpbmcodCk7cmV0dXJuIXJ8fHIubGVuZ3RoPj1lP3Q6XCJcIitBcnJheShlKzEtci5sZW5ndGgpLmpvaW4obikrdH0sdj17czptLHo6ZnVuY3Rpb24odCl7dmFyIGU9LXQudXRjT2Zmc2V0KCksbj1NYXRoLmFicyhlKSxyPU1hdGguZmxvb3Iobi82MCksaT1uJTYwO3JldHVybihlPD0wP1wiK1wiOlwiLVwiKSttKHIsMixcIjBcIikrXCI6XCIrbShpLDIsXCIwXCIpfSxtOmZ1bmN0aW9uIHQoZSxuKXtpZihlLmRhdGUoKTxuLmRhdGUoKSlyZXR1cm4tdChuLGUpO3ZhciByPTEyKihuLnllYXIoKS1lLnllYXIoKSkrKG4ubW9udGgoKS1lLm1vbnRoKCkpLGk9ZS5jbG9uZSgpLmFkZChyLGMpLHM9bi1pPDAsdT1lLmNsb25lKCkuYWRkKHIrKHM/LTE6MSksYyk7cmV0dXJuKygtKHIrKG4taSkvKHM/aS11OnUtaSkpfHwwKX0sYTpmdW5jdGlvbih0KXtyZXR1cm4gdDwwP01hdGguY2VpbCh0KXx8MDpNYXRoLmZsb29yKHQpfSxwOmZ1bmN0aW9uKHQpe3JldHVybntNOmMseTpoLHc6byxkOmEsRDpkLGg6dSxtOnMsczppLG1zOnIsUTpmfVt0XXx8U3RyaW5nKHR8fFwiXCIpLnRvTG93ZXJDYXNlKCkucmVwbGFjZSgvcyQvLFwiXCIpfSx1OmZ1bmN0aW9uKHQpe3JldHVybiB2b2lkIDA9PT10fX0sZz1cImVuXCIsRD17fTtEW2ddPU07dmFyIHA9XCIkaXNEYXlqc09iamVjdFwiLFM9ZnVuY3Rpb24odCl7cmV0dXJuIHQgaW5zdGFuY2VvZiBffHwhKCF0fHwhdFtwXSl9LHc9ZnVuY3Rpb24gdChlLG4scil7dmFyIGk7aWYoIWUpcmV0dXJuIGc7aWYoXCJzdHJpbmdcIj09dHlwZW9mIGUpe3ZhciBzPWUudG9Mb3dlckNhc2UoKTtEW3NdJiYoaT1zKSxuJiYoRFtzXT1uLGk9cyk7dmFyIHU9ZS5zcGxpdChcIi1cIik7aWYoIWkmJnUubGVuZ3RoPjEpcmV0dXJuIHQodVswXSl9ZWxzZXt2YXIgYT1lLm5hbWU7RFthXT1lLGk9YX1yZXR1cm4hciYmaSYmKGc9aSksaXx8IXImJmd9LE89ZnVuY3Rpb24odCxlKXtpZihTKHQpKXJldHVybiB0LmNsb25lKCk7dmFyIG49XCJvYmplY3RcIj09dHlwZW9mIGU/ZTp7fTtyZXR1cm4gbi5kYXRlPXQsbi5hcmdzPWFyZ3VtZW50cyxuZXcgXyhuKX0sYj12O2IubD13LGIuaT1TLGIudz1mdW5jdGlvbih0LGUpe3JldHVybiBPKHQse2xvY2FsZTplLiRMLHV0YzplLiR1LHg6ZS4keCwkb2Zmc2V0OmUuJG9mZnNldH0pfTt2YXIgXz1mdW5jdGlvbigpe2Z1bmN0aW9uIE0odCl7dGhpcy4kTD13KHQubG9jYWxlLG51bGwsITApLHRoaXMucGFyc2UodCksdGhpcy4keD10aGlzLiR4fHx0Lnh8fHt9LHRoaXNbcF09ITB9dmFyIG09TS5wcm90b3R5cGU7cmV0dXJuIG0ucGFyc2U9ZnVuY3Rpb24odCl7dGhpcy4kZD1mdW5jdGlvbih0KXt2YXIgZT10LmRhdGUsbj10LnV0YztpZihudWxsPT09ZSlyZXR1cm4gbmV3IERhdGUoTmFOKTtpZihiLnUoZSkpcmV0dXJuIG5ldyBEYXRlO2lmKGUgaW5zdGFuY2VvZiBEYXRlKXJldHVybiBuZXcgRGF0ZShlKTtpZihcInN0cmluZ1wiPT10eXBlb2YgZSYmIS9aJC9pLnRlc3QoZSkpe3ZhciByPWUubWF0Y2goJCk7aWYocil7dmFyIGk9clsyXS0xfHwwLHM9KHJbN118fFwiMFwiKS5zdWJzdHJpbmcoMCwzKTtyZXR1cm4gbj9uZXcgRGF0ZShEYXRlLlVUQyhyWzFdLGksclszXXx8MSxyWzRdfHwwLHJbNV18fDAscls2XXx8MCxzKSk6bmV3IERhdGUoclsxXSxpLHJbM118fDEscls0XXx8MCxyWzVdfHwwLHJbNl18fDAscyl9fXJldHVybiBuZXcgRGF0ZShlKX0odCksdGhpcy5pbml0KCl9LG0uaW5pdD1mdW5jdGlvbigpe3ZhciB0PXRoaXMuJGQ7dGhpcy4keT10LmdldEZ1bGxZZWFyKCksdGhpcy4kTT10LmdldE1vbnRoKCksdGhpcy4kRD10LmdldERhdGUoKSx0aGlzLiRXPXQuZ2V0RGF5KCksdGhpcy4kSD10LmdldEhvdXJzKCksdGhpcy4kbT10LmdldE1pbnV0ZXMoKSx0aGlzLiRzPXQuZ2V0U2Vjb25kcygpLHRoaXMuJG1zPXQuZ2V0TWlsbGlzZWNvbmRzKCl9LG0uJHV0aWxzPWZ1bmN0aW9uKCl7cmV0dXJuIGJ9LG0uaXNWYWxpZD1mdW5jdGlvbigpe3JldHVybiEodGhpcy4kZC50b1N0cmluZygpPT09bCl9LG0uaXNTYW1lPWZ1bmN0aW9uKHQsZSl7dmFyIG49Tyh0KTtyZXR1cm4gdGhpcy5zdGFydE9mKGUpPD1uJiZuPD10aGlzLmVuZE9mKGUpfSxtLmlzQWZ0ZXI9ZnVuY3Rpb24odCxlKXtyZXR1cm4gTyh0KTx0aGlzLnN0YXJ0T2YoZSl9LG0uaXNCZWZvcmU9ZnVuY3Rpb24odCxlKXtyZXR1cm4gdGhpcy5lbmRPZihlKTxPKHQpfSxtLiRnPWZ1bmN0aW9uKHQsZSxuKXtyZXR1cm4gYi51KHQpP3RoaXNbZV06dGhpcy5zZXQobix0KX0sbS51bml4PWZ1bmN0aW9uKCl7cmV0dXJuIE1hdGguZmxvb3IodGhpcy52YWx1ZU9mKCkvMWUzKX0sbS52YWx1ZU9mPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuJGQuZ2V0VGltZSgpfSxtLnN0YXJ0T2Y9ZnVuY3Rpb24odCxlKXt2YXIgbj10aGlzLHI9ISFiLnUoZSl8fGUsZj1iLnAodCksbD1mdW5jdGlvbih0LGUpe3ZhciBpPWIudyhuLiR1P0RhdGUuVVRDKG4uJHksZSx0KTpuZXcgRGF0ZShuLiR5LGUsdCksbik7cmV0dXJuIHI/aTppLmVuZE9mKGEpfSwkPWZ1bmN0aW9uKHQsZSl7cmV0dXJuIGIudyhuLnRvRGF0ZSgpW3RdLmFwcGx5KG4udG9EYXRlKFwic1wiKSwocj9bMCwwLDAsMF06WzIzLDU5LDU5LDk5OV0pLnNsaWNlKGUpKSxuKX0seT10aGlzLiRXLE09dGhpcy4kTSxtPXRoaXMuJEQsdj1cInNldFwiKyh0aGlzLiR1P1wiVVRDXCI6XCJcIik7c3dpdGNoKGYpe2Nhc2UgaDpyZXR1cm4gcj9sKDEsMCk6bCgzMSwxMSk7Y2FzZSBjOnJldHVybiByP2woMSxNKTpsKDAsTSsxKTtjYXNlIG86dmFyIGc9dGhpcy4kbG9jYWxlKCkud2Vla1N0YXJ0fHwwLEQ9KHk8Zz95Kzc6eSktZztyZXR1cm4gbChyP20tRDptKyg2LUQpLE0pO2Nhc2UgYTpjYXNlIGQ6cmV0dXJuICQoditcIkhvdXJzXCIsMCk7Y2FzZSB1OnJldHVybiAkKHYrXCJNaW51dGVzXCIsMSk7Y2FzZSBzOnJldHVybiAkKHYrXCJTZWNvbmRzXCIsMik7Y2FzZSBpOnJldHVybiAkKHYrXCJNaWxsaXNlY29uZHNcIiwzKTtkZWZhdWx0OnJldHVybiB0aGlzLmNsb25lKCl9fSxtLmVuZE9mPWZ1bmN0aW9uKHQpe3JldHVybiB0aGlzLnN0YXJ0T2YodCwhMSl9LG0uJHNldD1mdW5jdGlvbih0LGUpe3ZhciBuLG89Yi5wKHQpLGY9XCJzZXRcIisodGhpcy4kdT9cIlVUQ1wiOlwiXCIpLGw9KG49e30sblthXT1mK1wiRGF0ZVwiLG5bZF09ZitcIkRhdGVcIixuW2NdPWYrXCJNb250aFwiLG5baF09ZitcIkZ1bGxZZWFyXCIsblt1XT1mK1wiSG91cnNcIixuW3NdPWYrXCJNaW51dGVzXCIsbltpXT1mK1wiU2Vjb25kc1wiLG5bcl09ZitcIk1pbGxpc2Vjb25kc1wiLG4pW29dLCQ9bz09PWE/dGhpcy4kRCsoZS10aGlzLiRXKTplO2lmKG89PT1jfHxvPT09aCl7dmFyIHk9dGhpcy5jbG9uZSgpLnNldChkLDEpO3kuJGRbbF0oJCkseS5pbml0KCksdGhpcy4kZD15LnNldChkLE1hdGgubWluKHRoaXMuJEQseS5kYXlzSW5Nb250aCgpKSkuJGR9ZWxzZSBsJiZ0aGlzLiRkW2xdKCQpO3JldHVybiB0aGlzLmluaXQoKSx0aGlzfSxtLnNldD1mdW5jdGlvbih0LGUpe3JldHVybiB0aGlzLmNsb25lKCkuJHNldCh0LGUpfSxtLmdldD1mdW5jdGlvbih0KXtyZXR1cm4gdGhpc1tiLnAodCldKCl9LG0uYWRkPWZ1bmN0aW9uKHIsZil7dmFyIGQsbD10aGlzO3I9TnVtYmVyKHIpO3ZhciAkPWIucChmKSx5PWZ1bmN0aW9uKHQpe3ZhciBlPU8obCk7cmV0dXJuIGIudyhlLmRhdGUoZS5kYXRlKCkrTWF0aC5yb3VuZCh0KnIpKSxsKX07aWYoJD09PWMpcmV0dXJuIHRoaXMuc2V0KGMsdGhpcy4kTStyKTtpZigkPT09aClyZXR1cm4gdGhpcy5zZXQoaCx0aGlzLiR5K3IpO2lmKCQ9PT1hKXJldHVybiB5KDEpO2lmKCQ9PT1vKXJldHVybiB5KDcpO3ZhciBNPShkPXt9LGRbc109ZSxkW3VdPW4sZFtpXT10LGQpWyRdfHwxLG09dGhpcy4kZC5nZXRUaW1lKCkrcipNO3JldHVybiBiLncobSx0aGlzKX0sbS5zdWJ0cmFjdD1mdW5jdGlvbih0LGUpe3JldHVybiB0aGlzLmFkZCgtMSp0LGUpfSxtLmZvcm1hdD1mdW5jdGlvbih0KXt2YXIgZT10aGlzLG49dGhpcy4kbG9jYWxlKCk7aWYoIXRoaXMuaXNWYWxpZCgpKXJldHVybiBuLmludmFsaWREYXRlfHxsO3ZhciByPXR8fFwiWVlZWS1NTS1ERFRISDptbTpzc1pcIixpPWIueih0aGlzKSxzPXRoaXMuJEgsdT10aGlzLiRtLGE9dGhpcy4kTSxvPW4ud2Vla2RheXMsYz1uLm1vbnRocyxmPW4ubWVyaWRpZW0saD1mdW5jdGlvbih0LG4saSxzKXtyZXR1cm4gdCYmKHRbbl18fHQoZSxyKSl8fGlbbl0uc2xpY2UoMCxzKX0sZD1mdW5jdGlvbih0KXtyZXR1cm4gYi5zKHMlMTJ8fDEyLHQsXCIwXCIpfSwkPWZ8fGZ1bmN0aW9uKHQsZSxuKXt2YXIgcj10PDEyP1wiQU1cIjpcIlBNXCI7cmV0dXJuIG4/ci50b0xvd2VyQ2FzZSgpOnJ9O3JldHVybiByLnJlcGxhY2UoeSwoZnVuY3Rpb24odCxyKXtyZXR1cm4gcnx8ZnVuY3Rpb24odCl7c3dpdGNoKHQpe2Nhc2VcIllZXCI6cmV0dXJuIFN0cmluZyhlLiR5KS5zbGljZSgtMik7Y2FzZVwiWVlZWVwiOnJldHVybiBiLnMoZS4keSw0LFwiMFwiKTtjYXNlXCJNXCI6cmV0dXJuIGErMTtjYXNlXCJNTVwiOnJldHVybiBiLnMoYSsxLDIsXCIwXCIpO2Nhc2VcIk1NTVwiOnJldHVybiBoKG4ubW9udGhzU2hvcnQsYSxjLDMpO2Nhc2VcIk1NTU1cIjpyZXR1cm4gaChjLGEpO2Nhc2VcIkRcIjpyZXR1cm4gZS4kRDtjYXNlXCJERFwiOnJldHVybiBiLnMoZS4kRCwyLFwiMFwiKTtjYXNlXCJkXCI6cmV0dXJuIFN0cmluZyhlLiRXKTtjYXNlXCJkZFwiOnJldHVybiBoKG4ud2Vla2RheXNNaW4sZS4kVyxvLDIpO2Nhc2VcImRkZFwiOnJldHVybiBoKG4ud2Vla2RheXNTaG9ydCxlLiRXLG8sMyk7Y2FzZVwiZGRkZFwiOnJldHVybiBvW2UuJFddO2Nhc2VcIkhcIjpyZXR1cm4gU3RyaW5nKHMpO2Nhc2VcIkhIXCI6cmV0dXJuIGIucyhzLDIsXCIwXCIpO2Nhc2VcImhcIjpyZXR1cm4gZCgxKTtjYXNlXCJoaFwiOnJldHVybiBkKDIpO2Nhc2VcImFcIjpyZXR1cm4gJChzLHUsITApO2Nhc2VcIkFcIjpyZXR1cm4gJChzLHUsITEpO2Nhc2VcIm1cIjpyZXR1cm4gU3RyaW5nKHUpO2Nhc2VcIm1tXCI6cmV0dXJuIGIucyh1LDIsXCIwXCIpO2Nhc2VcInNcIjpyZXR1cm4gU3RyaW5nKGUuJHMpO2Nhc2VcInNzXCI6cmV0dXJuIGIucyhlLiRzLDIsXCIwXCIpO2Nhc2VcIlNTU1wiOnJldHVybiBiLnMoZS4kbXMsMyxcIjBcIik7Y2FzZVwiWlwiOnJldHVybiBpfXJldHVybiBudWxsfSh0KXx8aS5yZXBsYWNlKFwiOlwiLFwiXCIpfSkpfSxtLnV0Y09mZnNldD1mdW5jdGlvbigpe3JldHVybiAxNSotTWF0aC5yb3VuZCh0aGlzLiRkLmdldFRpbWV6b25lT2Zmc2V0KCkvMTUpfSxtLmRpZmY9ZnVuY3Rpb24ocixkLGwpe3ZhciAkLHk9dGhpcyxNPWIucChkKSxtPU8ociksdj0obS51dGNPZmZzZXQoKS10aGlzLnV0Y09mZnNldCgpKSplLGc9dGhpcy1tLEQ9ZnVuY3Rpb24oKXtyZXR1cm4gYi5tKHksbSl9O3N3aXRjaChNKXtjYXNlIGg6JD1EKCkvMTI7YnJlYWs7Y2FzZSBjOiQ9RCgpO2JyZWFrO2Nhc2UgZjokPUQoKS8zO2JyZWFrO2Nhc2UgbzokPShnLXYpLzYwNDhlNTticmVhaztjYXNlIGE6JD0oZy12KS84NjRlNTticmVhaztjYXNlIHU6JD1nL247YnJlYWs7Y2FzZSBzOiQ9Zy9lO2JyZWFrO2Nhc2UgaTokPWcvdDticmVhaztkZWZhdWx0OiQ9Z31yZXR1cm4gbD8kOmIuYSgkKX0sbS5kYXlzSW5Nb250aD1mdW5jdGlvbigpe3JldHVybiB0aGlzLmVuZE9mKGMpLiREfSxtLiRsb2NhbGU9ZnVuY3Rpb24oKXtyZXR1cm4gRFt0aGlzLiRMXX0sbS5sb2NhbGU9ZnVuY3Rpb24odCxlKXtpZighdClyZXR1cm4gdGhpcy4kTDt2YXIgbj10aGlzLmNsb25lKCkscj13KHQsZSwhMCk7cmV0dXJuIHImJihuLiRMPXIpLG59LG0uY2xvbmU9ZnVuY3Rpb24oKXtyZXR1cm4gYi53KHRoaXMuJGQsdGhpcyl9LG0udG9EYXRlPWZ1bmN0aW9uKCl7cmV0dXJuIG5ldyBEYXRlKHRoaXMudmFsdWVPZigpKX0sbS50b0pTT049ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5pc1ZhbGlkKCk/dGhpcy50b0lTT1N0cmluZygpOm51bGx9LG0udG9JU09TdHJpbmc9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy4kZC50b0lTT1N0cmluZygpfSxtLnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuJGQudG9VVENTdHJpbmcoKX0sTX0oKSxrPV8ucHJvdG90eXBlO3JldHVybiBPLnByb3RvdHlwZT1rLFtbXCIkbXNcIixyXSxbXCIkc1wiLGldLFtcIiRtXCIsc10sW1wiJEhcIix1XSxbXCIkV1wiLGFdLFtcIiRNXCIsY10sW1wiJHlcIixoXSxbXCIkRFwiLGRdXS5mb3JFYWNoKChmdW5jdGlvbih0KXtrW3RbMV1dPWZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLiRnKGUsdFswXSx0WzFdKX19KSksTy5leHRlbmQ9ZnVuY3Rpb24odCxlKXtyZXR1cm4gdC4kaXx8KHQoZSxfLE8pLHQuJGk9ITApLE99LE8ubG9jYWxlPXcsTy5pc0RheWpzPVMsTy51bml4PWZ1bmN0aW9uKHQpe3JldHVybiBPKDFlMyp0KX0sTy5lbj1EW2ddLE8uTHM9RCxPLnA9e30sT30pKTsiLCAiIWZ1bmN0aW9uKHQsaSl7XCJvYmplY3RcIj09dHlwZW9mIGV4cG9ydHMmJlwidW5kZWZpbmVkXCIhPXR5cGVvZiBtb2R1bGU/bW9kdWxlLmV4cG9ydHM9aSgpOlwiZnVuY3Rpb25cIj09dHlwZW9mIGRlZmluZSYmZGVmaW5lLmFtZD9kZWZpbmUoaSk6KHQ9XCJ1bmRlZmluZWRcIiE9dHlwZW9mIGdsb2JhbFRoaXM/Z2xvYmFsVGhpczp0fHxzZWxmKS5kYXlqc19wbHVnaW5fdXRjPWkoKX0odGhpcywoZnVuY3Rpb24oKXtcInVzZSBzdHJpY3RcIjt2YXIgdD1cIm1pbnV0ZVwiLGk9L1srLV1cXGRcXGQoPzo6P1xcZFxcZCk/L2csZT0vKFsrLV18XFxkXFxkKS9nO3JldHVybiBmdW5jdGlvbihzLGYsbil7dmFyIHU9Zi5wcm90b3R5cGU7bi51dGM9ZnVuY3Rpb24odCl7dmFyIGk9e2RhdGU6dCx1dGM6ITAsYXJnczphcmd1bWVudHN9O3JldHVybiBuZXcgZihpKX0sdS51dGM9ZnVuY3Rpb24oaSl7dmFyIGU9bih0aGlzLnRvRGF0ZSgpLHtsb2NhbGU6dGhpcy4kTCx1dGM6ITB9KTtyZXR1cm4gaT9lLmFkZCh0aGlzLnV0Y09mZnNldCgpLHQpOmV9LHUubG9jYWw9ZnVuY3Rpb24oKXtyZXR1cm4gbih0aGlzLnRvRGF0ZSgpLHtsb2NhbGU6dGhpcy4kTCx1dGM6ITF9KX07dmFyIHI9dS5wYXJzZTt1LnBhcnNlPWZ1bmN0aW9uKHQpe3QudXRjJiYodGhpcy4kdT0hMCksdGhpcy4kdXRpbHMoKS51KHQuJG9mZnNldCl8fCh0aGlzLiRvZmZzZXQ9dC4kb2Zmc2V0KSxyLmNhbGwodGhpcyx0KX07dmFyIG89dS5pbml0O3UuaW5pdD1mdW5jdGlvbigpe2lmKHRoaXMuJHUpe3ZhciB0PXRoaXMuJGQ7dGhpcy4keT10LmdldFVUQ0Z1bGxZZWFyKCksdGhpcy4kTT10LmdldFVUQ01vbnRoKCksdGhpcy4kRD10LmdldFVUQ0RhdGUoKSx0aGlzLiRXPXQuZ2V0VVRDRGF5KCksdGhpcy4kSD10LmdldFVUQ0hvdXJzKCksdGhpcy4kbT10LmdldFVUQ01pbnV0ZXMoKSx0aGlzLiRzPXQuZ2V0VVRDU2Vjb25kcygpLHRoaXMuJG1zPXQuZ2V0VVRDTWlsbGlzZWNvbmRzKCl9ZWxzZSBvLmNhbGwodGhpcyl9O3ZhciBhPXUudXRjT2Zmc2V0O3UudXRjT2Zmc2V0PWZ1bmN0aW9uKHMsZil7dmFyIG49dGhpcy4kdXRpbHMoKS51O2lmKG4ocykpcmV0dXJuIHRoaXMuJHU/MDpuKHRoaXMuJG9mZnNldCk/YS5jYWxsKHRoaXMpOnRoaXMuJG9mZnNldDtpZihcInN0cmluZ1wiPT10eXBlb2YgcyYmKHM9ZnVuY3Rpb24odCl7dm9pZCAwPT09dCYmKHQ9XCJcIik7dmFyIHM9dC5tYXRjaChpKTtpZighcylyZXR1cm4gbnVsbDt2YXIgZj0oXCJcIitzWzBdKS5tYXRjaChlKXx8W1wiLVwiLDAsMF0sbj1mWzBdLHU9NjAqK2ZbMV0rICtmWzJdO3JldHVybiAwPT09dT8wOlwiK1wiPT09bj91Oi11fShzKSxudWxsPT09cykpcmV0dXJuIHRoaXM7dmFyIHU9TWF0aC5hYnMocyk8PTE2PzYwKnM6cztpZigwPT09dSlyZXR1cm4gdGhpcy51dGMoZik7dmFyIHI9dGhpcy5jbG9uZSgpO2lmKGYpcmV0dXJuIHIuJG9mZnNldD11LHIuJHU9ITEscjt2YXIgbz10aGlzLiR1P3RoaXMudG9EYXRlKCkuZ2V0VGltZXpvbmVPZmZzZXQoKTotMSp0aGlzLnV0Y09mZnNldCgpO3JldHVybihyPXRoaXMubG9jYWwoKS5hZGQodStvLHQpKS4kb2Zmc2V0PXUsci4keC4kbG9jYWxPZmZzZXQ9byxyfTt2YXIgaD11LmZvcm1hdDt1LmZvcm1hdD1mdW5jdGlvbih0KXt2YXIgaT10fHwodGhpcy4kdT9cIllZWVktTU0tRERUSEg6bW06c3NbWl1cIjpcIlwiKTtyZXR1cm4gaC5jYWxsKHRoaXMsaSl9LHUudmFsdWVPZj1mdW5jdGlvbigpe3ZhciB0PXRoaXMuJHV0aWxzKCkudSh0aGlzLiRvZmZzZXQpPzA6dGhpcy4kb2Zmc2V0Kyh0aGlzLiR4LiRsb2NhbE9mZnNldHx8dGhpcy4kZC5nZXRUaW1lem9uZU9mZnNldCgpKTtyZXR1cm4gdGhpcy4kZC52YWx1ZU9mKCktNmU0KnR9LHUuaXNVVEM9ZnVuY3Rpb24oKXtyZXR1cm4hIXRoaXMuJHV9LHUudG9JU09TdHJpbmc9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy50b0RhdGUoKS50b0lTT1N0cmluZygpfSx1LnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMudG9EYXRlKCkudG9VVENTdHJpbmcoKX07dmFyIGw9dS50b0RhdGU7dS50b0RhdGU9ZnVuY3Rpb24odCl7cmV0dXJuXCJzXCI9PT10JiZ0aGlzLiRvZmZzZXQ/bih0aGlzLmZvcm1hdChcIllZWVktTU0tREQgSEg6bW06c3M6U1NTXCIpKS50b0RhdGUoKTpsLmNhbGwodGhpcyl9O3ZhciBjPXUuZGlmZjt1LmRpZmY9ZnVuY3Rpb24odCxpLGUpe2lmKHQmJnRoaXMuJHU9PT10LiR1KXJldHVybiBjLmNhbGwodGhpcyx0LGksZSk7dmFyIHM9dGhpcy5sb2NhbCgpLGY9bih0KS5sb2NhbCgpO3JldHVybiBjLmNhbGwocyxmLGksZSl9fX0pKTsiLCAiIWZ1bmN0aW9uKHQsZSl7XCJvYmplY3RcIj09dHlwZW9mIGV4cG9ydHMmJlwidW5kZWZpbmVkXCIhPXR5cGVvZiBtb2R1bGU/bW9kdWxlLmV4cG9ydHM9ZSgpOlwiZnVuY3Rpb25cIj09dHlwZW9mIGRlZmluZSYmZGVmaW5lLmFtZD9kZWZpbmUoZSk6KHQ9XCJ1bmRlZmluZWRcIiE9dHlwZW9mIGdsb2JhbFRoaXM/Z2xvYmFsVGhpczp0fHxzZWxmKS5kYXlqc19wbHVnaW5fdGltZXpvbmU9ZSgpfSh0aGlzLChmdW5jdGlvbigpe1widXNlIHN0cmljdFwiO3ZhciB0PXt5ZWFyOjAsbW9udGg6MSxkYXk6Mixob3VyOjMsbWludXRlOjQsc2Vjb25kOjV9LGU9e307cmV0dXJuIGZ1bmN0aW9uKG4saSxvKXt2YXIgcixhPWZ1bmN0aW9uKHQsbixpKXt2b2lkIDA9PT1pJiYoaT17fSk7dmFyIG89bmV3IERhdGUodCkscj1mdW5jdGlvbih0LG4pe3ZvaWQgMD09PW4mJihuPXt9KTt2YXIgaT1uLnRpbWVab25lTmFtZXx8XCJzaG9ydFwiLG89dCtcInxcIitpLHI9ZVtvXTtyZXR1cm4gcnx8KHI9bmV3IEludGwuRGF0ZVRpbWVGb3JtYXQoXCJlbi1VU1wiLHtob3VyMTI6ITEsdGltZVpvbmU6dCx5ZWFyOlwibnVtZXJpY1wiLG1vbnRoOlwiMi1kaWdpdFwiLGRheTpcIjItZGlnaXRcIixob3VyOlwiMi1kaWdpdFwiLG1pbnV0ZTpcIjItZGlnaXRcIixzZWNvbmQ6XCIyLWRpZ2l0XCIsdGltZVpvbmVOYW1lOml9KSxlW29dPXIpLHJ9KG4saSk7cmV0dXJuIHIuZm9ybWF0VG9QYXJ0cyhvKX0sdT1mdW5jdGlvbihlLG4pe2Zvcih2YXIgaT1hKGUsbikscj1bXSx1PTA7dTxpLmxlbmd0aDt1Kz0xKXt2YXIgZj1pW3VdLHM9Zi50eXBlLG09Zi52YWx1ZSxjPXRbc107Yz49MCYmKHJbY109cGFyc2VJbnQobSwxMCkpfXZhciBkPXJbM10sbD0yND09PWQ/MDpkLGg9clswXStcIi1cIityWzFdK1wiLVwiK3JbMl0rXCIgXCIrbCtcIjpcIityWzRdK1wiOlwiK3JbNV0rXCI6MDAwXCIsdj0rZTtyZXR1cm4oby51dGMoaCkudmFsdWVPZigpLSh2LT12JTFlMykpLzZlNH0sZj1pLnByb3RvdHlwZTtmLnR6PWZ1bmN0aW9uKHQsZSl7dm9pZCAwPT09dCYmKHQ9cik7dmFyIG4saT10aGlzLnV0Y09mZnNldCgpLGE9dGhpcy50b0RhdGUoKSx1PWEudG9Mb2NhbGVTdHJpbmcoXCJlbi1VU1wiLHt0aW1lWm9uZTp0fSksZj1NYXRoLnJvdW5kKChhLW5ldyBEYXRlKHUpKS8xZTMvNjApLHM9MTUqLU1hdGgucm91bmQoYS5nZXRUaW1lem9uZU9mZnNldCgpLzE1KS1mO2lmKCFOdW1iZXIocykpbj10aGlzLnV0Y09mZnNldCgwLGUpO2Vsc2UgaWYobj1vKHUse2xvY2FsZTp0aGlzLiRMfSkuJHNldChcIm1pbGxpc2Vjb25kXCIsdGhpcy4kbXMpLnV0Y09mZnNldChzLCEwKSxlKXt2YXIgbT1uLnV0Y09mZnNldCgpO249bi5hZGQoaS1tLFwibWludXRlXCIpfXJldHVybiBuLiR4LiR0aW1lem9uZT10LG59LGYub2Zmc2V0TmFtZT1mdW5jdGlvbih0KXt2YXIgZT10aGlzLiR4LiR0aW1lem9uZXx8by50ei5ndWVzcygpLG49YSh0aGlzLnZhbHVlT2YoKSxlLHt0aW1lWm9uZU5hbWU6dH0pLmZpbmQoKGZ1bmN0aW9uKHQpe3JldHVyblwidGltZXpvbmVuYW1lXCI9PT10LnR5cGUudG9Mb3dlckNhc2UoKX0pKTtyZXR1cm4gbiYmbi52YWx1ZX07dmFyIHM9Zi5zdGFydE9mO2Yuc3RhcnRPZj1mdW5jdGlvbih0LGUpe2lmKCF0aGlzLiR4fHwhdGhpcy4keC4kdGltZXpvbmUpcmV0dXJuIHMuY2FsbCh0aGlzLHQsZSk7dmFyIG49byh0aGlzLmZvcm1hdChcIllZWVktTU0tREQgSEg6bW06c3M6U1NTXCIpLHtsb2NhbGU6dGhpcy4kTH0pO3JldHVybiBzLmNhbGwobix0LGUpLnR6KHRoaXMuJHguJHRpbWV6b25lLCEwKX0sby50ej1mdW5jdGlvbih0LGUsbil7dmFyIGk9biYmZSxhPW58fGV8fHIsZj11KCtvKCksYSk7aWYoXCJzdHJpbmdcIiE9dHlwZW9mIHQpcmV0dXJuIG8odCkudHooYSk7dmFyIHM9ZnVuY3Rpb24odCxlLG4pe3ZhciBpPXQtNjAqZSoxZTMsbz11KGksbik7aWYoZT09PW8pcmV0dXJuW2ksZV07dmFyIHI9dShpLT02MCooby1lKSoxZTMsbik7cmV0dXJuIG89PT1yP1tpLG9dOlt0LTYwKk1hdGgubWluKG8scikqMWUzLE1hdGgubWF4KG8scildfShvLnV0Yyh0LGkpLnZhbHVlT2YoKSxmLGEpLG09c1swXSxjPXNbMV0sZD1vKG0pLnV0Y09mZnNldChjKTtyZXR1cm4gZC4keC4kdGltZXpvbmU9YSxkfSxvLnR6Lmd1ZXNzPWZ1bmN0aW9uKCl7cmV0dXJuIEludGwuRGF0ZVRpbWVGb3JtYXQoKS5yZXNvbHZlZE9wdGlvbnMoKS50aW1lWm9uZX0sby50ei5zZXREZWZhdWx0PWZ1bmN0aW9uKHQpe3I9dH19fSkpOyIsICIhZnVuY3Rpb24oZSx0KXtcIm9iamVjdFwiPT10eXBlb2YgZXhwb3J0cyYmXCJ1bmRlZmluZWRcIiE9dHlwZW9mIG1vZHVsZT9tb2R1bGUuZXhwb3J0cz10KCk6XCJmdW5jdGlvblwiPT10eXBlb2YgZGVmaW5lJiZkZWZpbmUuYW1kP2RlZmluZSh0KTooZT1cInVuZGVmaW5lZFwiIT10eXBlb2YgZ2xvYmFsVGhpcz9nbG9iYWxUaGlzOmV8fHNlbGYpLmRheWpzX3BsdWdpbl9pc29XZWVrPXQoKX0odGhpcywoZnVuY3Rpb24oKXtcInVzZSBzdHJpY3RcIjt2YXIgZT1cImRheVwiO3JldHVybiBmdW5jdGlvbih0LGkscyl7dmFyIGE9ZnVuY3Rpb24odCl7cmV0dXJuIHQuYWRkKDQtdC5pc29XZWVrZGF5KCksZSl9LGQ9aS5wcm90b3R5cGU7ZC5pc29XZWVrWWVhcj1mdW5jdGlvbigpe3JldHVybiBhKHRoaXMpLnllYXIoKX0sZC5pc29XZWVrPWZ1bmN0aW9uKHQpe2lmKCF0aGlzLiR1dGlscygpLnUodCkpcmV0dXJuIHRoaXMuYWRkKDcqKHQtdGhpcy5pc29XZWVrKCkpLGUpO3ZhciBpLGQsbixvLHI9YSh0aGlzKSx1PShpPXRoaXMuaXNvV2Vla1llYXIoKSxkPXRoaXMuJHUsbj0oZD9zLnV0YzpzKSgpLnllYXIoaSkuc3RhcnRPZihcInllYXJcIiksbz00LW4uaXNvV2Vla2RheSgpLG4uaXNvV2Vla2RheSgpPjQmJihvKz03KSxuLmFkZChvLGUpKTtyZXR1cm4gci5kaWZmKHUsXCJ3ZWVrXCIpKzF9LGQuaXNvV2Vla2RheT1mdW5jdGlvbihlKXtyZXR1cm4gdGhpcy4kdXRpbHMoKS51KGUpP3RoaXMuZGF5KCl8fDc6dGhpcy5kYXkodGhpcy5kYXkoKSU3P2U6ZS03KX07dmFyIG49ZC5zdGFydE9mO2Quc3RhcnRPZj1mdW5jdGlvbihlLHQpe3ZhciBpPXRoaXMuJHV0aWxzKCkscz0hIWkudSh0KXx8dDtyZXR1cm5cImlzb3dlZWtcIj09PWkucChlKT9zP3RoaXMuZGF0ZSh0aGlzLmRhdGUoKS0odGhpcy5pc29XZWVrZGF5KCktMSkpLnN0YXJ0T2YoXCJkYXlcIik6dGhpcy5kYXRlKHRoaXMuZGF0ZSgpLTEtKHRoaXMuaXNvV2Vla2RheSgpLTEpKzcpLmVuZE9mKFwiZGF5XCIpOm4uYmluZCh0aGlzKShlLHQpfX19KSk7IiwgImxldCB0b2tlbkNvdW50ZXIgPSAwO1xuLyoqXG4gKiBDcmVhdGVzIGEgbmV3IHVuaXF1ZSB0b2tlbiBmb3IgZGVwZW5kZW5jeSBpbmplY3Rpb24uXG4gKlxuICogQHBhcmFtIGRlc2NyaXB0aW9uIE9wdGlvbmFsIGRlc2NyaXB0aW9uIGZvciBkZWJ1Z2dpbmcgcHVycG9zZXNcbiAqIEByZXR1cm5zIEEgdW5pcXVlIHRva2VuIHRoYXQgY2FuIGJlIHVzZWQgYXMgYSBNYXAga2V5XG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHRzXG4gKiBpbnRlcmZhY2UgSUxvZ2dlciB7IGxvZyhtc2c6IHN0cmluZyk6IHZvaWQgfVxuICogY29uc3QgTG9nZ2VyVG9rZW4gPSBUb2tlbjxJTG9nZ2VyPignTG9nZ2VyJylcbiAqIGBgYFxuICovXG5leHBvcnQgZnVuY3Rpb24gVG9rZW4oZGVzY3JpcHRpb24pIHtcbiAgICBjb25zdCBpZCA9ICsrdG9rZW5Db3VudGVyO1xuICAgIGNvbnN0IHN5bSA9IFN5bWJvbChkZXNjcmlwdGlvbiA/IGBUb2tlbigke2Rlc2NyaXB0aW9ufSlgIDogYFRva2VuIyR7aWR9YCk7XG4gICAgY29uc3QgdG9rZW4gPSB7XG4gICAgICAgIHN5bWJvbDogc3ltLFxuICAgICAgICBkZXNjcmlwdGlvbixcbiAgICAgICAgdG9TdHJpbmcoKSB7XG4gICAgICAgICAgICByZXR1cm4gZGVzY3JpcHRpb25cbiAgICAgICAgICAgICAgICA/IGBUb2tlbjwke2Rlc2NyaXB0aW9ufT5gXG4gICAgICAgICAgICAgICAgOiBgVG9rZW48IyR7aWR9PmA7XG4gICAgICAgIH1cbiAgICB9O1xuICAgIHJldHVybiB0b2tlbjtcbn1cbi8qKlxuICogQ3JlYXRlcyBhIG5ldyB1bmlxdWUgdG9rZW4gd2l0aG91dCBhIHN0cmluZyBsaXRlcmFsLlxuICogUHJlZmVycmVkIGZvciBBdXRvZmFjLXN0eWxlIERJIHRvIGF2b2lkIHN0cmluZyBsaXRlcmFscy5cbiAqXG4gKiBAcmV0dXJucyBBIHVuaXF1ZSB0b2tlbiB0aGF0IGNhbiBiZSB1c2VkIGFzIGEgTWFwIGtleVxuICpcbiAqIEBleGFtcGxlXG4gKiBgYGB0c1xuICogaW50ZXJmYWNlIElMb2dnZXIgeyBsb2cobXNnOiBzdHJpbmcpOiB2b2lkIH1cbiAqIGNvbnN0IExvZ2dlclRva2VuID0gdG9rZW48SUxvZ2dlcj4oKVxuICogYGBgXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB0b2tlbigpIHtcbiAgICByZXR1cm4gVG9rZW4oKTtcbn1cbiIsICIvKipcbiAqIEVycm9yIGNsYXNzZXMgZm9yIE5vdmFESSBjb250YWluZXJcbiAqL1xuZXhwb3J0IGNsYXNzIENvbnRhaW5lckVycm9yIGV4dGVuZHMgRXJyb3Ige1xuICAgIGNvbnN0cnVjdG9yKG1lc3NhZ2UpIHtcbiAgICAgICAgc3VwZXIobWVzc2FnZSk7XG4gICAgICAgIHRoaXMubmFtZSA9ICdDb250YWluZXJFcnJvcic7XG4gICAgfVxufVxuZXhwb3J0IGNsYXNzIEJpbmRpbmdOb3RGb3VuZEVycm9yIGV4dGVuZHMgQ29udGFpbmVyRXJyb3Ige1xuICAgIGNvbnN0cnVjdG9yKHRva2VuRGVzY3JpcHRpb24sIHBhdGggPSBbXSkge1xuICAgICAgICBjb25zdCBwYXRoU3RyID0gcGF0aC5sZW5ndGggPiAwID8gYFxcbiAgRGVwZW5kZW5jeSBwYXRoOiAke3BhdGguam9pbignIC0+ICcpfWAgOiAnJztcbiAgICAgICAgc3VwZXIoYFRva2VuIFwiJHt0b2tlbkRlc2NyaXB0aW9ufVwiIGlzIG5vdCBib3VuZCBvciByZWdpc3RlcmVkIGluIHRoZSBjb250YWluZXIuJHtwYXRoU3RyfWApO1xuICAgICAgICB0aGlzLm5hbWUgPSAnQmluZGluZ05vdEZvdW5kRXJyb3InO1xuICAgIH1cbn1cbmV4cG9ydCBjbGFzcyBDaXJjdWxhckRlcGVuZGVuY3lFcnJvciBleHRlbmRzIENvbnRhaW5lckVycm9yIHtcbiAgICBjb25zdHJ1Y3RvcihwYXRoKSB7XG4gICAgICAgIHN1cGVyKGBDaXJjdWxhciBkZXBlbmRlbmN5IGRldGVjdGVkOiAke3BhdGguam9pbignIC0+ICcpfWApO1xuICAgICAgICB0aGlzLm5hbWUgPSAnQ2lyY3VsYXJEZXBlbmRlbmN5RXJyb3InO1xuICAgIH1cbn1cbiIsICIvKipcbiAqIEF1dG9XaXJlIC0gQXV0b21hdGljIGRlcGVuZGVuY3kgaW5qZWN0aW9uIGZvciBOb3ZhRElcbiAqIFN1cHBvcnRzIHR3byBzdHJhdGVnaWVzOiBtYXBSZXNvbHZlcnMgKHRyYW5zZm9ybWVyLWdlbmVyYXRlZCkgYW5kIG1hcCAobWFudWFsIG92ZXJyaWRlKVxuICovXG4vKipcbiAqIFBlcmZvcm1hbmNlOiBDYWNoZSBleHRyYWN0ZWQgcGFyYW1ldGVyIG5hbWVzIHRvIGF2b2lkIHJlcGVhdGVkIHJlZ2V4IHBhcnNpbmdcbiAqIFdlYWtNYXAgYWxsb3dzIGdhcmJhZ2UgY29sbGVjdGlvbiB3aGVuIGNvbnN0cnVjdG9yIGlzIG5vIGxvbmdlciByZWZlcmVuY2VkXG4gKi9cbmNvbnN0IHBhcmFtTmFtZUNhY2hlID0gbmV3IFdlYWtNYXAoKTtcbi8qKlxuICogRXh0cmFjdCBwYXJhbWV0ZXIgbmFtZXMgZnJvbSBhIGNvbnN0cnVjdG9yIGZ1bmN0aW9uXG4gKiBVc2VzIHJlZ2V4IHRvIHBhcnNlIHRoZSB0b1N0cmluZygpIHJlcHJlc2VudGF0aW9uXG4gKiBQZXJmb3JtYW5jZSBvcHRpbWl6ZWQ6IFJlc3VsdHMgYXJlIGNhY2hlZCBwZXIgY29uc3RydWN0b3JcbiAqXG4gKiBOb3RlOiBPbmx5IHVzZWQgYnkgcmVzb2x2ZUJ5TWFwKCkgZm9yIG1hbnVhbCBtYXAgc3RyYXRlZ3lcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGV4dHJhY3RQYXJhbWV0ZXJOYW1lcyhjb25zdHJ1Y3Rvcikge1xuICAgIC8vIENoZWNrIGNhY2hlIGZpcnN0IC0gYXZvaWRzIGV4cGVuc2l2ZSByZWdleCBwYXJzaW5nXG4gICAgY29uc3QgY2FjaGVkID0gcGFyYW1OYW1lQ2FjaGUuZ2V0KGNvbnN0cnVjdG9yKTtcbiAgICBpZiAoY2FjaGVkKSB7XG4gICAgICAgIHJldHVybiBjYWNoZWQ7XG4gICAgfVxuICAgIC8vIEV4dHJhY3QgcGFyYW1ldGVyIG5hbWVzIChleHBlbnNpdmUgb3BlcmF0aW9uKVxuICAgIGNvbnN0IGZuU3RyID0gY29uc3RydWN0b3IudG9TdHJpbmcoKTtcbiAgICAvLyBNYXRjaCBjb25zdHJ1Y3RvciguLi5hcmdzKSBvciBjbGFzcyB7IGNvbnN0cnVjdG9yKC4uLmFyZ3MpIH1cbiAgICBjb25zdCBtYXRjaCA9IGZuU3RyLm1hdGNoKC9jb25zdHJ1Y3RvclxccypcXCgoW14pXSopXFwpLykgfHwgZm5TdHIubWF0Y2goL15bXihdKlxcKChbXildKilcXCkvKTtcbiAgICBpZiAoIW1hdGNoIHx8ICFtYXRjaFsxXSkge1xuICAgICAgICByZXR1cm4gW107XG4gICAgfVxuICAgIGNvbnN0IHBhcmFtcyA9IG1hdGNoWzFdXG4gICAgICAgIC5zcGxpdCgnLCcpXG4gICAgICAgIC5tYXAocGFyYW0gPT4gcGFyYW0udHJpbSgpKVxuICAgICAgICAuZmlsdGVyKHBhcmFtID0+IHBhcmFtLmxlbmd0aCA+IDApXG4gICAgICAgIC5tYXAocGFyYW0gPT4ge1xuICAgICAgICAvLyBSZW1vdmUgZGVmYXVsdCB2YWx1ZXMsIHR5cGUgYW5ub3RhdGlvbnMsIGFuZCBleHRyYWN0IGp1c3QgdGhlIG5hbWVcbiAgICAgICAgbGV0IG5hbWUgPSBwYXJhbS5zcGxpdCgvWzo9XS8pWzBdLnRyaW0oKTtcbiAgICAgICAgLy8gUmVtb3ZlIFR5cGVTY3JpcHQgbW9kaWZpZXJzIChwdWJsaWMsIHByaXZhdGUsIHByb3RlY3RlZCwgcmVhZG9ubHkpXG4gICAgICAgIC8vIENhbiBhcHBlYXIgbXVsdGlwbGUgdGltZXMsIGUuZy4sIFwicHVibGljIHJlYWRvbmx5IHNlcnZpY2VcIlxuICAgICAgICBuYW1lID0gbmFtZS5yZXBsYWNlKC9eKChwdWJsaWN8cHJpdmF0ZXxwcm90ZWN0ZWR8cmVhZG9ubHkpXFxzKykrLywgJycpO1xuICAgICAgICAvLyBIYW5kbGUgZGVzdHJ1Y3R1cmluZyAtIHNraXAgZm9yIG5vd1xuICAgICAgICBpZiAobmFtZS5pbmNsdWRlcygneycpIHx8IG5hbWUuaW5jbHVkZXMoJ1snKSkge1xuICAgICAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIG5hbWU7XG4gICAgfSlcbiAgICAgICAgLmZpbHRlcigobmFtZSkgPT4gbmFtZSAhPT0gbnVsbCk7XG4gICAgLy8gQ2FjaGUgcmVzdWx0IGZvciBmdXR1cmUgY2FsbHNcbiAgICBwYXJhbU5hbWVDYWNoZS5zZXQoY29uc3RydWN0b3IsIHBhcmFtcyk7XG4gICAgcmV0dXJuIHBhcmFtcztcbn1cbi8qKlxuICogUmVzb2x2ZSBkZXBlbmRlbmNpZXMgdXNpbmcgbWFwIHN0cmF0ZWd5XG4gKiBVc2VzIGV4cGxpY2l0IG1hcHBpbmcgZnJvbSBwYXJhbWV0ZXIgbmFtZXMgdG8gcmVzb2x2ZXJzXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNvbHZlQnlNYXAoY29uc3RydWN0b3IsIGNvbnRhaW5lciwgb3B0aW9ucykge1xuICAgIGlmICghb3B0aW9ucy5tYXApIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdBdXRvV2lyZSBtYXAgc3RyYXRlZ3kgcmVxdWlyZXMgb3B0aW9ucy5tYXAgdG8gYmUgZGVmaW5lZCcpO1xuICAgIH1cbiAgICBjb25zdCBwYXJhbU5hbWVzID0gZXh0cmFjdFBhcmFtZXRlck5hbWVzKGNvbnN0cnVjdG9yKTtcbiAgICBjb25zdCByZXNvbHZlZERlcHMgPSBbXTtcbiAgICBmb3IgKGNvbnN0IHBhcmFtTmFtZSBvZiBwYXJhbU5hbWVzKSB7XG4gICAgICAgIGNvbnN0IHJlc29sdmVyID0gb3B0aW9ucy5tYXBbcGFyYW1OYW1lXTtcbiAgICAgICAgaWYgKHJlc29sdmVyID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIGlmIChvcHRpb25zLnN0cmljdCkge1xuICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgQ2Fubm90IHJlc29sdmUgcGFyYW1ldGVyIFwiJHtwYXJhbU5hbWV9XCIgb24gJHtjb25zdHJ1Y3Rvci5uYW1lfS4gYCArXG4gICAgICAgICAgICAgICAgICAgIGBOb3QgZm91bmQgaW4gYXV0b3dpcmUgbWFwLiBgICtcbiAgICAgICAgICAgICAgICAgICAgYEFkZCBpdCB0byB0aGUgbWFwOiAuYXV0b1dpcmUoeyBtYXA6IHsgJHtwYXJhbU5hbWV9OiAuLi4gfSB9KWApO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgLy8gU2lsZW50bHkgcHVzaCB1bmRlZmluZWQgZm9yIG1pc3NpbmcgcGFyYW1ldGVyc1xuICAgICAgICAgICAgICAgIC8vIFRoaXMgaXMgZXhwZWN0ZWQ6IHRyYW5zZm9ybWVyIGZpbHRlcnMgb3V0IHByaW1pdGl2ZSB0eXBlcyBhdCBjb21waWxlLXRpbWUsXG4gICAgICAgICAgICAgICAgLy8gc28gbWlzc2luZyBwYXJhbXMgYXJlIHR5cGljYWxseSBwcmltaXRpdmVzIHRoYXQgZG9uJ3QgbmVlZCBESSByZXNvbHV0aW9uXG4gICAgICAgICAgICAgICAgcmVzb2x2ZWREZXBzLnB1c2godW5kZWZpbmVkKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICB9XG4gICAgICAgIC8vIFJlc29sdmVyIGNhbiBiZSBhIGZ1bmN0aW9uIG9yIGEgVG9rZW5cbiAgICAgICAgaWYgKHR5cGVvZiByZXNvbHZlciA9PT0gJ2Z1bmN0aW9uJykge1xuICAgICAgICAgICAgcmVzb2x2ZWREZXBzLnB1c2gocmVzb2x2ZXIoY29udGFpbmVyKSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAvLyBBc3N1bWUgaXQncyBhIFRva2VuXG4gICAgICAgICAgICByZXNvbHZlZERlcHMucHVzaChjb250YWluZXIucmVzb2x2ZShyZXNvbHZlcikpO1xuICAgICAgICB9XG4gICAgfVxuICAgIHJldHVybiByZXNvbHZlZERlcHM7XG59XG4vKipcbiAqIFJlc29sdmUgZGVwZW5kZW5jaWVzIHVzaW5nIG1hcFJlc29sdmVycyBhcnJheSBzdHJhdGVneVxuICogT1BUSU1BTCBQRVJGT1JNQU5DRTogTygxKSBhcnJheSBhY2Nlc3MgcGVyIHBhcmFtZXRlclxuICogTWluaWZpY2F0aW9uLXNhZmU6IFVzZXMgcG9zaXRpb24tYmFzZWQgYXJyYXlcbiAqIFJlZmFjdG9yaW5nLWZyaWVuZGx5OiBUcmFuc2Zvcm1lciByZWdlbmVyYXRlcyBhcnJheSBvbiByZWNvbXBpbGVcbiAqXG4gKiBSZXF1aXJlcyBidWlsZC10aW1lIHRyYW5zZm9ybWVyIHRvIGdlbmVyYXRlIG1hcFJlc29sdmVycyBhcnJheVxuICovXG5leHBvcnQgZnVuY3Rpb24gcmVzb2x2ZUJ5TWFwUmVzb2x2ZXJzKF9jb25zdHJ1Y3RvciwgY29udGFpbmVyLCBvcHRpb25zKSB7XG4gICAgaWYgKCFvcHRpb25zLm1hcFJlc29sdmVycyB8fCBvcHRpb25zLm1hcFJlc29sdmVycy5sZW5ndGggPT09IDApIHtcbiAgICAgICAgcmV0dXJuIFtdO1xuICAgIH1cbiAgICBjb25zdCByZXNvbHZlZERlcHMgPSBbXTtcbiAgICAvLyBTaW1wbGUgTygxKSBhcnJheSBhY2Nlc3MgLSB1bHRyYSBmYXN0IVxuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgb3B0aW9ucy5tYXBSZXNvbHZlcnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgY29uc3QgcmVzb2x2ZXIgPSBvcHRpb25zLm1hcFJlc29sdmVyc1tpXTtcbiAgICAgICAgaWYgKHJlc29sdmVyID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIC8vIHVuZGVmaW5lZCBpbmRpY2F0ZXMgcHJpbWl0aXZlIHR5cGUgb3IgcGFyYW1ldGVyIHdpdGhvdXQgRElcbiAgICAgICAgICAgIHJlc29sdmVkRGVwcy5wdXNoKHVuZGVmaW5lZCk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAodHlwZW9mIHJlc29sdmVyID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICAgICAgICAvLyBSZXNvbHZlciBmdW5jdGlvbjogKGMpID0+IGMucmVzb2x2ZVR5cGUoLi4uKVxuICAgICAgICAgICAgcmVzb2x2ZWREZXBzLnB1c2gocmVzb2x2ZXIoY29udGFpbmVyKSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAvLyBUb2tlbi1iYXNlZCByZXNvbHV0aW9uXG4gICAgICAgICAgICByZXNvbHZlZERlcHMucHVzaChjb250YWluZXIucmVzb2x2ZShyZXNvbHZlcikpO1xuICAgICAgICB9XG4gICAgfVxuICAgIHJldHVybiByZXNvbHZlZERlcHM7XG59XG4vKipcbiAqIE1haW4gYXV0b3dpcmUgZnVuY3Rpb24gLSBkaXNwYXRjaGVzIHRvIGFwcHJvcHJpYXRlIHN0cmF0ZWd5XG4gKiBQcmlvcml0eTogbWFwUmVzb2x2ZXJzICh0cmFuc2Zvcm1lci1nZW5lcmF0ZWQpID4gbWFwIChtYW51YWwgb3ZlcnJpZGUpXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBhdXRvd2lyZShjb25zdHJ1Y3RvciwgY29udGFpbmVyLCBvcHRpb25zKSB7XG4gICAgY29uc3Qgb3B0cyA9IHtcbiAgICAgICAgYnk6ICdwYXJhbU5hbWUnLFxuICAgICAgICBzdHJpY3Q6IGZhbHNlLFxuICAgICAgICAuLi5vcHRpb25zXG4gICAgfTtcbiAgICAvLyBISUdIRVNUIFBSSU9SSVRZOiBtYXBSZXNvbHZlcnMgYXJyYXkgKHRyYW5zZm9ybWVyLWdlbmVyYXRlZCwgb3B0aW1hbCBwZXJmb3JtYW5jZSlcbiAgICAvLyBPKDEpIGFycmF5IGFjY2VzcyBwZXIgcGFyYW1ldGVyIC0gbWluaWZpY2F0aW9uLXNhZmUgYW5kIHJlZmFjdG9yaW5nLWZyaWVuZGx5XG4gICAgaWYgKG9wdHMubWFwUmVzb2x2ZXJzICYmIG9wdHMubWFwUmVzb2x2ZXJzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgcmV0dXJuIHJlc29sdmVCeU1hcFJlc29sdmVycyhjb25zdHJ1Y3RvciwgY29udGFpbmVyLCBvcHRzKTtcbiAgICB9XG4gICAgLy8gRkFMTEJBQ0s6IE1hbnVhbCBtYXAgc3RyYXRlZ3kgZm9yIGV4cGxpY2l0IG92ZXJyaWRlc1xuICAgIGlmIChvcHRzLm1hcCAmJiBPYmplY3Qua2V5cyhvcHRzLm1hcCkubGVuZ3RoID4gMCkge1xuICAgICAgICByZXR1cm4gcmVzb2x2ZUJ5TWFwKGNvbnN0cnVjdG9yLCBjb250YWluZXIsIG9wdHMpO1xuICAgIH1cbiAgICAvLyBObyBhdXRvd2lyaW5nIGNvbmZpZ3VyZWQsIHJldHVybiBlbXB0eSBhcnJheVxuICAgIHJldHVybiBbXTtcbn1cbiIsICIvKipcbiAqIEZsdWVudCBidWlsZGVyIEFQSSBmb3IgTm92YURJIENvbnRhaW5lciAoQXV0b2ZhYy1zdHlsZSlcbiAqL1xuaW1wb3J0IHsgVG9rZW4gfSBmcm9tICcuL3Rva2VuLmpzJztcbmltcG9ydCB7IGF1dG93aXJlIH0gZnJvbSAnLi9hdXRvd2lyZS5qcyc7XG4vKipcbiAqIEZsdWVudCByZWdpc3RyYXRpb24gYnVpbGRlciByZXR1cm5lZCBhZnRlciBlYWNoIHJlZ2lzdHJhdGlvbiBtZXRob2RcbiAqL1xuZXhwb3J0IGNsYXNzIFJlZ2lzdHJhdGlvbkJ1aWxkZXIge1xuICAgIGNvbnN0cnVjdG9yKHBlbmRpbmcsIHJlZ2lzdHJhdGlvbnMpIHtcbiAgICAgICAgdGhpcy5yZWdpc3RyYXRpb25zID0gcmVnaXN0cmF0aW9ucztcbiAgICAgICAgdGhpcy5jb25maWdzID0gW107XG4gICAgICAgIHRoaXMuZGVmYXVsdExpZmV0aW1lID0gJ3NpbmdsZXRvbic7XG4gICAgICAgIHRoaXMucGVuZGluZyA9IHBlbmRpbmc7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEJpbmQgdGhpcyByZWdpc3RyYXRpb24gdG8gYSB0b2tlbiBvciBpbnRlcmZhY2UgdHlwZVxuICAgICAqXG4gICAgICogQG92ZXJsb2FkXG4gICAgICogQHBhcmFtIHtUb2tlbjxVPn0gdG9rZW4gLSBFeHBsaWNpdCB0b2tlbiBmb3IgYmluZGluZ1xuICAgICAqXG4gICAgICogQG92ZXJsb2FkXG4gICAgICogQHBhcmFtIHtzdHJpbmd9IHR5cGVOYW1lIC0gSW50ZXJmYWNlIHR5cGUgbmFtZSAoYXV0by1nZW5lcmF0ZWQgYnkgdHJhbnNmb3JtZXIpXG4gICAgICovXG4gICAgYXModG9rZW5PclR5cGVOYW1lKSB7XG4gICAgICAgIC8vIENoZWNrIGlmIGFyZ3VtZW50IGlzIGEgVG9rZW4gb2JqZWN0IChoYXMgc3ltYm9sIHByb3BlcnR5KVxuICAgICAgICBpZiAodG9rZW5PclR5cGVOYW1lICYmIHR5cGVvZiB0b2tlbk9yVHlwZU5hbWUgPT09ICdvYmplY3QnICYmICdzeW1ib2wnIGluIHRva2VuT3JUeXBlTmFtZSkge1xuICAgICAgICAgICAgLy8gVG9rZW4tYmFzZWQgcmVnaXN0cmF0aW9uXG4gICAgICAgICAgICBjb25zdCBjb25maWcgPSB7XG4gICAgICAgICAgICAgICAgdG9rZW46IHRva2VuT3JUeXBlTmFtZSxcbiAgICAgICAgICAgICAgICB0eXBlOiB0aGlzLnBlbmRpbmcudHlwZSxcbiAgICAgICAgICAgICAgICB2YWx1ZTogdGhpcy5wZW5kaW5nLnZhbHVlLFxuICAgICAgICAgICAgICAgIGZhY3Rvcnk6IHRoaXMucGVuZGluZy5mYWN0b3J5LFxuICAgICAgICAgICAgICAgIGNvbnN0cnVjdG9yOiB0aGlzLnBlbmRpbmcuY29uc3RydWN0b3IsXG4gICAgICAgICAgICAgICAgbGlmZXRpbWU6IHRoaXMuZGVmYXVsdExpZmV0aW1lXG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgdGhpcy5jb25maWdzLnB1c2goY29uZmlnKTtcbiAgICAgICAgICAgIHRoaXMucmVnaXN0cmF0aW9ucy5wdXNoKGNvbmZpZyk7XG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIC8vIEludGVyZmFjZS1iYXNlZCByZWdpc3RyYXRpb24gKHR5cGVOYW1lIHN0cmluZyBvciB1bmRlZmluZWQpXG4gICAgICAgICAgICBjb25zdCBjb25maWcgPSB7XG4gICAgICAgICAgICAgICAgdG9rZW46IG51bGwsIC8vIFdpbGwgYmUgc2V0IGR1cmluZyBidWlsZCgpXG4gICAgICAgICAgICAgICAgdHlwZTogdGhpcy5wZW5kaW5nLnR5cGUsXG4gICAgICAgICAgICAgICAgdmFsdWU6IHRoaXMucGVuZGluZy52YWx1ZSxcbiAgICAgICAgICAgICAgICBmYWN0b3J5OiB0aGlzLnBlbmRpbmcuZmFjdG9yeSxcbiAgICAgICAgICAgICAgICBjb25zdHJ1Y3RvcjogdGhpcy5wZW5kaW5nLmNvbnN0cnVjdG9yLFxuICAgICAgICAgICAgICAgIGxpZmV0aW1lOiB0aGlzLmRlZmF1bHRMaWZldGltZSxcbiAgICAgICAgICAgICAgICBpbnRlcmZhY2VUeXBlOiB0b2tlbk9yVHlwZU5hbWVcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICB0aGlzLmNvbmZpZ3MucHVzaChjb25maWcpO1xuICAgICAgICAgICAgdGhpcy5yZWdpc3RyYXRpb25zLnB1c2goY29uZmlnKTtcbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJlZ2lzdGVyIGFzIGRlZmF1bHQgaW1wbGVtZW50YXRpb24gZm9yIGFuIGludGVyZmFjZVxuICAgICAqIENvbWJpbmVzIGFzKCkgKyBhc0RlZmF1bHQoKVxuICAgICAqL1xuICAgIGFzRGVmYXVsdEludGVyZmFjZSh0eXBlTmFtZSkge1xuICAgICAgICB0aGlzLmFzKFwiVEludGVyZmFjZVwiLCB0eXBlTmFtZSk7XG4gICAgICAgIHJldHVybiB0aGlzLmFzRGVmYXVsdCgpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZWdpc3RlciBhcyBhIGtleWVkIGludGVyZmFjZSBpbXBsZW1lbnRhdGlvblxuICAgICAqIENvbWJpbmVzIGFzKCkgKyBrZXllZCgpXG4gICAgICovXG4gICAgYXNLZXllZEludGVyZmFjZShrZXksIHR5cGVOYW1lKSB7XG4gICAgICAgIHRoaXMuYXMoXCJUSW50ZXJmYWNlXCIsIHR5cGVOYW1lKTtcbiAgICAgICAgcmV0dXJuIHRoaXMua2V5ZWQoa2V5KTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogUmVnaXN0ZXIgYXMgbXVsdGlwbGUgaW1wbGVtZW50ZWQgaW50ZXJmYWNlc1xuICAgICAqL1xuICAgIGFzSW1wbGVtZW50ZWRJbnRlcmZhY2VzKHRva2Vucykge1xuICAgICAgICBpZiAodG9rZW5zLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cbiAgICAgICAgLy8gSWYgdGhlcmUgYXJlIGV4aXN0aW5nIGNvbmZpZ3MgKGZyb20gcHJldmlvdXMgYXMoKSBjYWxscyksIGFkZCB0aGVzZSBhcyBhZGRpdGlvbmFsIGludGVyZmFjZXNcbiAgICAgICAgaWYgKHRoaXMuY29uZmlncy5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICAvLyBBZGQgYWxsIHRva2VucyBhcyBhZGRpdGlvbmFsIGludGVyZmFjZXMgdG8gZXhpc3RpbmcgY29uZmlnc1xuICAgICAgICAgICAgZm9yIChjb25zdCBjb25maWcgb2YgdGhpcy5jb25maWdzKSB7XG4gICAgICAgICAgICAgICAgY29uZmlnLmxpZmV0aW1lID0gJ3NpbmdsZXRvbic7IC8vIGFzSW1wbGVtZW50ZWRJbnRlcmZhY2VzIGRlZmF1bHRzIHRvIHNpbmdsZXRvblxuICAgICAgICAgICAgICAgIGNvbmZpZy5hZGRpdGlvbmFsVG9rZW5zID0gY29uZmlnLmFkZGl0aW9uYWxUb2tlbnMgfHwgW107XG4gICAgICAgICAgICAgICAgY29uZmlnLmFkZGl0aW9uYWxUb2tlbnMucHVzaCguLi50b2tlbnMpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cbiAgICAgICAgLy8gTm8gZXhpc3RpbmcgY29uZmlncywgY3JlYXRlIG5ldyBvbmUgd2l0aCBmaXJzdCB0b2tlblxuICAgICAgICBjb25zdCBmaXJzdENvbmZpZyA9IHtcbiAgICAgICAgICAgIHRva2VuOiB0b2tlbnNbMF0sXG4gICAgICAgICAgICB0eXBlOiB0aGlzLnBlbmRpbmcudHlwZSxcbiAgICAgICAgICAgIHZhbHVlOiB0aGlzLnBlbmRpbmcudmFsdWUsXG4gICAgICAgICAgICBmYWN0b3J5OiB0aGlzLnBlbmRpbmcuZmFjdG9yeSxcbiAgICAgICAgICAgIGNvbnN0cnVjdG9yOiB0aGlzLnBlbmRpbmcuY29uc3RydWN0b3IsXG4gICAgICAgICAgICBsaWZldGltZTogJ3NpbmdsZXRvbidcbiAgICAgICAgfTtcbiAgICAgICAgdGhpcy5jb25maWdzLnB1c2goZmlyc3RDb25maWcpO1xuICAgICAgICB0aGlzLnJlZ2lzdHJhdGlvbnMucHVzaChmaXJzdENvbmZpZyk7XG4gICAgICAgIC8vIEFkZGl0aW9uYWwgdG9rZW5zIHJlZmVyZW5jZSB0aGUgc2FtZSByZWdpc3RyYXRpb25cbiAgICAgICAgZm9yIChsZXQgaSA9IDE7IGkgPCB0b2tlbnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIGZpcnN0Q29uZmlnLmFkZGl0aW9uYWxUb2tlbnMgPSBmaXJzdENvbmZpZy5hZGRpdGlvbmFsVG9rZW5zIHx8IFtdO1xuICAgICAgICAgICAgZmlyc3RDb25maWcuYWRkaXRpb25hbFRva2Vucy5wdXNoKHRva2Vuc1tpXSk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFNldCBzaW5nbGV0b24gbGlmZXRpbWUgKG9uZSBpbnN0YW5jZSBmb3IgZW50aXJlIGNvbnRhaW5lcilcbiAgICAgKi9cbiAgICBzaW5nbGVJbnN0YW5jZSgpIHtcbiAgICAgICAgZm9yIChjb25zdCBjb25maWcgb2YgdGhpcy5jb25maWdzKSB7XG4gICAgICAgICAgICBjb25maWcubGlmZXRpbWUgPSAnc2luZ2xldG9uJztcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcztcbiAgICB9XG4gICAgLyoqXG4gICAgICogU2V0IHBlci1yZXF1ZXN0IGxpZmV0aW1lIChvbmUgaW5zdGFuY2UgcGVyIHJlc29sdmUgY2FsbCB0cmVlKVxuICAgICAqL1xuICAgIGluc3RhbmNlUGVyUmVxdWVzdCgpIHtcbiAgICAgICAgZm9yIChjb25zdCBjb25maWcgb2YgdGhpcy5jb25maWdzKSB7XG4gICAgICAgICAgICBjb25maWcubGlmZXRpbWUgPSAncGVyLXJlcXVlc3QnO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBTZXQgdHJhbnNpZW50IGxpZmV0aW1lIChuZXcgaW5zdGFuY2UgZXZlcnkgdGltZSlcbiAgICAgKiBBbGlhcyBmb3IgZGVmYXVsdCBiZWhhdmlvclxuICAgICAqL1xuICAgIGluc3RhbmNlUGVyRGVwZW5kZW5jeSgpIHtcbiAgICAgICAgZm9yIChjb25zdCBjb25maWcgb2YgdGhpcy5jb25maWdzKSB7XG4gICAgICAgICAgICBjb25maWcubGlmZXRpbWUgPSAndHJhbnNpZW50JztcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcztcbiAgICB9XG4gICAgLyoqXG4gICAgICogTmFtZSB0aGlzIHJlZ2lzdHJhdGlvbiBmb3IgbmFtZWQgcmVzb2x1dGlvblxuICAgICAqL1xuICAgIG5hbWVkKG5hbWUpIHtcbiAgICAgICAgZm9yIChjb25zdCBjb25maWcgb2YgdGhpcy5jb25maWdzKSB7XG4gICAgICAgICAgICBjb25maWcubmFtZSA9IG5hbWU7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEtleSB0aGlzIHJlZ2lzdHJhdGlvbiBmb3Iga2V5ZWQgcmVzb2x1dGlvblxuICAgICAqL1xuICAgIGtleWVkKGtleSkge1xuICAgICAgICBmb3IgKGNvbnN0IGNvbmZpZyBvZiB0aGlzLmNvbmZpZ3MpIHtcbiAgICAgICAgICAgIGNvbmZpZy5rZXkgPSBrZXk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIE1hcmsgdGhpcyBhcyBkZWZhdWx0IHJlZ2lzdHJhdGlvblxuICAgICAqIERlZmF1bHQgcmVnaXN0cmF0aW9ucyBkb24ndCBvdmVycmlkZSBleGlzdGluZyBvbmVzXG4gICAgICovXG4gICAgYXNEZWZhdWx0KCkge1xuICAgICAgICBmb3IgKGNvbnN0IGNvbmZpZyBvZiB0aGlzLmNvbmZpZ3MpIHtcbiAgICAgICAgICAgIGNvbmZpZy5pc0RlZmF1bHQgPSB0cnVlO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBPbmx5IHJlZ2lzdGVyIGlmIHRva2VuIG5vdCBhbHJlYWR5IHJlZ2lzdGVyZWRcbiAgICAgKi9cbiAgICBpZk5vdFJlZ2lzdGVyZWQoKSB7XG4gICAgICAgIGZvciAoY29uc3QgY29uZmlnIG9mIHRoaXMuY29uZmlncykge1xuICAgICAgICAgICAgY29uZmlnLmlmTm90UmVnaXN0ZXJlZCA9IHRydWU7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFNwZWNpZnkgcGFyYW1ldGVyIHZhbHVlcyBmb3IgY29uc3RydWN0b3IgKHByaW1pdGl2ZXMgYW5kIGNvbnN0YW50cylcbiAgICAgKiBVc2UgdGhpcyBmb3Igbm9uLURJIHBhcmFtZXRlcnMgbGlrZSBzdHJpbmdzLCBudW1iZXJzLCBjb25maWcgdmFsdWVzXG4gICAgICovXG4gICAgd2l0aFBhcmFtZXRlcnMocGFyYW1ldGVycykge1xuICAgICAgICBmb3IgKGNvbnN0IGNvbmZpZyBvZiB0aGlzLmNvbmZpZ3MpIHtcbiAgICAgICAgICAgIGNvbmZpZy5wYXJhbWV0ZXJWYWx1ZXMgPSBwYXJhbWV0ZXJzO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBFbmFibGUgYXV0b21hdGljIGRlcGVuZGVuY3kgaW5qZWN0aW9uIChhdXRvd2lyaW5nKVxuICAgICAqIFN1cHBvcnRzIHRocmVlIHN0cmF0ZWdpZXM6IHBhcmFtTmFtZSAoZGVmYXVsdCksIG1hcCwgYW5kIGNsYXNzXG4gICAgICpcbiAgICAgKiBAZXhhbXBsZVxuICAgICAqIGBgYHRzXG4gICAgICogLy8gU3RyYXRlZ3kgMTogcGFyYW1OYW1lIChkZWZhdWx0LCByZXF1aXJlcyBub24tbWluaWZpZWQgY29kZSBpbiBkZXYpXG4gICAgICogYnVpbGRlci5yZWdpc3RlclR5cGUoRXZlbnRCdXMpLmFzPElFdmVudEJ1cz4oKS5hdXRvV2lyZSgpXG4gICAgICpcbiAgICAgKiAvLyBTdHJhdGVneSAyOiBtYXAgKG1pbmlmeS1zYWZlLCBleHBsaWNpdClcbiAgICAgKiBidWlsZGVyLnJlZ2lzdGVyVHlwZShFdmVudEJ1cykuYXM8SUV2ZW50QnVzPigpLmF1dG9XaXJlKHtcbiAgICAgKiAgIG1hcDoge1xuICAgICAqICAgICBsb2dnZXI6IChjKSA9PiBjLnJlc29sdmVUeXBlPElMb2dnZXI+KClcbiAgICAgKiAgIH1cbiAgICAgKiB9KVxuICAgICAqXG4gICAgICogLy8gU3RyYXRlZ3kgMzogY2xhc3MgKHJlcXVpcmVzIGJ1aWxkLXRpbWUgY29kZWdlbilcbiAgICAgKiBidWlsZGVyLnJlZ2lzdGVyVHlwZShFdmVudEJ1cykuYXM8SUV2ZW50QnVzPigpLmF1dG9XaXJlKHsgYnk6ICdjbGFzcycgfSlcbiAgICAgKiBgYGBcbiAgICAgKi9cbiAgICBhdXRvV2lyZShvcHRpb25zKSB7XG4gICAgICAgIGZvciAoY29uc3QgY29uZmlnIG9mIHRoaXMuY29uZmlncykge1xuICAgICAgICAgICAgY29uZmlnLmF1dG93aXJlT3B0aW9ucyA9IG9wdGlvbnMgfHwgeyBieTogJ3BhcmFtTmFtZScsIHN0cmljdDogZmFsc2UgfTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcztcbiAgICB9XG59XG4vKipcbiAqIEZsdWVudCBidWlsZGVyIGZvciBDb250YWluZXIgY29uZmlndXJhdGlvblxuICovXG5leHBvcnQgY2xhc3MgQnVpbGRlciB7XG4gICAgY29uc3RydWN0b3IoYmFzZUNvbnRhaW5lcikge1xuICAgICAgICB0aGlzLmJhc2VDb250YWluZXIgPSBiYXNlQ29udGFpbmVyO1xuICAgICAgICB0aGlzLnJlZ2lzdHJhdGlvbnMgPSBbXTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogUmVnaXN0ZXIgYSBjbGFzcyBjb25zdHJ1Y3RvclxuICAgICAqL1xuICAgIHJlZ2lzdGVyVHlwZShjb25zdHJ1Y3Rvcikge1xuICAgICAgICBjb25zdCBwZW5kaW5nID0ge1xuICAgICAgICAgICAgdHlwZTogJ3R5cGUnLFxuICAgICAgICAgICAgdmFsdWU6IG51bGwsXG4gICAgICAgICAgICBjb25zdHJ1Y3RvclxuICAgICAgICB9O1xuICAgICAgICByZXR1cm4gbmV3IFJlZ2lzdHJhdGlvbkJ1aWxkZXIocGVuZGluZywgdGhpcy5yZWdpc3RyYXRpb25zKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogUmVnaXN0ZXIgYSBwcmUtY3JlYXRlZCBpbnN0YW5jZVxuICAgICAqL1xuICAgIHJlZ2lzdGVySW5zdGFuY2UoaW5zdGFuY2UpIHtcbiAgICAgICAgY29uc3QgcGVuZGluZyA9IHtcbiAgICAgICAgICAgIHR5cGU6ICdpbnN0YW5jZScsXG4gICAgICAgICAgICB2YWx1ZTogaW5zdGFuY2UsXG4gICAgICAgICAgICBjb25zdHJ1Y3RvcjogdW5kZWZpbmVkXG4gICAgICAgIH07XG4gICAgICAgIHJldHVybiBuZXcgUmVnaXN0cmF0aW9uQnVpbGRlcihwZW5kaW5nLCB0aGlzLnJlZ2lzdHJhdGlvbnMpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZWdpc3RlciBhIGZhY3RvcnkgZnVuY3Rpb25cbiAgICAgKi9cbiAgICByZWdpc3RlcihmYWN0b3J5KSB7XG4gICAgICAgIGNvbnN0IHBlbmRpbmcgPSB7XG4gICAgICAgICAgICB0eXBlOiAnZmFjdG9yeScsXG4gICAgICAgICAgICB2YWx1ZTogbnVsbCxcbiAgICAgICAgICAgIGZhY3RvcnksXG4gICAgICAgICAgICBjb25zdHJ1Y3RvcjogdW5kZWZpbmVkXG4gICAgICAgIH07XG4gICAgICAgIHJldHVybiBuZXcgUmVnaXN0cmF0aW9uQnVpbGRlcihwZW5kaW5nLCB0aGlzLnJlZ2lzdHJhdGlvbnMpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZWdpc3RlciBhIG1vZHVsZSAoZnVuY3Rpb24gdGhhdCBhZGRzIG11bHRpcGxlIHJlZ2lzdHJhdGlvbnMpXG4gICAgICovXG4gICAgbW9kdWxlKG1vZHVsZUZ1bmMpIHtcbiAgICAgICAgbW9kdWxlRnVuYyh0aGlzKTtcbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJlc29sdmUgaW50ZXJmYWNlIHR5cGUgbmFtZXMgdG8gdG9rZW5zXG4gICAgICogQGludGVybmFsXG4gICAgICovXG4gICAgcmVzb2x2ZUludGVyZmFjZVRva2Vucyhjb250YWluZXIpIHtcbiAgICAgICAgZm9yIChjb25zdCBjb25maWcgb2YgdGhpcy5yZWdpc3RyYXRpb25zKSB7XG4gICAgICAgICAgICBpZiAoY29uZmlnLmludGVyZmFjZVR5cGUgIT09IHVuZGVmaW5lZCAmJiAhY29uZmlnLnRva2VuKSB7XG4gICAgICAgICAgICAgICAgY29uZmlnLnRva2VuID0gY29udGFpbmVyLmludGVyZmFjZVRva2VuKGNvbmZpZy5pbnRlcmZhY2VUeXBlKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBJZGVudGlmeSB0b2tlbnMgdGhhdCBoYXZlIG5vbi1kZWZhdWx0IHJlZ2lzdHJhdGlvbnNcbiAgICAgKiBAaW50ZXJuYWxcbiAgICAgKi9cbiAgICBpZGVudGlmeU5vbkRlZmF1bHRUb2tlbnMoKSB7XG4gICAgICAgIGNvbnN0IHRva2Vuc1dpdGhOb25EZWZhdWx0cyA9IG5ldyBTZXQoKTtcbiAgICAgICAgZm9yIChjb25zdCBjb25maWcgb2YgdGhpcy5yZWdpc3RyYXRpb25zKSB7XG4gICAgICAgICAgICBpZiAoIWNvbmZpZy5pc0RlZmF1bHQgJiYgIWNvbmZpZy5uYW1lICYmIGNvbmZpZy5rZXkgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgICAgIHRva2Vuc1dpdGhOb25EZWZhdWx0cy5hZGQoY29uZmlnLnRva2VuKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdG9rZW5zV2l0aE5vbkRlZmF1bHRzO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBDaGVjayBpZiByZWdpc3RyYXRpb24gc2hvdWxkIGJlIHNraXBwZWRcbiAgICAgKiBAaW50ZXJuYWxcbiAgICAgKi9cbiAgICBzaG91bGRTa2lwUmVnaXN0cmF0aW9uKGNvbmZpZywgdG9rZW5zV2l0aE5vbkRlZmF1bHRzLCByZWdpc3RlcmVkVG9rZW5zKSB7XG4gICAgICAgIC8vIFNraXAgZGVmYXVsdCByZWdpc3RyYXRpb25zIGlmIHRoZXJlJ3MgYSBub24tZGVmYXVsdCBmb3IgdGhlIHNhbWUgdG9rZW5cbiAgICAgICAgaWYgKGNvbmZpZy5pc0RlZmF1bHQgJiYgIWNvbmZpZy5uYW1lICYmIGNvbmZpZy5rZXkgPT09IHVuZGVmaW5lZCAmJiB0b2tlbnNXaXRoTm9uRGVmYXVsdHMuaGFzKGNvbmZpZy50b2tlbikpIHtcbiAgICAgICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgICB9XG4gICAgICAgIC8vIEhhbmRsZSBpZk5vdFJlZ2lzdGVyZWRcbiAgICAgICAgaWYgKGNvbmZpZy5pZk5vdFJlZ2lzdGVyZWQgJiYgcmVnaXN0ZXJlZFRva2Vucy5oYXMoY29uZmlnLnRva2VuKSkge1xuICAgICAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICAgIH1cbiAgICAgICAgLy8gSGFuZGxlIGFzRGVmYXVsdFxuICAgICAgICBpZiAoY29uZmlnLmlzRGVmYXVsdCAmJiByZWdpc3RlcmVkVG9rZW5zLmhhcyhjb25maWcudG9rZW4pKSB7XG4gICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIENyZWF0ZSBiaW5kaW5nIHRva2VuIGZvciByZWdpc3RyYXRpb24gKG5hbWVkLCBrZXllZCwgb3IgbXVsdGkpXG4gICAgICogQGludGVybmFsXG4gICAgICovXG4gICAgY3JlYXRlQmluZGluZ1Rva2VuKGNvbmZpZywgbmFtZWRSZWdpc3RyYXRpb25zLCBrZXllZFJlZ2lzdHJhdGlvbnMsIG11bHRpUmVnaXN0cmF0aW9ucykge1xuICAgICAgICBpZiAoY29uZmlnLm5hbWUpIHtcbiAgICAgICAgICAgIC8vIE5hbWVkIHJlZ2lzdHJhdGlvbiBnZXRzIHVuaXF1ZSB0b2tlblxuICAgICAgICAgICAgY29uc3QgYmluZGluZ1Rva2VuID0gVG9rZW4oYF9fbmFtZWRfJHtjb25maWcubmFtZX1gKTtcbiAgICAgICAgICAgIG5hbWVkUmVnaXN0cmF0aW9ucy5zZXQoY29uZmlnLm5hbWUsIHsgLi4uY29uZmlnLCB0b2tlbjogYmluZGluZ1Rva2VuIH0pO1xuICAgICAgICAgICAgcmV0dXJuIGJpbmRpbmdUb2tlbjtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmIChjb25maWcua2V5ICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIC8vIEtleWVkIHJlZ2lzdHJhdGlvbiBnZXRzIHVuaXF1ZSB0b2tlblxuICAgICAgICAgICAgY29uc3Qga2V5U3RyID0gdHlwZW9mIGNvbmZpZy5rZXkgPT09ICdzeW1ib2wnID8gY29uZmlnLmtleS50b1N0cmluZygpIDogY29uZmlnLmtleTtcbiAgICAgICAgICAgIGNvbnN0IGJpbmRpbmdUb2tlbiA9IFRva2VuKGBfX2tleWVkXyR7a2V5U3RyfWApO1xuICAgICAgICAgICAga2V5ZWRSZWdpc3RyYXRpb25zLnNldChjb25maWcua2V5LCB7IC4uLmNvbmZpZywgdG9rZW46IGJpbmRpbmdUb2tlbiB9KTtcbiAgICAgICAgICAgIHJldHVybiBiaW5kaW5nVG9rZW47XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAvLyBNdWx0aS1yZWdpc3RyYXRpb24gaGFuZGxpbmdcbiAgICAgICAgICAgIGlmIChtdWx0aVJlZ2lzdHJhdGlvbnMuaGFzKGNvbmZpZy50b2tlbikpIHtcbiAgICAgICAgICAgICAgICAvLyBTdWJzZXF1ZW50IHJlZ2lzdHJhdGlvbiBmb3IgdGhpcyB0b2tlblxuICAgICAgICAgICAgICAgIGNvbnN0IGJpbmRpbmdUb2tlbiA9IFRva2VuKGBfX211bHRpXyR7Y29uZmlnLnRva2VuLnRvU3RyaW5nKCl9XyR7bXVsdGlSZWdpc3RyYXRpb25zLmdldChjb25maWcudG9rZW4pLmxlbmd0aH1gKTtcbiAgICAgICAgICAgICAgICBtdWx0aVJlZ2lzdHJhdGlvbnMuZ2V0KGNvbmZpZy50b2tlbikucHVzaChiaW5kaW5nVG9rZW4pO1xuICAgICAgICAgICAgICAgIHJldHVybiBiaW5kaW5nVG9rZW47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICAvLyBGaXJzdCByZWdpc3RyYXRpb24gZm9yIHRoaXMgdG9rZW4sIHVzZSB0aGUgb3JpZ2luYWwgdG9rZW5cbiAgICAgICAgICAgICAgICBtdWx0aVJlZ2lzdHJhdGlvbnMuc2V0KGNvbmZpZy50b2tlbiwgW2NvbmZpZy50b2tlbl0pO1xuICAgICAgICAgICAgICAgIHJldHVybiBjb25maWcudG9rZW47XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG4gICAgLyoqXG4gICAgICogUmVnaXN0ZXIgYWRkaXRpb25hbCBpbnRlcmZhY2VzIGZvciBhIGNvbmZpZ1xuICAgICAqIEBpbnRlcm5hbFxuICAgICAqL1xuICAgIHJlZ2lzdGVyQWRkaXRpb25hbEludGVyZmFjZXMoY29udGFpbmVyLCBjb25maWcsIGJpbmRpbmdUb2tlbiwgcmVnaXN0ZXJlZFRva2Vucykge1xuICAgICAgICBpZiAoY29uZmlnLmFkZGl0aW9uYWxUb2tlbnMpIHtcbiAgICAgICAgICAgIGZvciAoY29uc3QgYWRkaXRpb25hbFRva2VuIG9mIGNvbmZpZy5hZGRpdGlvbmFsVG9rZW5zKSB7XG4gICAgICAgICAgICAgICAgLy8gQ3JlYXRlIGEgZmFjdG9yeSB0aGF0IHJlc29sdmVzIHRoZSBiaW5kaW5nIHRva2VuXG4gICAgICAgICAgICAgICAgY29udGFpbmVyLmJpbmRGYWN0b3J5KGFkZGl0aW9uYWxUb2tlbiwgKGMpID0+IGMucmVzb2x2ZShiaW5kaW5nVG9rZW4pLCB7IGxpZmV0aW1lOiBjb25maWcubGlmZXRpbWUgfSk7XG4gICAgICAgICAgICAgICAgcmVnaXN0ZXJlZFRva2Vucy5hZGQoYWRkaXRpb25hbFRva2VuKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBCdWlsZCB0aGUgY29udGFpbmVyIHdpdGggYWxsIHJlZ2lzdGVyZWQgYmluZGluZ3NcbiAgICAgKi9cbiAgICBidWlsZCgpIHtcbiAgICAgICAgLy8gQ3JlYXRlIG5ldyBjb250YWluZXIgaW5oZXJpdGluZyBmcm9tIGJhc2VcbiAgICAgICAgY29uc3QgY29udGFpbmVyID0gdGhpcy5iYXNlQ29udGFpbmVyLmNyZWF0ZUNoaWxkKCk7XG4gICAgICAgIC8vIFByZS1wcm9jZXNzOiByZXNvbHZlIGludGVyZmFjZSB0eXBlcyB0byB0b2tlbnNcbiAgICAgICAgdGhpcy5yZXNvbHZlSW50ZXJmYWNlVG9rZW5zKGNvbnRhaW5lcik7XG4gICAgICAgIC8vIFRyYWNrIHdoYXQncyBiZWVuIHJlZ2lzdGVyZWQgZm9yIGlmTm90UmVnaXN0ZXJlZCBjaGVja3NcbiAgICAgICAgY29uc3QgcmVnaXN0ZXJlZFRva2VucyA9IG5ldyBTZXQoKTtcbiAgICAgICAgY29uc3QgbmFtZWRSZWdpc3RyYXRpb25zID0gbmV3IE1hcCgpO1xuICAgICAgICBjb25zdCBrZXllZFJlZ2lzdHJhdGlvbnMgPSBuZXcgTWFwKCk7XG4gICAgICAgIGNvbnN0IG11bHRpUmVnaXN0cmF0aW9ucyA9IG5ldyBNYXAoKTtcbiAgICAgICAgLy8gUHJlLXByb2Nlc3M6IGlkZW50aWZ5IHRva2VucyB0aGF0IGhhdmUgbm9uLWRlZmF1bHQgcmVnaXN0cmF0aW9uc1xuICAgICAgICBjb25zdCB0b2tlbnNXaXRoTm9uRGVmYXVsdHMgPSB0aGlzLmlkZW50aWZ5Tm9uRGVmYXVsdFRva2VucygpO1xuICAgICAgICBmb3IgKGNvbnN0IGNvbmZpZyBvZiB0aGlzLnJlZ2lzdHJhdGlvbnMpIHtcbiAgICAgICAgICAgIC8vIENoZWNrIGlmIHJlZ2lzdHJhdGlvbiBzaG91bGQgYmUgc2tpcHBlZFxuICAgICAgICAgICAgaWYgKHRoaXMuc2hvdWxkU2tpcFJlZ2lzdHJhdGlvbihjb25maWcsIHRva2Vuc1dpdGhOb25EZWZhdWx0cywgcmVnaXN0ZXJlZFRva2VucykpIHtcbiAgICAgICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIENyZWF0ZSBiaW5kaW5nIHRva2VuIChuYW1lZCwga2V5ZWQsIG9yIG11bHRpKVxuICAgICAgICAgICAgY29uc3QgYmluZGluZ1Rva2VuID0gdGhpcy5jcmVhdGVCaW5kaW5nVG9rZW4oY29uZmlnLCBuYW1lZFJlZ2lzdHJhdGlvbnMsIGtleWVkUmVnaXN0cmF0aW9ucywgbXVsdGlSZWdpc3RyYXRpb25zKTtcbiAgICAgICAgICAgIC8vIEFwcGx5IHJlZ2lzdHJhdGlvbiB0byBjb250YWluZXIgdXNpbmcgdGhlIGJpbmRpbmcgdG9rZW5cbiAgICAgICAgICAgIHRoaXMuYXBwbHlSZWdpc3RyYXRpb24oY29udGFpbmVyLCB7IC4uLmNvbmZpZywgdG9rZW46IGJpbmRpbmdUb2tlbiB9KTtcbiAgICAgICAgICAgIC8vIE1hcmsgb3JpZ2luYWwgdG9rZW4gYXMgcmVnaXN0ZXJlZFxuICAgICAgICAgICAgcmVnaXN0ZXJlZFRva2Vucy5hZGQoY29uZmlnLnRva2VuKTtcbiAgICAgICAgICAgIC8vIFJlZ2lzdGVyIGFkZGl0aW9uYWwgaW50ZXJmYWNlc1xuICAgICAgICAgICAgdGhpcy5yZWdpc3RlckFkZGl0aW9uYWxJbnRlcmZhY2VzKGNvbnRhaW5lciwgY29uZmlnLCBiaW5kaW5nVG9rZW4sIHJlZ2lzdGVyZWRUb2tlbnMpO1xuICAgICAgICB9XG4gICAgICAgIC8vIEF0dGFjaCBtZXRhZGF0YSBmb3IgbmFtZWQva2V5ZWQgcmVzb2x1dGlvblxuICAgICAgICA7XG4gICAgICAgIGNvbnRhaW5lci5fX25hbWVkUmVnaXN0cmF0aW9ucyA9IG5hbWVkUmVnaXN0cmF0aW9ucztcbiAgICAgICAgY29udGFpbmVyLl9fa2V5ZWRSZWdpc3RyYXRpb25zID0ga2V5ZWRSZWdpc3RyYXRpb25zO1xuICAgICAgICBjb250YWluZXIuX19tdWx0aVJlZ2lzdHJhdGlvbnMgPSBtdWx0aVJlZ2lzdHJhdGlvbnM7XG4gICAgICAgIHJldHVybiBjb250YWluZXI7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEFuYWx5emUgY29uc3RydWN0b3IgdG8gZGV0ZWN0IGRlcGVuZGVuY2llc1xuICAgICAqIEBpbnRlcm5hbFxuICAgICAqL1xuICAgIGFuYWx5emVDb25zdHJ1Y3Rvcihjb25zdHJ1Y3Rvcikge1xuICAgICAgICBjb25zdCBjb25zdHJ1Y3RvclN0ciA9IGNvbnN0cnVjdG9yLnRvU3RyaW5nKCk7XG4gICAgICAgIGNvbnN0IGhhc0RlcGVuZGVuY2llcyA9IC9jb25zdHJ1Y3RvclxccypcXChbXildK1xcKS8udGVzdChjb25zdHJ1Y3RvclN0cik7XG4gICAgICAgIHJldHVybiB7IGhhc0RlcGVuZGVuY2llcyB9O1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBDcmVhdGUgb3B0aW1pemVkIGZhY3RvcnkgZm9yIHplcm8tZGVwZW5kZW5jeSBjb25zdHJ1Y3RvcnNcbiAgICAgKiBAaW50ZXJuYWxcbiAgICAgKi9cbiAgICBjcmVhdGVPcHRpbWl6ZWRGYWN0b3J5KGNvbnRhaW5lciwgY29uZmlnLCBvcHRpb25zKSB7XG4gICAgICAgIGlmIChjb25maWcubGlmZXRpbWUgPT09ICdzaW5nbGV0b24nKSB7XG4gICAgICAgICAgICAvLyBTaW5nbGV0b246IENyZWF0ZSBpbnN0YW5jZSBkaXJlY3RseSAoZmFzdGVzdCBwYXRoIC0gbm8gZmFjdG9yeSBvdmVyaGVhZClcbiAgICAgICAgICAgIGNvbnN0IGluc3RhbmNlID0gbmV3IGNvbmZpZy5jb25zdHJ1Y3RvcigpO1xuICAgICAgICAgICAgY29udGFpbmVyLmJpbmRWYWx1ZShjb25maWcudG9rZW4sIGluc3RhbmNlKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmIChjb25maWcubGlmZXRpbWUgPT09ICd0cmFuc2llbnQnKSB7XG4gICAgICAgICAgICAvLyBUcmFuc2llbnQgRmFzdCBQYXRoOiBSZWdpc3RlciBpbiBmYXN0IHRyYW5zaWVudCBjYWNoZVxuICAgICAgICAgICAgY29uc3QgY3RvciA9IGNvbmZpZy5jb25zdHJ1Y3RvcjtcbiAgICAgICAgICAgIGNvbnN0IGZhc3RGYWN0b3J5ID0gKCkgPT4gbmV3IGN0b3IoKTtcbiAgICAgICAgICAgIGNvbnRhaW5lci5mYXN0VHJhbnNpZW50Q2FjaGUuc2V0KGNvbmZpZy50b2tlbiwgZmFzdEZhY3RvcnkpO1xuICAgICAgICAgICAgY29udGFpbmVyLmJpbmRGYWN0b3J5KGNvbmZpZy50b2tlbiwgZmFzdEZhY3RvcnksIG9wdGlvbnMpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgLy8gUGVyLXJlcXVlc3Q6IFVzZSBzaW1wbGUgZmFjdG9yeSB3aXRob3V0IGF1dG93aXJlIG92ZXJoZWFkXG4gICAgICAgICAgICBjb25zdCBmYWN0b3J5ID0gKCkgPT4gbmV3IGNvbmZpZy5jb25zdHJ1Y3RvcigpO1xuICAgICAgICAgICAgY29udGFpbmVyLmJpbmRGYWN0b3J5KGNvbmZpZy50b2tlbiwgZmFjdG9yeSwgb3B0aW9ucyk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgLyoqXG4gICAgICogQ3JlYXRlIGF1dG93aXJlIGZhY3RvcnlcbiAgICAgKiBAaW50ZXJuYWxcbiAgICAgKi9cbiAgICBjcmVhdGVBdXRvV2lyZUZhY3RvcnkoY29udGFpbmVyLCBjb25maWcsIG9wdGlvbnMpIHtcbiAgICAgICAgY29uc3QgZmFjdG9yeSA9IChjKSA9PiB7XG4gICAgICAgICAgICBjb25zdCByZXNvbHZlZERlcHMgPSBhdXRvd2lyZShjb25maWcuY29uc3RydWN0b3IsIGMsIGNvbmZpZy5hdXRvd2lyZU9wdGlvbnMpO1xuICAgICAgICAgICAgcmV0dXJuIG5ldyBjb25maWcuY29uc3RydWN0b3IoLi4ucmVzb2x2ZWREZXBzKTtcbiAgICAgICAgfTtcbiAgICAgICAgY29udGFpbmVyLmJpbmRGYWN0b3J5KGNvbmZpZy50b2tlbiwgZmFjdG9yeSwgb3B0aW9ucyk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIENyZWF0ZSB3aXRoUGFyYW1ldGVycyBmYWN0b3J5XG4gICAgICogQGludGVybmFsXG4gICAgICovXG4gICAgY3JlYXRlUGFyYW1ldGVyRmFjdG9yeShjb250YWluZXIsIGNvbmZpZywgb3B0aW9ucykge1xuICAgICAgICBjb25zdCBmYWN0b3J5ID0gKCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgdmFsdWVzID0gT2JqZWN0LnZhbHVlcyhjb25maWcucGFyYW1ldGVyVmFsdWVzKTtcbiAgICAgICAgICAgIHJldHVybiBuZXcgY29uZmlnLmNvbnN0cnVjdG9yKC4uLnZhbHVlcyk7XG4gICAgICAgIH07XG4gICAgICAgIGNvbnRhaW5lci5iaW5kRmFjdG9yeShjb25maWcudG9rZW4sIGZhY3RvcnksIG9wdGlvbnMpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBBcHBseSB0eXBlIHJlZ2lzdHJhdGlvbiAoY2xhc3MgY29uc3RydWN0b3IpXG4gICAgICogQGludGVybmFsXG4gICAgICovXG4gICAgYXBwbHlUeXBlUmVnaXN0cmF0aW9uKGNvbnRhaW5lciwgY29uZmlnLCBvcHRpb25zKSB7XG4gICAgICAgIGNvbnN0IHsgaGFzRGVwZW5kZW5jaWVzIH0gPSB0aGlzLmFuYWx5emVDb25zdHJ1Y3Rvcihjb25maWcuY29uc3RydWN0b3IpO1xuICAgICAgICAvLyBGYXN0IHBhdGg6IE5vIGRlcGVuZGVuY2llcyBhbmQgbm8gc3BlY2lhbCBjb25maWdcbiAgICAgICAgaWYgKCFoYXNEZXBlbmRlbmNpZXMgJiYgIWNvbmZpZy5hdXRvd2lyZU9wdGlvbnMgJiYgIWNvbmZpZy5wYXJhbWV0ZXJWYWx1ZXMpIHtcbiAgICAgICAgICAgIHRoaXMuY3JlYXRlT3B0aW1pemVkRmFjdG9yeShjb250YWluZXIsIGNvbmZpZywgb3B0aW9ucyk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgLy8gQXV0b1dpcmUgcGF0aFxuICAgICAgICBpZiAoY29uZmlnLmF1dG93aXJlT3B0aW9ucykge1xuICAgICAgICAgICAgdGhpcy5jcmVhdGVBdXRvV2lyZUZhY3RvcnkoY29udGFpbmVyLCBjb25maWcsIG9wdGlvbnMpO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIC8vIHdpdGhQYXJhbWV0ZXJzIHBhdGhcbiAgICAgICAgaWYgKGNvbmZpZy5wYXJhbWV0ZXJWYWx1ZXMpIHtcbiAgICAgICAgICAgIHRoaXMuY3JlYXRlUGFyYW1ldGVyRmFjdG9yeShjb250YWluZXIsIGNvbmZpZywgb3B0aW9ucyk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgLy8gRXJyb3I6IENvbnN0cnVjdG9yIGhhcyBkZXBlbmRlbmNpZXMgYnV0IG5vIGNvbmZpZ1xuICAgICAgICBpZiAoaGFzRGVwZW5kZW5jaWVzKSB7XG4gICAgICAgICAgICBjb25zdCBjbGFzc05hbWUgPSBjb25maWcuY29uc3RydWN0b3IubmFtZSB8fCAnVW5uYW1lZENsYXNzJztcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgU2VydmljZSBcIiR7Y2xhc3NOYW1lfVwiIGhhcyBjb25zdHJ1Y3RvciBkZXBlbmRlbmNpZXMgYnV0IG5vIGF1dG93aXJpbmcgY29uZmlndXJhdGlvbi5cXG5cXG5gICtcbiAgICAgICAgICAgICAgICBgU29sdXRpb25zOlxcbmAgK1xuICAgICAgICAgICAgICAgIGAgIDEuIFx1MkI1MCBVc2UgdGhlIE5vdmFESSB0cmFuc2Zvcm1lciAocmVjb21tZW5kZWQpOlxcbmAgK1xuICAgICAgICAgICAgICAgIGAgICAgIC0gQWRkIFwiQG5vdmFkaS9jb3JlL3VucGx1Z2luXCIgdG8geW91ciBidWlsZCBjb25maWdcXG5gICtcbiAgICAgICAgICAgICAgICBgICAgICAtIFRyYW5zZm9ybWVyIGF1dG9tYXRpY2FsbHkgZ2VuZXJhdGVzIC5hdXRvV2lyZSgpIGZvciBhbGwgZGVwZW5kZW5jaWVzXFxuXFxuYCArXG4gICAgICAgICAgICAgICAgYCAgMi4gQWRkIG1hbnVhbCBhdXRvd2lyaW5nOlxcbmAgK1xuICAgICAgICAgICAgICAgIGAgICAgIC5hdXRvV2lyZSh7IG1hcDogeyAvKiBwYXJhbTogcmVzb2x2ZXIgKi8gfSB9KVxcblxcbmAgK1xuICAgICAgICAgICAgICAgIGAgIDMuIFVzZSBhIGZhY3RvcnkgZnVuY3Rpb246XFxuYCArXG4gICAgICAgICAgICAgICAgYCAgICAgLnJlZ2lzdGVyKChjKSA9PiBuZXcgJHtjbGFzc05hbWV9KC4uLikpXFxuXFxuYCArXG4gICAgICAgICAgICAgICAgYFNlZSBkb2NzOiBodHRwczovL2dpdGh1Yi5jb20vamFudXMwMDcvTm92YURJI2F1dG93aXJlYCk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gTm8gZGVwZW5kZW5jaWVzIC0gY3JlYXRlIHNpbXBsZSBmYWN0b3J5XG4gICAgICAgIGNvbnN0IGZhY3RvcnkgPSAoKSA9PiBuZXcgY29uZmlnLmNvbnN0cnVjdG9yKCk7XG4gICAgICAgIGNvbnRhaW5lci5iaW5kRmFjdG9yeShjb25maWcudG9rZW4sIGZhY3RvcnksIG9wdGlvbnMpO1xuICAgIH1cbiAgICBhcHBseVJlZ2lzdHJhdGlvbihjb250YWluZXIsIGNvbmZpZykge1xuICAgICAgICBjb25zdCBvcHRpb25zID0geyBsaWZldGltZTogY29uZmlnLmxpZmV0aW1lIH07XG4gICAgICAgIHN3aXRjaCAoY29uZmlnLnR5cGUpIHtcbiAgICAgICAgICAgIGNhc2UgJ2luc3RhbmNlJzpcbiAgICAgICAgICAgICAgICBjb250YWluZXIuYmluZFZhbHVlKGNvbmZpZy50b2tlbiwgY29uZmlnLnZhbHVlKTtcbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIGNhc2UgJ2ZhY3RvcnknOlxuICAgICAgICAgICAgICAgIGNvbnRhaW5lci5iaW5kRmFjdG9yeShjb25maWcudG9rZW4sIGNvbmZpZy5mYWN0b3J5LCBvcHRpb25zKTtcbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIGNhc2UgJ3R5cGUnOlxuICAgICAgICAgICAgICAgIHRoaXMuYXBwbHlUeXBlUmVnaXN0cmF0aW9uKGNvbnRhaW5lciwgY29uZmlnLCBvcHRpb25zKTtcbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgfVxuICAgIH1cbn1cbiIsICIvKipcbiAqIENvcmUgZGVwZW5kZW5jeSBpbmplY3Rpb24gY29udGFpbmVyIGZvciBOb3ZhRElcbiAqL1xuaW1wb3J0IHsgVG9rZW4gfSBmcm9tICcuL3Rva2VuLmpzJztcbmltcG9ydCB7IEJpbmRpbmdOb3RGb3VuZEVycm9yLCBDaXJjdWxhckRlcGVuZGVuY3lFcnJvciB9IGZyb20gJy4vZXJyb3JzLmpzJztcbmltcG9ydCB7IEJ1aWxkZXIgfSBmcm9tICcuL2J1aWxkZXIuanMnO1xuZnVuY3Rpb24gaXNEaXNwb3NhYmxlKG9iaikge1xuICAgIHJldHVybiBvYmogJiYgdHlwZW9mIG9iai5kaXNwb3NlID09PSAnZnVuY3Rpb24nO1xufVxuLyoqXG4gKiBSZXNvbHV0aW9uIGNvbnRleHQgdHJhY2tzIHRoZSBjdXJyZW50IGRlcGVuZGVuY3kgcmVzb2x1dGlvbiBwYXRoXG4gKiBmb3IgY2lyY3VsYXIgZGVwZW5kZW5jeSBkZXRlY3Rpb24gYW5kIHBlci1yZXF1ZXN0IHNjb3BpbmdcbiAqL1xuY2xhc3MgUmVzb2x1dGlvbkNvbnRleHQge1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICB0aGlzLnJlc29sdmluZ1N0YWNrID0gbmV3IFNldCgpO1xuICAgICAgICB0aGlzLnBlclJlcXVlc3RDYWNoZSA9IG5ldyBNYXAoKTtcbiAgICB9XG4gICAgaXNSZXNvbHZpbmcodG9rZW4pIHtcbiAgICAgICAgcmV0dXJuIHRoaXMucmVzb2x2aW5nU3RhY2suaGFzKHRva2VuKTtcbiAgICB9XG4gICAgZW50ZXJSZXNvbHZlKHRva2VuKSB7XG4gICAgICAgIHRoaXMucmVzb2x2aW5nU3RhY2suYWRkKHRva2VuKTtcbiAgICAgICAgLy8gUGVyZm9ybWFuY2U6IERvbid0IGJ1aWxkIHBhdGggdW5sZXNzIHdlIG5lZWQgaXQgKG9ubHkgdXNlZCBpbiBlcnJvciBtZXNzYWdlcylcbiAgICAgICAgLy8gVGhpcyBhdm9pZHMgZXhwZW5zaXZlIHRva2VuLnRvU3RyaW5nKCkgY2FsbHMgb24gZXZlcnkgcmVzb2x2ZVxuICAgIH1cbiAgICBleGl0UmVzb2x2ZSh0b2tlbikge1xuICAgICAgICB0aGlzLnJlc29sdmluZ1N0YWNrLmRlbGV0ZSh0b2tlbik7XG4gICAgICAgIC8vIFBlcmZvcm1hbmNlOiBDbGVhciBsYXp5IHBhdGggY2FjaGUgd2hlbiBleGl0aW5nXG4gICAgICAgIHRoaXMucGF0aCA9IHVuZGVmaW5lZDtcbiAgICB9XG4gICAgZ2V0UGF0aCgpIHtcbiAgICAgICAgLy8gUGVyZm9ybWFuY2U6IEJ1aWxkIHBhdGggb24tZGVtYW5kIG9ubHkgd2hlbiBuZWVkZWQgKHR5cGljYWxseSBmb3IgZXJyb3IgbWVzc2FnZXMpXG4gICAgICAgIGlmICghdGhpcy5wYXRoKSB7XG4gICAgICAgICAgICB0aGlzLnBhdGggPSBBcnJheS5mcm9tKHRoaXMucmVzb2x2aW5nU3RhY2spLm1hcCh0ID0+IHQudG9TdHJpbmcoKSk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIFsuLi50aGlzLnBhdGhdO1xuICAgIH1cbiAgICBjYWNoZVBlclJlcXVlc3QodG9rZW4sIGluc3RhbmNlKSB7XG4gICAgICAgIHRoaXMucGVyUmVxdWVzdENhY2hlLnNldCh0b2tlbiwgaW5zdGFuY2UpO1xuICAgIH1cbiAgICBnZXRQZXJSZXF1ZXN0KHRva2VuKSB7XG4gICAgICAgIHJldHVybiB0aGlzLnBlclJlcXVlc3RDYWNoZS5nZXQodG9rZW4pO1xuICAgIH1cbiAgICBoYXNQZXJSZXF1ZXN0KHRva2VuKSB7XG4gICAgICAgIHJldHVybiB0aGlzLnBlclJlcXVlc3RDYWNoZS5oYXModG9rZW4pO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZXNldCBjb250ZXh0IGZvciByZXVzZSBpbiBvYmplY3QgcG9vbFxuICAgICAqIFBlcmZvcm1hbmNlOiBSZXVzaW5nIGNvbnRleHRzIGF2b2lkcyBoZWFwIGFsbG9jYXRpb25zXG4gICAgICovXG4gICAgcmVzZXQoKSB7XG4gICAgICAgIHRoaXMucmVzb2x2aW5nU3RhY2suY2xlYXIoKTtcbiAgICAgICAgdGhpcy5wZXJSZXF1ZXN0Q2FjaGUuY2xlYXIoKTtcbiAgICAgICAgdGhpcy5wYXRoID0gdW5kZWZpbmVkO1xuICAgIH1cbn1cbi8qKlxuICogT2JqZWN0IHBvb2wgZm9yIFJlc29sdXRpb25Db250ZXh0IGluc3RhbmNlc1xuICogUGVyZm9ybWFuY2U6IFJldXNpbmcgY29udGV4dHMgcmVkdWNlcyBoZWFwIGFsbG9jYXRpb25zIGFuZCBHQyBwcmVzc3VyZVxuICovXG5jbGFzcyBSZXNvbHV0aW9uQ29udGV4dFBvb2wge1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICB0aGlzLnBvb2wgPSBbXTtcbiAgICAgICAgdGhpcy5tYXhTaXplID0gMTA7XG4gICAgfVxuICAgIGFjcXVpcmUoKSB7XG4gICAgICAgIGNvbnN0IGNvbnRleHQgPSB0aGlzLnBvb2wucG9wKCk7XG4gICAgICAgIGlmIChjb250ZXh0KSB7XG4gICAgICAgICAgICAvLyBSZXNldCBleGlzdGluZyBjb250ZXh0IGZvciByZXVzZVxuICAgICAgICAgICAgY29udGV4dC5yZXNldCgpO1xuICAgICAgICAgICAgcmV0dXJuIGNvbnRleHQ7XG4gICAgICAgIH1cbiAgICAgICAgLy8gQ3JlYXRlIG5ldyBpZiBwb29sIGVtcHR5XG4gICAgICAgIHJldHVybiBuZXcgUmVzb2x1dGlvbkNvbnRleHQoKTtcbiAgICB9XG4gICAgcmVsZWFzZShjb250ZXh0KSB7XG4gICAgICAgIGlmICh0aGlzLnBvb2wubGVuZ3RoIDwgdGhpcy5tYXhTaXplKSB7XG4gICAgICAgICAgICB0aGlzLnBvb2wucHVzaChjb250ZXh0KTtcbiAgICAgICAgfVxuICAgICAgICAvLyBPdGhlcndpc2UgbGV0IGl0IGJlIEdDJ2RcbiAgICB9XG59XG4vKipcbiAqIERlcGVuZGVuY3kgSW5qZWN0aW9uIENvbnRhaW5lclxuICpcbiAqIE1hbmFnZXMgcmVnaXN0cmF0aW9uIGFuZCByZXNvbHV0aW9uIG9mIGRlcGVuZGVuY2llcyB3aXRoIHN1cHBvcnQgZm9yOlxuICogLSBNdWx0aXBsZSBiaW5kaW5nIHR5cGVzICh2YWx1ZSwgZmFjdG9yeSwgY2xhc3MpXG4gKiAtIExpZmV0aW1lIG1hbmFnZW1lbnQgKHNpbmdsZXRvbiwgdHJhbnNpZW50LCBwZXItcmVxdWVzdClcbiAqIC0gQ2hpbGQgY29udGFpbmVycyB3aXRoIGluaGVyaXRhbmNlXG4gKiAtIENpcmN1bGFyIGRlcGVuZGVuY3kgZGV0ZWN0aW9uXG4gKiAtIEF1dG9tYXRpYyBkaXNwb3NhbFxuICovXG5leHBvcnQgY2xhc3MgQ29udGFpbmVyIHtcbiAgICBjb25zdHJ1Y3RvcihwYXJlbnQpIHtcbiAgICAgICAgdGhpcy5iaW5kaW5ncyA9IG5ldyBNYXAoKTtcbiAgICAgICAgdGhpcy5zaW5nbGV0b25DYWNoZSA9IG5ldyBNYXAoKTtcbiAgICAgICAgdGhpcy5zaW5nbGV0b25PcmRlciA9IFtdO1xuICAgICAgICB0aGlzLmludGVyZmFjZVJlZ2lzdHJ5ID0gbmV3IE1hcCgpO1xuICAgICAgICB0aGlzLmludGVyZmFjZVRva2VuQ2FjaGUgPSBuZXcgTWFwKCk7IC8vIFBlcmZvcm1hbmNlOiBDYWNoZSBmb3IgcmVzb2x2ZVR5cGUoKSBsb29rdXBzXG4gICAgICAgIHRoaXMuZmFzdFRyYW5zaWVudENhY2hlID0gbmV3IE1hcCgpOyAvLyBQZXJmb3JtYW5jZTogRmFzdCBwYXRoIGZvciBzaW1wbGUgdHJhbnNpZW50c1xuICAgICAgICB0aGlzLnVsdHJhRmFzdFNpbmdsZXRvbkNhY2hlID0gbmV3IE1hcCgpOyAvLyBQZXJmb3JtYW5jZTogVWx0cmEtZmFzdCBzaW5nbGV0b24tb25seSBjYWNoZVxuICAgICAgICB0aGlzLnBhcmVudCA9IHBhcmVudDtcbiAgICB9XG4gICAgLyoqXG4gICAgICogQmluZCBhIHByZS1jcmVhdGVkIHZhbHVlIHRvIGEgdG9rZW5cbiAgICAgKi9cbiAgICBiaW5kVmFsdWUodG9rZW4sIHZhbHVlKSB7XG4gICAgICAgIHRoaXMuYmluZGluZ3Muc2V0KHRva2VuLCB7XG4gICAgICAgICAgICB0eXBlOiAndmFsdWUnLFxuICAgICAgICAgICAgbGlmZXRpbWU6ICdzaW5nbGV0b24nLFxuICAgICAgICAgICAgdmFsdWUsXG4gICAgICAgICAgICBjb25zdHJ1Y3RvcjogdW5kZWZpbmVkXG4gICAgICAgIH0pO1xuICAgICAgICB0aGlzLmludmFsaWRhdGVCaW5kaW5nQ2FjaGUoKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogQmluZCBhIGZhY3RvcnkgZnVuY3Rpb24gdG8gYSB0b2tlblxuICAgICAqL1xuICAgIGJpbmRGYWN0b3J5KHRva2VuLCBmYWN0b3J5LCBvcHRpb25zKSB7XG4gICAgICAgIHRoaXMuYmluZGluZ3Muc2V0KHRva2VuLCB7XG4gICAgICAgICAgICB0eXBlOiAnZmFjdG9yeScsXG4gICAgICAgICAgICBsaWZldGltZTogb3B0aW9ucz8ubGlmZXRpbWUgfHwgJ3RyYW5zaWVudCcsXG4gICAgICAgICAgICBmYWN0b3J5LFxuICAgICAgICAgICAgZGVwZW5kZW5jaWVzOiBvcHRpb25zPy5kZXBlbmRlbmNpZXMsXG4gICAgICAgICAgICBjb25zdHJ1Y3RvcjogdW5kZWZpbmVkXG4gICAgICAgIH0pO1xuICAgICAgICB0aGlzLmludmFsaWRhdGVCaW5kaW5nQ2FjaGUoKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogQmluZCBhIGNsYXNzIGNvbnN0cnVjdG9yIHRvIGEgdG9rZW5cbiAgICAgKi9cbiAgICBiaW5kQ2xhc3ModG9rZW4sIGNvbnN0cnVjdG9yLCBvcHRpb25zKSB7XG4gICAgICAgIGNvbnN0IGJpbmRpbmcgPSB7XG4gICAgICAgICAgICB0eXBlOiAnY2xhc3MnLFxuICAgICAgICAgICAgbGlmZXRpbWU6IG9wdGlvbnM/LmxpZmV0aW1lIHx8ICd0cmFuc2llbnQnLFxuICAgICAgICAgICAgY29uc3RydWN0b3IsXG4gICAgICAgICAgICBkZXBlbmRlbmNpZXM6IG9wdGlvbnM/LmRlcGVuZGVuY2llc1xuICAgICAgICB9O1xuICAgICAgICB0aGlzLmJpbmRpbmdzLnNldCh0b2tlbiwgYmluZGluZyk7XG4gICAgICAgIHRoaXMuaW52YWxpZGF0ZUJpbmRpbmdDYWNoZSgpO1xuICAgICAgICAvLyBQZXJmb3JtYW5jZTogUHJlLWNvbXBpbGUgZmFzdCB0cmFuc2llbnQgZmFjdG9yeSBmb3IgemVyby1kZXBlbmRlbmN5IGNsYXNzZXNcbiAgICAgICAgaWYgKGJpbmRpbmcubGlmZXRpbWUgPT09ICd0cmFuc2llbnQnICYmICghYmluZGluZy5kZXBlbmRlbmNpZXMgfHwgYmluZGluZy5kZXBlbmRlbmNpZXMubGVuZ3RoID09PSAwKSkge1xuICAgICAgICAgICAgdGhpcy5mYXN0VHJhbnNpZW50Q2FjaGUuc2V0KHRva2VuLCAoKSA9PiBuZXcgY29uc3RydWN0b3IoKSk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgLyoqXG4gICAgICogUmVzb2x2ZSBhIGRlcGVuZGVuY3kgc3luY2hyb25vdXNseVxuICAgICAqIFBlcmZvcm1hbmNlIG9wdGltaXplZCB3aXRoIG11bHRpcGxlIGZhc3QgcGF0aHNcbiAgICAgKi9cbiAgICByZXNvbHZlKHRva2VuKSB7XG4gICAgICAgIC8vIFRyeSBhbGwgY2FjaGUgbGV2ZWxzIGZpcnN0ICh1bHRyYS1mYXN0LCBzaW5nbGV0b24sIGZhc3QgdHJhbnNpZW50KVxuICAgICAgICBjb25zdCBjYWNoZWQgPSB0aGlzLnRyeUdldEZyb21DYWNoZXModG9rZW4pO1xuICAgICAgICBpZiAoY2FjaGVkICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIHJldHVybiBjYWNoZWQ7XG4gICAgICAgIH1cbiAgICAgICAgLy8gSWYgd2UncmUgYWxyZWFkeSByZXNvbHZpbmcgKGNhbGxlZCBmcm9tIHdpdGhpbiBhIGZhY3RvcnkpLCByZXVzZSB0aGUgY29udGV4dFxuICAgICAgICBpZiAodGhpcy5jdXJyZW50Q29udGV4dCkge1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMucmVzb2x2ZVdpdGhDb250ZXh0KHRva2VuLCB0aGlzLmN1cnJlbnRDb250ZXh0KTtcbiAgICAgICAgfVxuICAgICAgICAvLyBDb21wbGV4IHJlc29sdXRpb24gd2l0aCBwb29sZWQgY29udGV4dFxuICAgICAgICBjb25zdCBjb250ZXh0ID0gQ29udGFpbmVyLmNvbnRleHRQb29sLmFjcXVpcmUoKTtcbiAgICAgICAgdGhpcy5jdXJyZW50Q29udGV4dCA9IGNvbnRleHQ7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5yZXNvbHZlV2l0aENvbnRleHQodG9rZW4sIGNvbnRleHQpO1xuICAgICAgICB9XG4gICAgICAgIGZpbmFsbHkge1xuICAgICAgICAgICAgdGhpcy5jdXJyZW50Q29udGV4dCA9IHVuZGVmaW5lZDtcbiAgICAgICAgICAgIENvbnRhaW5lci5jb250ZXh0UG9vbC5yZWxlYXNlKGNvbnRleHQpO1xuICAgICAgICB9XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFNQRUNJQUxJWkVEOiBVbHRyYS1mYXN0IHNpbmdsZXRvbiByZXNvbHZlIChubyBzYWZldHkgY2hlY2tzKVxuICAgICAqIFVzZSBPTkxZIHdoZW4geW91J3JlIDEwMCUgc3VyZSB0aGUgdG9rZW4gaXMgYSByZWdpc3RlcmVkIHNpbmdsZXRvblxuICAgICAqIEBpbnRlcm5hbCBGb3IgcGVyZm9ybWFuY2UtY3JpdGljYWwgcGF0aHMgb25seVxuICAgICAqL1xuICAgIHJlc29sdmVTaW5nbGV0b25VbnNhZmUodG9rZW4pIHtcbiAgICAgICAgLy8gRGlyZWN0IHJldHVybiwgbm8gY2hlY2tzIC0gbWF4aW11bSBzcGVlZFxuICAgICAgICByZXR1cm4gdGhpcy51bHRyYUZhc3RTaW5nbGV0b25DYWNoZS5nZXQodG9rZW4pID8/IHRoaXMuc2luZ2xldG9uQ2FjaGUuZ2V0KHRva2VuKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogU1BFQ0lBTElaRUQ6IEZhc3QgdHJhbnNpZW50IHJlc29sdmUgZm9yIHplcm8tZGVwZW5kZW5jeSBjbGFzc2VzXG4gICAgICogU2tpcHMgYWxsIGNvbnRleHQgY3JlYXRpb24gYW5kIGNpcmN1bGFyIGRlcGVuZGVuY3kgY2hlY2tzXG4gICAgICogQGludGVybmFsIEZvciBwZXJmb3JtYW5jZS1jcml0aWNhbCBwYXRocyBvbmx5XG4gICAgICovXG4gICAgcmVzb2x2ZVRyYW5zaWVudFNpbXBsZSh0b2tlbikge1xuICAgICAgICBjb25zdCBmYWN0b3J5ID0gdGhpcy5mYXN0VHJhbnNpZW50Q2FjaGUuZ2V0KHRva2VuKTtcbiAgICAgICAgaWYgKGZhY3RvcnkpIHtcbiAgICAgICAgICAgIHJldHVybiBmYWN0b3J5KCk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gRmFsbGJhY2sgdG8gcmVndWxhciByZXNvbHZlIGlmIG5vdCBpbiBmYXN0IGNhY2hlXG4gICAgICAgIHJldHVybiB0aGlzLnJlc29sdmUodG9rZW4pO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBTUEVDSUFMSVpFRDogQmF0Y2ggcmVzb2x2ZSBtdWx0aXBsZSBkZXBlbmRlbmNpZXMgYXQgb25jZVxuICAgICAqIE1vcmUgZWZmaWNpZW50IHRoYW4gbXVsdGlwbGUgaW5kaXZpZHVhbCByZXNvbHZlc1xuICAgICAqL1xuICAgIHJlc29sdmVCYXRjaCh0b2tlbnMpIHtcbiAgICAgICAgLy8gUmV1c2Ugc2luZ2xlIGNvbnRleHQgZm9yIGFsbCByZXNvbHV0aW9uc1xuICAgICAgICBjb25zdCB3YXNSZXNvbHZpbmcgPSAhIXRoaXMuY3VycmVudENvbnRleHQ7XG4gICAgICAgIGNvbnN0IGNvbnRleHQgPSB0aGlzLmN1cnJlbnRDb250ZXh0IHx8IENvbnRhaW5lci5jb250ZXh0UG9vbC5hY3F1aXJlKCk7XG4gICAgICAgIGlmICghd2FzUmVzb2x2aW5nKSB7XG4gICAgICAgICAgICB0aGlzLmN1cnJlbnRDb250ZXh0ID0gY29udGV4dDtcbiAgICAgICAgfVxuICAgICAgICB0cnkge1xuICAgICAgICAgICAgY29uc3QgcmVzdWx0cyA9IHRva2Vucy5tYXAodG9rZW4gPT4ge1xuICAgICAgICAgICAgICAgIC8vIFRyeSBhbGwgY2FjaGUgbGV2ZWxzIGZpcnN0XG4gICAgICAgICAgICAgICAgY29uc3QgY2FjaGVkID0gdGhpcy50cnlHZXRGcm9tQ2FjaGVzKHRva2VuKTtcbiAgICAgICAgICAgICAgICBpZiAoY2FjaGVkICE9PSB1bmRlZmluZWQpXG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBjYWNoZWQ7XG4gICAgICAgICAgICAgICAgLy8gRnVsbCByZXNvbHZlIHdpdGggc2hhcmVkIGNvbnRleHRcbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcy5yZXNvbHZlV2l0aENvbnRleHQodG9rZW4sIGNvbnRleHQpO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICByZXR1cm4gcmVzdWx0cztcbiAgICAgICAgfVxuICAgICAgICBmaW5hbGx5IHtcbiAgICAgICAgICAgIGlmICghd2FzUmVzb2x2aW5nKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5jdXJyZW50Q29udGV4dCA9IHVuZGVmaW5lZDtcbiAgICAgICAgICAgICAgICBDb250YWluZXIuY29udGV4dFBvb2wucmVsZWFzZShjb250ZXh0KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZXNvbHZlIGEgZGVwZW5kZW5jeSBhc3luY2hyb25vdXNseSAoc3VwcG9ydHMgYXN5bmMgZmFjdG9yaWVzKVxuICAgICAqL1xuICAgIGFzeW5jIHJlc29sdmVBc3luYyh0b2tlbikge1xuICAgICAgICAvLyBJZiB3ZSdyZSBhbHJlYWR5IHJlc29sdmluZyAoY2FsbGVkIGZyb20gd2l0aGluIGEgZmFjdG9yeSksIHJldXNlIHRoZSBjb250ZXh0XG4gICAgICAgIGlmICh0aGlzLmN1cnJlbnRDb250ZXh0KSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5yZXNvbHZlQXN5bmNXaXRoQ29udGV4dCh0b2tlbiwgdGhpcy5jdXJyZW50Q29udGV4dCk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gTmV3IHRvcC1sZXZlbCByZXNvbHZlXG4gICAgICAgIC8vIFBlcmZvcm1hbmNlOiBVc2UgcG9vbGVkIGNvbnRleHQgdG8gYXZvaWQgaGVhcCBhbGxvY2F0aW9uXG4gICAgICAgIGNvbnN0IGNvbnRleHQgPSBDb250YWluZXIuY29udGV4dFBvb2wuYWNxdWlyZSgpO1xuICAgICAgICB0aGlzLmN1cnJlbnRDb250ZXh0ID0gY29udGV4dDtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIHJldHVybiBhd2FpdCB0aGlzLnJlc29sdmVBc3luY1dpdGhDb250ZXh0KHRva2VuLCBjb250ZXh0KTtcbiAgICAgICAgfVxuICAgICAgICBmaW5hbGx5IHtcbiAgICAgICAgICAgIHRoaXMuY3VycmVudENvbnRleHQgPSB1bmRlZmluZWQ7XG4gICAgICAgICAgICBDb250YWluZXIuY29udGV4dFBvb2wucmVsZWFzZShjb250ZXh0KTsgLy8gUmV0dXJuIHRvIHBvb2wgZm9yIHJldXNlXG4gICAgICAgIH1cbiAgICB9XG4gICAgLyoqXG4gICAgICogVHJ5IHRvIGdldCBpbnN0YW5jZSBmcm9tIGFsbCBjYWNoZSBsZXZlbHNcbiAgICAgKiBSZXR1cm5zIHVuZGVmaW5lZCBpZiBub3QgY2FjaGVkXG4gICAgICogQGludGVybmFsXG4gICAgICovXG4gICAgdHJ5R2V0RnJvbUNhY2hlcyh0b2tlbikge1xuICAgICAgICAvLyBMZXZlbCAxOiBVbHRyYS1mYXN0IHNpbmdsZXRvbiBjYWNoZSAoemVybyBvdmVyaGVhZClcbiAgICAgICAgY29uc3QgdWx0cmFGYXN0ID0gdGhpcy51bHRyYUZhc3RTaW5nbGV0b25DYWNoZS5nZXQodG9rZW4pO1xuICAgICAgICBpZiAodWx0cmFGYXN0ICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIHJldHVybiB1bHRyYUZhc3Q7XG4gICAgICAgIH1cbiAgICAgICAgLy8gTGV2ZWwgMjogUmVndWxhciBzaW5nbGV0b24gY2FjaGVcbiAgICAgICAgaWYgKHRoaXMuc2luZ2xldG9uQ2FjaGUuaGFzKHRva2VuKSkge1xuICAgICAgICAgICAgY29uc3QgY2FjaGVkID0gdGhpcy5zaW5nbGV0b25DYWNoZS5nZXQodG9rZW4pO1xuICAgICAgICAgICAgLy8gUHJvbW90ZSB0byB1bHRyYS1mYXN0IGNhY2hlIGZvciBuZXh0IHRpbWVcbiAgICAgICAgICAgIHRoaXMudWx0cmFGYXN0U2luZ2xldG9uQ2FjaGUuc2V0KHRva2VuLCBjYWNoZWQpO1xuICAgICAgICAgICAgcmV0dXJuIGNhY2hlZDtcbiAgICAgICAgfVxuICAgICAgICAvLyBMZXZlbCAzOiBGYXN0IHRyYW5zaWVudCBjYWNoZSAobm8gZGVwZW5kZW5jaWVzKVxuICAgICAgICBjb25zdCBmYXN0RmFjdG9yeSA9IHRoaXMuZmFzdFRyYW5zaWVudENhY2hlLmdldCh0b2tlbik7XG4gICAgICAgIGlmIChmYXN0RmFjdG9yeSkge1xuICAgICAgICAgICAgcmV0dXJuIGZhc3RGYWN0b3J5KCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9XG4gICAgLyoqXG4gICAgICogQ2FjaGUgaW5zdGFuY2UgYmFzZWQgb24gbGlmZXRpbWUgc3RyYXRlZ3lcbiAgICAgKiBAaW50ZXJuYWxcbiAgICAgKi9cbiAgICBjYWNoZUluc3RhbmNlKHRva2VuLCBpbnN0YW5jZSwgbGlmZXRpbWUsIGNvbnRleHQpIHtcbiAgICAgICAgaWYgKGxpZmV0aW1lID09PSAnc2luZ2xldG9uJykge1xuICAgICAgICAgICAgdGhpcy5zaW5nbGV0b25DYWNoZS5zZXQodG9rZW4sIGluc3RhbmNlKTtcbiAgICAgICAgICAgIHRoaXMuc2luZ2xldG9uT3JkZXIucHVzaCh0b2tlbik7XG4gICAgICAgICAgICAvLyBBbHNvIGFkZCB0byB1bHRyYS1mYXN0IGNhY2hlXG4gICAgICAgICAgICB0aGlzLnVsdHJhRmFzdFNpbmdsZXRvbkNhY2hlLnNldCh0b2tlbiwgaW5zdGFuY2UpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKGxpZmV0aW1lID09PSAncGVyLXJlcXVlc3QnICYmIGNvbnRleHQpIHtcbiAgICAgICAgICAgIGNvbnRleHQuY2FjaGVQZXJSZXF1ZXN0KHRva2VuLCBpbnN0YW5jZSk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgLyoqXG4gICAgICogVmFsaWRhdGUgYW5kIGdldCBiaW5kaW5nIHdpdGggY2lyY3VsYXIgZGVwZW5kZW5jeSBjaGVja1xuICAgICAqIFJldHVybnMgYmluZGluZyBvciB0aHJvd3MgZXJyb3JcbiAgICAgKiBAaW50ZXJuYWxcbiAgICAgKi9cbiAgICB2YWxpZGF0ZUFuZEdldEJpbmRpbmcodG9rZW4sIGNvbnRleHQpIHtcbiAgICAgICAgLy8gQ2hlY2sgY2lyY3VsYXIgZGVwZW5kZW5jeVxuICAgICAgICBpZiAoY29udGV4dC5pc1Jlc29sdmluZyh0b2tlbikpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBDaXJjdWxhckRlcGVuZGVuY3lFcnJvcihbLi4uY29udGV4dC5nZXRQYXRoKCksIHRva2VuLnRvU3RyaW5nKCldKTtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBiaW5kaW5nID0gdGhpcy5nZXRCaW5kaW5nKHRva2VuKTtcbiAgICAgICAgaWYgKCFiaW5kaW5nKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgQmluZGluZ05vdEZvdW5kRXJyb3IodG9rZW4udG9TdHJpbmcoKSwgY29udGV4dC5nZXRQYXRoKCkpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBiaW5kaW5nO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBJbnN0YW50aWF0ZSBmcm9tIGJpbmRpbmcgc3luY2hyb25vdXNseVxuICAgICAqIEBpbnRlcm5hbFxuICAgICAqL1xuICAgIGluc3RhbnRpYXRlQmluZGluZ1N5bmMoYmluZGluZywgdG9rZW4sIGNvbnRleHQpIHtcbiAgICAgICAgc3dpdGNoIChiaW5kaW5nLnR5cGUpIHtcbiAgICAgICAgICAgIGNhc2UgJ3ZhbHVlJzpcbiAgICAgICAgICAgICAgICByZXR1cm4gYmluZGluZy52YWx1ZTtcbiAgICAgICAgICAgIGNhc2UgJ2ZhY3RvcnknOlxuICAgICAgICAgICAgICAgIGNvbnN0IHJlc3VsdCA9IGJpbmRpbmcuZmFjdG9yeSh0aGlzKTtcbiAgICAgICAgICAgICAgICBpZiAocmVzdWx0IGluc3RhbmNlb2YgUHJvbWlzZSkge1xuICAgICAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEFzeW5jIGZhY3RvcnkgZGV0ZWN0ZWQgZm9yICR7dG9rZW4udG9TdHJpbmcoKX0uIFVzZSByZXNvbHZlQXN5bmMoKSBpbnN0ZWFkLmApO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICByZXR1cm4gcmVzdWx0O1xuICAgICAgICAgICAgY2FzZSAnY2xhc3MnOlxuICAgICAgICAgICAgICAgIGNvbnN0IGRlcHMgPSBiaW5kaW5nLmRlcGVuZGVuY2llcyB8fCBbXTtcbiAgICAgICAgICAgICAgICBjb25zdCByZXNvbHZlZERlcHMgPSBkZXBzLm1hcChkZXAgPT4gdGhpcy5yZXNvbHZlV2l0aENvbnRleHQoZGVwLCBjb250ZXh0KSk7XG4gICAgICAgICAgICAgICAgcmV0dXJuIG5ldyBiaW5kaW5nLmNvbnN0cnVjdG9yKC4uLnJlc29sdmVkRGVwcyk7XG4gICAgICAgICAgICBjYXNlICdpbmxpbmUtY2xhc3MnOlxuICAgICAgICAgICAgICAgIHJldHVybiBuZXcgYmluZGluZy5jb25zdHJ1Y3RvcigpO1xuICAgICAgICAgICAgZGVmYXVsdDpcbiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYFVua25vd24gYmluZGluZyB0eXBlOiAke2JpbmRpbmcudHlwZX1gKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBJbnN0YW50aWF0ZSBmcm9tIGJpbmRpbmcgYXN5bmNocm9ub3VzbHlcbiAgICAgKiBAaW50ZXJuYWxcbiAgICAgKi9cbiAgICBhc3luYyBpbnN0YW50aWF0ZUJpbmRpbmdBc3luYyhiaW5kaW5nLCBjb250ZXh0KSB7XG4gICAgICAgIHN3aXRjaCAoYmluZGluZy50eXBlKSB7XG4gICAgICAgICAgICBjYXNlICd2YWx1ZSc6XG4gICAgICAgICAgICAgICAgcmV0dXJuIGJpbmRpbmcudmFsdWU7XG4gICAgICAgICAgICBjYXNlICdmYWN0b3J5JzpcbiAgICAgICAgICAgICAgICByZXR1cm4gYXdhaXQgUHJvbWlzZS5yZXNvbHZlKGJpbmRpbmcuZmFjdG9yeSh0aGlzKSk7XG4gICAgICAgICAgICBjYXNlICdjbGFzcyc6XG4gICAgICAgICAgICAgICAgY29uc3QgZGVwcyA9IGJpbmRpbmcuZGVwZW5kZW5jaWVzIHx8IFtdO1xuICAgICAgICAgICAgICAgIGNvbnN0IHJlc29sdmVkRGVwcyA9IGF3YWl0IFByb21pc2UuYWxsKGRlcHMubWFwKGRlcCA9PiB0aGlzLnJlc29sdmVBc3luY1dpdGhDb250ZXh0KGRlcCwgY29udGV4dCkpKTtcbiAgICAgICAgICAgICAgICByZXR1cm4gbmV3IGJpbmRpbmcuY29uc3RydWN0b3IoLi4ucmVzb2x2ZWREZXBzKTtcbiAgICAgICAgICAgIGNhc2UgJ2lubGluZS1jbGFzcyc6XG4gICAgICAgICAgICAgICAgcmV0dXJuIG5ldyBiaW5kaW5nLmNvbnN0cnVjdG9yKCk7XG4gICAgICAgICAgICBkZWZhdWx0OlxuICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgVW5rbm93biBiaW5kaW5nIHR5cGU6ICR7YmluZGluZy50eXBlfWApO1xuICAgICAgICB9XG4gICAgfVxuICAgIC8qKlxuICAgICAqIENyZWF0ZSBhIGNoaWxkIGNvbnRhaW5lciB0aGF0IGluaGVyaXRzIGJpbmRpbmdzIGZyb20gdGhpcyBjb250YWluZXJcbiAgICAgKi9cbiAgICBjcmVhdGVDaGlsZCgpIHtcbiAgICAgICAgcmV0dXJuIG5ldyBDb250YWluZXIodGhpcyk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIERpc3Bvc2UgYWxsIHNpbmdsZXRvbiBpbnN0YW5jZXMgaW4gcmV2ZXJzZSByZWdpc3RyYXRpb24gb3JkZXJcbiAgICAgKi9cbiAgICBhc3luYyBkaXNwb3NlKCkge1xuICAgICAgICBjb25zdCBlcnJvcnMgPSBbXTtcbiAgICAgICAgLy8gRGlzcG9zZSBpbiByZXZlcnNlIG9yZGVyXG4gICAgICAgIGZvciAobGV0IGkgPSB0aGlzLnNpbmdsZXRvbk9yZGVyLmxlbmd0aCAtIDE7IGkgPj0gMDsgaS0tKSB7XG4gICAgICAgICAgICBjb25zdCB0b2tlbiA9IHRoaXMuc2luZ2xldG9uT3JkZXJbaV07XG4gICAgICAgICAgICBjb25zdCBpbnN0YW5jZSA9IHRoaXMuc2luZ2xldG9uQ2FjaGUuZ2V0KHRva2VuKTtcbiAgICAgICAgICAgIGlmIChpbnN0YW5jZSAmJiBpc0Rpc3Bvc2FibGUoaW5zdGFuY2UpKSB7XG4gICAgICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICAgICAgYXdhaXQgaW5zdGFuY2UuZGlzcG9zZSgpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICAgICAgICAgICAgZXJyb3JzLnB1c2goZXJyb3IpO1xuICAgICAgICAgICAgICAgICAgICAvLyBDb250aW51ZSBkaXNwb3Npbmcgb3RoZXIgaW5zdGFuY2VzIGV2ZW4gaWYgb25lIGZhaWxzXG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIC8vIENsZWFyIGNhY2hlc1xuICAgICAgICB0aGlzLnNpbmdsZXRvbkNhY2hlLmNsZWFyKCk7XG4gICAgICAgIHRoaXMuc2luZ2xldG9uT3JkZXIubGVuZ3RoID0gMDtcbiAgICAgICAgLy8gTm90ZTogV2UgZG9uJ3QgdGhyb3cgZXJyb3JzIHRvIGFsbG93IGFsbCBkaXNwb3NhbHMgdG8gY29tcGxldGVcbiAgICAgICAgLy8gSW4gcHJvZHVjdGlvbiwgeW91IG1pZ2h0IHdhbnQgdG8gbG9nIHRoZXNlIGVycm9yc1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBDcmVhdGUgYSBmbHVlbnQgYnVpbGRlciBmb3IgcmVnaXN0ZXJpbmcgZGVwZW5kZW5jaWVzXG4gICAgICovXG4gICAgYnVpbGRlcigpIHtcbiAgICAgICAgcmV0dXJuIG5ldyBCdWlsZGVyKHRoaXMpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZXNvbHZlIGEgbmFtZWQgc2VydmljZVxuICAgICAqL1xuICAgIHJlc29sdmVOYW1lZChuYW1lKSB7XG4gICAgICAgIGNvbnN0IG5hbWVkUmVnaXN0cmF0aW9ucyA9IHRoaXMuX19uYW1lZFJlZ2lzdHJhdGlvbnM7XG4gICAgICAgIGlmICghbmFtZWRSZWdpc3RyYXRpb25zKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYE5hbWVkIHNlcnZpY2UgXCIke25hbWV9XCIgbm90IGZvdW5kLiBObyBuYW1lZCByZWdpc3RyYXRpb25zIGV4aXN0LmApO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IGNvbmZpZyA9IG5hbWVkUmVnaXN0cmF0aW9ucy5nZXQobmFtZSk7XG4gICAgICAgIGlmICghY29uZmlnKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYE5hbWVkIHNlcnZpY2UgXCIke25hbWV9XCIgbm90IGZvdW5kYCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXMucmVzb2x2ZShjb25maWcudG9rZW4pO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZXNvbHZlIGEga2V5ZWQgc2VydmljZVxuICAgICAqL1xuICAgIHJlc29sdmVLZXllZChrZXkpIHtcbiAgICAgICAgY29uc3Qga2V5ZWRSZWdpc3RyYXRpb25zID0gdGhpcy5fX2tleWVkUmVnaXN0cmF0aW9ucztcbiAgICAgICAgaWYgKCFrZXllZFJlZ2lzdHJhdGlvbnMpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgS2V5ZWQgc2VydmljZSBub3QgZm91bmQuIE5vIGtleWVkIHJlZ2lzdHJhdGlvbnMgZXhpc3QuYCk7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgY29uZmlnID0ga2V5ZWRSZWdpc3RyYXRpb25zLmdldChrZXkpO1xuICAgICAgICBpZiAoIWNvbmZpZykge1xuICAgICAgICAgICAgY29uc3Qga2V5U3RyID0gdHlwZW9mIGtleSA9PT0gJ3N5bWJvbCcgPyBrZXkudG9TdHJpbmcoKSA6IGBcIiR7a2V5fVwiYDtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgS2V5ZWQgc2VydmljZSAke2tleVN0cn0gbm90IGZvdW5kYCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXMucmVzb2x2ZShjb25maWcudG9rZW4pO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZXNvbHZlIGFsbCByZWdpc3RyYXRpb25zIGZvciBhIHRva2VuXG4gICAgICovXG4gICAgcmVzb2x2ZUFsbCh0b2tlbikge1xuICAgICAgICBjb25zdCBtdWx0aVJlZ2lzdHJhdGlvbnMgPSB0aGlzLl9fbXVsdGlSZWdpc3RyYXRpb25zO1xuICAgICAgICBpZiAoIW11bHRpUmVnaXN0cmF0aW9ucykge1xuICAgICAgICAgICAgcmV0dXJuIFtdO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IHRva2VucyA9IG11bHRpUmVnaXN0cmF0aW9ucy5nZXQodG9rZW4pO1xuICAgICAgICBpZiAoIXRva2VucyB8fCB0b2tlbnMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgICByZXR1cm4gW107XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRva2Vucy5tYXAoKHQpID0+IHRoaXMucmVzb2x2ZSh0KSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCByZWdpc3RyeSBpbmZvcm1hdGlvbiBmb3IgZGVidWdnaW5nL3Zpc3VhbGl6YXRpb25cbiAgICAgKiBSZXR1cm5zIGFycmF5IG9mIGJpbmRpbmcgaW5mb3JtYXRpb25cbiAgICAgKi9cbiAgICBnZXRSZWdpc3RyeSgpIHtcbiAgICAgICAgY29uc3QgcmVnaXN0cnkgPSBbXTtcbiAgICAgICAgdGhpcy5iaW5kaW5ncy5mb3JFYWNoKChiaW5kaW5nLCB0b2tlbikgPT4ge1xuICAgICAgICAgICAgcmVnaXN0cnkucHVzaCh7XG4gICAgICAgICAgICAgICAgdG9rZW46IHRva2VuLmRlc2NyaXB0aW9uIHx8IHRva2VuLnN5bWJvbC50b1N0cmluZygpLFxuICAgICAgICAgICAgICAgIHR5cGU6IGJpbmRpbmcudHlwZSxcbiAgICAgICAgICAgICAgICBsaWZldGltZTogYmluZGluZy5saWZldGltZSxcbiAgICAgICAgICAgICAgICBkZXBlbmRlbmNpZXM6IGJpbmRpbmcuZGVwZW5kZW5jaWVzPy5tYXAoZCA9PiBkLmRlc2NyaXB0aW9uIHx8IGQuc3ltYm9sLnRvU3RyaW5nKCkpXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfSk7XG4gICAgICAgIHJldHVybiByZWdpc3RyeTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IG9yIGNyZWF0ZSBhIHRva2VuIGZvciBhbiBpbnRlcmZhY2UgdHlwZVxuICAgICAqIFVzZXMgYSB0eXBlIG5hbWUgaGFzaCBhcyBrZXkgZm9yIHRoZSBpbnRlcmZhY2UgcmVnaXN0cnlcbiAgICAgKi9cbiAgICBpbnRlcmZhY2VUb2tlbih0eXBlTmFtZSkge1xuICAgICAgICAvLyBHZW5lcmF0ZSBhIHVuaXF1ZSBrZXkgZm9yIHRoaXMgaW50ZXJmYWNlIHR5cGVcbiAgICAgICAgLy8gSW4gcHJvZHVjdGlvbiwgdGhpcyB3b3VsZCBiZSByZXBsYWNlZCBieSBhIFRTIHRyYW5zZm9ybWVyXG4gICAgICAgIGNvbnN0IGtleSA9IHR5cGVOYW1lIHx8IGBJbnRlcmZhY2VfJHtNYXRoLnJhbmRvbSgpLnRvU3RyaW5nKDM2KS5zdWJzdHIoMiwgOSl9YDtcbiAgICAgICAgLy8gQ2hlY2sgaWYgdG9rZW4gYWxyZWFkeSBleGlzdHMgaW4gdGhpcyBjb250YWluZXJcbiAgICAgICAgaWYgKHRoaXMuaW50ZXJmYWNlUmVnaXN0cnkuaGFzKGtleSkpIHtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLmludGVyZmFjZVJlZ2lzdHJ5LmdldChrZXkpO1xuICAgICAgICB9XG4gICAgICAgIC8vIENoZWNrIHBhcmVudCBjb250YWluZXIgKHJlY3Vyc2l2ZWx5IHRocm91Z2ggcGFyZW50IGNoYWluKVxuICAgICAgICBpZiAodGhpcy5wYXJlbnQpIHtcbiAgICAgICAgICAgIC8vIFJlY3Vyc2l2ZWx5IGNoZWNrIHRocm91Z2ggZW50aXJlIHBhcmVudCBjaGFpblxuICAgICAgICAgICAgY29uc3QgcGFyZW50VG9rZW4gPSB0aGlzLnBhcmVudC5pbnRlcmZhY2VUb2tlbihrZXkpO1xuICAgICAgICAgICAgLy8gSWYgcGFyZW50IGNyZWF0ZWQgYSBuZXcgdG9rZW4sIGRvbid0IGNyZWF0ZSBhbm90aGVyIG9uZVxuICAgICAgICAgICAgcmV0dXJuIHBhcmVudFRva2VuO1xuICAgICAgICB9XG4gICAgICAgIC8vIENyZWF0ZSBuZXcgdG9rZW4gKG9ubHkgaWYgbm8gcGFyZW50IGV4aXN0cylcbiAgICAgICAgY29uc3QgdG9rZW4gPSBUb2tlbihrZXkpO1xuICAgICAgICB0aGlzLmludGVyZmFjZVJlZ2lzdHJ5LnNldChrZXksIHRva2VuKTtcbiAgICAgICAgcmV0dXJuIHRva2VuO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZXNvbHZlIGEgZGVwZW5kZW5jeSBieSBpbnRlcmZhY2UgdHlwZSB3aXRob3V0IGV4cGxpY2l0IHRva2VuXG4gICAgICovXG4gICAgcmVzb2x2ZVR5cGUodHlwZU5hbWUpIHtcbiAgICAgICAgLy8gUGVyZm9ybWFuY2U6IENhY2hlIHRva2VuIGxvb2t1cHMgdG8gYXZvaWQgcmVwZWF0ZWQgaW50ZXJmYWNlUmVnaXN0cnkgYWNjZXNzXG4gICAgICAgIGNvbnN0IGtleSA9IHR5cGVOYW1lIHx8ICcnO1xuICAgICAgICBsZXQgdG9rZW4gPSB0aGlzLmludGVyZmFjZVRva2VuQ2FjaGUuZ2V0KGtleSk7XG4gICAgICAgIGlmICghdG9rZW4pIHtcbiAgICAgICAgICAgIHRva2VuID0gdGhpcy5pbnRlcmZhY2VUb2tlbih0eXBlTmFtZSk7XG4gICAgICAgICAgICB0aGlzLmludGVyZmFjZVRva2VuQ2FjaGUuc2V0KGtleSwgdG9rZW4pO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzLnJlc29sdmUodG9rZW4pO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZXNvbHZlIGEga2V5ZWQgaW50ZXJmYWNlXG4gICAgICovXG4gICAgcmVzb2x2ZVR5cGVLZXllZChrZXksIF90eXBlTmFtZSkge1xuICAgICAgICAvLyBGb3Iga2V5ZWQgaW50ZXJmYWNlcywgd2UgdXNlIHRoZSBleGlzdGluZyByZXNvbHZlS2V5ZWQgbWVjaGFuaXNtXG4gICAgICAgIHJldHVybiB0aGlzLnJlc29sdmVLZXllZChrZXkpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZXNvbHZlIGFsbCByZWdpc3RyYXRpb25zIGZvciBhbiBpbnRlcmZhY2UgdHlwZVxuICAgICAqL1xuICAgIHJlc29sdmVUeXBlQWxsKHR5cGVOYW1lKSB7XG4gICAgICAgIGNvbnN0IHRva2VuID0gdGhpcy5pbnRlcmZhY2VUb2tlbih0eXBlTmFtZSk7XG4gICAgICAgIHJldHVybiB0aGlzLnJlc29sdmVBbGwodG9rZW4pO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBJbnRlcm5hbDogUmVzb2x2ZSB3aXRoIGNvbnRleHQgZm9yIGNpcmN1bGFyIGRlcGVuZGVuY3kgZGV0ZWN0aW9uXG4gICAgICovXG4gICAgcmVzb2x2ZVdpdGhDb250ZXh0KHRva2VuLCBjb250ZXh0KSB7XG4gICAgICAgIC8vIFZhbGlkYXRlIGFuZCBnZXQgYmluZGluZyAod2l0aCBjaXJjdWxhciBkZXBlbmRlbmN5IGNoZWNrKVxuICAgICAgICBjb25zdCBiaW5kaW5nID0gdGhpcy52YWxpZGF0ZUFuZEdldEJpbmRpbmcodG9rZW4sIGNvbnRleHQpO1xuICAgICAgICAvLyBDaGVjayBwZXItcmVxdWVzdCBjYWNoZVxuICAgICAgICBpZiAoYmluZGluZy5saWZldGltZSA9PT0gJ3Blci1yZXF1ZXN0JyAmJiBjb250ZXh0Lmhhc1BlclJlcXVlc3QodG9rZW4pKSB7XG4gICAgICAgICAgICByZXR1cm4gY29udGV4dC5nZXRQZXJSZXF1ZXN0KHRva2VuKTtcbiAgICAgICAgfVxuICAgICAgICAvLyBDaGVjayBzaW5nbGV0b24gY2FjaGUgKGxvY2FsIGNvbnRhaW5lciBvbmx5KVxuICAgICAgICBpZiAoYmluZGluZy5saWZldGltZSA9PT0gJ3NpbmdsZXRvbicgJiYgdGhpcy5zaW5nbGV0b25DYWNoZS5oYXModG9rZW4pKSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5zaW5nbGV0b25DYWNoZS5nZXQodG9rZW4pO1xuICAgICAgICB9XG4gICAgICAgIC8vIE1hcmsgYXMgcmVzb2x2aW5nXG4gICAgICAgIGNvbnRleHQuZW50ZXJSZXNvbHZlKHRva2VuKTtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIC8vIEluc3RhbnRpYXRlIGZyb20gYmluZGluZ1xuICAgICAgICAgICAgY29uc3QgaW5zdGFuY2UgPSB0aGlzLmluc3RhbnRpYXRlQmluZGluZ1N5bmMoYmluZGluZywgdG9rZW4sIGNvbnRleHQpO1xuICAgICAgICAgICAgLy8gQ2FjaGUgYmFzZWQgb24gbGlmZXRpbWVcbiAgICAgICAgICAgIHRoaXMuY2FjaGVJbnN0YW5jZSh0b2tlbiwgaW5zdGFuY2UsIGJpbmRpbmcubGlmZXRpbWUsIGNvbnRleHQpO1xuICAgICAgICAgICAgcmV0dXJuIGluc3RhbmNlO1xuICAgICAgICB9XG4gICAgICAgIGZpbmFsbHkge1xuICAgICAgICAgICAgY29udGV4dC5leGl0UmVzb2x2ZSh0b2tlbik7XG4gICAgICAgIH1cbiAgICB9XG4gICAgLyoqXG4gICAgICogSW50ZXJuYWw6IEFzeW5jIHJlc29sdmUgd2l0aCBjb250ZXh0XG4gICAgICovXG4gICAgYXN5bmMgcmVzb2x2ZUFzeW5jV2l0aENvbnRleHQodG9rZW4sIGNvbnRleHQpIHtcbiAgICAgICAgLy8gVmFsaWRhdGUgYW5kIGdldCBiaW5kaW5nICh3aXRoIGNpcmN1bGFyIGRlcGVuZGVuY3kgY2hlY2spXG4gICAgICAgIGNvbnN0IGJpbmRpbmcgPSB0aGlzLnZhbGlkYXRlQW5kR2V0QmluZGluZyh0b2tlbiwgY29udGV4dCk7XG4gICAgICAgIC8vIENoZWNrIHBlci1yZXF1ZXN0IGNhY2hlXG4gICAgICAgIGlmIChiaW5kaW5nLmxpZmV0aW1lID09PSAncGVyLXJlcXVlc3QnICYmIGNvbnRleHQuaGFzUGVyUmVxdWVzdCh0b2tlbikpIHtcbiAgICAgICAgICAgIHJldHVybiBjb250ZXh0LmdldFBlclJlcXVlc3QodG9rZW4pO1xuICAgICAgICB9XG4gICAgICAgIC8vIENoZWNrIHNpbmdsZXRvbiBjYWNoZSAobG9jYWwgY29udGFpbmVyIG9ubHkpXG4gICAgICAgIGlmIChiaW5kaW5nLmxpZmV0aW1lID09PSAnc2luZ2xldG9uJyAmJiB0aGlzLnNpbmdsZXRvbkNhY2hlLmhhcyh0b2tlbikpIHtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLnNpbmdsZXRvbkNhY2hlLmdldCh0b2tlbik7XG4gICAgICAgIH1cbiAgICAgICAgLy8gTWFyayBhcyByZXNvbHZpbmdcbiAgICAgICAgY29udGV4dC5lbnRlclJlc29sdmUodG9rZW4pO1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgLy8gSW5zdGFudGlhdGUgZnJvbSBiaW5kaW5nIGFzeW5jaHJvbm91c2x5XG4gICAgICAgICAgICBjb25zdCBpbnN0YW5jZSA9IGF3YWl0IHRoaXMuaW5zdGFudGlhdGVCaW5kaW5nQXN5bmMoYmluZGluZywgY29udGV4dCk7XG4gICAgICAgICAgICAvLyBDYWNoZSBiYXNlZCBvbiBsaWZldGltZVxuICAgICAgICAgICAgdGhpcy5jYWNoZUluc3RhbmNlKHRva2VuLCBpbnN0YW5jZSwgYmluZGluZy5saWZldGltZSwgY29udGV4dCk7XG4gICAgICAgICAgICByZXR1cm4gaW5zdGFuY2U7XG4gICAgICAgIH1cbiAgICAgICAgZmluYWxseSB7XG4gICAgICAgICAgICBjb250ZXh0LmV4aXRSZXNvbHZlKHRva2VuKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgYmluZGluZyBmcm9tIHRoaXMgY29udGFpbmVyIG9yIHBhcmVudCBjaGFpblxuICAgICAqIFBlcmZvcm1hbmNlIG9wdGltaXplZDogVXNlcyBmbGF0IGNhY2hlIHRvIGF2b2lkIHJlY3Vyc2l2ZSBwYXJlbnQgbG9va3Vwc1xuICAgICAqL1xuICAgIGdldEJpbmRpbmcodG9rZW4pIHtcbiAgICAgICAgLy8gQnVpbGQgZmxhdCBjYWNoZSBvbiBmaXJzdCBhY2Nlc3NcbiAgICAgICAgaWYgKCF0aGlzLmJpbmRpbmdDYWNoZSkge1xuICAgICAgICAgICAgdGhpcy5idWlsZEJpbmRpbmdDYWNoZSgpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzLmJpbmRpbmdDYWNoZS5nZXQodG9rZW4pO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBCdWlsZCBmbGF0IGNhY2hlIG9mIGFsbCBiaW5kaW5ncyBpbmNsdWRpbmcgcGFyZW50IGNoYWluXG4gICAgICogVGhpcyBjb252ZXJ0cyBPKG4pIHBhcmVudCBjaGFpbiB0cmF2ZXJzYWwgdG8gTygxKSBsb29rdXBcbiAgICAgKi9cbiAgICBidWlsZEJpbmRpbmdDYWNoZSgpIHtcbiAgICAgICAgdGhpcy5iaW5kaW5nQ2FjaGUgPSBuZXcgTWFwKCk7XG4gICAgICAgIC8vIFRyYXZlcnNlIHBhcmVudCBjaGFpbiBhbmQgZmxhdHRlbiBhbGwgYmluZGluZ3NcbiAgICAgICAgbGV0IGN1cnJlbnQgPSB0aGlzO1xuICAgICAgICB3aGlsZSAoY3VycmVudCkge1xuICAgICAgICAgICAgY3VycmVudC5iaW5kaW5ncy5mb3JFYWNoKChiaW5kaW5nLCB0b2tlbikgPT4ge1xuICAgICAgICAgICAgICAgIC8vIENoaWxkIGJpbmRpbmdzIG92ZXJyaWRlIHBhcmVudCBiaW5kaW5ncyAoZmlyc3Qgd2lucylcbiAgICAgICAgICAgICAgICBpZiAoIXRoaXMuYmluZGluZ0NhY2hlLmhhcyh0b2tlbikpIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5iaW5kaW5nQ2FjaGUuc2V0KHRva2VuLCBiaW5kaW5nKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIGN1cnJlbnQgPSBjdXJyZW50LnBhcmVudDtcbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBJbnZhbGlkYXRlIGJpbmRpbmcgY2FjaGUgd2hlbiBuZXcgYmluZGluZ3MgYXJlIGFkZGVkXG4gICAgICogQ2FsbGVkIGJ5IGJpbmRWYWx1ZSwgYmluZEZhY3RvcnksIGJpbmRDbGFzc1xuICAgICAqL1xuICAgIGludmFsaWRhdGVCaW5kaW5nQ2FjaGUoKSB7XG4gICAgICAgIHRoaXMuYmluZGluZ0NhY2hlID0gdW5kZWZpbmVkO1xuICAgICAgICB0aGlzLnVsdHJhRmFzdFNpbmdsZXRvbkNhY2hlLmNsZWFyKCk7IC8vIENsZWFyIHVsdHJhLWZhc3QgY2FjaGUgd2hlbiBiaW5kaW5ncyBjaGFuZ2VcbiAgICB9XG59XG5Db250YWluZXIuY29udGV4dFBvb2wgPSBuZXcgUmVzb2x1dGlvbkNvbnRleHRQb29sKCk7IC8vIFBlcmZvcm1hbmNlOiBQb29sZWQgY29udGV4dHMgcmVkdWNlIGFsbG9jYXRpb25zXG4iLCAiZXhwb3J0IGNsYXNzIERhdGVSZW5kZXJlciB7XG4gICAgY29uc3RydWN0b3IoZGF0ZVNlcnZpY2UpIHtcbiAgICAgICAgdGhpcy5kYXRlU2VydmljZSA9IGRhdGVTZXJ2aWNlO1xuICAgICAgICB0aGlzLnR5cGUgPSAnZGF0ZSc7XG4gICAgfVxuICAgIHJlbmRlcihjb250ZXh0KSB7XG4gICAgICAgIGNvbnN0IGRhdGVzID0gY29udGV4dC5maWx0ZXJbJ2RhdGUnXSB8fCBbXTtcbiAgICAgICAgY29uc3QgcmVzb3VyY2VJZHMgPSBjb250ZXh0LmZpbHRlclsncmVzb3VyY2UnXSB8fCBbXTtcbiAgICAgICAgLy8gQ2hlY2sgaWYgZGF0ZSBoZWFkZXJzIHNob3VsZCBiZSBoaWRkZW4gKGUuZy4sIGluIGRheSB2aWV3KVxuICAgICAgICBjb25zdCBkYXRlR3JvdXBpbmcgPSBjb250ZXh0Lmdyb3VwaW5ncz8uZmluZChnID0+IGcudHlwZSA9PT0gJ2RhdGUnKTtcbiAgICAgICAgY29uc3QgaGlkZUhlYWRlciA9IGRhdGVHcm91cGluZz8uaGlkZUhlYWRlciA9PT0gdHJ1ZTtcbiAgICAgICAgLy8gUmVuZGVyIGRhdGVzIGZvciBIVkVSIHJlc291cmNlIChlbGxlciAxIGdhbmcgaHZpcyBpbmdlbiByZXNvdXJjZXMpXG4gICAgICAgIGNvbnN0IGl0ZXJhdGlvbnMgPSByZXNvdXJjZUlkcy5sZW5ndGggfHwgMTtcbiAgICAgICAgbGV0IGNvbHVtbkNvdW50ID0gMDtcbiAgICAgICAgZm9yIChsZXQgciA9IDA7IHIgPCBpdGVyYXRpb25zOyByKyspIHtcbiAgICAgICAgICAgIGNvbnN0IHJlc291cmNlSWQgPSByZXNvdXJjZUlkc1tyXTsgLy8gdW5kZWZpbmVkIGh2aXMgaW5nZW4gcmVzb3VyY2VzXG4gICAgICAgICAgICBmb3IgKGNvbnN0IGRhdGVTdHIgb2YgZGF0ZXMpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBkYXRlID0gdGhpcy5kYXRlU2VydmljZS5wYXJzZUlTTyhkYXRlU3RyKTtcbiAgICAgICAgICAgICAgICAvLyBCdWlsZCBjb2x1bW5LZXkgZm9yIHVuaWZvcm0gaWRlbnRpZmljYXRpb25cbiAgICAgICAgICAgICAgICBjb25zdCBzZWdtZW50cyA9IHsgZGF0ZTogZGF0ZVN0ciB9O1xuICAgICAgICAgICAgICAgIGlmIChyZXNvdXJjZUlkKVxuICAgICAgICAgICAgICAgICAgICBzZWdtZW50cy5yZXNvdXJjZSA9IHJlc291cmNlSWQ7XG4gICAgICAgICAgICAgICAgY29uc3QgY29sdW1uS2V5ID0gdGhpcy5kYXRlU2VydmljZS5idWlsZENvbHVtbktleShzZWdtZW50cyk7XG4gICAgICAgICAgICAgICAgLy8gSGVhZGVyXG4gICAgICAgICAgICAgICAgY29uc3QgaGVhZGVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3dwLWRheS1oZWFkZXInKTtcbiAgICAgICAgICAgICAgICBoZWFkZXIuZGF0YXNldC5kYXRlID0gZGF0ZVN0cjtcbiAgICAgICAgICAgICAgICBoZWFkZXIuZGF0YXNldC5jb2x1bW5LZXkgPSBjb2x1bW5LZXk7XG4gICAgICAgICAgICAgICAgaWYgKHJlc291cmNlSWQpIHtcbiAgICAgICAgICAgICAgICAgICAgaGVhZGVyLmRhdGFzZXQucmVzb3VyY2VJZCA9IHJlc291cmNlSWQ7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGlmIChoaWRlSGVhZGVyKSB7XG4gICAgICAgICAgICAgICAgICAgIGhlYWRlci5kYXRhc2V0LmhpZGRlbiA9ICd0cnVlJztcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgaGVhZGVyLmlubmVySFRNTCA9IGBcclxuICAgICAgICAgIDxzd3AtZGF5LW5hbWU+JHt0aGlzLmRhdGVTZXJ2aWNlLmdldERheU5hbWUoZGF0ZSwgJ3Nob3J0Jyl9PC9zd3AtZGF5LW5hbWU+XHJcbiAgICAgICAgICA8c3dwLWRheS1kYXRlPiR7ZGF0ZS5nZXREYXRlKCl9PC9zd3AtZGF5LWRhdGU+XHJcbiAgICAgICAgYDtcbiAgICAgICAgICAgICAgICBjb250ZXh0LmhlYWRlckNvbnRhaW5lci5hcHBlbmRDaGlsZChoZWFkZXIpO1xuICAgICAgICAgICAgICAgIC8vIENvbHVtblxuICAgICAgICAgICAgICAgIGNvbnN0IGNvbHVtbiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3N3cC1kYXktY29sdW1uJyk7XG4gICAgICAgICAgICAgICAgY29sdW1uLmRhdGFzZXQuZGF0ZSA9IGRhdGVTdHI7XG4gICAgICAgICAgICAgICAgY29sdW1uLmRhdGFzZXQuY29sdW1uS2V5ID0gY29sdW1uS2V5O1xuICAgICAgICAgICAgICAgIGlmIChyZXNvdXJjZUlkKSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbHVtbi5kYXRhc2V0LnJlc291cmNlSWQgPSByZXNvdXJjZUlkO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBjb2x1bW4uaW5uZXJIVE1MID0gJzxzd3AtZXZlbnRzLWxheWVyPjwvc3dwLWV2ZW50cy1sYXllcj4nO1xuICAgICAgICAgICAgICAgIGNvbnRleHQuY29sdW1uQ29udGFpbmVyLmFwcGVuZENoaWxkKGNvbHVtbik7XG4gICAgICAgICAgICAgICAgY29sdW1uQ291bnQrKztcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICAvLyBTZXQgZ3JpZCBjb2x1bW5zIG9uIGNvbnRhaW5lclxuICAgICAgICBjb25zdCBjb250YWluZXIgPSBjb250ZXh0LmNvbHVtbkNvbnRhaW5lci5jbG9zZXN0KCdzd3AtY2FsZW5kYXItY29udGFpbmVyJyk7XG4gICAgICAgIGlmIChjb250YWluZXIpIHtcbiAgICAgICAgICAgIGNvbnRhaW5lci5zdHlsZS5zZXRQcm9wZXJ0eSgnLS1ncmlkLWNvbHVtbnMnLCBTdHJpbmcoY29sdW1uQ291bnQpKTtcbiAgICAgICAgfVxuICAgIH1cbn1cbiIsICJpbXBvcnQgZGF5anMgZnJvbSAnZGF5anMnO1xuaW1wb3J0IHV0YyBmcm9tICdkYXlqcy9wbHVnaW4vdXRjJztcbmltcG9ydCB0aW1lem9uZSBmcm9tICdkYXlqcy9wbHVnaW4vdGltZXpvbmUnO1xuaW1wb3J0IGlzb1dlZWsgZnJvbSAnZGF5anMvcGx1Z2luL2lzb1dlZWsnO1xuLy8gRW5hYmxlIGRheWpzIHBsdWdpbnNcbmRheWpzLmV4dGVuZCh1dGMpO1xuZGF5anMuZXh0ZW5kKHRpbWV6b25lKTtcbmRheWpzLmV4dGVuZChpc29XZWVrKTtcbmV4cG9ydCBjbGFzcyBEYXRlU2VydmljZSB7XG4gICAgY29uc3RydWN0b3IoY29uZmlnLCBiYXNlRGF0ZSkge1xuICAgICAgICB0aGlzLmNvbmZpZyA9IGNvbmZpZztcbiAgICAgICAgdGhpcy50aW1lem9uZSA9IGNvbmZpZy50aW1lem9uZTtcbiAgICAgICAgLy8gQWxsb3cgc2V0dGluZyBhIGZpeGVkIGJhc2UgZGF0ZSBmb3IgZGVtby90ZXN0aW5nIHB1cnBvc2VzXG4gICAgICAgIHRoaXMuYmFzZURhdGUgPSBiYXNlRGF0ZSA/IGRheWpzKGJhc2VEYXRlKSA6IGRheWpzKCk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFNldCBhIGZpeGVkIGJhc2UgZGF0ZSAodXNlZnVsIGZvciBkZW1vcyB3aXRoIHN0YXRpYyBtb2NrIGRhdGEpXG4gICAgICovXG4gICAgc2V0QmFzZURhdGUoZGF0ZSkge1xuICAgICAgICB0aGlzLmJhc2VEYXRlID0gZGF5anMoZGF0ZSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCB0aGUgY3VycmVudCBiYXNlIGRhdGUgKGVpdGhlciBmaXhlZCBvciB0b2RheSlcbiAgICAgKi9cbiAgICBnZXRCYXNlRGF0ZSgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuYmFzZURhdGUudG9EYXRlKCk7XG4gICAgfVxuICAgIHBhcnNlSVNPKGlzb1N0cmluZykge1xuICAgICAgICByZXR1cm4gZGF5anMoaXNvU3RyaW5nKS50b0RhdGUoKTtcbiAgICB9XG4gICAgZ2V0RGF5TmFtZShkYXRlLCBmb3JtYXQgPSAnc2hvcnQnKSB7XG4gICAgICAgIHJldHVybiBuZXcgSW50bC5EYXRlVGltZUZvcm1hdCh0aGlzLmNvbmZpZy5sb2NhbGUsIHsgd2Vla2RheTogZm9ybWF0IH0pLmZvcm1hdChkYXRlKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IGRhdGVzIHN0YXJ0aW5nIGZyb20gYSBkYXkgb2Zmc2V0XG4gICAgICogQHBhcmFtIGRheU9mZnNldCAtIERheSBvZmZzZXQgZnJvbSBiYXNlIGRhdGVcbiAgICAgKiBAcGFyYW0gY291bnQgLSBOdW1iZXIgb2YgY29uc2VjdXRpdmUgZGF5cyB0byByZXR1cm5cbiAgICAgKiBAcmV0dXJucyBBcnJheSBvZiBkYXRlIHN0cmluZ3MgaW4gWVlZWS1NTS1ERCBmb3JtYXRcbiAgICAgKi9cbiAgICBnZXREYXRlc0Zyb21PZmZzZXQoZGF5T2Zmc2V0LCBjb3VudCkge1xuICAgICAgICBjb25zdCBzdGFydERhdGUgPSB0aGlzLmJhc2VEYXRlLmFkZChkYXlPZmZzZXQsICdkYXknKTtcbiAgICAgICAgcmV0dXJuIEFycmF5LmZyb20oeyBsZW5ndGg6IGNvdW50IH0sIChfLCBpKSA9PiBzdGFydERhdGUuYWRkKGksICdkYXknKS5mb3JtYXQoJ1lZWVktTU0tREQnKSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCBzcGVjaWZpYyB3ZWVrZGF5cyBmcm9tIHRoZSB3ZWVrIGNvbnRhaW5pbmcgdGhlIG9mZnNldCBkYXRlXG4gICAgICogQHBhcmFtIGRheU9mZnNldCAtIERheSBvZmZzZXQgZnJvbSBiYXNlIGRhdGVcbiAgICAgKiBAcGFyYW0gd29ya0RheXMgLSBBcnJheSBvZiBJU08gd2Vla2RheSBudW1iZXJzICgxPU1vbmRheSwgNz1TdW5kYXkpXG4gICAgICogQHJldHVybnMgQXJyYXkgb2YgZGF0ZSBzdHJpbmdzIGluIFlZWVktTU0tREQgZm9ybWF0XG4gICAgICovXG4gICAgZ2V0V29ya0RheXNGcm9tT2Zmc2V0KGRheU9mZnNldCwgd29ya0RheXMpIHtcbiAgICAgICAgLy8gR2V0IHRoZSBkYXRlIGF0IG9mZnNldCwgdGhlbiBmaW5kIGl0cyB3ZWVrJ3MgTW9uZGF5XG4gICAgICAgIGNvbnN0IHRhcmdldERhdGUgPSB0aGlzLmJhc2VEYXRlLmFkZChkYXlPZmZzZXQsICdkYXknKTtcbiAgICAgICAgY29uc3QgbW9uZGF5ID0gdGFyZ2V0RGF0ZS5zdGFydE9mKCd3ZWVrJykuYWRkKDEsICdkYXknKTtcbiAgICAgICAgcmV0dXJuIHdvcmtEYXlzLm1hcChpc29EYXkgPT4ge1xuICAgICAgICAgICAgLy8gSVNPOiAxPU1vbmRheSwgNz1TdW5kYXkgXHUyMTkyIGRheXMgZnJvbSBNb25kYXk6IDAtNlxuICAgICAgICAgICAgY29uc3QgZGF5c0Zyb21Nb25kYXkgPSBpc29EYXkgPT09IDcgPyA2IDogaXNvRGF5IC0gMTtcbiAgICAgICAgICAgIHJldHVybiBtb25kYXkuYWRkKGRheXNGcm9tTW9uZGF5LCAnZGF5JykuZm9ybWF0KCdZWVlZLU1NLUREJyk7XG4gICAgICAgIH0pO1xuICAgIH1cbiAgICAvLyBMZWdhY3kgbWV0aG9kcyBmb3IgYmFja3dhcmRzIGNvbXBhdGliaWxpdHlcbiAgICBnZXRXZWVrRGF0ZXMod2Vla09mZnNldCA9IDAsIGRheXMgPSA3KSB7XG4gICAgICAgIHJldHVybiB0aGlzLmdldERhdGVzRnJvbU9mZnNldCh3ZWVrT2Zmc2V0ICogNywgZGF5cyk7XG4gICAgfVxuICAgIGdldFdvcmtXZWVrRGF0ZXMod2Vla09mZnNldCwgd29ya0RheXMpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuZ2V0V29ya0RheXNGcm9tT2Zmc2V0KHdlZWtPZmZzZXQgKiA3LCB3b3JrRGF5cyk7XG4gICAgfVxuICAgIC8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG4gICAgLy8gRk9STUFUVElOR1xuICAgIC8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG4gICAgZm9ybWF0VGltZShkYXRlLCBzaG93U2Vjb25kcyA9IGZhbHNlKSB7XG4gICAgICAgIGNvbnN0IHBhdHRlcm4gPSBzaG93U2Vjb25kcyA/ICdISDptbTpzcycgOiAnSEg6bW0nO1xuICAgICAgICByZXR1cm4gZGF5anMoZGF0ZSkuZm9ybWF0KHBhdHRlcm4pO1xuICAgIH1cbiAgICBmb3JtYXRUaW1lUmFuZ2Uoc3RhcnQsIGVuZCkge1xuICAgICAgICByZXR1cm4gYCR7dGhpcy5mb3JtYXRUaW1lKHN0YXJ0KX0gLSAke3RoaXMuZm9ybWF0VGltZShlbmQpfWA7XG4gICAgfVxuICAgIGZvcm1hdERhdGUoZGF0ZSkge1xuICAgICAgICByZXR1cm4gZGF5anMoZGF0ZSkuZm9ybWF0KCdZWVlZLU1NLUREJyk7XG4gICAgfVxuICAgIGdldERhdGVLZXkoZGF0ZSkge1xuICAgICAgICByZXR1cm4gdGhpcy5mb3JtYXREYXRlKGRhdGUpO1xuICAgIH1cbiAgICAvLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuICAgIC8vIENPTFVNTiBLRVlcbiAgICAvLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuICAgIC8qKlxuICAgICAqIEJ1aWxkIGEgdW5pZm9ybSBjb2x1bW5LZXkgZnJvbSBncm91cGluZyBzZWdtZW50c1xuICAgICAqIEhhbmRsZXMgYW55IGNvbWJpbmF0aW9uIG9mIGRhdGUsIHJlc291cmNlLCB0ZWFtLCBldGMuXG4gICAgICpcbiAgICAgKiBAZXhhbXBsZVxuICAgICAqIGJ1aWxkQ29sdW1uS2V5KHsgZGF0ZTogJzIwMjUtMTItMDknIH0pIFx1MjE5MiBcIjIwMjUtMTItMDlcIlxuICAgICAqIGJ1aWxkQ29sdW1uS2V5KHsgZGF0ZTogJzIwMjUtMTItMDknLCByZXNvdXJjZTogJ0VNUDAwMScgfSkgXHUyMTkyIFwiMjAyNS0xMi0wOTpFTVAwMDFcIlxuICAgICAqL1xuICAgIGJ1aWxkQ29sdW1uS2V5KHNlZ21lbnRzKSB7XG4gICAgICAgIC8vIEFsd2F5cyBwdXQgZGF0ZSBmaXJzdCBpZiBwcmVzZW50LCB0aGVuIG90aGVyIHNlZ21lbnRzIGFscGhhYmV0aWNhbGx5XG4gICAgICAgIGNvbnN0IGRhdGUgPSBzZWdtZW50cy5kYXRlO1xuICAgICAgICBjb25zdCBvdGhlcnMgPSBPYmplY3QuZW50cmllcyhzZWdtZW50cylcbiAgICAgICAgICAgIC5maWx0ZXIoKFtrXSkgPT4gayAhPT0gJ2RhdGUnKVxuICAgICAgICAgICAgLnNvcnQoKFthXSwgW2JdKSA9PiBhLmxvY2FsZUNvbXBhcmUoYikpXG4gICAgICAgICAgICAubWFwKChbLCB2XSkgPT4gdik7XG4gICAgICAgIHJldHVybiBkYXRlID8gW2RhdGUsIC4uLm90aGVyc10uam9pbignOicpIDogb3RoZXJzLmpvaW4oJzonKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogUGFyc2UgYSBjb2x1bW5LZXkgYmFjayBpbnRvIHNlZ21lbnRzXG4gICAgICogQXNzdW1lcyBmb3JtYXQ6IFwiZGF0ZTpyZXNvdXJjZTouLi5cIiBvciBqdXN0IFwiZGF0ZVwiXG4gICAgICovXG4gICAgcGFyc2VDb2x1bW5LZXkoY29sdW1uS2V5KSB7XG4gICAgICAgIGNvbnN0IHBhcnRzID0gY29sdW1uS2V5LnNwbGl0KCc6Jyk7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICBkYXRlOiBwYXJ0c1swXSxcbiAgICAgICAgICAgIHJlc291cmNlOiBwYXJ0c1sxXVxuICAgICAgICB9O1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBFeHRyYWN0IGRhdGVLZXkgZnJvbSBjb2x1bW5LZXkgKGZpcnN0IHNlZ21lbnQpXG4gICAgICovXG4gICAgZ2V0RGF0ZUZyb21Db2x1bW5LZXkoY29sdW1uS2V5KSB7XG4gICAgICAgIHJldHVybiBjb2x1bW5LZXkuc3BsaXQoJzonKVswXTtcbiAgICB9XG4gICAgLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cbiAgICAvLyBUSU1FIENBTENVTEFUSU9OU1xuICAgIC8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG4gICAgdGltZVRvTWludXRlcyh0aW1lU3RyaW5nKSB7XG4gICAgICAgIGNvbnN0IHBhcnRzID0gdGltZVN0cmluZy5zcGxpdCgnOicpLm1hcChOdW1iZXIpO1xuICAgICAgICBjb25zdCBob3VycyA9IHBhcnRzWzBdIHx8IDA7XG4gICAgICAgIGNvbnN0IG1pbnV0ZXMgPSBwYXJ0c1sxXSB8fCAwO1xuICAgICAgICByZXR1cm4gaG91cnMgKiA2MCArIG1pbnV0ZXM7XG4gICAgfVxuICAgIG1pbnV0ZXNUb1RpbWUodG90YWxNaW51dGVzKSB7XG4gICAgICAgIGNvbnN0IGhvdXJzID0gTWF0aC5mbG9vcih0b3RhbE1pbnV0ZXMgLyA2MCk7XG4gICAgICAgIGNvbnN0IG1pbnV0ZXMgPSB0b3RhbE1pbnV0ZXMgJSA2MDtcbiAgICAgICAgcmV0dXJuIGRheWpzKCkuaG91cihob3VycykubWludXRlKG1pbnV0ZXMpLmZvcm1hdCgnSEg6bW0nKTtcbiAgICB9XG4gICAgZ2V0TWludXRlc1NpbmNlTWlkbmlnaHQoZGF0ZSkge1xuICAgICAgICBjb25zdCBkID0gZGF5anMoZGF0ZSk7XG4gICAgICAgIHJldHVybiBkLmhvdXIoKSAqIDYwICsgZC5taW51dGUoKTtcbiAgICB9XG4gICAgLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cbiAgICAvLyBVVEMgQ09OVkVSU0lPTlNcbiAgICAvLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuICAgIHRvVVRDKGxvY2FsRGF0ZSkge1xuICAgICAgICByZXR1cm4gZGF5anMudHoobG9jYWxEYXRlLCB0aGlzLnRpbWV6b25lKS51dGMoKS50b0lTT1N0cmluZygpO1xuICAgIH1cbiAgICBmcm9tVVRDKHV0Y1N0cmluZykge1xuICAgICAgICByZXR1cm4gZGF5anMudXRjKHV0Y1N0cmluZykudHoodGhpcy50aW1lem9uZSkudG9EYXRlKCk7XG4gICAgfVxuICAgIC8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG4gICAgLy8gREFURSBDUkVBVElPTlxuICAgIC8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG4gICAgY3JlYXRlRGF0ZUF0VGltZShiYXNlRGF0ZSwgdGltZVN0cmluZykge1xuICAgICAgICBjb25zdCB0b3RhbE1pbnV0ZXMgPSB0aGlzLnRpbWVUb01pbnV0ZXModGltZVN0cmluZyk7XG4gICAgICAgIGNvbnN0IGhvdXJzID0gTWF0aC5mbG9vcih0b3RhbE1pbnV0ZXMgLyA2MCk7XG4gICAgICAgIGNvbnN0IG1pbnV0ZXMgPSB0b3RhbE1pbnV0ZXMgJSA2MDtcbiAgICAgICAgcmV0dXJuIGRheWpzKGJhc2VEYXRlKS5zdGFydE9mKCdkYXknKS5ob3VyKGhvdXJzKS5taW51dGUobWludXRlcykudG9EYXRlKCk7XG4gICAgfVxuICAgIGdldElTT1dlZWtEYXkoZGF0ZSkge1xuICAgICAgICByZXR1cm4gZGF5anMoZGF0ZSkuaXNvV2Vla2RheSgpOyAvLyAxPU1vbmRheSwgNz1TdW5kYXlcbiAgICB9XG59XG4iLCAiLyoqXG4gKiBBYnN0cmFjdCBiYXNlIGNsYXNzIGZvciBncm91cGluZyByZW5kZXJlcnNcbiAqXG4gKiBIYW5kbGVzOlxuICogLSBGZXRjaGluZyBlbnRpdGllcyBieSBJRHNcbiAqIC0gQ2FsY3VsYXRpbmcgY29sc3BhbiBmcm9tIHBhcmVudENoaWxkTWFwXG4gKiAtIENyZWF0aW5nIGhlYWRlciBlbGVtZW50c1xuICogLSBBcHBlbmRpbmcgdG8gY29udGFpbmVyXG4gKlxuICogU3ViY2xhc3NlcyBvdmVycmlkZTpcbiAqIC0gcmVuZGVySGVhZGVyKCkgZm9yIGN1c3RvbSBjb250ZW50XG4gKiAtIGdldERpc3BsYXlOYW1lKCkgZm9yIGVudGl0eSBkaXNwbGF5IHRleHRcbiAqL1xuZXhwb3J0IGNsYXNzIEJhc2VHcm91cGluZ1JlbmRlcmVyIHtcbiAgICAvKipcbiAgICAgKiBNYWluIHJlbmRlciBtZXRob2QgLSBoYW5kbGVzIGNvbW1vbiBsb2dpY1xuICAgICAqL1xuICAgIGFzeW5jIHJlbmRlcihjb250ZXh0KSB7XG4gICAgICAgIGNvbnN0IGFsbG93ZWRJZHMgPSBjb250ZXh0LmZpbHRlclt0aGlzLnR5cGVdIHx8IFtdO1xuICAgICAgICBpZiAoYWxsb3dlZElkcy5sZW5ndGggPT09IDApXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIGNvbnN0IGVudGl0aWVzID0gYXdhaXQgdGhpcy5nZXRFbnRpdGllcyhhbGxvd2VkSWRzKTtcbiAgICAgICAgY29uc3QgZGF0ZUNvdW50ID0gY29udGV4dC5maWx0ZXJbJ2RhdGUnXT8ubGVuZ3RoIHx8IDE7XG4gICAgICAgIGNvbnN0IGNoaWxkSWRzID0gY29udGV4dC5jaGlsZFR5cGUgPyBjb250ZXh0LmZpbHRlcltjb250ZXh0LmNoaWxkVHlwZV0gfHwgW10gOiBbXTtcbiAgICAgICAgZm9yIChjb25zdCBlbnRpdHkgb2YgZW50aXRpZXMpIHtcbiAgICAgICAgICAgIGNvbnN0IGVudGl0eUNoaWxkSWRzID0gY29udGV4dC5wYXJlbnRDaGlsZE1hcD8uW2VudGl0eS5pZF0gfHwgW107XG4gICAgICAgICAgICBjb25zdCBjaGlsZENvdW50ID0gZW50aXR5Q2hpbGRJZHMuZmlsdGVyKGlkID0+IGNoaWxkSWRzLmluY2x1ZGVzKGlkKSkubGVuZ3RoO1xuICAgICAgICAgICAgY29uc3QgY29sc3BhbiA9IGNoaWxkQ291bnQgKiBkYXRlQ291bnQ7XG4gICAgICAgICAgICBjb25zdCBoZWFkZXIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KHRoaXMuY29uZmlnLmVsZW1lbnRUYWcpO1xuICAgICAgICAgICAgaGVhZGVyLmRhdGFzZXRbdGhpcy5jb25maWcuaWRBdHRyaWJ1dGVdID0gZW50aXR5LmlkO1xuICAgICAgICAgICAgaGVhZGVyLnN0eWxlLnNldFByb3BlcnR5KHRoaXMuY29uZmlnLmNvbHNwYW5WYXIsIFN0cmluZyhjb2xzcGFuKSk7XG4gICAgICAgICAgICAvLyBBbGxvdyBzdWJjbGFzcyB0byBjdXN0b21pemUgaGVhZGVyIGNvbnRlbnRcbiAgICAgICAgICAgIHRoaXMucmVuZGVySGVhZGVyKGVudGl0eSwgaGVhZGVyLCBjb250ZXh0KTtcbiAgICAgICAgICAgIGNvbnRleHQuaGVhZGVyQ29udGFpbmVyLmFwcGVuZENoaWxkKGhlYWRlcik7XG4gICAgICAgIH1cbiAgICB9XG4gICAgLyoqXG4gICAgICogT3ZlcnJpZGUgdGhpcyBtZXRob2QgZm9yIGN1c3RvbSBoZWFkZXIgcmVuZGVyaW5nXG4gICAgICogRGVmYXVsdDoganVzdCBzZXRzIHRleHRDb250ZW50IHRvIGRpc3BsYXkgbmFtZVxuICAgICAqL1xuICAgIHJlbmRlckhlYWRlcihlbnRpdHksIGhlYWRlciwgX2NvbnRleHQpIHtcbiAgICAgICAgaGVhZGVyLnRleHRDb250ZW50ID0gdGhpcy5nZXREaXNwbGF5TmFtZShlbnRpdHkpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBIZWxwZXIgdG8gcmVuZGVyIGEgc2luZ2xlIGVudGl0eSBoZWFkZXIuXG4gICAgICogQ2FuIGJlIHVzZWQgYnkgc3ViY2xhc3NlcyB0aGF0IG92ZXJyaWRlIHJlbmRlcigpIGJ1dCB3YW50IGNvbnNpc3RlbnQgaGVhZGVyIGNyZWF0aW9uLlxuICAgICAqL1xuICAgIGNyZWF0ZUhlYWRlcihlbnRpdHksIGNvbnRleHQpIHtcbiAgICAgICAgY29uc3QgaGVhZGVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCh0aGlzLmNvbmZpZy5lbGVtZW50VGFnKTtcbiAgICAgICAgaGVhZGVyLmRhdGFzZXRbdGhpcy5jb25maWcuaWRBdHRyaWJ1dGVdID0gZW50aXR5LmlkO1xuICAgICAgICB0aGlzLnJlbmRlckhlYWRlcihlbnRpdHksIGhlYWRlciwgY29udGV4dCk7XG4gICAgICAgIHJldHVybiBoZWFkZXI7XG4gICAgfVxufVxuIiwgImltcG9ydCB7IEJhc2VHcm91cGluZ1JlbmRlcmVyIH0gZnJvbSAnLi4vLi4vY29yZS9CYXNlR3JvdXBpbmdSZW5kZXJlcic7XG5leHBvcnQgY2xhc3MgUmVzb3VyY2VSZW5kZXJlciBleHRlbmRzIEJhc2VHcm91cGluZ1JlbmRlcmVyIHtcbiAgICBjb25zdHJ1Y3RvcihyZXNvdXJjZVNlcnZpY2UpIHtcbiAgICAgICAgc3VwZXIoKTtcbiAgICAgICAgdGhpcy5yZXNvdXJjZVNlcnZpY2UgPSByZXNvdXJjZVNlcnZpY2U7XG4gICAgICAgIHRoaXMudHlwZSA9ICdyZXNvdXJjZSc7XG4gICAgICAgIHRoaXMuY29uZmlnID0ge1xuICAgICAgICAgICAgZWxlbWVudFRhZzogJ3N3cC1yZXNvdXJjZS1oZWFkZXInLFxuICAgICAgICAgICAgaWRBdHRyaWJ1dGU6ICdyZXNvdXJjZUlkJyxcbiAgICAgICAgICAgIGNvbHNwYW5WYXI6ICctLXJlc291cmNlLWNvbHMnXG4gICAgICAgIH07XG4gICAgfVxuICAgIGdldEVudGl0aWVzKGlkcykge1xuICAgICAgICByZXR1cm4gdGhpcy5yZXNvdXJjZVNlcnZpY2UuZ2V0QnlJZHMoaWRzKTtcbiAgICB9XG4gICAgZ2V0RGlzcGxheU5hbWUoZW50aXR5KSB7XG4gICAgICAgIHJldHVybiBlbnRpdHkuZGlzcGxheU5hbWU7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIE92ZXJyaWRlIHJlbmRlciB0byBoYW5kbGU6XG4gICAgICogMS4gU3BlY2lhbCBvcmRlcmluZyB3aGVuIHBhcmVudENoaWxkTWFwIGV4aXN0cyAocmVzb3VyY2VzIGdyb3VwZWQgYnkgcGFyZW50KVxuICAgICAqIDIuIERpZmZlcmVudCBjb2xzcGFuIGNhbGN1bGF0aW9uIChqdXN0IGRhdGVDb3VudCwgbm90IGNoaWxkQ291bnQgKiBkYXRlQ291bnQpXG4gICAgICovXG4gICAgYXN5bmMgcmVuZGVyKGNvbnRleHQpIHtcbiAgICAgICAgY29uc3QgcmVzb3VyY2VJZHMgPSBjb250ZXh0LmZpbHRlclsncmVzb3VyY2UnXSB8fCBbXTtcbiAgICAgICAgY29uc3QgZGF0ZUNvdW50ID0gY29udGV4dC5maWx0ZXJbJ2RhdGUnXT8ubGVuZ3RoIHx8IDE7XG4gICAgICAgIC8vIERldGVybWluZSByZW5kZXIgb3JkZXIgYmFzZWQgb24gcGFyZW50Q2hpbGRNYXBcbiAgICAgICAgLy8gSWYgcGFyZW50Q2hpbGRNYXAgZXhpc3RzLCByZW5kZXIgcmVzb3VyY2VzIGdyb3VwZWQgYnkgcGFyZW50IChlLmcuLCB0ZWFtKVxuICAgICAgICAvLyBPdGhlcndpc2UsIHJlbmRlciBpbiBmaWx0ZXIgb3JkZXJcbiAgICAgICAgbGV0IG9yZGVyZWRSZXNvdXJjZUlkcztcbiAgICAgICAgaWYgKGNvbnRleHQucGFyZW50Q2hpbGRNYXApIHtcbiAgICAgICAgICAgIC8vIFJlbmRlciByZXNvdXJjZXMgaW4gcGFyZW50LWNoaWxkIG9yZGVyXG4gICAgICAgICAgICBvcmRlcmVkUmVzb3VyY2VJZHMgPSBbXTtcbiAgICAgICAgICAgIGZvciAoY29uc3QgY2hpbGRJZHMgb2YgT2JqZWN0LnZhbHVlcyhjb250ZXh0LnBhcmVudENoaWxkTWFwKSkge1xuICAgICAgICAgICAgICAgIGZvciAoY29uc3QgY2hpbGRJZCBvZiBjaGlsZElkcykge1xuICAgICAgICAgICAgICAgICAgICBpZiAocmVzb3VyY2VJZHMuaW5jbHVkZXMoY2hpbGRJZCkpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIG9yZGVyZWRSZXNvdXJjZUlkcy5wdXNoKGNoaWxkSWQpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgb3JkZXJlZFJlc291cmNlSWRzID0gcmVzb3VyY2VJZHM7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgcmVzb3VyY2VzID0gYXdhaXQgdGhpcy5nZXRFbnRpdGllcyhvcmRlcmVkUmVzb3VyY2VJZHMpO1xuICAgICAgICAvLyBDcmVhdGUgYSBtYXAgZm9yIHF1aWNrIGxvb2t1cCB0byBwcmVzZXJ2ZSBvcmRlclxuICAgICAgICBjb25zdCByZXNvdXJjZU1hcCA9IG5ldyBNYXAocmVzb3VyY2VzLm1hcChyID0+IFtyLmlkLCByXSkpO1xuICAgICAgICBmb3IgKGNvbnN0IHJlc291cmNlSWQgb2Ygb3JkZXJlZFJlc291cmNlSWRzKSB7XG4gICAgICAgICAgICBjb25zdCByZXNvdXJjZSA9IHJlc291cmNlTWFwLmdldChyZXNvdXJjZUlkKTtcbiAgICAgICAgICAgIGlmICghcmVzb3VyY2UpXG4gICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICBjb25zdCBoZWFkZXIgPSB0aGlzLmNyZWF0ZUhlYWRlcihyZXNvdXJjZSwgY29udGV4dCk7XG4gICAgICAgICAgICBoZWFkZXIuc3R5bGUuZ3JpZENvbHVtbiA9IGBzcGFuICR7ZGF0ZUNvdW50fWA7XG4gICAgICAgICAgICBjb250ZXh0LmhlYWRlckNvbnRhaW5lci5hcHBlbmRDaGlsZChoZWFkZXIpO1xuICAgICAgICB9XG4gICAgfVxufVxuIiwgImltcG9ydCB7IEJhc2VHcm91cGluZ1JlbmRlcmVyIH0gZnJvbSAnLi4vLi4vY29yZS9CYXNlR3JvdXBpbmdSZW5kZXJlcic7XG5leHBvcnQgY2xhc3MgVGVhbVJlbmRlcmVyIGV4dGVuZHMgQmFzZUdyb3VwaW5nUmVuZGVyZXIge1xuICAgIGNvbnN0cnVjdG9yKHRlYW1TZXJ2aWNlKSB7XG4gICAgICAgIHN1cGVyKCk7XG4gICAgICAgIHRoaXMudGVhbVNlcnZpY2UgPSB0ZWFtU2VydmljZTtcbiAgICAgICAgdGhpcy50eXBlID0gJ3RlYW0nO1xuICAgICAgICB0aGlzLmNvbmZpZyA9IHtcbiAgICAgICAgICAgIGVsZW1lbnRUYWc6ICdzd3AtdGVhbS1oZWFkZXInLFxuICAgICAgICAgICAgaWRBdHRyaWJ1dGU6ICd0ZWFtSWQnLFxuICAgICAgICAgICAgY29sc3BhblZhcjogJy0tdGVhbS1jb2xzJ1xuICAgICAgICB9O1xuICAgIH1cbiAgICBnZXRFbnRpdGllcyhpZHMpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMudGVhbVNlcnZpY2UuZ2V0QnlJZHMoaWRzKTtcbiAgICB9XG4gICAgZ2V0RGlzcGxheU5hbWUoZW50aXR5KSB7XG4gICAgICAgIHJldHVybiBlbnRpdHkubmFtZTtcbiAgICB9XG59XG4iLCAiaW1wb3J0IHsgQmFzZUdyb3VwaW5nUmVuZGVyZXIgfSBmcm9tICcuLi8uLi9jb3JlL0Jhc2VHcm91cGluZ1JlbmRlcmVyJztcbmV4cG9ydCBjbGFzcyBEZXBhcnRtZW50UmVuZGVyZXIgZXh0ZW5kcyBCYXNlR3JvdXBpbmdSZW5kZXJlciB7XG4gICAgY29uc3RydWN0b3IoZGVwYXJ0bWVudFNlcnZpY2UpIHtcbiAgICAgICAgc3VwZXIoKTtcbiAgICAgICAgdGhpcy5kZXBhcnRtZW50U2VydmljZSA9IGRlcGFydG1lbnRTZXJ2aWNlO1xuICAgICAgICB0aGlzLnR5cGUgPSAnZGVwYXJ0bWVudCc7XG4gICAgICAgIHRoaXMuY29uZmlnID0ge1xuICAgICAgICAgICAgZWxlbWVudFRhZzogJ3N3cC1kZXBhcnRtZW50LWhlYWRlcicsXG4gICAgICAgICAgICBpZEF0dHJpYnV0ZTogJ2RlcGFydG1lbnRJZCcsXG4gICAgICAgICAgICBjb2xzcGFuVmFyOiAnLS1kZXBhcnRtZW50LWNvbHMnXG4gICAgICAgIH07XG4gICAgfVxuICAgIGdldEVudGl0aWVzKGlkcykge1xuICAgICAgICByZXR1cm4gdGhpcy5kZXBhcnRtZW50U2VydmljZS5nZXRCeUlkcyhpZHMpO1xuICAgIH1cbiAgICBnZXREaXNwbGF5TmFtZShlbnRpdHkpIHtcbiAgICAgICAgcmV0dXJuIGVudGl0eS5uYW1lO1xuICAgIH1cbn1cbiIsICJleHBvcnQgZnVuY3Rpb24gYnVpbGRQaXBlbGluZShyZW5kZXJlcnMpIHtcbiAgICByZXR1cm4ge1xuICAgICAgICBhc3luYyBydW4oY29udGV4dCkge1xuICAgICAgICAgICAgZm9yIChjb25zdCByZW5kZXJlciBvZiByZW5kZXJlcnMpIHtcbiAgICAgICAgICAgICAgICBhd2FpdCByZW5kZXJlci5yZW5kZXIoY29udGV4dCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9O1xufVxuIiwgIi8qKlxuICogRmlsdGVyVGVtcGxhdGUgLSBCeWdnZXIgblx1MDBGOGdsZXIgdGlsIGV2ZW50LWtvbG9ubmUgbWF0Y2hpbmdcbiAqXG4gKiBWaWV3Q29uZmlnIGRlZmluZXJlciBodmlsa2UgZmVsdGVyIChpZFByb3BlcnRpZXMpIGRlciBpbmRnXHUwMEU1ciBpIGtvbG9ubmVucyBuXHUwMEY4Z2xlLlxuICogU2FtbWUgdGVtcGxhdGUgYnJ1Z2VzIHRpbCBhdCBieWdnZSBuXHUwMEY4Z2xlIGZvciBiXHUwMEU1ZGUga29sb25uZSBvZyBldmVudC5cbiAqXG4gKiBTdXBwb3J0cyBkb3Qtbm90YXRpb24gZm9yIGhpZXJhcmNoaWNhbCByZWxhdGlvbnM6XG4gKiAtICdyZXNvdXJjZS50ZWFtSWQnIFx1MjE5MiBsb29rcyB1cCBldmVudC5yZXNvdXJjZUlkIFx1MjE5MiByZXNvdXJjZSBlbnRpdHkgXHUyMTkyIHRlYW1JZFxuICpcbiAqIFByaW5jaXA6IEtvbG9ubmVucyBuXHUwMEY4Z2xlLXRlbXBsYXRlIGJlc3RlbW1lciBodmFkIGRlciBtYXRjaGVzIHBcdTAwRTUuXG4gKlxuICogQHNlZSBkb2NzL2ZpbHRlci10ZW1wbGF0ZS5tZFxuICovXG5leHBvcnQgY2xhc3MgRmlsdGVyVGVtcGxhdGUge1xuICAgIGNvbnN0cnVjdG9yKGRhdGVTZXJ2aWNlLCBlbnRpdHlSZXNvbHZlcikge1xuICAgICAgICB0aGlzLmRhdGVTZXJ2aWNlID0gZGF0ZVNlcnZpY2U7XG4gICAgICAgIHRoaXMuZW50aXR5UmVzb2x2ZXIgPSBlbnRpdHlSZXNvbHZlcjtcbiAgICAgICAgdGhpcy5maWVsZHMgPSBbXTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogVGlsZlx1MDBGOGogZmVsdCB0aWwgdGVtcGxhdGVcbiAgICAgKiBAcGFyYW0gaWRQcm9wZXJ0eSAtIFByb3BlcnR5LW5hdm4gKGJydWdlcyBwXHUwMEU1IGJcdTAwRTVkZSBldmVudCBvZyBjb2x1bW4uZGF0YXNldClcbiAgICAgKiBAcGFyYW0gZGVyaXZlZEZyb20gLSBIdmlzIGZlbHRldCB1ZGxlZGVzIGZyYSBhbmRlbiBwcm9wZXJ0eSAoZi5la3MuIGRhdGUgZnJhIHN0YXJ0KVxuICAgICAqL1xuICAgIGFkZEZpZWxkKGlkUHJvcGVydHksIGRlcml2ZWRGcm9tKSB7XG4gICAgICAgIHRoaXMuZmllbGRzLnB1c2goeyBpZFByb3BlcnR5LCBkZXJpdmVkRnJvbSB9KTtcbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFBhcnNlIGRvdC1ub3RhdGlvbiBzdHJpbmcgaW50byBjb21wb25lbnRzXG4gICAgICogQGV4YW1wbGUgJ3Jlc291cmNlLnRlYW1JZCcgXHUyMTkyIHsgZW50aXR5VHlwZTogJ3Jlc291cmNlJywgcHJvcGVydHk6ICd0ZWFtSWQnLCBmb3JlaWduS2V5OiAncmVzb3VyY2VJZCcgfVxuICAgICAqL1xuICAgIHBhcnNlRG90Tm90YXRpb24oaWRQcm9wZXJ0eSkge1xuICAgICAgICBpZiAoIWlkUHJvcGVydHkuaW5jbHVkZXMoJy4nKSlcbiAgICAgICAgICAgIHJldHVybiBudWxsO1xuICAgICAgICBjb25zdCBbZW50aXR5VHlwZSwgcHJvcGVydHldID0gaWRQcm9wZXJ0eS5zcGxpdCgnLicpO1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgZW50aXR5VHlwZSxcbiAgICAgICAgICAgIHByb3BlcnR5LFxuICAgICAgICAgICAgZm9yZWlnbktleTogZW50aXR5VHlwZSArICdJZCcgLy8gQ29udmVudGlvbjogcmVzb3VyY2UgXHUyMTkyIHJlc291cmNlSWRcbiAgICAgICAgfTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IGRhdGFzZXQga2V5IGZvciBjb2x1bW4gbG9va3VwXG4gICAgICogRm9yIGRvdC1ub3RhdGlvbiAncmVzb3VyY2UudGVhbUlkJywgd2UgbG9vayBmb3IgJ3RlYW1JZCcgaW4gZGF0YXNldFxuICAgICAqL1xuICAgIGdldERhdGFzZXRLZXkoaWRQcm9wZXJ0eSkge1xuICAgICAgICBjb25zdCBkb3ROb3RhdGlvbiA9IHRoaXMucGFyc2VEb3ROb3RhdGlvbihpZFByb3BlcnR5KTtcbiAgICAgICAgaWYgKGRvdE5vdGF0aW9uKSB7XG4gICAgICAgICAgICByZXR1cm4gZG90Tm90YXRpb24ucHJvcGVydHk7IC8vICd0ZWFtSWQnXG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGlkUHJvcGVydHk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEJ5ZyBuXHUwMEY4Z2xlIGZyYSBrb2xvbm5lXG4gICAgICogTFx1MDBFNnNlciB2XHUwMEU2cmRpZXIgZnJhIGNvbHVtbi5kYXRhc2V0W2lkUHJvcGVydHldXG4gICAgICogRm9yIGRvdC1ub3RhdGlvbiwgdXNlcyB0aGUgcHJvcGVydHkgcGFydCAocmVzb3VyY2UudGVhbUlkIFx1MjE5MiB0ZWFtSWQpXG4gICAgICovXG4gICAgYnVpbGRLZXlGcm9tQ29sdW1uKGNvbHVtbikge1xuICAgICAgICByZXR1cm4gdGhpcy5maWVsZHNcbiAgICAgICAgICAgIC5tYXAoZiA9PiB7XG4gICAgICAgICAgICBjb25zdCBrZXkgPSB0aGlzLmdldERhdGFzZXRLZXkoZi5pZFByb3BlcnR5KTtcbiAgICAgICAgICAgIHJldHVybiBjb2x1bW4uZGF0YXNldFtrZXldIHx8ICcnO1xuICAgICAgICB9KVxuICAgICAgICAgICAgLmpvaW4oJzonKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogQnlnIG5cdTAwRjhnbGUgZnJhIGV2ZW50XG4gICAgICogTFx1MDBFNnNlciB2XHUwMEU2cmRpZXIgZnJhIGV2ZW50W2lkUHJvcGVydHldIGVsbGVyIHVkbGVkZXIgZnJhIGRlcml2ZWRGcm9tXG4gICAgICogRm9yIGRvdC1ub3RhdGlvbiwgcmVzb2x2ZXMgdmlhIEVudGl0eVJlc29sdmVyXG4gICAgICovXG4gICAgYnVpbGRLZXlGcm9tRXZlbnQoZXZlbnQpIHtcbiAgICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1leHBsaWNpdC1hbnlcbiAgICAgICAgY29uc3QgZXZlbnRSZWNvcmQgPSBldmVudDtcbiAgICAgICAgcmV0dXJuIHRoaXMuZmllbGRzXG4gICAgICAgICAgICAubWFwKGYgPT4ge1xuICAgICAgICAgICAgLy8gQ2hlY2sgZm9yIGRvdC1ub3RhdGlvbiAoZS5nLiwgJ3Jlc291cmNlLnRlYW1JZCcpXG4gICAgICAgICAgICBjb25zdCBkb3ROb3RhdGlvbiA9IHRoaXMucGFyc2VEb3ROb3RhdGlvbihmLmlkUHJvcGVydHkpO1xuICAgICAgICAgICAgaWYgKGRvdE5vdGF0aW9uKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXMucmVzb2x2ZURvdE5vdGF0aW9uKGV2ZW50UmVjb3JkLCBkb3ROb3RhdGlvbik7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBpZiAoZi5kZXJpdmVkRnJvbSkge1xuICAgICAgICAgICAgICAgIC8vIFVkbGVkIHZcdTAwRTZyZGkgKGYuZWtzLiBkYXRlIGZyYSBzdGFydClcbiAgICAgICAgICAgICAgICBjb25zdCBzb3VyY2VWYWx1ZSA9IGV2ZW50UmVjb3JkW2YuZGVyaXZlZEZyb21dO1xuICAgICAgICAgICAgICAgIGlmIChzb3VyY2VWYWx1ZSBpbnN0YW5jZW9mIERhdGUpIHtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHRoaXMuZGF0ZVNlcnZpY2UuZ2V0RGF0ZUtleShzb3VyY2VWYWx1ZSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIHJldHVybiBTdHJpbmcoc291cmNlVmFsdWUgfHwgJycpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmV0dXJuIFN0cmluZyhldmVudFJlY29yZFtmLmlkUHJvcGVydHldIHx8ICcnKTtcbiAgICAgICAgfSlcbiAgICAgICAgICAgIC5qb2luKCc6Jyk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJlc29sdmUgZG90LW5vdGF0aW9uIHJlZmVyZW5jZSB2aWEgRW50aXR5UmVzb2x2ZXJcbiAgICAgKi9cbiAgICByZXNvbHZlRG90Tm90YXRpb24oZXZlbnRSZWNvcmQsIGRvdE5vdGF0aW9uKSB7XG4gICAgICAgIGlmICghdGhpcy5lbnRpdHlSZXNvbHZlcikge1xuICAgICAgICAgICAgY29uc29sZS53YXJuKGBGaWx0ZXJUZW1wbGF0ZTogRW50aXR5UmVzb2x2ZXIgcmVxdWlyZWQgZm9yIGRvdC1ub3RhdGlvbiAnJHtkb3ROb3RhdGlvbi5lbnRpdHlUeXBlfS4ke2RvdE5vdGF0aW9uLnByb3BlcnR5fSdgKTtcbiAgICAgICAgICAgIHJldHVybiAnJztcbiAgICAgICAgfVxuICAgICAgICAvLyBHZXQgZm9yZWlnbiBrZXkgdmFsdWUgZnJvbSBldmVudCAoZS5nLiwgcmVzb3VyY2VJZClcbiAgICAgICAgY29uc3QgZm9yZWlnbklkID0gZXZlbnRSZWNvcmRbZG90Tm90YXRpb24uZm9yZWlnbktleV07XG4gICAgICAgIGlmICghZm9yZWlnbklkKVxuICAgICAgICAgICAgcmV0dXJuICcnO1xuICAgICAgICAvLyBSZXNvbHZlIGVudGl0eVxuICAgICAgICBjb25zdCBlbnRpdHkgPSB0aGlzLmVudGl0eVJlc29sdmVyLnJlc29sdmUoZG90Tm90YXRpb24uZW50aXR5VHlwZSwgU3RyaW5nKGZvcmVpZ25JZCkpO1xuICAgICAgICBpZiAoIWVudGl0eSlcbiAgICAgICAgICAgIHJldHVybiAnJztcbiAgICAgICAgLy8gUmV0dXJuIHByb3BlcnR5IHZhbHVlIGZyb20gZW50aXR5XG4gICAgICAgIHJldHVybiBTdHJpbmcoZW50aXR5W2RvdE5vdGF0aW9uLnByb3BlcnR5XSB8fCAnJyk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIE1hdGNoIGV2ZW50IG1vZCBrb2xvbm5lXG4gICAgICovXG4gICAgbWF0Y2hlcyhldmVudCwgY29sdW1uKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmJ1aWxkS2V5RnJvbUV2ZW50KGV2ZW50KSA9PT0gdGhpcy5idWlsZEtleUZyb21Db2x1bW4oY29sdW1uKTtcbiAgICB9XG59XG4iLCAiaW1wb3J0IHsgYnVpbGRQaXBlbGluZSB9IGZyb20gJy4vUmVuZGVyQnVpbGRlcic7XG5pbXBvcnQgeyBGaWx0ZXJUZW1wbGF0ZSB9IGZyb20gJy4vRmlsdGVyVGVtcGxhdGUnO1xuZXhwb3J0IGNsYXNzIENhbGVuZGFyT3JjaGVzdHJhdG9yIHtcbiAgICBjb25zdHJ1Y3RvcihhbGxSZW5kZXJlcnMsIGV2ZW50UmVuZGVyZXIsIHNjaGVkdWxlUmVuZGVyZXIsIGhlYWRlckRyYXdlclJlbmRlcmVyLCBkYXRlU2VydmljZSwgZW50aXR5U2VydmljZXMpIHtcbiAgICAgICAgdGhpcy5hbGxSZW5kZXJlcnMgPSBhbGxSZW5kZXJlcnM7XG4gICAgICAgIHRoaXMuZXZlbnRSZW5kZXJlciA9IGV2ZW50UmVuZGVyZXI7XG4gICAgICAgIHRoaXMuc2NoZWR1bGVSZW5kZXJlciA9IHNjaGVkdWxlUmVuZGVyZXI7XG4gICAgICAgIHRoaXMuaGVhZGVyRHJhd2VyUmVuZGVyZXIgPSBoZWFkZXJEcmF3ZXJSZW5kZXJlcjtcbiAgICAgICAgdGhpcy5kYXRlU2VydmljZSA9IGRhdGVTZXJ2aWNlO1xuICAgICAgICB0aGlzLmVudGl0eVNlcnZpY2VzID0gZW50aXR5U2VydmljZXM7XG4gICAgfVxuICAgIGFzeW5jIHJlbmRlcih2aWV3Q29uZmlnLCBjb250YWluZXIpIHtcbiAgICAgICAgY29uc3QgaGVhZGVyQ29udGFpbmVyID0gY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3IoJ3N3cC1jYWxlbmRhci1oZWFkZXInKTtcbiAgICAgICAgY29uc3QgY29sdW1uQ29udGFpbmVyID0gY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3IoJ3N3cC1kYXktY29sdW1ucycpO1xuICAgICAgICBpZiAoIWhlYWRlckNvbnRhaW5lciB8fCAhY29sdW1uQ29udGFpbmVyKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ01pc3Npbmcgc3dwLWNhbGVuZGFyLWhlYWRlciBvciBzd3AtZGF5LWNvbHVtbnMnKTtcbiAgICAgICAgfVxuICAgICAgICAvLyBCeWcgZmlsdGVyIGZyYSB2aWV3Q29uZmlnXG4gICAgICAgIGNvbnN0IGZpbHRlciA9IHt9O1xuICAgICAgICBmb3IgKGNvbnN0IGdyb3VwaW5nIG9mIHZpZXdDb25maWcuZ3JvdXBpbmdzKSB7XG4gICAgICAgICAgICBmaWx0ZXJbZ3JvdXBpbmcudHlwZV0gPSBncm91cGluZy52YWx1ZXM7XG4gICAgICAgIH1cbiAgICAgICAgLy8gQnlnIEZpbHRlclRlbXBsYXRlIGZyYSB2aWV3Q29uZmlnIGdyb3VwaW5ncyAoa3VuIGRlIG1lZCBpZFByb3BlcnR5KVxuICAgICAgICBjb25zdCBmaWx0ZXJUZW1wbGF0ZSA9IG5ldyBGaWx0ZXJUZW1wbGF0ZSh0aGlzLmRhdGVTZXJ2aWNlKTtcbiAgICAgICAgZm9yIChjb25zdCBncm91cGluZyBvZiB2aWV3Q29uZmlnLmdyb3VwaW5ncykge1xuICAgICAgICAgICAgaWYgKGdyb3VwaW5nLmlkUHJvcGVydHkpIHtcbiAgICAgICAgICAgICAgICBmaWx0ZXJUZW1wbGF0ZS5hZGRGaWVsZChncm91cGluZy5pZFByb3BlcnR5LCBncm91cGluZy5kZXJpdmVkRnJvbSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgLy8gUmVzb2x2ZSBiZWxvbmdzVG8gcmVsYXRpb25zIChlLmcuLCB0ZWFtLnJlc291cmNlSWRzKVxuICAgICAgICBjb25zdCB7IHBhcmVudENoaWxkTWFwLCBjaGlsZFR5cGUgfSA9IGF3YWl0IHRoaXMucmVzb2x2ZUJlbG9uZ3NUbyh2aWV3Q29uZmlnLmdyb3VwaW5ncywgZmlsdGVyKTtcbiAgICAgICAgY29uc3QgY29udGV4dCA9IHsgaGVhZGVyQ29udGFpbmVyLCBjb2x1bW5Db250YWluZXIsIGZpbHRlciwgZ3JvdXBpbmdzOiB2aWV3Q29uZmlnLmdyb3VwaW5ncywgcGFyZW50Q2hpbGRNYXAsIGNoaWxkVHlwZSB9O1xuICAgICAgICAvLyBDbGVhclxuICAgICAgICBoZWFkZXJDb250YWluZXIuaW5uZXJIVE1MID0gJyc7XG4gICAgICAgIGNvbHVtbkNvbnRhaW5lci5pbm5lckhUTUwgPSAnJztcbiAgICAgICAgLy8gU1x1MDBFNnQgZGF0YS1sZXZlbHMgYXR0cmlidXQgZm9yIENTUyBncmlkLXJvdyBzdHlsaW5nXG4gICAgICAgIGNvbnN0IGxldmVscyA9IHZpZXdDb25maWcuZ3JvdXBpbmdzLm1hcChnID0+IGcudHlwZSkuam9pbignICcpO1xuICAgICAgICBoZWFkZXJDb250YWluZXIuZGF0YXNldC5sZXZlbHMgPSBsZXZlbHM7XG4gICAgICAgIC8vIFZcdTAwRTZsZyByZW5kZXJlcnMgYmFzZXJldCBwXHUwMEU1IGdyb3VwaW5ncyB0eXBlc1xuICAgICAgICBjb25zdCBhY3RpdmVSZW5kZXJlcnMgPSB0aGlzLnNlbGVjdFJlbmRlcmVycyh2aWV3Q29uZmlnKTtcbiAgICAgICAgLy8gQnlnIG9nIGtcdTAwRjhyIHBpcGVsaW5lXG4gICAgICAgIGNvbnN0IHBpcGVsaW5lID0gYnVpbGRQaXBlbGluZShhY3RpdmVSZW5kZXJlcnMpO1xuICAgICAgICBhd2FpdCBwaXBlbGluZS5ydW4oY29udGV4dCk7XG4gICAgICAgIC8vIFJlbmRlciBzY2hlZHVsZSB1bmF2YWlsYWJsZSB6b25lcyAoZlx1MDBGOHIgZXZlbnRzKVxuICAgICAgICBhd2FpdCB0aGlzLnNjaGVkdWxlUmVuZGVyZXIucmVuZGVyKGNvbnRhaW5lciwgZmlsdGVyKTtcbiAgICAgICAgLy8gUmVuZGVyIHRpbWVkIGV2ZW50cyBpbiBncmlkIChtZWQgZmlsdGVyVGVtcGxhdGUgdGlsIG1hdGNoaW5nKVxuICAgICAgICBhd2FpdCB0aGlzLmV2ZW50UmVuZGVyZXIucmVuZGVyKGNvbnRhaW5lciwgZmlsdGVyLCBmaWx0ZXJUZW1wbGF0ZSk7XG4gICAgICAgIC8vIFJlbmRlciBhbGxEYXkgZXZlbnRzIGluIGhlYWRlciBkcmF3ZXIgKG1lZCBmaWx0ZXJUZW1wbGF0ZSB0aWwgbWF0Y2hpbmcpXG4gICAgICAgIGF3YWl0IHRoaXMuaGVhZGVyRHJhd2VyUmVuZGVyZXIucmVuZGVyKGNvbnRhaW5lciwgZmlsdGVyLCBmaWx0ZXJUZW1wbGF0ZSk7XG4gICAgfVxuICAgIHNlbGVjdFJlbmRlcmVycyh2aWV3Q29uZmlnKSB7XG4gICAgICAgIGNvbnN0IHR5cGVzID0gdmlld0NvbmZpZy5ncm91cGluZ3MubWFwKGcgPT4gZy50eXBlKTtcbiAgICAgICAgLy8gU29ydFx1MDBFOXIgcmVuZGVyZXJzIGkgc2FtbWUgclx1MDBFNmtrZWZcdTAwRjhsZ2Ugc29tIHZpZXdDb25maWcuZ3JvdXBpbmdzXG4gICAgICAgIHJldHVybiB0eXBlc1xuICAgICAgICAgICAgLm1hcCh0eXBlID0+IHRoaXMuYWxsUmVuZGVyZXJzLmZpbmQociA9PiByLnR5cGUgPT09IHR5cGUpKVxuICAgICAgICAgICAgLmZpbHRlcigocikgPT4gciAhPT0gdW5kZWZpbmVkKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogUmVzb2x2ZSBiZWxvbmdzVG8gcmVsYXRpb25zIHRvIGJ1aWxkIHBhcmVudC1jaGlsZCBtYXBcbiAgICAgKiBlLmcuLCBiZWxvbmdzVG86ICd0ZWFtLnJlc291cmNlSWRzJyBcdTIxOTIgeyB0ZWFtMTogWydFTVAwMDEnLCAnRU1QMDAyJ10sIHRlYW0yOiBbLi4uXSB9XG4gICAgICogQWxzbyByZXR1cm5zIHRoZSBjaGlsZFR5cGUgKHRoZSBncm91cGluZyB0eXBlIHRoYXQgaGFzIGJlbG9uZ3NUbylcbiAgICAgKi9cbiAgICBhc3luYyByZXNvbHZlQmVsb25nc1RvKGdyb3VwaW5ncywgZmlsdGVyKSB7XG4gICAgICAgIC8vIEZpbmQgZ3JvdXBpbmcgd2l0aCBiZWxvbmdzVG9cbiAgICAgICAgY29uc3QgY2hpbGRHcm91cGluZyA9IGdyb3VwaW5ncy5maW5kKGcgPT4gZy5iZWxvbmdzVG8pO1xuICAgICAgICBpZiAoIWNoaWxkR3JvdXBpbmc/LmJlbG9uZ3NUbylcbiAgICAgICAgICAgIHJldHVybiB7fTtcbiAgICAgICAgLy8gUGFyc2UgYmVsb25nc1RvOiAndGVhbS5yZXNvdXJjZUlkcydcbiAgICAgICAgY29uc3QgW2VudGl0eVR5cGUsIHByb3BlcnR5XSA9IGNoaWxkR3JvdXBpbmcuYmVsb25nc1RvLnNwbGl0KCcuJyk7XG4gICAgICAgIGlmICghZW50aXR5VHlwZSB8fCAhcHJvcGVydHkpXG4gICAgICAgICAgICByZXR1cm4ge307XG4gICAgICAgIC8vIEdldCBwYXJlbnQgSURzIGZyb20gZmlsdGVyXG4gICAgICAgIGNvbnN0IHBhcmVudElkcyA9IGZpbHRlcltlbnRpdHlUeXBlXSB8fCBbXTtcbiAgICAgICAgaWYgKHBhcmVudElkcy5sZW5ndGggPT09IDApXG4gICAgICAgICAgICByZXR1cm4ge307XG4gICAgICAgIC8vIEZpbmQgc2VydmljZSBkeW5hbWlzayBiYXNlcmV0IHBcdTAwRTUgZW50aXR5VHlwZSAoaW5nZW4gaGFyZGNvZGVkIHR5cGUgY2hlY2spXG4gICAgICAgIGNvbnN0IHNlcnZpY2UgPSB0aGlzLmVudGl0eVNlcnZpY2VzLmZpbmQocyA9PiBzLmVudGl0eVR5cGUudG9Mb3dlckNhc2UoKSA9PT0gZW50aXR5VHlwZSk7XG4gICAgICAgIGlmICghc2VydmljZSlcbiAgICAgICAgICAgIHJldHVybiB7fTtcbiAgICAgICAgLy8gSGVudCBhbGxlIGVudGl0aWVzIG9nIGZpbHRyZXIgcFx1MDBFNSBwYXJlbnRJZHNcbiAgICAgICAgY29uc3QgYWxsRW50aXRpZXMgPSBhd2FpdCBzZXJ2aWNlLmdldEFsbCgpO1xuICAgICAgICBjb25zdCBlbnRpdGllcyA9IGFsbEVudGl0aWVzLmZpbHRlcihlID0+IHBhcmVudElkcy5pbmNsdWRlcyhlLmlkKSk7XG4gICAgICAgIC8vIEJ5ZyBwYXJlbnQtY2hpbGQgbWFwXG4gICAgICAgIGNvbnN0IG1hcCA9IHt9O1xuICAgICAgICBmb3IgKGNvbnN0IGVudGl0eSBvZiBlbnRpdGllcykge1xuICAgICAgICAgICAgY29uc3QgZW50aXR5UmVjb3JkID0gZW50aXR5O1xuICAgICAgICAgICAgY29uc3QgY2hpbGRyZW4gPSBlbnRpdHlSZWNvcmRbcHJvcGVydHldIHx8IFtdO1xuICAgICAgICAgICAgbWFwW2VudGl0eVJlY29yZC5pZF0gPSBjaGlsZHJlbjtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4geyBwYXJlbnRDaGlsZE1hcDogbWFwLCBjaGlsZFR5cGU6IGNoaWxkR3JvdXBpbmcudHlwZSB9O1xuICAgIH1cbn1cbiIsICJleHBvcnQgY2xhc3MgTmF2aWdhdGlvbkFuaW1hdG9yIHtcbiAgICBjb25zdHJ1Y3RvcihoZWFkZXJUcmFjaywgY29udGVudFRyYWNrLCBoZWFkZXJEcmF3ZXIpIHtcbiAgICAgICAgdGhpcy5oZWFkZXJUcmFjayA9IGhlYWRlclRyYWNrO1xuICAgICAgICB0aGlzLmNvbnRlbnRUcmFjayA9IGNvbnRlbnRUcmFjaztcbiAgICAgICAgdGhpcy5oZWFkZXJEcmF3ZXIgPSBoZWFkZXJEcmF3ZXI7XG4gICAgfVxuICAgIGFzeW5jIHNsaWRlKGRpcmVjdGlvbiwgcmVuZGVyRm4pIHtcbiAgICAgICAgY29uc3Qgb3V0ID0gZGlyZWN0aW9uID09PSAnbGVmdCcgPyAnLTEwMCUnIDogJzEwMCUnO1xuICAgICAgICBjb25zdCBpbnRvID0gZGlyZWN0aW9uID09PSAnbGVmdCcgPyAnMTAwJScgOiAnLTEwMCUnO1xuICAgICAgICBhd2FpdCB0aGlzLmFuaW1hdGVPdXQob3V0KTtcbiAgICAgICAgYXdhaXQgcmVuZGVyRm4oKTtcbiAgICAgICAgYXdhaXQgdGhpcy5hbmltYXRlSW4oaW50byk7XG4gICAgfVxuICAgIGFzeW5jIGFuaW1hdGVPdXQodHJhbnNsYXRlKSB7XG4gICAgICAgIGNvbnN0IGFuaW1hdGlvbnMgPSBbXG4gICAgICAgICAgICB0aGlzLmhlYWRlclRyYWNrLmFuaW1hdGUoW3sgdHJhbnNmb3JtOiAndHJhbnNsYXRlWCgwKScgfSwgeyB0cmFuc2Zvcm06IGB0cmFuc2xhdGVYKCR7dHJhbnNsYXRlfSlgIH1dLCB7IGR1cmF0aW9uOiAyMDAsIGVhc2luZzogJ2Vhc2UtaW4nIH0pLmZpbmlzaGVkLFxuICAgICAgICAgICAgdGhpcy5jb250ZW50VHJhY2suYW5pbWF0ZShbeyB0cmFuc2Zvcm06ICd0cmFuc2xhdGVYKDApJyB9LCB7IHRyYW5zZm9ybTogYHRyYW5zbGF0ZVgoJHt0cmFuc2xhdGV9KWAgfV0sIHsgZHVyYXRpb246IDIwMCwgZWFzaW5nOiAnZWFzZS1pbicgfSkuZmluaXNoZWRcbiAgICAgICAgXTtcbiAgICAgICAgaWYgKHRoaXMuaGVhZGVyRHJhd2VyKSB7XG4gICAgICAgICAgICBhbmltYXRpb25zLnB1c2godGhpcy5oZWFkZXJEcmF3ZXIuYW5pbWF0ZShbeyB0cmFuc2Zvcm06ICd0cmFuc2xhdGVYKDApJyB9LCB7IHRyYW5zZm9ybTogYHRyYW5zbGF0ZVgoJHt0cmFuc2xhdGV9KWAgfV0sIHsgZHVyYXRpb246IDIwMCwgZWFzaW5nOiAnZWFzZS1pbicgfSkuZmluaXNoZWQpO1xuICAgICAgICB9XG4gICAgICAgIGF3YWl0IFByb21pc2UuYWxsKGFuaW1hdGlvbnMpO1xuICAgIH1cbiAgICBhc3luYyBhbmltYXRlSW4odHJhbnNsYXRlKSB7XG4gICAgICAgIGNvbnN0IGFuaW1hdGlvbnMgPSBbXG4gICAgICAgICAgICB0aGlzLmhlYWRlclRyYWNrLmFuaW1hdGUoW3sgdHJhbnNmb3JtOiBgdHJhbnNsYXRlWCgke3RyYW5zbGF0ZX0pYCB9LCB7IHRyYW5zZm9ybTogJ3RyYW5zbGF0ZVgoMCknIH1dLCB7IGR1cmF0aW9uOiAyMDAsIGVhc2luZzogJ2Vhc2Utb3V0JyB9KS5maW5pc2hlZCxcbiAgICAgICAgICAgIHRoaXMuY29udGVudFRyYWNrLmFuaW1hdGUoW3sgdHJhbnNmb3JtOiBgdHJhbnNsYXRlWCgke3RyYW5zbGF0ZX0pYCB9LCB7IHRyYW5zZm9ybTogJ3RyYW5zbGF0ZVgoMCknIH1dLCB7IGR1cmF0aW9uOiAyMDAsIGVhc2luZzogJ2Vhc2Utb3V0JyB9KS5maW5pc2hlZFxuICAgICAgICBdO1xuICAgICAgICBpZiAodGhpcy5oZWFkZXJEcmF3ZXIpIHtcbiAgICAgICAgICAgIGFuaW1hdGlvbnMucHVzaCh0aGlzLmhlYWRlckRyYXdlci5hbmltYXRlKFt7IHRyYW5zZm9ybTogYHRyYW5zbGF0ZVgoJHt0cmFuc2xhdGV9KWAgfSwgeyB0cmFuc2Zvcm06ICd0cmFuc2xhdGVYKDApJyB9XSwgeyBkdXJhdGlvbjogMjAwLCBlYXNpbmc6ICdlYXNlLW91dCcgfSkuZmluaXNoZWQpO1xuICAgICAgICB9XG4gICAgICAgIGF3YWl0IFByb21pc2UuYWxsKGFuaW1hdGlvbnMpO1xuICAgIH1cbn1cbiIsICIvKipcbiAqIENhbGVuZGFyRXZlbnRzIC0gQ29tbWFuZCBhbmQgc3RhdHVzIGV2ZW50cyBmb3IgQ2FsZW5kYXJBcHBcbiAqL1xuZXhwb3J0IGNvbnN0IENhbGVuZGFyRXZlbnRzID0ge1xuICAgIC8vIENvbW1hbmQgZXZlbnRzIChob3N0IFx1MjE5MiBjYWxlbmRhcilcbiAgICBDTURfTkFWSUdBVEVfUFJFVjogJ2NhbGVuZGFyOmNtZDpuYXZpZ2F0ZTpwcmV2JyxcbiAgICBDTURfTkFWSUdBVEVfTkVYVDogJ2NhbGVuZGFyOmNtZDpuYXZpZ2F0ZTpuZXh0JyxcbiAgICBDTURfRFJBV0VSX1RPR0dMRTogJ2NhbGVuZGFyOmNtZDpkcmF3ZXI6dG9nZ2xlJyxcbiAgICBDTURfUkVOREVSOiAnY2FsZW5kYXI6Y21kOnJlbmRlcicsXG4gICAgQ01EX1dPUktXRUVLX0NIQU5HRTogJ2NhbGVuZGFyOmNtZDp3b3Jrd2VlazpjaGFuZ2UnLFxuICAgIENNRF9WSUVXX1VQREFURTogJ2NhbGVuZGFyOmNtZDp2aWV3OnVwZGF0ZSdcbn07XG4iLCAiaW1wb3J0IHsgTmF2aWdhdGlvbkFuaW1hdG9yIH0gZnJvbSAnLi9OYXZpZ2F0aW9uQW5pbWF0b3InO1xuaW1wb3J0IHsgQ2FsZW5kYXJFdmVudHMgfSBmcm9tICcuL0NhbGVuZGFyRXZlbnRzJztcbmV4cG9ydCBjbGFzcyBDYWxlbmRhckFwcCB7XG4gICAgY29uc3RydWN0b3Iob3JjaGVzdHJhdG9yLCB0aW1lQXhpc1JlbmRlcmVyLCBkYXRlU2VydmljZSwgc2Nyb2xsTWFuYWdlciwgaGVhZGVyRHJhd2VyTWFuYWdlciwgZHJhZ0Ryb3BNYW5hZ2VyLCBlZGdlU2Nyb2xsTWFuYWdlciwgcmVzaXplTWFuYWdlciwgaGVhZGVyRHJhd2VyUmVuZGVyZXIsIGV2ZW50UGVyc2lzdGVuY2VNYW5hZ2VyLCBzZXR0aW5nc1NlcnZpY2UsIHZpZXdDb25maWdTZXJ2aWNlLCBldmVudEJ1cykge1xuICAgICAgICB0aGlzLm9yY2hlc3RyYXRvciA9IG9yY2hlc3RyYXRvcjtcbiAgICAgICAgdGhpcy50aW1lQXhpc1JlbmRlcmVyID0gdGltZUF4aXNSZW5kZXJlcjtcbiAgICAgICAgdGhpcy5kYXRlU2VydmljZSA9IGRhdGVTZXJ2aWNlO1xuICAgICAgICB0aGlzLnNjcm9sbE1hbmFnZXIgPSBzY3JvbGxNYW5hZ2VyO1xuICAgICAgICB0aGlzLmhlYWRlckRyYXdlck1hbmFnZXIgPSBoZWFkZXJEcmF3ZXJNYW5hZ2VyO1xuICAgICAgICB0aGlzLmRyYWdEcm9wTWFuYWdlciA9IGRyYWdEcm9wTWFuYWdlcjtcbiAgICAgICAgdGhpcy5lZGdlU2Nyb2xsTWFuYWdlciA9IGVkZ2VTY3JvbGxNYW5hZ2VyO1xuICAgICAgICB0aGlzLnJlc2l6ZU1hbmFnZXIgPSByZXNpemVNYW5hZ2VyO1xuICAgICAgICB0aGlzLmhlYWRlckRyYXdlclJlbmRlcmVyID0gaGVhZGVyRHJhd2VyUmVuZGVyZXI7XG4gICAgICAgIHRoaXMuZXZlbnRQZXJzaXN0ZW5jZU1hbmFnZXIgPSBldmVudFBlcnNpc3RlbmNlTWFuYWdlcjtcbiAgICAgICAgdGhpcy5zZXR0aW5nc1NlcnZpY2UgPSBzZXR0aW5nc1NlcnZpY2U7XG4gICAgICAgIHRoaXMudmlld0NvbmZpZ1NlcnZpY2UgPSB2aWV3Q29uZmlnU2VydmljZTtcbiAgICAgICAgdGhpcy5ldmVudEJ1cyA9IGV2ZW50QnVzO1xuICAgICAgICB0aGlzLmRheU9mZnNldCA9IDA7XG4gICAgICAgIHRoaXMuY3VycmVudFZpZXdJZCA9ICdzaW1wbGUnO1xuICAgICAgICB0aGlzLndvcmt3ZWVrUHJlc2V0ID0gbnVsbDtcbiAgICAgICAgdGhpcy5ncm91cGluZ092ZXJyaWRlcyA9IG5ldyBNYXAoKTtcbiAgICB9XG4gICAgYXN5bmMgaW5pdChjb250YWluZXIpIHtcbiAgICAgICAgdGhpcy5jb250YWluZXIgPSBjb250YWluZXI7XG4gICAgICAgIC8vIExvYWQgc2V0dGluZ3NcbiAgICAgICAgY29uc3QgZ3JpZFNldHRpbmdzID0gYXdhaXQgdGhpcy5zZXR0aW5nc1NlcnZpY2UuZ2V0R3JpZFNldHRpbmdzKCk7XG4gICAgICAgIGlmICghZ3JpZFNldHRpbmdzKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ0dyaWRTZXR0aW5ncyBub3QgZm91bmQnKTtcbiAgICAgICAgfVxuICAgICAgICB0aGlzLndvcmt3ZWVrUHJlc2V0ID0gYXdhaXQgdGhpcy5zZXR0aW5nc1NlcnZpY2UuZ2V0RGVmYXVsdFdvcmt3ZWVrUHJlc2V0KCk7XG4gICAgICAgIC8vIENyZWF0ZSBOYXZpZ2F0aW9uQW5pbWF0b3Igd2l0aCBET00gZWxlbWVudHNcbiAgICAgICAgdGhpcy5hbmltYXRvciA9IG5ldyBOYXZpZ2F0aW9uQW5pbWF0b3IoY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3IoJ3N3cC1oZWFkZXItdHJhY2snKSwgY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3IoJ3N3cC1jb250ZW50LXRyYWNrJyksIGNvbnRhaW5lci5xdWVyeVNlbGVjdG9yKCdzd3AtaGVhZGVyLWRyYXdlcicpKTtcbiAgICAgICAgLy8gUmVuZGVyIHRpbWUgYXhpcyBmcm9tIHNldHRpbmdzXG4gICAgICAgIHRoaXMudGltZUF4aXNSZW5kZXJlci5yZW5kZXIoY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3IoJyN0aW1lLWF4aXMnKSwgZ3JpZFNldHRpbmdzLmRheVN0YXJ0SG91ciwgZ3JpZFNldHRpbmdzLmRheUVuZEhvdXIpO1xuICAgICAgICAvLyBJbml0IG1hbmFnZXJzXG4gICAgICAgIHRoaXMuc2Nyb2xsTWFuYWdlci5pbml0KGNvbnRhaW5lcik7XG4gICAgICAgIHRoaXMuaGVhZGVyRHJhd2VyTWFuYWdlci5pbml0KGNvbnRhaW5lcik7XG4gICAgICAgIHRoaXMuZHJhZ0Ryb3BNYW5hZ2VyLmluaXQoY29udGFpbmVyKTtcbiAgICAgICAgdGhpcy5yZXNpemVNYW5hZ2VyLmluaXQoY29udGFpbmVyKTtcbiAgICAgICAgY29uc3Qgc2Nyb2xsYWJsZUNvbnRlbnQgPSBjb250YWluZXIucXVlcnlTZWxlY3Rvcignc3dwLXNjcm9sbGFibGUtY29udGVudCcpO1xuICAgICAgICB0aGlzLmVkZ2VTY3JvbGxNYW5hZ2VyLmluaXQoc2Nyb2xsYWJsZUNvbnRlbnQpO1xuICAgICAgICAvLyBTZXR1cCBjb21tYW5kIGV2ZW50IGxpc3RlbmVyc1xuICAgICAgICB0aGlzLnNldHVwRXZlbnRMaXN0ZW5lcnMoKTtcbiAgICAgICAgLy8gRW1pdCByZWFkeSBzdGF0dXNcbiAgICAgICAgdGhpcy5lbWl0U3RhdHVzKCdyZWFkeScpO1xuICAgIH1cbiAgICBzZXR1cEV2ZW50TGlzdGVuZXJzKCkge1xuICAgICAgICAvLyBOYXZpZ2F0aW9uIGNvbW1hbmRzIHZpYSBFdmVudEJ1c1xuICAgICAgICB0aGlzLmV2ZW50QnVzLm9uKENhbGVuZGFyRXZlbnRzLkNNRF9OQVZJR0FURV9QUkVWLCAoKSA9PiB7XG4gICAgICAgICAgICB0aGlzLmhhbmRsZU5hdmlnYXRlUHJldigpO1xuICAgICAgICB9KTtcbiAgICAgICAgdGhpcy5ldmVudEJ1cy5vbihDYWxlbmRhckV2ZW50cy5DTURfTkFWSUdBVEVfTkVYVCwgKCkgPT4ge1xuICAgICAgICAgICAgdGhpcy5oYW5kbGVOYXZpZ2F0ZU5leHQoKTtcbiAgICAgICAgfSk7XG4gICAgICAgIC8vIERyYXdlciB0b2dnbGUgdmlhIEV2ZW50QnVzXG4gICAgICAgIHRoaXMuZXZlbnRCdXMub24oQ2FsZW5kYXJFdmVudHMuQ01EX0RSQVdFUl9UT0dHTEUsICgpID0+IHtcbiAgICAgICAgICAgIHRoaXMuaGVhZGVyRHJhd2VyTWFuYWdlci50b2dnbGUoKTtcbiAgICAgICAgfSk7XG4gICAgICAgIC8vIFJlbmRlciBjb21tYW5kIHZpYSBFdmVudEJ1c1xuICAgICAgICB0aGlzLmV2ZW50QnVzLm9uKENhbGVuZGFyRXZlbnRzLkNNRF9SRU5ERVIsIChlKSA9PiB7XG4gICAgICAgICAgICBjb25zdCB7IHZpZXdJZCB9ID0gZS5kZXRhaWw7XG4gICAgICAgICAgICB0aGlzLmhhbmRsZVJlbmRlckNvbW1hbmQodmlld0lkKTtcbiAgICAgICAgfSk7XG4gICAgICAgIC8vIFdvcmt3ZWVrIGNoYW5nZSB2aWEgRXZlbnRCdXNcbiAgICAgICAgdGhpcy5ldmVudEJ1cy5vbihDYWxlbmRhckV2ZW50cy5DTURfV09SS1dFRUtfQ0hBTkdFLCAoZSkgPT4ge1xuICAgICAgICAgICAgY29uc3QgeyBwcmVzZXRJZCB9ID0gZS5kZXRhaWw7XG4gICAgICAgICAgICB0aGlzLmhhbmRsZVdvcmt3ZWVrQ2hhbmdlKHByZXNldElkKTtcbiAgICAgICAgfSk7XG4gICAgICAgIC8vIFZpZXcgdXBkYXRlIHZpYSBFdmVudEJ1c1xuICAgICAgICB0aGlzLmV2ZW50QnVzLm9uKENhbGVuZGFyRXZlbnRzLkNNRF9WSUVXX1VQREFURSwgKGUpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHsgdHlwZSwgdmFsdWVzIH0gPSBlLmRldGFpbDtcbiAgICAgICAgICAgIHRoaXMuaGFuZGxlVmlld1VwZGF0ZSh0eXBlLCB2YWx1ZXMpO1xuICAgICAgICB9KTtcbiAgICB9XG4gICAgYXN5bmMgaGFuZGxlUmVuZGVyQ29tbWFuZCh2aWV3SWQpIHtcbiAgICAgICAgdGhpcy5jdXJyZW50Vmlld0lkID0gdmlld0lkO1xuICAgICAgICBhd2FpdCB0aGlzLnJlbmRlcigpO1xuICAgICAgICB0aGlzLmVtaXRTdGF0dXMoJ3JlbmRlcmVkJywgeyB2aWV3SWQgfSk7XG4gICAgfVxuICAgIGFzeW5jIGhhbmRsZU5hdmlnYXRlUHJldigpIHtcbiAgICAgICAgY29uc3Qgc3RlcCA9IHRoaXMud29ya3dlZWtQcmVzZXQ/LnBlcmlvZERheXMgPz8gNztcbiAgICAgICAgdGhpcy5kYXlPZmZzZXQgLT0gc3RlcDtcbiAgICAgICAgYXdhaXQgdGhpcy5hbmltYXRvci5zbGlkZSgncmlnaHQnLCAoKSA9PiB0aGlzLnJlbmRlcigpKTtcbiAgICAgICAgdGhpcy5lbWl0U3RhdHVzKCdyZW5kZXJlZCcsIHsgdmlld0lkOiB0aGlzLmN1cnJlbnRWaWV3SWQgfSk7XG4gICAgfVxuICAgIGFzeW5jIGhhbmRsZU5hdmlnYXRlTmV4dCgpIHtcbiAgICAgICAgY29uc3Qgc3RlcCA9IHRoaXMud29ya3dlZWtQcmVzZXQ/LnBlcmlvZERheXMgPz8gNztcbiAgICAgICAgdGhpcy5kYXlPZmZzZXQgKz0gc3RlcDtcbiAgICAgICAgYXdhaXQgdGhpcy5hbmltYXRvci5zbGlkZSgnbGVmdCcsICgpID0+IHRoaXMucmVuZGVyKCkpO1xuICAgICAgICB0aGlzLmVtaXRTdGF0dXMoJ3JlbmRlcmVkJywgeyB2aWV3SWQ6IHRoaXMuY3VycmVudFZpZXdJZCB9KTtcbiAgICB9XG4gICAgYXN5bmMgaGFuZGxlV29ya3dlZWtDaGFuZ2UocHJlc2V0SWQpIHtcbiAgICAgICAgY29uc3QgcHJlc2V0ID0gYXdhaXQgdGhpcy5zZXR0aW5nc1NlcnZpY2UuZ2V0V29ya3dlZWtQcmVzZXQocHJlc2V0SWQpO1xuICAgICAgICBpZiAocHJlc2V0KSB7XG4gICAgICAgICAgICB0aGlzLndvcmt3ZWVrUHJlc2V0ID0gcHJlc2V0O1xuICAgICAgICAgICAgYXdhaXQgdGhpcy5yZW5kZXIoKTtcbiAgICAgICAgICAgIHRoaXMuZW1pdFN0YXR1cygncmVuZGVyZWQnLCB7IHZpZXdJZDogdGhpcy5jdXJyZW50Vmlld0lkIH0pO1xuICAgICAgICB9XG4gICAgfVxuICAgIGFzeW5jIGhhbmRsZVZpZXdVcGRhdGUodHlwZSwgdmFsdWVzKSB7XG4gICAgICAgIHRoaXMuZ3JvdXBpbmdPdmVycmlkZXMuc2V0KHR5cGUsIHZhbHVlcyk7XG4gICAgICAgIGF3YWl0IHRoaXMucmVuZGVyKCk7XG4gICAgICAgIHRoaXMuZW1pdFN0YXR1cygncmVuZGVyZWQnLCB7IHZpZXdJZDogdGhpcy5jdXJyZW50Vmlld0lkIH0pO1xuICAgIH1cbiAgICBhc3luYyByZW5kZXIoKSB7XG4gICAgICAgIGNvbnN0IHN0b3JlZENvbmZpZyA9IGF3YWl0IHRoaXMudmlld0NvbmZpZ1NlcnZpY2UuZ2V0QnlJZCh0aGlzLmN1cnJlbnRWaWV3SWQpO1xuICAgICAgICBpZiAoIXN0b3JlZENvbmZpZykge1xuICAgICAgICAgICAgdGhpcy5lbWl0U3RhdHVzKCdlcnJvcicsIHsgbWVzc2FnZTogYFZpZXdDb25maWcgbm90IGZvdW5kOiAke3RoaXMuY3VycmVudFZpZXdJZH1gIH0pO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIC8vIFBvcHVsYXRlIGRhdGUgdmFsdWVzIGJhc2VkIG9uIHdvcmt3ZWVrIHByZXNldCBhbmQgZGF5IG9mZnNldFxuICAgICAgICBjb25zdCB3b3JrRGF5cyA9IHRoaXMud29ya3dlZWtQcmVzZXQ/LndvcmtEYXlzIHx8IFsxLCAyLCAzLCA0LCA1XTtcbiAgICAgICAgY29uc3QgcGVyaW9kRGF5cyA9IHRoaXMud29ya3dlZWtQcmVzZXQ/LnBlcmlvZERheXMgPz8gNztcbiAgICAgICAgLy8gRm9yIHNpbmdsZS1kYXkgbmF2aWdhdGlvbiAocGVyaW9kRGF5cz0xKSwgc2hvdyBjb25zZWN1dGl2ZSBkYXlzIGZyb20gb2Zmc2V0XG4gICAgICAgIC8vIEZvciB3ZWVrIG5hdmlnYXRpb24gKHBlcmlvZERheXM9NyksIHNob3cgd29ya0RheXMgZnJvbSB0aGUgd2VlayBjb250YWluaW5nIG9mZnNldFxuICAgICAgICBjb25zdCBkYXRlcyA9IHBlcmlvZERheXMgPT09IDFcbiAgICAgICAgICAgID8gdGhpcy5kYXRlU2VydmljZS5nZXREYXRlc0Zyb21PZmZzZXQodGhpcy5kYXlPZmZzZXQsIHdvcmtEYXlzLmxlbmd0aClcbiAgICAgICAgICAgIDogdGhpcy5kYXRlU2VydmljZS5nZXRXb3JrRGF5c0Zyb21PZmZzZXQodGhpcy5kYXlPZmZzZXQsIHdvcmtEYXlzKTtcbiAgICAgICAgLy8gQ2xvbmUgY29uZmlnIGFuZCBhcHBseSBvdmVycmlkZXNcbiAgICAgICAgY29uc3Qgdmlld0NvbmZpZyA9IHtcbiAgICAgICAgICAgIC4uLnN0b3JlZENvbmZpZyxcbiAgICAgICAgICAgIGdyb3VwaW5nczogc3RvcmVkQ29uZmlnLmdyb3VwaW5ncy5tYXAoZyA9PiB7XG4gICAgICAgICAgICAgICAgLy8gQXBwbHkgZGF0ZSB2YWx1ZXNcbiAgICAgICAgICAgICAgICBpZiAoZy50eXBlID09PSAnZGF0ZScpIHtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHsgLi4uZywgdmFsdWVzOiBkYXRlcyB9O1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAvLyBBcHBseSBncm91cGluZyBvdmVycmlkZXNcbiAgICAgICAgICAgICAgICBjb25zdCBvdmVycmlkZSA9IHRoaXMuZ3JvdXBpbmdPdmVycmlkZXMuZ2V0KGcudHlwZSk7XG4gICAgICAgICAgICAgICAgaWYgKG92ZXJyaWRlKSB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiB7IC4uLmcsIHZhbHVlczogb3ZlcnJpZGUgfTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgcmV0dXJuIGc7XG4gICAgICAgICAgICB9KVxuICAgICAgICB9O1xuICAgICAgICBhd2FpdCB0aGlzLm9yY2hlc3RyYXRvci5yZW5kZXIodmlld0NvbmZpZywgdGhpcy5jb250YWluZXIpO1xuICAgIH1cbiAgICBlbWl0U3RhdHVzKHN0YXR1cywgZGV0YWlsKSB7XG4gICAgICAgIHRoaXMuY29udGFpbmVyLmRpc3BhdGNoRXZlbnQobmV3IEN1c3RvbUV2ZW50KGBjYWxlbmRhcjpzdGF0dXM6JHtzdGF0dXN9YCwge1xuICAgICAgICAgICAgZGV0YWlsLFxuICAgICAgICAgICAgYnViYmxlczogdHJ1ZVxuICAgICAgICB9KSk7XG4gICAgfVxufVxuIiwgImV4cG9ydCBjbGFzcyBUaW1lQXhpc1JlbmRlcmVyIHtcbiAgICByZW5kZXIoY29udGFpbmVyLCBzdGFydEhvdXIgPSA2LCBlbmRIb3VyID0gMjApIHtcbiAgICAgICAgY29udGFpbmVyLmlubmVySFRNTCA9ICcnO1xuICAgICAgICBmb3IgKGxldCBob3VyID0gc3RhcnRIb3VyOyBob3VyIDw9IGVuZEhvdXI7IGhvdXIrKykge1xuICAgICAgICAgICAgY29uc3QgbWFya2VyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3dwLWhvdXItbWFya2VyJyk7XG4gICAgICAgICAgICBtYXJrZXIudGV4dENvbnRlbnQgPSBgJHtob3VyLnRvU3RyaW5nKCkucGFkU3RhcnQoMiwgJzAnKX06MDBgO1xuICAgICAgICAgICAgY29udGFpbmVyLmFwcGVuZENoaWxkKG1hcmtlcik7XG4gICAgICAgIH1cbiAgICB9XG59XG4iLCAiZXhwb3J0IGNsYXNzIFNjcm9sbE1hbmFnZXIge1xuICAgIGluaXQoY29udGFpbmVyKSB7XG4gICAgICAgIHRoaXMuc2Nyb2xsYWJsZUNvbnRlbnQgPSBjb250YWluZXIucXVlcnlTZWxlY3Rvcignc3dwLXNjcm9sbGFibGUtY29udGVudCcpO1xuICAgICAgICB0aGlzLnRpbWVBeGlzQ29udGVudCA9IGNvbnRhaW5lci5xdWVyeVNlbGVjdG9yKCdzd3AtdGltZS1heGlzLWNvbnRlbnQnKTtcbiAgICAgICAgdGhpcy5jYWxlbmRhckhlYWRlciA9IGNvbnRhaW5lci5xdWVyeVNlbGVjdG9yKCdzd3AtY2FsZW5kYXItaGVhZGVyJyk7XG4gICAgICAgIHRoaXMuaGVhZGVyRHJhd2VyID0gY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3IoJ3N3cC1oZWFkZXItZHJhd2VyJyk7XG4gICAgICAgIHRoaXMuaGVhZGVyVmlld3BvcnQgPSBjb250YWluZXIucXVlcnlTZWxlY3Rvcignc3dwLWhlYWRlci12aWV3cG9ydCcpO1xuICAgICAgICB0aGlzLmhlYWRlclNwYWNlciA9IGNvbnRhaW5lci5xdWVyeVNlbGVjdG9yKCdzd3AtaGVhZGVyLXNwYWNlcicpO1xuICAgICAgICB0aGlzLnNjcm9sbGFibGVDb250ZW50LmFkZEV2ZW50TGlzdGVuZXIoJ3Njcm9sbCcsICgpID0+IHRoaXMub25TY3JvbGwoKSk7XG4gICAgICAgIC8vIFN5bmtyb25pc2VyIGhlYWRlci1zcGFjZXIgaFx1MDBGOGpkZSBtZWQgaGVhZGVyLXZpZXdwb3J0XG4gICAgICAgIHRoaXMucmVzaXplT2JzZXJ2ZXIgPSBuZXcgUmVzaXplT2JzZXJ2ZXIoKCkgPT4gdGhpcy5zeW5jSGVhZGVyU3BhY2VySGVpZ2h0KCkpO1xuICAgICAgICB0aGlzLnJlc2l6ZU9ic2VydmVyLm9ic2VydmUodGhpcy5oZWFkZXJWaWV3cG9ydCk7XG4gICAgICAgIHRoaXMuc3luY0hlYWRlclNwYWNlckhlaWdodCgpO1xuICAgIH1cbiAgICBzeW5jSGVhZGVyU3BhY2VySGVpZ2h0KCkge1xuICAgICAgICAvLyBLb3BpZXIgZGVuIGZha3Rpc2tlIGNvbXB1dGVkIGhlaWdodCBkaXJla3RlIGZyYSBoZWFkZXItdmlld3BvcnRcbiAgICAgICAgY29uc3QgY29tcHV0ZWRIZWlnaHQgPSBnZXRDb21wdXRlZFN0eWxlKHRoaXMuaGVhZGVyVmlld3BvcnQpLmhlaWdodDtcbiAgICAgICAgdGhpcy5oZWFkZXJTcGFjZXIuc3R5bGUuaGVpZ2h0ID0gY29tcHV0ZWRIZWlnaHQ7XG4gICAgfVxuICAgIG9uU2Nyb2xsKCkge1xuICAgICAgICBjb25zdCB7IHNjcm9sbFRvcCwgc2Nyb2xsTGVmdCB9ID0gdGhpcy5zY3JvbGxhYmxlQ29udGVudDtcbiAgICAgICAgLy8gU3lua3JvbmlzZXIgdGltZS1heGlzIHZlcnRpa2FsdFxuICAgICAgICB0aGlzLnRpbWVBeGlzQ29udGVudC5zdHlsZS50cmFuc2Zvcm0gPSBgdHJhbnNsYXRlWSgtJHtzY3JvbGxUb3B9cHgpYDtcbiAgICAgICAgLy8gU3lua3JvbmlzZXIgaGVhZGVyIG9nIGRyYXdlciBob3Jpc29udGFsdFxuICAgICAgICB0aGlzLmNhbGVuZGFySGVhZGVyLnN0eWxlLnRyYW5zZm9ybSA9IGB0cmFuc2xhdGVYKC0ke3Njcm9sbExlZnR9cHgpYDtcbiAgICAgICAgdGhpcy5oZWFkZXJEcmF3ZXIuc3R5bGUudHJhbnNmb3JtID0gYHRyYW5zbGF0ZVgoLSR7c2Nyb2xsTGVmdH1weClgO1xuICAgIH1cbn1cbiIsICJleHBvcnQgY2xhc3MgSGVhZGVyRHJhd2VyTWFuYWdlciB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuZXhwYW5kZWQgPSBmYWxzZTtcbiAgICAgICAgdGhpcy5jdXJyZW50Um93cyA9IDA7XG4gICAgICAgIHRoaXMucm93SGVpZ2h0ID0gMjU7XG4gICAgICAgIHRoaXMuZHVyYXRpb24gPSAyMDA7XG4gICAgfVxuICAgIGluaXQoY29udGFpbmVyKSB7XG4gICAgICAgIHRoaXMuZHJhd2VyID0gY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3IoJ3N3cC1oZWFkZXItZHJhd2VyJyk7XG4gICAgICAgIGlmICghdGhpcy5kcmF3ZXIpXG4gICAgICAgICAgICBjb25zb2xlLmVycm9yKCdIZWFkZXJEcmF3ZXJNYW5hZ2VyOiBzd3AtaGVhZGVyLWRyYXdlciBub3QgZm91bmQnKTtcbiAgICB9XG4gICAgdG9nZ2xlKCkge1xuICAgICAgICB0aGlzLmV4cGFuZGVkID8gdGhpcy5jb2xsYXBzZSgpIDogdGhpcy5leHBhbmQoKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogRXhwYW5kIGRyYXdlciB0byBzaW5nbGUgcm93IChsZWdhY3kgc3VwcG9ydClcbiAgICAgKi9cbiAgICBleHBhbmQoKSB7XG4gICAgICAgIHRoaXMuZXhwYW5kVG9Sb3dzKDEpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBFeHBhbmQgZHJhd2VyIHRvIGZpdCBzcGVjaWZpZWQgbnVtYmVyIG9mIHJvd3NcbiAgICAgKi9cbiAgICBleHBhbmRUb1Jvd3Mocm93Q291bnQpIHtcbiAgICAgICAgY29uc3QgdGFyZ2V0SGVpZ2h0ID0gcm93Q291bnQgKiB0aGlzLnJvd0hlaWdodDtcbiAgICAgICAgY29uc3QgY3VycmVudEhlaWdodCA9IHRoaXMuZXhwYW5kZWQgPyB0aGlzLmN1cnJlbnRSb3dzICogdGhpcy5yb3dIZWlnaHQgOiAwO1xuICAgICAgICAvLyBTa2lwIGlmIGFscmVhZHkgYXQgdGFyZ2V0XG4gICAgICAgIGlmICh0aGlzLmV4cGFuZGVkICYmIHRoaXMuY3VycmVudFJvd3MgPT09IHJvd0NvdW50KVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB0aGlzLmN1cnJlbnRSb3dzID0gcm93Q291bnQ7XG4gICAgICAgIHRoaXMuZXhwYW5kZWQgPSB0cnVlO1xuICAgICAgICB0aGlzLmFuaW1hdGUoY3VycmVudEhlaWdodCwgdGFyZ2V0SGVpZ2h0KTtcbiAgICB9XG4gICAgY29sbGFwc2UoKSB7XG4gICAgICAgIGlmICghdGhpcy5leHBhbmRlZClcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgY29uc3QgY3VycmVudEhlaWdodCA9IHRoaXMuY3VycmVudFJvd3MgKiB0aGlzLnJvd0hlaWdodDtcbiAgICAgICAgdGhpcy5leHBhbmRlZCA9IGZhbHNlO1xuICAgICAgICB0aGlzLmN1cnJlbnRSb3dzID0gMDtcbiAgICAgICAgdGhpcy5hbmltYXRlKGN1cnJlbnRIZWlnaHQsIDApO1xuICAgIH1cbiAgICBhbmltYXRlKGZyb20sIHRvKSB7XG4gICAgICAgIGNvbnN0IGtleWZyYW1lcyA9IFtcbiAgICAgICAgICAgIHsgaGVpZ2h0OiBgJHtmcm9tfXB4YCB9LFxuICAgICAgICAgICAgeyBoZWlnaHQ6IGAke3RvfXB4YCB9XG4gICAgICAgIF07XG4gICAgICAgIGNvbnN0IG9wdGlvbnMgPSB7XG4gICAgICAgICAgICBkdXJhdGlvbjogdGhpcy5kdXJhdGlvbixcbiAgICAgICAgICAgIGVhc2luZzogJ2Vhc2UnLFxuICAgICAgICAgICAgZmlsbDogJ2ZvcndhcmRzJ1xuICAgICAgICB9O1xuICAgICAgICAvLyBLdW4gYW5pbVx1MDBFOXIgZHJhd2VyIC0gU2Nyb2xsTWFuYWdlciBzeW5rcm9uaXNlcmVyIGhlYWRlci1zcGFjZXIgdmlhIFJlc2l6ZU9ic2VydmVyXG4gICAgICAgIHRoaXMuZHJhd2VyLmFuaW1hdGUoa2V5ZnJhbWVzLCBvcHRpb25zKTtcbiAgICB9XG4gICAgaXNFeHBhbmRlZCgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuZXhwYW5kZWQ7XG4gICAgfVxuICAgIGdldFJvd0NvdW50KCkge1xuICAgICAgICByZXR1cm4gdGhpcy5jdXJyZW50Um93cztcbiAgICB9XG59XG4iLCAiZXhwb3J0IGNsYXNzIE1vY2tUZWFtU3RvcmUge1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICB0aGlzLnR5cGUgPSAndGVhbSc7XG4gICAgICAgIHRoaXMudGVhbXMgPSBbXG4gICAgICAgICAgICB7IGlkOiAnYWxwaGEnLCBuYW1lOiAnVGVhbSBBbHBoYScgfSxcbiAgICAgICAgICAgIHsgaWQ6ICdiZXRhJywgbmFtZTogJ1RlYW0gQmV0YScgfVxuICAgICAgICBdO1xuICAgIH1cbiAgICBnZXRCeUlkcyhpZHMpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMudGVhbXMuZmlsdGVyKHQgPT4gaWRzLmluY2x1ZGVzKHQuaWQpKTtcbiAgICB9XG59XG5leHBvcnQgY2xhc3MgTW9ja1Jlc291cmNlU3RvcmUge1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICB0aGlzLnR5cGUgPSAncmVzb3VyY2UnO1xuICAgICAgICB0aGlzLnJlc291cmNlcyA9IFtcbiAgICAgICAgICAgIHsgaWQ6ICdhbGljZScsIG5hbWU6ICdBbGljZScsIHRlYW1JZDogJ2FscGhhJyB9LFxuICAgICAgICAgICAgeyBpZDogJ2JvYicsIG5hbWU6ICdCb2InLCB0ZWFtSWQ6ICdhbHBoYScgfSxcbiAgICAgICAgICAgIHsgaWQ6ICdjYXJvbCcsIG5hbWU6ICdDYXJvbCcsIHRlYW1JZDogJ2JldGEnIH0sXG4gICAgICAgICAgICB7IGlkOiAnZGF2ZScsIG5hbWU6ICdEYXZlJywgdGVhbUlkOiAnYmV0YScgfVxuICAgICAgICBdO1xuICAgIH1cbiAgICBnZXRCeUlkcyhpZHMpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMucmVzb3VyY2VzLmZpbHRlcihyID0+IGlkcy5pbmNsdWRlcyhyLmlkKSk7XG4gICAgfVxufVxuIiwgImltcG9ydCB7IENhbGVuZGFyRXZlbnRzIH0gZnJvbSAnLi4vY29yZS9DYWxlbmRhckV2ZW50cyc7XG5leHBvcnQgY2xhc3MgRGVtb0FwcCB7XG4gICAgY29uc3RydWN0b3IoaW5kZXhlZERCQ29udGV4dCwgZGF0YVNlZWRlciwgYXVkaXRTZXJ2aWNlLCBjYWxlbmRhckFwcCwgZGF0ZVNlcnZpY2UsIHJlc291cmNlU2VydmljZSwgZXZlbnRCdXMpIHtcbiAgICAgICAgdGhpcy5pbmRleGVkREJDb250ZXh0ID0gaW5kZXhlZERCQ29udGV4dDtcbiAgICAgICAgdGhpcy5kYXRhU2VlZGVyID0gZGF0YVNlZWRlcjtcbiAgICAgICAgdGhpcy5hdWRpdFNlcnZpY2UgPSBhdWRpdFNlcnZpY2U7XG4gICAgICAgIHRoaXMuY2FsZW5kYXJBcHAgPSBjYWxlbmRhckFwcDtcbiAgICAgICAgdGhpcy5kYXRlU2VydmljZSA9IGRhdGVTZXJ2aWNlO1xuICAgICAgICB0aGlzLnJlc291cmNlU2VydmljZSA9IHJlc291cmNlU2VydmljZTtcbiAgICAgICAgdGhpcy5ldmVudEJ1cyA9IGV2ZW50QnVzO1xuICAgICAgICB0aGlzLmN1cnJlbnRWaWV3ID0gJ3NpbXBsZSc7XG4gICAgfVxuICAgIGFzeW5jIGluaXQoKSB7XG4gICAgICAgIC8vIFNldCBiYXNlIGRhdGUgdG8gbWF0Y2ggbW9jayBkYXRhICg4LiBkZWNlbWJlciAyMDI1ID0gbWFuZGFnKVxuICAgICAgICB0aGlzLmRhdGVTZXJ2aWNlLnNldEJhc2VEYXRlKG5ldyBEYXRlKCcyMDI1LTEyLTA4JykpO1xuICAgICAgICAvLyBJbml0aWFsaXplIEluZGV4ZWREQlxuICAgICAgICBhd2FpdCB0aGlzLmluZGV4ZWREQkNvbnRleHQuaW5pdGlhbGl6ZSgpO1xuICAgICAgICBjb25zb2xlLmxvZygnW0RlbW9BcHBdIEluZGV4ZWREQiBpbml0aWFsaXplZCcpO1xuICAgICAgICAvLyBTZWVkIGRhdGEgaWYgZW1wdHlcbiAgICAgICAgYXdhaXQgdGhpcy5kYXRhU2VlZGVyLnNlZWRJZkVtcHR5KCk7XG4gICAgICAgIGNvbnNvbGUubG9nKCdbRGVtb0FwcF0gRGF0YSBzZWVkaW5nIGNvbXBsZXRlJyk7XG4gICAgICAgIHRoaXMuY29udGFpbmVyID0gZG9jdW1lbnQucXVlcnlTZWxlY3Rvcignc3dwLWNhbGVuZGFyLWNvbnRhaW5lcicpO1xuICAgICAgICAvLyBJbml0aWFsaXplIENhbGVuZGFyQXBwXG4gICAgICAgIGF3YWl0IHRoaXMuY2FsZW5kYXJBcHAuaW5pdCh0aGlzLmNvbnRhaW5lcik7XG4gICAgICAgIGNvbnNvbGUubG9nKCdbRGVtb0FwcF0gQ2FsZW5kYXJBcHAgaW5pdGlhbGl6ZWQnKTtcbiAgICAgICAgLy8gU2V0dXAgZGVtbyBVSSBoYW5kbGVyc1xuICAgICAgICB0aGlzLnNldHVwTmF2aWdhdGlvbigpO1xuICAgICAgICB0aGlzLnNldHVwRHJhd2VyVG9nZ2xlKCk7XG4gICAgICAgIHRoaXMuc2V0dXBWaWV3U3dpdGNoaW5nKCk7XG4gICAgICAgIHRoaXMuc2V0dXBXb3Jrd2Vla1NlbGVjdG9yKCk7XG4gICAgICAgIGF3YWl0IHRoaXMuc2V0dXBSZXNvdXJjZVNlbGVjdG9yKCk7XG4gICAgICAgIC8vIExpc3RlbiBmb3IgY2FsZW5kYXIgc3RhdHVzIGV2ZW50c1xuICAgICAgICB0aGlzLnNldHVwU3RhdHVzTGlzdGVuZXJzKCk7XG4gICAgICAgIC8vIEluaXRpYWwgcmVuZGVyXG4gICAgICAgIHRoaXMuZXZlbnRCdXMuZW1pdChDYWxlbmRhckV2ZW50cy5DTURfUkVOREVSLCB7IHZpZXdJZDogdGhpcy5jdXJyZW50VmlldyB9KTtcbiAgICB9XG4gICAgc2V0dXBOYXZpZ2F0aW9uKCkge1xuICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnYnRuLXByZXYnKS5vbmNsaWNrID0gKCkgPT4ge1xuICAgICAgICAgICAgdGhpcy5ldmVudEJ1cy5lbWl0KENhbGVuZGFyRXZlbnRzLkNNRF9OQVZJR0FURV9QUkVWKTtcbiAgICAgICAgfTtcbiAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2J0bi1uZXh0Jykub25jbGljayA9ICgpID0+IHtcbiAgICAgICAgICAgIHRoaXMuZXZlbnRCdXMuZW1pdChDYWxlbmRhckV2ZW50cy5DTURfTkFWSUdBVEVfTkVYVCk7XG4gICAgICAgIH07XG4gICAgfVxuICAgIHNldHVwVmlld1N3aXRjaGluZygpIHtcbiAgICAgICAgY29uc3QgY2hpcHMgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCcudmlldy1jaGlwJyk7XG4gICAgICAgIGNoaXBzLmZvckVhY2goY2hpcCA9PiB7XG4gICAgICAgICAgICBjaGlwLmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgKCkgPT4ge1xuICAgICAgICAgICAgICAgIGNoaXBzLmZvckVhY2goYyA9PiBjLmNsYXNzTGlzdC5yZW1vdmUoJ2FjdGl2ZScpKTtcbiAgICAgICAgICAgICAgICBjaGlwLmNsYXNzTGlzdC5hZGQoJ2FjdGl2ZScpO1xuICAgICAgICAgICAgICAgIGNvbnN0IHZpZXcgPSBjaGlwLmRhdGFzZXQudmlldztcbiAgICAgICAgICAgICAgICBpZiAodmlldykge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLmN1cnJlbnRWaWV3ID0gdmlldztcbiAgICAgICAgICAgICAgICAgICAgdGhpcy51cGRhdGVTZWxlY3RvclZpc2liaWxpdHkoKTtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5ldmVudEJ1cy5lbWl0KENhbGVuZGFyRXZlbnRzLkNNRF9SRU5ERVIsIHsgdmlld0lkOiB2aWV3IH0pO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9KTtcbiAgICB9XG4gICAgdXBkYXRlU2VsZWN0b3JWaXNpYmlsaXR5KCkge1xuICAgICAgICBjb25zdCBzZWxlY3RvciA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJ3N3cC1yZXNvdXJjZS1zZWxlY3RvcicpO1xuICAgICAgICBjb25zdCBzaG93U2VsZWN0b3IgPSB0aGlzLmN1cnJlbnRWaWV3ID09PSAncGlja2VyJyB8fCB0aGlzLmN1cnJlbnRWaWV3ID09PSAnZGF5JztcbiAgICAgICAgc2VsZWN0b3I/LmNsYXNzTGlzdC50b2dnbGUoJ2hpZGRlbicsICFzaG93U2VsZWN0b3IpO1xuICAgIH1cbiAgICBzZXR1cERyYXdlclRvZ2dsZSgpIHtcbiAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2J0bi1kcmF3ZXInKS5vbmNsaWNrID0gKCkgPT4ge1xuICAgICAgICAgICAgdGhpcy5ldmVudEJ1cy5lbWl0KENhbGVuZGFyRXZlbnRzLkNNRF9EUkFXRVJfVE9HR0xFKTtcbiAgICAgICAgfTtcbiAgICB9XG4gICAgc2V0dXBXb3Jrd2Vla1NlbGVjdG9yKCkge1xuICAgICAgICBjb25zdCB3b3Jrd2Vla1NlbGVjdCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd3b3Jrd2Vlay1zZWxlY3QnKTtcbiAgICAgICAgd29ya3dlZWtTZWxlY3Q/LmFkZEV2ZW50TGlzdGVuZXIoJ2NoYW5nZScsICgpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHByZXNldElkID0gd29ya3dlZWtTZWxlY3QudmFsdWU7XG4gICAgICAgICAgICB0aGlzLmV2ZW50QnVzLmVtaXQoQ2FsZW5kYXJFdmVudHMuQ01EX1dPUktXRUVLX0NIQU5HRSwgeyBwcmVzZXRJZCB9KTtcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIGFzeW5jIHNldHVwUmVzb3VyY2VTZWxlY3RvcigpIHtcbiAgICAgICAgY29uc3QgcmVzb3VyY2VzID0gYXdhaXQgdGhpcy5yZXNvdXJjZVNlcnZpY2UuZ2V0QWxsKCk7XG4gICAgICAgIGNvbnN0IGNvbnRhaW5lciA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJy5yZXNvdXJjZS1jaGVja2JveGVzJyk7XG4gICAgICAgIGlmICghY29udGFpbmVyKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICBjb250YWluZXIuaW5uZXJIVE1MID0gJyc7XG4gICAgICAgIHJlc291cmNlcy5mb3JFYWNoKHIgPT4ge1xuICAgICAgICAgICAgY29uc3QgbGFiZWwgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdsYWJlbCcpO1xuICAgICAgICAgICAgbGFiZWwuaW5uZXJIVE1MID0gYFxyXG4gICAgICAgIDxpbnB1dCB0eXBlPVwiY2hlY2tib3hcIiB2YWx1ZT1cIiR7ci5pZH1cIiBjaGVja2VkPlxyXG4gICAgICAgICR7ci5kaXNwbGF5TmFtZX1cclxuICAgICAgYDtcbiAgICAgICAgICAgIGNvbnRhaW5lci5hcHBlbmRDaGlsZChsYWJlbCk7XG4gICAgICAgIH0pO1xuICAgICAgICBjb250YWluZXIuYWRkRXZlbnRMaXN0ZW5lcignY2hhbmdlJywgKCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgY2hlY2tlZCA9IGNvbnRhaW5lci5xdWVyeVNlbGVjdG9yQWxsKCdpbnB1dDpjaGVja2VkJyk7XG4gICAgICAgICAgICBjb25zdCB2YWx1ZXMgPSBBcnJheS5mcm9tKGNoZWNrZWQpLm1hcChjYiA9PiBjYi52YWx1ZSk7XG4gICAgICAgICAgICB0aGlzLmV2ZW50QnVzLmVtaXQoQ2FsZW5kYXJFdmVudHMuQ01EX1ZJRVdfVVBEQVRFLCB7IHR5cGU6ICdyZXNvdXJjZScsIHZhbHVlcyB9KTtcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIHNldHVwU3RhdHVzTGlzdGVuZXJzKCkge1xuICAgICAgICB0aGlzLmNvbnRhaW5lci5hZGRFdmVudExpc3RlbmVyKCdjYWxlbmRhcjpzdGF0dXM6cmVhZHknLCAoKSA9PiB7XG4gICAgICAgICAgICBjb25zb2xlLmxvZygnW0RlbW9BcHBdIENhbGVuZGFyIHJlYWR5Jyk7XG4gICAgICAgIH0pO1xuICAgICAgICB0aGlzLmNvbnRhaW5lci5hZGRFdmVudExpc3RlbmVyKCdjYWxlbmRhcjpzdGF0dXM6cmVuZGVyZWQnLCAoKGUpID0+IHtcbiAgICAgICAgICAgIGNvbnNvbGUubG9nKCdbRGVtb0FwcF0gQ2FsZW5kYXIgcmVuZGVyZWQ6JywgZS5kZXRhaWwudmlld0lkKTtcbiAgICAgICAgfSkpO1xuICAgICAgICB0aGlzLmNvbnRhaW5lci5hZGRFdmVudExpc3RlbmVyKCdjYWxlbmRhcjpzdGF0dXM6ZXJyb3InLCAoKGUpID0+IHtcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoJ1tEZW1vQXBwXSBDYWxlbmRhciBlcnJvcjonLCBlLmRldGFpbC5tZXNzYWdlKTtcbiAgICAgICAgfSkpO1xuICAgIH1cbn1cbiIsICIvKipcbiAqIENlbnRyYWwgZXZlbnQgZGlzcGF0Y2hlciBmb3IgY2FsZW5kYXIgdXNpbmcgRE9NIEN1c3RvbUV2ZW50c1xuICogUHJvdmlkZXMgbG9nZ2luZyBhbmQgZGVidWdnaW5nIGNhcGFiaWxpdGllc1xuICovXG5leHBvcnQgY2xhc3MgRXZlbnRCdXMge1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICB0aGlzLmV2ZW50TG9nID0gW107XG4gICAgICAgIHRoaXMuZGVidWcgPSBmYWxzZTtcbiAgICAgICAgdGhpcy5saXN0ZW5lcnMgPSBuZXcgU2V0KCk7XG4gICAgICAgIC8vIExvZyBjb25maWd1cmF0aW9uIGZvciBkaWZmZXJlbnQgY2F0ZWdvcmllc1xuICAgICAgICB0aGlzLmxvZ0NvbmZpZyA9IHtcbiAgICAgICAgICAgIGNhbGVuZGFyOiB0cnVlLFxuICAgICAgICAgICAgZ3JpZDogdHJ1ZSxcbiAgICAgICAgICAgIGV2ZW50OiB0cnVlLFxuICAgICAgICAgICAgc2Nyb2xsOiB0cnVlLFxuICAgICAgICAgICAgbmF2aWdhdGlvbjogdHJ1ZSxcbiAgICAgICAgICAgIHZpZXc6IHRydWUsXG4gICAgICAgICAgICBkZWZhdWx0OiB0cnVlXG4gICAgICAgIH07XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFN1YnNjcmliZSB0byBhbiBldmVudCB2aWEgRE9NIGFkZEV2ZW50TGlzdGVuZXJcbiAgICAgKi9cbiAgICBvbihldmVudFR5cGUsIGhhbmRsZXIsIG9wdGlvbnMpIHtcbiAgICAgICAgZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcihldmVudFR5cGUsIGhhbmRsZXIsIG9wdGlvbnMpO1xuICAgICAgICAvLyBUcmFjayBmb3IgY2xlYW51cFxuICAgICAgICB0aGlzLmxpc3RlbmVycy5hZGQoeyBldmVudFR5cGUsIGhhbmRsZXIsIG9wdGlvbnMgfSk7XG4gICAgICAgIC8vIFJldHVybiB1bnN1YnNjcmliZSBmdW5jdGlvblxuICAgICAgICByZXR1cm4gKCkgPT4gdGhpcy5vZmYoZXZlbnRUeXBlLCBoYW5kbGVyKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogU3Vic2NyaWJlIHRvIGFuIGV2ZW50IG9uY2VcbiAgICAgKi9cbiAgICBvbmNlKGV2ZW50VHlwZSwgaGFuZGxlcikge1xuICAgICAgICByZXR1cm4gdGhpcy5vbihldmVudFR5cGUsIGhhbmRsZXIsIHsgb25jZTogdHJ1ZSB9KTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogVW5zdWJzY3JpYmUgZnJvbSBhbiBldmVudFxuICAgICAqL1xuICAgIG9mZihldmVudFR5cGUsIGhhbmRsZXIpIHtcbiAgICAgICAgZG9jdW1lbnQucmVtb3ZlRXZlbnRMaXN0ZW5lcihldmVudFR5cGUsIGhhbmRsZXIpO1xuICAgICAgICAvLyBSZW1vdmUgZnJvbSB0cmFja2luZ1xuICAgICAgICBmb3IgKGNvbnN0IGxpc3RlbmVyIG9mIHRoaXMubGlzdGVuZXJzKSB7XG4gICAgICAgICAgICBpZiAobGlzdGVuZXIuZXZlbnRUeXBlID09PSBldmVudFR5cGUgJiYgbGlzdGVuZXIuaGFuZGxlciA9PT0gaGFuZGxlcikge1xuICAgICAgICAgICAgICAgIHRoaXMubGlzdGVuZXJzLmRlbGV0ZShsaXN0ZW5lcik7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG4gICAgLyoqXG4gICAgICogRW1pdCBhbiBldmVudCB2aWEgRE9NIEN1c3RvbUV2ZW50XG4gICAgICovXG4gICAgZW1pdChldmVudFR5cGUsIGRldGFpbCA9IHt9KSB7XG4gICAgICAgIC8vIFZhbGlkYXRlIGV2ZW50VHlwZVxuICAgICAgICBpZiAoIWV2ZW50VHlwZSkge1xuICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IGV2ZW50ID0gbmV3IEN1c3RvbUV2ZW50KGV2ZW50VHlwZSwge1xuICAgICAgICAgICAgZGV0YWlsOiBkZXRhaWwgPz8ge30sXG4gICAgICAgICAgICBidWJibGVzOiB0cnVlLFxuICAgICAgICAgICAgY2FuY2VsYWJsZTogdHJ1ZVxuICAgICAgICB9KTtcbiAgICAgICAgLy8gTG9nIGV2ZW50IHdpdGggZ3JvdXBpbmdcbiAgICAgICAgaWYgKHRoaXMuZGVidWcpIHtcbiAgICAgICAgICAgIHRoaXMubG9nRXZlbnRXaXRoR3JvdXBpbmcoZXZlbnRUeXBlLCBkZXRhaWwpO1xuICAgICAgICB9XG4gICAgICAgIHRoaXMuZXZlbnRMb2cucHVzaCh7XG4gICAgICAgICAgICB0eXBlOiBldmVudFR5cGUsXG4gICAgICAgICAgICBkZXRhaWw6IGRldGFpbCA/PyB7fSxcbiAgICAgICAgICAgIHRpbWVzdGFtcDogRGF0ZS5ub3coKVxuICAgICAgICB9KTtcbiAgICAgICAgLy8gRW1pdCBvbiBkb2N1bWVudCAob25seSBET00gZXZlbnRzIG5vdylcbiAgICAgICAgcmV0dXJuICFkb2N1bWVudC5kaXNwYXRjaEV2ZW50KGV2ZW50KTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogTG9nIGV2ZW50IHdpdGggY29uc29sZSBncm91cGluZ1xuICAgICAqL1xuICAgIGxvZ0V2ZW50V2l0aEdyb3VwaW5nKGV2ZW50VHlwZSwgX2RldGFpbCkge1xuICAgICAgICAvLyBFeHRyYWN0IGNhdGVnb3J5IGZyb20gZXZlbnQgdHlwZSAoZS5nLiwgJ2NhbGVuZGFyOmRhdGVjaGFuZ2VkJyBcdTIxOTIgJ2NhbGVuZGFyJylcbiAgICAgICAgY29uc3QgY2F0ZWdvcnkgPSB0aGlzLmV4dHJhY3RDYXRlZ29yeShldmVudFR5cGUpO1xuICAgICAgICAvLyBPbmx5IGxvZyBpZiBjYXRlZ29yeSBpcyBlbmFibGVkXG4gICAgICAgIGlmICghdGhpcy5sb2dDb25maWdbY2F0ZWdvcnldKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgLy8gR2V0IGNhdGVnb3J5IGVtb2ppIGFuZCBjb2xvciAodXNlZCBmb3IgZnV0dXJlIGNvbnNvbGUgc3R5bGluZylcbiAgICAgICAgdGhpcy5nZXRDYXRlZ29yeVN0eWxlKGNhdGVnb3J5KTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogRXh0cmFjdCBjYXRlZ29yeSBmcm9tIGV2ZW50IHR5cGVcbiAgICAgKi9cbiAgICBleHRyYWN0Q2F0ZWdvcnkoZXZlbnRUeXBlKSB7XG4gICAgICAgIGlmICghZXZlbnRUeXBlKSB7XG4gICAgICAgICAgICByZXR1cm4gJ3Vua25vd24nO1xuICAgICAgICB9XG4gICAgICAgIGlmIChldmVudFR5cGUuaW5jbHVkZXMoJzonKSkge1xuICAgICAgICAgICAgcmV0dXJuIGV2ZW50VHlwZS5zcGxpdCgnOicpWzBdO1xuICAgICAgICB9XG4gICAgICAgIC8vIEZhbGxiYWNrOiB0cnkgdG8gZGV0ZWN0IGNhdGVnb3J5IGZyb20gZXZlbnQgbmFtZSBwYXR0ZXJuc1xuICAgICAgICBjb25zdCBsb3dlclR5cGUgPSBldmVudFR5cGUudG9Mb3dlckNhc2UoKTtcbiAgICAgICAgaWYgKGxvd2VyVHlwZS5pbmNsdWRlcygnZ3JpZCcpIHx8IGxvd2VyVHlwZS5pbmNsdWRlcygncmVuZGVyZWQnKSlcbiAgICAgICAgICAgIHJldHVybiAnZ3JpZCc7XG4gICAgICAgIGlmIChsb3dlclR5cGUuaW5jbHVkZXMoJ2V2ZW50JykgfHwgbG93ZXJUeXBlLmluY2x1ZGVzKCdzeW5jJykpXG4gICAgICAgICAgICByZXR1cm4gJ2V2ZW50JztcbiAgICAgICAgaWYgKGxvd2VyVHlwZS5pbmNsdWRlcygnc2Nyb2xsJykpXG4gICAgICAgICAgICByZXR1cm4gJ3Njcm9sbCc7XG4gICAgICAgIGlmIChsb3dlclR5cGUuaW5jbHVkZXMoJ25hdicpIHx8IGxvd2VyVHlwZS5pbmNsdWRlcygnZGF0ZScpKVxuICAgICAgICAgICAgcmV0dXJuICduYXZpZ2F0aW9uJztcbiAgICAgICAgaWYgKGxvd2VyVHlwZS5pbmNsdWRlcygndmlldycpKVxuICAgICAgICAgICAgcmV0dXJuICd2aWV3JztcbiAgICAgICAgcmV0dXJuICdkZWZhdWx0JztcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IHN0eWxpbmcgZm9yIGRpZmZlcmVudCBjYXRlZ29yaWVzXG4gICAgICovXG4gICAgZ2V0Q2F0ZWdvcnlTdHlsZShjYXRlZ29yeSkge1xuICAgICAgICBjb25zdCBzdHlsZXMgPSB7XG4gICAgICAgICAgICBjYWxlbmRhcjogeyBlbW9qaTogJ1x1RDgzRFx1RENDNScsIGNvbG9yOiAnIzIxOTZGMycgfSxcbiAgICAgICAgICAgIGdyaWQ6IHsgZW1vamk6ICdcdUQ4M0RcdURDQ0EnLCBjb2xvcjogJyM0Q0FGNTAnIH0sXG4gICAgICAgICAgICBldmVudDogeyBlbW9qaTogJ1x1RDgzRFx1RENDQycsIGNvbG9yOiAnI0ZGOTgwMCcgfSxcbiAgICAgICAgICAgIHNjcm9sbDogeyBlbW9qaTogJ1x1RDgzRFx1RENEQycsIGNvbG9yOiAnIzlDMjdCMCcgfSxcbiAgICAgICAgICAgIG5hdmlnYXRpb246IHsgZW1vamk6ICdcdUQ4M0VcdURERUQnLCBjb2xvcjogJyNGNDQzMzYnIH0sXG4gICAgICAgICAgICB2aWV3OiB7IGVtb2ppOiAnXHVEODNEXHVEQzQxJywgY29sb3I6ICcjMDBCQ0Q0JyB9LFxuICAgICAgICAgICAgZGVmYXVsdDogeyBlbW9qaTogJ1x1RDgzRFx1RENFMicsIGNvbG9yOiAnIzYwN0Q4QicgfVxuICAgICAgICB9O1xuICAgICAgICByZXR1cm4gc3R5bGVzW2NhdGVnb3J5XSB8fCBzdHlsZXMuZGVmYXVsdDtcbiAgICB9XG4gICAgLyoqXG4gICAgICogQ29uZmlndXJlIGxvZ2dpbmcgZm9yIHNwZWNpZmljIGNhdGVnb3JpZXNcbiAgICAgKi9cbiAgICBzZXRMb2dDb25maWcoY29uZmlnKSB7XG4gICAgICAgIHRoaXMubG9nQ29uZmlnID0geyAuLi50aGlzLmxvZ0NvbmZpZywgLi4uY29uZmlnIH07XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCBjdXJyZW50IGxvZyBjb25maWd1cmF0aW9uXG4gICAgICovXG4gICAgZ2V0TG9nQ29uZmlnKCkge1xuICAgICAgICByZXR1cm4geyAuLi50aGlzLmxvZ0NvbmZpZyB9O1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgZXZlbnQgaGlzdG9yeVxuICAgICAqL1xuICAgIGdldEV2ZW50TG9nKGV2ZW50VHlwZSkge1xuICAgICAgICBpZiAoZXZlbnRUeXBlKSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5ldmVudExvZy5maWx0ZXIoZSA9PiBlLnR5cGUgPT09IGV2ZW50VHlwZSk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXMuZXZlbnRMb2c7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEVuYWJsZS9kaXNhYmxlIGRlYnVnIG1vZGVcbiAgICAgKi9cbiAgICBzZXREZWJ1ZyhlbmFibGVkKSB7XG4gICAgICAgIHRoaXMuZGVidWcgPSBlbmFibGVkO1xuICAgIH1cbn1cbiIsICIvKipcbiAqIEluZGV4ZWREQkNvbnRleHQgLSBEYXRhYmFzZSBjb25uZWN0aW9uIG1hbmFnZXJcbiAqXG4gKiBSRVNQT05TSUJJTElUWTpcbiAqIC0gT3BlbnMgYW5kIG1hbmFnZXMgSURCRGF0YWJhc2UgY29ubmVjdGlvbiBsaWZlY3ljbGVcbiAqIC0gQ3JlYXRlcyBvYmplY3Qgc3RvcmVzIHZpYSBpbmplY3RlZCBJU3RvcmUgaW1wbGVtZW50YXRpb25zXG4gKiAtIFByb3ZpZGVzIHNoYXJlZCBJREJEYXRhYmFzZSBpbnN0YW5jZSB0byBhbGwgc2VydmljZXNcbiAqL1xuZXhwb3J0IGNsYXNzIEluZGV4ZWREQkNvbnRleHQge1xuICAgIGNvbnN0cnVjdG9yKHN0b3Jlcykge1xuICAgICAgICB0aGlzLmRiID0gbnVsbDtcbiAgICAgICAgdGhpcy5pbml0aWFsaXplZCA9IGZhbHNlO1xuICAgICAgICB0aGlzLnN0b3JlcyA9IHN0b3JlcztcbiAgICB9XG4gICAgLyoqXG4gICAgICogSW5pdGlhbGl6ZSBhbmQgb3BlbiB0aGUgZGF0YWJhc2VcbiAgICAgKi9cbiAgICBhc3luYyBpbml0aWFsaXplKCkge1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgcmVxdWVzdCA9IGluZGV4ZWREQi5vcGVuKEluZGV4ZWREQkNvbnRleHQuREJfTkFNRSwgSW5kZXhlZERCQ29udGV4dC5EQl9WRVJTSU9OKTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25lcnJvciA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICByZWplY3QobmV3IEVycm9yKGBGYWlsZWQgdG8gb3BlbiBJbmRleGVkREI6ICR7cmVxdWVzdC5lcnJvcn1gKSk7XG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgcmVxdWVzdC5vbnN1Y2Nlc3MgPSAoKSA9PiB7XG4gICAgICAgICAgICAgICAgdGhpcy5kYiA9IHJlcXVlc3QucmVzdWx0O1xuICAgICAgICAgICAgICAgIHRoaXMuaW5pdGlhbGl6ZWQgPSB0cnVlO1xuICAgICAgICAgICAgICAgIHJlc29sdmUoKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICByZXF1ZXN0Lm9udXBncmFkZW5lZWRlZCA9IChldmVudCkgPT4ge1xuICAgICAgICAgICAgICAgIGNvbnN0IGRiID0gZXZlbnQudGFyZ2V0LnJlc3VsdDtcbiAgICAgICAgICAgICAgICAvLyBDcmVhdGUgYWxsIGVudGl0eSBzdG9yZXMgdmlhIGluamVjdGVkIElTdG9yZSBpbXBsZW1lbnRhdGlvbnNcbiAgICAgICAgICAgICAgICB0aGlzLnN0b3Jlcy5mb3JFYWNoKHN0b3JlID0+IHtcbiAgICAgICAgICAgICAgICAgICAgaWYgKCFkYi5vYmplY3RTdG9yZU5hbWVzLmNvbnRhaW5zKHN0b3JlLnN0b3JlTmFtZSkpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHN0b3JlLmNyZWF0ZShkYik7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0pO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBDaGVjayBpZiBkYXRhYmFzZSBpcyBpbml0aWFsaXplZFxuICAgICAqL1xuICAgIGlzSW5pdGlhbGl6ZWQoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmluaXRpYWxpemVkO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgSURCRGF0YWJhc2UgaW5zdGFuY2VcbiAgICAgKi9cbiAgICBnZXREYXRhYmFzZSgpIHtcbiAgICAgICAgaWYgKCF0aGlzLmRiKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ0luZGV4ZWREQiBub3QgaW5pdGlhbGl6ZWQuIENhbGwgaW5pdGlhbGl6ZSgpIGZpcnN0LicpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzLmRiO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBDbG9zZSBkYXRhYmFzZSBjb25uZWN0aW9uXG4gICAgICovXG4gICAgY2xvc2UoKSB7XG4gICAgICAgIGlmICh0aGlzLmRiKSB7XG4gICAgICAgICAgICB0aGlzLmRiLmNsb3NlKCk7XG4gICAgICAgICAgICB0aGlzLmRiID0gbnVsbDtcbiAgICAgICAgICAgIHRoaXMuaW5pdGlhbGl6ZWQgPSBmYWxzZTtcbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBEZWxldGUgZW50aXJlIGRhdGFiYXNlIChmb3IgdGVzdGluZy9yZXNldClcbiAgICAgKi9cbiAgICBzdGF0aWMgYXN5bmMgZGVsZXRlRGF0YWJhc2UoKSB7XG4gICAgICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICBjb25zdCByZXF1ZXN0ID0gaW5kZXhlZERCLmRlbGV0ZURhdGFiYXNlKEluZGV4ZWREQkNvbnRleHQuREJfTkFNRSk7XG4gICAgICAgICAgICByZXF1ZXN0Lm9uc3VjY2VzcyA9ICgpID0+IHJlc29sdmUoKTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25lcnJvciA9ICgpID0+IHJlamVjdChuZXcgRXJyb3IoYEZhaWxlZCB0byBkZWxldGUgZGF0YWJhc2U6ICR7cmVxdWVzdC5lcnJvcn1gKSk7XG4gICAgICAgIH0pO1xuICAgIH1cbn1cbkluZGV4ZWREQkNvbnRleHQuREJfTkFNRSA9ICdDYWxlbmRhckRCJztcbkluZGV4ZWREQkNvbnRleHQuREJfVkVSU0lPTiA9IDQ7XG4iLCAiLyoqXG4gKiBFdmVudFN0b3JlIC0gSW5kZXhlZERCIE9iamVjdFN0b3JlIGRlZmluaXRpb24gZm9yIGNhbGVuZGFyIGV2ZW50c1xuICovXG5leHBvcnQgY2xhc3MgRXZlbnRTdG9yZSB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuc3RvcmVOYW1lID0gRXZlbnRTdG9yZS5TVE9SRV9OQU1FO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBDcmVhdGUgdGhlIGV2ZW50cyBPYmplY3RTdG9yZSB3aXRoIGluZGV4ZXNcbiAgICAgKi9cbiAgICBjcmVhdGUoZGIpIHtcbiAgICAgICAgY29uc3Qgc3RvcmUgPSBkYi5jcmVhdGVPYmplY3RTdG9yZShFdmVudFN0b3JlLlNUT1JFX05BTUUsIHsga2V5UGF0aDogJ2lkJyB9KTtcbiAgICAgICAgLy8gSW5kZXg6IHN0YXJ0IChmb3IgZGF0ZSByYW5nZSBxdWVyaWVzKVxuICAgICAgICBzdG9yZS5jcmVhdGVJbmRleCgnc3RhcnQnLCAnc3RhcnQnLCB7IHVuaXF1ZTogZmFsc2UgfSk7XG4gICAgICAgIC8vIEluZGV4OiBlbmQgKGZvciBkYXRlIHJhbmdlIHF1ZXJpZXMpXG4gICAgICAgIHN0b3JlLmNyZWF0ZUluZGV4KCdlbmQnLCAnZW5kJywgeyB1bmlxdWU6IGZhbHNlIH0pO1xuICAgICAgICAvLyBJbmRleDogc3luY1N0YXR1cyAoZm9yIGZpbHRlcmluZyBieSBzeW5jIHN0YXRlKVxuICAgICAgICBzdG9yZS5jcmVhdGVJbmRleCgnc3luY1N0YXR1cycsICdzeW5jU3RhdHVzJywgeyB1bmlxdWU6IGZhbHNlIH0pO1xuICAgICAgICAvLyBJbmRleDogcmVzb3VyY2VJZCAoZm9yIHJlc291cmNlLW1vZGUgZmlsdGVyaW5nKVxuICAgICAgICBzdG9yZS5jcmVhdGVJbmRleCgncmVzb3VyY2VJZCcsICdyZXNvdXJjZUlkJywgeyB1bmlxdWU6IGZhbHNlIH0pO1xuICAgICAgICAvLyBJbmRleDogY3VzdG9tZXJJZCAoZm9yIGN1c3RvbWVyLWNlbnRyaWMgcXVlcmllcylcbiAgICAgICAgc3RvcmUuY3JlYXRlSW5kZXgoJ2N1c3RvbWVySWQnLCAnY3VzdG9tZXJJZCcsIHsgdW5pcXVlOiBmYWxzZSB9KTtcbiAgICAgICAgLy8gSW5kZXg6IGJvb2tpbmdJZCAoZm9yIGV2ZW50LXRvLWJvb2tpbmcgbG9va3VwcylcbiAgICAgICAgc3RvcmUuY3JlYXRlSW5kZXgoJ2Jvb2tpbmdJZCcsICdib29raW5nSWQnLCB7IHVuaXF1ZTogZmFsc2UgfSk7XG4gICAgICAgIC8vIENvbXBvdW5kIGluZGV4OiBzdGFydEVuZCAoZm9yIG9wdGltaXplZCByYW5nZSBxdWVyaWVzKVxuICAgICAgICBzdG9yZS5jcmVhdGVJbmRleCgnc3RhcnRFbmQnLCBbJ3N0YXJ0JywgJ2VuZCddLCB7IHVuaXF1ZTogZmFsc2UgfSk7XG4gICAgfVxufVxuRXZlbnRTdG9yZS5TVE9SRV9OQU1FID0gJ2V2ZW50cyc7XG4iLCAiLyoqXG4gKiBFdmVudFNlcmlhbGl6YXRpb24gLSBIYW5kbGVzIERhdGUgZmllbGQgc2VyaWFsaXphdGlvbiBmb3IgSW5kZXhlZERCXG4gKlxuICogSW5kZXhlZERCIGRvZXNuJ3Qgc3RvcmUgRGF0ZSBvYmplY3RzIGRpcmVjdGx5LCBzbyB3ZSBjb252ZXJ0OlxuICogLSBEYXRlIFx1MjE5MiBJU08gc3RyaW5nIChzZXJpYWxpemUpIHdoZW4gd3JpdGluZ1xuICogLSBJU08gc3RyaW5nIFx1MjE5MiBEYXRlIChkZXNlcmlhbGl6ZSkgd2hlbiByZWFkaW5nXG4gKi9cbmV4cG9ydCBjbGFzcyBFdmVudFNlcmlhbGl6YXRpb24ge1xuICAgIC8qKlxuICAgICAqIFNlcmlhbGl6ZSBldmVudCBmb3IgSW5kZXhlZERCIHN0b3JhZ2VcbiAgICAgKi9cbiAgICBzdGF0aWMgc2VyaWFsaXplKGV2ZW50KSB7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICAuLi5ldmVudCxcbiAgICAgICAgICAgIHN0YXJ0OiBldmVudC5zdGFydCBpbnN0YW5jZW9mIERhdGUgPyBldmVudC5zdGFydC50b0lTT1N0cmluZygpIDogZXZlbnQuc3RhcnQsXG4gICAgICAgICAgICBlbmQ6IGV2ZW50LmVuZCBpbnN0YW5jZW9mIERhdGUgPyBldmVudC5lbmQudG9JU09TdHJpbmcoKSA6IGV2ZW50LmVuZFxuICAgICAgICB9O1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBEZXNlcmlhbGl6ZSBldmVudCBmcm9tIEluZGV4ZWREQiBzdG9yYWdlXG4gICAgICovXG4gICAgc3RhdGljIGRlc2VyaWFsaXplKGRhdGEpIHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIC4uLmRhdGEsXG4gICAgICAgICAgICBzdGFydDogdHlwZW9mIGRhdGEuc3RhcnQgPT09ICdzdHJpbmcnID8gbmV3IERhdGUoZGF0YS5zdGFydCkgOiBkYXRhLnN0YXJ0LFxuICAgICAgICAgICAgZW5kOiB0eXBlb2YgZGF0YS5lbmQgPT09ICdzdHJpbmcnID8gbmV3IERhdGUoZGF0YS5lbmQpIDogZGF0YS5lbmRcbiAgICAgICAgfTtcbiAgICB9XG59XG4iLCAiLyoqXG4gKiBTeW5jUGx1Z2luPFQgZXh0ZW5kcyBJU3luYz4gLSBQbHVnZ2FibGUgc3luYyBmdW5jdGlvbmFsaXR5IGZvciBlbnRpdHkgc2VydmljZXNcbiAqXG4gKiBDT01QT1NJVElPTiBQQVRURVJOOlxuICogLSBFbmNhcHN1bGF0ZXMgYWxsIHN5bmMtcmVsYXRlZCBsb2dpYyBpbiBzZXBhcmF0ZSBjbGFzc1xuICogLSBDb21wb3NlZCBpbnRvIEJhc2VFbnRpdHlTZXJ2aWNlIChub3QgaW5oZXJpdGFuY2UpXG4gKi9cbmV4cG9ydCBjbGFzcyBTeW5jUGx1Z2luIHtcbiAgICBjb25zdHJ1Y3RvcihzZXJ2aWNlKSB7XG4gICAgICAgIHRoaXMuc2VydmljZSA9IHNlcnZpY2U7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIE1hcmsgZW50aXR5IGFzIHN1Y2Nlc3NmdWxseSBzeW5jZWRcbiAgICAgKi9cbiAgICBhc3luYyBtYXJrQXNTeW5jZWQoaWQpIHtcbiAgICAgICAgY29uc3QgZW50aXR5ID0gYXdhaXQgdGhpcy5zZXJ2aWNlLmdldChpZCk7XG4gICAgICAgIGlmIChlbnRpdHkpIHtcbiAgICAgICAgICAgIGVudGl0eS5zeW5jU3RhdHVzID0gJ3N5bmNlZCc7XG4gICAgICAgICAgICBhd2FpdCB0aGlzLnNlcnZpY2Uuc2F2ZShlbnRpdHkpO1xuICAgICAgICB9XG4gICAgfVxuICAgIC8qKlxuICAgICAqIE1hcmsgZW50aXR5IGFzIHN5bmMgZXJyb3JcbiAgICAgKi9cbiAgICBhc3luYyBtYXJrQXNFcnJvcihpZCkge1xuICAgICAgICBjb25zdCBlbnRpdHkgPSBhd2FpdCB0aGlzLnNlcnZpY2UuZ2V0KGlkKTtcbiAgICAgICAgaWYgKGVudGl0eSkge1xuICAgICAgICAgICAgZW50aXR5LnN5bmNTdGF0dXMgPSAnZXJyb3InO1xuICAgICAgICAgICAgYXdhaXQgdGhpcy5zZXJ2aWNlLnNhdmUoZW50aXR5KTtcbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgY3VycmVudCBzeW5jIHN0YXR1cyBmb3IgYW4gZW50aXR5XG4gICAgICovXG4gICAgYXN5bmMgZ2V0U3luY1N0YXR1cyhpZCkge1xuICAgICAgICBjb25zdCBlbnRpdHkgPSBhd2FpdCB0aGlzLnNlcnZpY2UuZ2V0KGlkKTtcbiAgICAgICAgcmV0dXJuIGVudGl0eSA/IGVudGl0eS5zeW5jU3RhdHVzIDogbnVsbDtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IGVudGl0aWVzIGJ5IHN5bmMgc3RhdHVzIHVzaW5nIEluZGV4ZWREQiBpbmRleFxuICAgICAqL1xuICAgIGFzeW5jIGdldEJ5U3luY1N0YXR1cyhzeW5jU3RhdHVzKSB7XG4gICAgICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICBjb25zdCB0cmFuc2FjdGlvbiA9IHRoaXMuc2VydmljZS5kYi50cmFuc2FjdGlvbihbdGhpcy5zZXJ2aWNlLnN0b3JlTmFtZV0sICdyZWFkb25seScpO1xuICAgICAgICAgICAgY29uc3Qgc3RvcmUgPSB0cmFuc2FjdGlvbi5vYmplY3RTdG9yZSh0aGlzLnNlcnZpY2Uuc3RvcmVOYW1lKTtcbiAgICAgICAgICAgIGNvbnN0IGluZGV4ID0gc3RvcmUuaW5kZXgoJ3N5bmNTdGF0dXMnKTtcbiAgICAgICAgICAgIGNvbnN0IHJlcXVlc3QgPSBpbmRleC5nZXRBbGwoc3luY1N0YXR1cyk7XG4gICAgICAgICAgICByZXF1ZXN0Lm9uc3VjY2VzcyA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICBjb25zdCBkYXRhID0gcmVxdWVzdC5yZXN1bHQ7XG4gICAgICAgICAgICAgICAgY29uc3QgZW50aXRpZXMgPSBkYXRhLm1hcChpdGVtID0+IHRoaXMuc2VydmljZS5kZXNlcmlhbGl6ZShpdGVtKSk7XG4gICAgICAgICAgICAgICAgcmVzb2x2ZShlbnRpdGllcyk7XG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgcmVxdWVzdC5vbmVycm9yID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIHJlamVjdChuZXcgRXJyb3IoYEZhaWxlZCB0byBnZXQgYnkgc3luYyBzdGF0dXMgJHtzeW5jU3RhdHVzfTogJHtyZXF1ZXN0LmVycm9yfWApKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0pO1xuICAgIH1cbn1cbiIsICIvKipcbiAqIENvcmVFdmVudHMgLSBDb25zb2xpZGF0ZWQgZXNzZW50aWFsIGV2ZW50cyBmb3IgdGhlIGNhbGVuZGFyXG4gKi9cbmV4cG9ydCBjb25zdCBDb3JlRXZlbnRzID0ge1xuICAgIC8vIExpZmVjeWNsZSBldmVudHNcbiAgICBJTklUSUFMSVpFRDogJ2NvcmU6aW5pdGlhbGl6ZWQnLFxuICAgIFJFQURZOiAnY29yZTpyZWFkeScsXG4gICAgREVTVFJPWUVEOiAnY29yZTpkZXN0cm95ZWQnLFxuICAgIC8vIFZpZXcgZXZlbnRzXG4gICAgVklFV19DSEFOR0VEOiAndmlldzpjaGFuZ2VkJyxcbiAgICBWSUVXX1JFTkRFUkVEOiAndmlldzpyZW5kZXJlZCcsXG4gICAgLy8gTmF2aWdhdGlvbiBldmVudHNcbiAgICBEQVRFX0NIQU5HRUQ6ICduYXY6ZGF0ZS1jaGFuZ2VkJyxcbiAgICBOQVZJR0FUSU9OX0NPTVBMRVRFRDogJ25hdjpuYXZpZ2F0aW9uLWNvbXBsZXRlZCcsXG4gICAgLy8gRGF0YSBldmVudHNcbiAgICBEQVRBX0xPQURJTkc6ICdkYXRhOmxvYWRpbmcnLFxuICAgIERBVEFfTE9BREVEOiAnZGF0YTpsb2FkZWQnLFxuICAgIERBVEFfRVJST1I6ICdkYXRhOmVycm9yJyxcbiAgICAvLyBHcmlkIGV2ZW50c1xuICAgIEdSSURfUkVOREVSRUQ6ICdncmlkOnJlbmRlcmVkJyxcbiAgICBHUklEX0NMSUNLRUQ6ICdncmlkOmNsaWNrZWQnLFxuICAgIC8vIEV2ZW50IG1hbmFnZW1lbnRcbiAgICBFVkVOVF9DUkVBVEVEOiAnZXZlbnQ6Y3JlYXRlZCcsXG4gICAgRVZFTlRfVVBEQVRFRDogJ2V2ZW50OnVwZGF0ZWQnLFxuICAgIEVWRU5UX0RFTEVURUQ6ICdldmVudDpkZWxldGVkJyxcbiAgICBFVkVOVF9TRUxFQ1RFRDogJ2V2ZW50OnNlbGVjdGVkJyxcbiAgICAvLyBFdmVudCBkcmFnLWRyb3BcbiAgICBFVkVOVF9EUkFHX1NUQVJUOiAnZXZlbnQ6ZHJhZy1zdGFydCcsXG4gICAgRVZFTlRfRFJBR19NT1ZFOiAnZXZlbnQ6ZHJhZy1tb3ZlJyxcbiAgICBFVkVOVF9EUkFHX0VORDogJ2V2ZW50OmRyYWctZW5kJyxcbiAgICBFVkVOVF9EUkFHX0NBTkNFTDogJ2V2ZW50OmRyYWctY2FuY2VsJyxcbiAgICBFVkVOVF9EUkFHX0NPTFVNTl9DSEFOR0U6ICdldmVudDpkcmFnLWNvbHVtbi1jaGFuZ2UnLFxuICAgIC8vIEhlYWRlciBkcmFnICh0aW1lZCBcdTIxOTIgaGVhZGVyIGNvbnZlcnNpb24pXG4gICAgRVZFTlRfRFJBR19FTlRFUl9IRUFERVI6ICdldmVudDpkcmFnLWVudGVyLWhlYWRlcicsXG4gICAgRVZFTlRfRFJBR19NT1ZFX0hFQURFUjogJ2V2ZW50OmRyYWctbW92ZS1oZWFkZXInLFxuICAgIEVWRU5UX0RSQUdfTEVBVkVfSEVBREVSOiAnZXZlbnQ6ZHJhZy1sZWF2ZS1oZWFkZXInLFxuICAgIC8vIEV2ZW50IHJlc2l6ZVxuICAgIEVWRU5UX1JFU0laRV9TVEFSVDogJ2V2ZW50OnJlc2l6ZS1zdGFydCcsXG4gICAgRVZFTlRfUkVTSVpFX0VORDogJ2V2ZW50OnJlc2l6ZS1lbmQnLFxuICAgIC8vIEVkZ2Ugc2Nyb2xsXG4gICAgRURHRV9TQ1JPTExfVElDSzogJ2VkZ2Utc2Nyb2xsOnRpY2snLFxuICAgIEVER0VfU0NST0xMX1NUQVJURUQ6ICdlZGdlLXNjcm9sbDpzdGFydGVkJyxcbiAgICBFREdFX1NDUk9MTF9TVE9QUEVEOiAnZWRnZS1zY3JvbGw6c3RvcHBlZCcsXG4gICAgLy8gU3lzdGVtIGV2ZW50c1xuICAgIEVSUk9SOiAnc3lzdGVtOmVycm9yJyxcbiAgICAvLyBTeW5jIGV2ZW50c1xuICAgIFNZTkNfU1RBUlRFRDogJ3N5bmM6c3RhcnRlZCcsXG4gICAgU1lOQ19DT01QTEVURUQ6ICdzeW5jOmNvbXBsZXRlZCcsXG4gICAgU1lOQ19GQUlMRUQ6ICdzeW5jOmZhaWxlZCcsXG4gICAgLy8gRW50aXR5IGV2ZW50cyAtIGZvciBhdWRpdCBhbmQgc3luY1xuICAgIEVOVElUWV9TQVZFRDogJ2VudGl0eTpzYXZlZCcsXG4gICAgRU5USVRZX0RFTEVURUQ6ICdlbnRpdHk6ZGVsZXRlZCcsXG4gICAgLy8gQXVkaXQgZXZlbnRzXG4gICAgQVVESVRfTE9HR0VEOiAnYXVkaXQ6bG9nZ2VkJyxcbiAgICAvLyBSZW5kZXJpbmcgZXZlbnRzXG4gICAgRVZFTlRTX1JFTkRFUkVEOiAnZXZlbnRzOnJlbmRlcmVkJ1xufTtcbiIsICJleHBvcnQgZnVuY3Rpb24gc3BsaXRKU09OUGF0aChwYXRoOiBzdHJpbmcpOiBzdHJpbmdbXSB7XG4gICAgY29uc3QgcGFydHM6IHN0cmluZ1tdID0gW107XG4gICAgbGV0IGN1cnJlbnRQYXJ0ID0gJyc7XG4gICAgbGV0IGluU2luZ2xlUXVvdGVzID0gZmFsc2U7XG4gICAgbGV0IGluQnJhY2tldHMgPSAwO1xuXG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBwYXRoLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGNvbnN0IGNoYXIgPSBwYXRoW2ldO1xuXG4gICAgICAgIGlmIChjaGFyID09PSBcIidcIiAmJiBwYXRoW2kgLSAxXSAhPT0gJ1xcXFwnKSB7XG4gICAgICAgICAgICAvLyBUb2dnbGUgc2luZ2xlIHF1b3RlIGZsYWcgaWYgbm90IGVzY2FwZWRcbiAgICAgICAgICAgIGluU2luZ2xlUXVvdGVzID0gIWluU2luZ2xlUXVvdGVzO1xuICAgICAgICB9IGVsc2UgaWYgKGNoYXIgPT09ICdbJyAmJiAhaW5TaW5nbGVRdW90ZXMpIHtcbiAgICAgICAgICAgIC8vIEluY3JlYXNlIGJyYWNrZXQgbmVzdGluZyBsZXZlbFxuICAgICAgICAgICAgaW5CcmFja2V0cysrO1xuICAgICAgICB9IGVsc2UgaWYgKGNoYXIgPT09ICddJyAmJiAhaW5TaW5nbGVRdW90ZXMpIHtcbiAgICAgICAgICAgIC8vIERlY3JlYXNlIGJyYWNrZXQgbmVzdGluZyBsZXZlbFxuICAgICAgICAgICAgaW5CcmFja2V0cy0tO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGNoYXIgPT09ICcuJyAmJiAhaW5TaW5nbGVRdW90ZXMgJiYgaW5CcmFja2V0cyA9PT0gMCkge1xuICAgICAgICAgICAgLy8gU3BsaXQgYXQgcGVyaW9kIGlmIG5vdCBpbiBxdW90ZXMgb3IgYnJhY2tldHNcbiAgICAgICAgICAgIHBhcnRzLnB1c2goY3VycmVudFBhcnQpO1xuICAgICAgICAgICAgY3VycmVudFBhcnQgPSAnJztcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIC8vIE90aGVyd2lzZSwga2VlcCBhZGRpbmcgdG8gdGhlIGN1cnJlbnQgcGFydFxuICAgICAgICAgICAgY3VycmVudFBhcnQgKz0gY2hhcjtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8vIEFkZCB0aGUgbGFzdCBwYXJ0IGlmIHRoZXJlJ3MgYW55XG4gICAgaWYgKGN1cnJlbnRQYXJ0ICE9PSAnJykge1xuICAgICAgICBwYXJ0cy5wdXNoKGN1cnJlbnRQYXJ0KTtcbiAgICB9XG5cbiAgICByZXR1cm4gcGFydHM7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBhcnJheURpZmZlcmVuY2U8VD4oZmlyc3Q6IFRbXSwgc2Vjb25kOiBUW10pOiBUW10ge1xuICAgIGNvbnN0IHNlY29uZFNldCA9IG5ldyBTZXQoc2Vjb25kKTtcbiAgICByZXR1cm4gZmlyc3QuZmlsdGVyKGl0ZW0gPT4gIXNlY29uZFNldC5oYXMoaXRlbSkpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gYXJyYXlJbnRlcnNlY3Rpb248VD4oZmlyc3Q6IFRbXSwgc2Vjb25kOiBUW10pOiBUW10ge1xuICAgIGNvbnN0IHNlY29uZFNldCA9IG5ldyBTZXQoc2Vjb25kKTtcbiAgICByZXR1cm4gZmlyc3QuZmlsdGVyKGl0ZW0gPT4gc2Vjb25kU2V0LmhhcyhpdGVtKSk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBrZXlCeTxUPihhcnI6IFRbXSwgZ2V0S2V5OiAoaXRlbTogVCkgPT4gYW55KTogUmVjb3JkPHN0cmluZywgVD4ge1xuICAgIGNvbnN0IHJlc3VsdDogUmVjb3JkPHN0cmluZywgVD4gPSB7fTtcbiAgICBmb3IgKGNvbnN0IGl0ZW0gb2YgYXJyKSB7XG4gICAgICAgIHJlc3VsdFtTdHJpbmcoZ2V0S2V5KGl0ZW0pKV0gPSBpdGVtO1xuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gc2V0QnlQYXRoKG9iajogYW55LCBwYXRoOiBzdHJpbmcsIHZhbHVlOiBhbnkpOiB2b2lkIHtcbiAgICBjb25zdCBwYXJ0cyA9IHBhdGgucmVwbGFjZSgvXFxbKFxcZCspXFxdL2csICcuJDEnKS5zcGxpdCgnLicpLmZpbHRlcihCb29sZWFuKTtcbiAgICBsZXQgY3VycmVudCA9IG9iajtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IHBhcnRzLmxlbmd0aCAtIDE7IGkrKykge1xuICAgICAgICBjb25zdCBwYXJ0ID0gcGFydHNbaV07XG4gICAgICAgIGlmICghKHBhcnQgaW4gY3VycmVudCkpIHtcbiAgICAgICAgICAgIGN1cnJlbnRbcGFydF0gPSAvXlxcZCskLy50ZXN0KHBhcnRzW2kgKyAxXSkgPyBbXSA6IHt9O1xuICAgICAgICB9XG4gICAgICAgIGN1cnJlbnQgPSBjdXJyZW50W3BhcnRdO1xuICAgIH1cbiAgICBjdXJyZW50W3BhcnRzW3BhcnRzLmxlbmd0aCAtIDFdXSA9IHZhbHVlO1xufVxuIiwgImltcG9ydCB7IGFycmF5RGlmZmVyZW5jZSBhcyBkaWZmZXJlbmNlLCBhcnJheUludGVyc2VjdGlvbiBhcyBpbnRlcnNlY3Rpb24sIGtleUJ5LCBzcGxpdEpTT05QYXRoIH0gZnJvbSAnLi9oZWxwZXJzLmpzJztcblxudHlwZSBGdW5jdGlvbktleSA9IChvYmo6IGFueSwgc2hvdWxkUmV0dXJuS2V5TmFtZT86IGJvb2xlYW4pID0+IGFueTtcbnR5cGUgRW1iZWRkZWRPYmpLZXlzVHlwZSA9IFJlY29yZDxzdHJpbmcsIHN0cmluZyB8IEZ1bmN0aW9uS2V5PjtcbnR5cGUgRW1iZWRkZWRPYmpLZXlzTWFwVHlwZSA9IE1hcDxzdHJpbmcgfCBSZWdFeHAsIHN0cmluZyB8IEZ1bmN0aW9uS2V5PjtcbmVudW0gT3BlcmF0aW9uIHtcbiAgUkVNT1ZFID0gJ1JFTU9WRScsXG4gIEFERCA9ICdBREQnLFxuICBVUERBVEUgPSAnVVBEQVRFJ1xufVxuXG5pbnRlcmZhY2UgSUNoYW5nZSB7XG4gIHR5cGU6IE9wZXJhdGlvbjtcbiAga2V5OiBzdHJpbmc7XG4gIGVtYmVkZGVkS2V5Pzogc3RyaW5nIHwgRnVuY3Rpb25LZXk7XG4gIHZhbHVlPzogYW55O1xuICBvbGRWYWx1ZT86IGFueTtcbiAgY2hhbmdlcz86IElDaGFuZ2VbXTtcbn1cbnR5cGUgQ2hhbmdlc2V0ID0gSUNoYW5nZVtdO1xuXG5pbnRlcmZhY2UgSUF0b21pY0NoYW5nZSB7XG4gIHR5cGU6IE9wZXJhdGlvbjtcbiAga2V5OiBzdHJpbmc7XG4gIHBhdGg6IHN0cmluZztcbiAgdmFsdWVUeXBlOiBzdHJpbmcgfCBudWxsO1xuICB2YWx1ZT86IGFueTtcbiAgb2xkVmFsdWU/OiBhbnk7XG59XG5cbmludGVyZmFjZSBPcHRpb25zIHtcbiAgZW1iZWRkZWRPYmpLZXlzPzogRW1iZWRkZWRPYmpLZXlzVHlwZSB8IEVtYmVkZGVkT2JqS2V5c01hcFR5cGU7XG4gIGtleXNUb1NraXA/OiBzdHJpbmdbXTtcbiAgdHJlYXRUeXBlQ2hhbmdlQXNSZXBsYWNlPzogYm9vbGVhbjtcbn1cblxuLyoqXG4gKiBDb21wdXRlcyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHR3byBvYmplY3RzLlxuICpcbiAqIEBwYXJhbSB7YW55fSBvbGRPYmogLSBUaGUgb3JpZ2luYWwgb2JqZWN0LlxuICogQHBhcmFtIHthbnl9IG5ld09iaiAtIFRoZSB1cGRhdGVkIG9iamVjdC5cbiAqIEBwYXJhbSB7T3B0aW9uc30gb3B0aW9ucyAtIEFuIG9wdGlvbmFsIHBhcmFtZXRlciBzcGVjaWZ5aW5nIGtleXMgb2YgZW1iZWRkZWQgb2JqZWN0cyBhbmQga2V5cyB0byBza2lwLlxuICogQHJldHVybnMge0lDaGFuZ2VbXX0gLSBBbiBhcnJheSBvZiBjaGFuZ2VzIHRoYXQgdHJhbnNmb3JtIHRoZSBvbGQgb2JqZWN0IGludG8gdGhlIG5ldyBvYmplY3QuXG4gKi9cbmZ1bmN0aW9uIGRpZmYob2xkT2JqOiBhbnksIG5ld09iajogYW55LCBvcHRpb25zOiBPcHRpb25zID0ge30pOiBJQ2hhbmdlW10ge1xuICBsZXQgeyBlbWJlZGRlZE9iaktleXMgfSA9IG9wdGlvbnM7XG4gIGNvbnN0IHsga2V5c1RvU2tpcCwgdHJlYXRUeXBlQ2hhbmdlQXNSZXBsYWNlIH0gPSBvcHRpb25zO1xuXG4gIC8vIFRyaW0gbGVhZGluZyAnLicgZnJvbSBrZXlzIGluIGVtYmVkZGVkT2JqS2V5c1xuICBpZiAoZW1iZWRkZWRPYmpLZXlzIGluc3RhbmNlb2YgTWFwKSB7XG4gICAgZW1iZWRkZWRPYmpLZXlzID0gbmV3IE1hcChcbiAgICAgIEFycmF5LmZyb20oZW1iZWRkZWRPYmpLZXlzLmVudHJpZXMoKSkubWFwKChba2V5LCB2YWx1ZV0pID0+IFtcbiAgICAgICAga2V5IGluc3RhbmNlb2YgUmVnRXhwID8ga2V5IDoga2V5LnJlcGxhY2UoL15cXC4vLCAnJyksXG4gICAgICAgIHZhbHVlXG4gICAgICBdKVxuICAgICk7XG4gIH0gZWxzZSBpZiAoZW1iZWRkZWRPYmpLZXlzKSB7XG4gICAgZW1iZWRkZWRPYmpLZXlzID0gT2JqZWN0LmZyb21FbnRyaWVzKFxuICAgICAgT2JqZWN0LmVudHJpZXMoZW1iZWRkZWRPYmpLZXlzKS5tYXAoKFtrZXksIHZhbHVlXSkgPT4gW2tleS5yZXBsYWNlKC9eXFwuLywgJycpLCB2YWx1ZV0pXG4gICAgKTtcbiAgfVxuXG4gIC8vIENvbXBhcmUgb2xkIGFuZCBuZXcgb2JqZWN0cyB0byBnZW5lcmF0ZSBhIGxpc3Qgb2YgY2hhbmdlc1xuICByZXR1cm4gY29tcGFyZShvbGRPYmosIG5ld09iaiwgW10sIFtdLCB7XG4gICAgZW1iZWRkZWRPYmpLZXlzLFxuICAgIGtleXNUb1NraXA6IGtleXNUb1NraXAgPz8gW10sXG4gICAgdHJlYXRUeXBlQ2hhbmdlQXNSZXBsYWNlOiB0cmVhdFR5cGVDaGFuZ2VBc1JlcGxhY2UgPz8gdHJ1ZVxuICB9KTtcbn1cblxuLyoqXG4gKiBBcHBsaWVzIGFsbCBjaGFuZ2VzIGluIHRoZSBjaGFuZ2VzZXQgdG8gdGhlIG9iamVjdC5cbiAqXG4gKiBAcGFyYW0ge2FueX0gb2JqIC0gVGhlIG9iamVjdCB0byBhcHBseSBjaGFuZ2VzIHRvLlxuICogQHBhcmFtIHtDaGFuZ2VzZXR9IGNoYW5nZXNldCAtIFRoZSBjaGFuZ2VzZXQgdG8gYXBwbHkuXG4gKiBAcmV0dXJucyB7YW55fSAtIFRoZSBvYmplY3QgYWZ0ZXIgdGhlIGNoYW5nZXMgZnJvbSB0aGUgY2hhbmdlc2V0IGhhdmUgYmVlbiBhcHBsaWVkLlxuICpcbiAqIFRoZSBmdW5jdGlvbiBmaXJzdCBjaGVja3MgaWYgYSBjaGFuZ2VzZXQgaXMgcHJvdmlkZWQuIElmIHNvLCBpdCBpdGVyYXRlcyBvdmVyIGVhY2ggY2hhbmdlIGluIHRoZSBjaGFuZ2VzZXQuXG4gKiBJZiB0aGUgY2hhbmdlIHZhbHVlIGlzIG5vdCBudWxsIG9yIHVuZGVmaW5lZCwgb3IgaWYgdGhlIGNoYW5nZSB0eXBlIGlzIFJFTU9WRSwgb3IgaWYgdGhlIHZhbHVlIGlzIG51bGwgYW5kIHRoZSB0eXBlIGlzIEFERCxcbiAqIGl0IGFwcGxpZXMgdGhlIGNoYW5nZSB0byB0aGUgb2JqZWN0IGRpcmVjdGx5LlxuICogT3RoZXJ3aXNlLCBpdCBhcHBsaWVzIHRoZSBjaGFuZ2UgdG8gdGhlIGNvcnJlc3BvbmRpbmcgYnJhbmNoIG9mIHRoZSBvYmplY3QuXG4gKi9cbmNvbnN0IGFwcGx5Q2hhbmdlc2V0ID0gKG9iajogYW55LCBjaGFuZ2VzZXQ6IENoYW5nZXNldCkgPT4ge1xuICBpZiAoY2hhbmdlc2V0KSB7XG4gICAgY2hhbmdlc2V0LmZvckVhY2goKGNoYW5nZSkgPT4ge1xuICAgICAgY29uc3QgeyB0eXBlLCBrZXksIHZhbHVlLCBlbWJlZGRlZEtleSB9ID0gY2hhbmdlO1xuXG4gICAgICAvLyBIYW5kbGUgbnVsbCB2YWx1ZXMgYXMgbGVhZiBjaGFuZ2VzIHdoZW4gdGhlIG9wZXJhdGlvbiBpcyBBRERcbiAgICAgIGlmICgodmFsdWUgIT09IG51bGwgJiYgdmFsdWUgIT09IHVuZGVmaW5lZCkgfHwgdHlwZSA9PT0gT3BlcmF0aW9uLlJFTU9WRSB8fCAodmFsdWUgPT09IG51bGwgJiYgdHlwZSA9PT0gT3BlcmF0aW9uLkFERCkpIHtcbiAgICAgICAgLy8gQXBwbHkgdGhlIGNoYW5nZSB0byB0aGUgb2JqZWN0XG4gICAgICAgIGFwcGx5TGVhZkNoYW5nZShvYmosIGNoYW5nZSwgZW1iZWRkZWRLZXkpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgLy8gQXBwbHkgdGhlIGNoYW5nZSB0byB0aGUgYnJhbmNoXG4gICAgICAgIGFwcGx5QnJhbmNoQ2hhbmdlKG9ialtrZXldLCBjaGFuZ2UpO1xuICAgICAgfVxuICAgIH0pO1xuICB9XG4gIHJldHVybiBvYmo7XG59O1xuXG4vKipcbiAqIFJldmVydHMgdGhlIGNoYW5nZXMgbWFkZSB0byBhbiBvYmplY3QgYmFzZWQgb24gYSBnaXZlbiBjaGFuZ2VzZXQuXG4gKlxuICogQHBhcmFtIHthbnl9IG9iaiAtIFRoZSBvYmplY3Qgb24gd2hpY2ggdG8gcmV2ZXJ0IGNoYW5nZXMuXG4gKiBAcGFyYW0ge0NoYW5nZXNldH0gY2hhbmdlc2V0IC0gVGhlIGNoYW5nZXNldCB0byByZXZlcnQuXG4gKiBAcmV0dXJucyB7YW55fSAtIFRoZSBvYmplY3QgYWZ0ZXIgdGhlIGNoYW5nZXMgZnJvbSB0aGUgY2hhbmdlc2V0IGhhdmUgYmVlbiByZXZlcnRlZC5cbiAqXG4gKiBUaGUgZnVuY3Rpb24gZmlyc3QgY2hlY2tzIGlmIGEgY2hhbmdlc2V0IGlzIHByb3ZpZGVkLiBJZiBzbywgaXQgcmV2ZXJzZXMgdGhlIGNoYW5nZXNldCB0byBzdGFydCByZXZlcnRpbmcgZnJvbSB0aGUgbGFzdCBjaGFuZ2UuXG4gKiBJdCB0aGVuIGl0ZXJhdGVzIG92ZXIgZWFjaCBjaGFuZ2UgaW4gdGhlIGNoYW5nZXNldC4gSWYgdGhlIGNoYW5nZSBkb2VzIG5vdCBoYXZlIGFueSBuZXN0ZWQgY2hhbmdlcywgb3IgaWYgdGhlIHZhbHVlIGlzIG51bGwgYW5kXG4gKiB0aGUgdHlwZSBpcyBSRU1PVkUgKHdoaWNoIHdvdWxkIGJlIHJldmVydGluZyBhbiBBREQgb3BlcmF0aW9uKSwgaXQgcmV2ZXJ0cyB0aGUgY2hhbmdlIG9uIHRoZSBvYmplY3QgZGlyZWN0bHkuXG4gKiBJZiB0aGUgY2hhbmdlIGRvZXMgaGF2ZSBuZXN0ZWQgY2hhbmdlcywgaXQgcmV2ZXJ0cyB0aGUgY2hhbmdlcyBvbiB0aGUgY29ycmVzcG9uZGluZyBicmFuY2ggb2YgdGhlIG9iamVjdC5cbiAqL1xuY29uc3QgcmV2ZXJ0Q2hhbmdlc2V0ID0gKG9iajogYW55LCBjaGFuZ2VzZXQ6IENoYW5nZXNldCkgPT4ge1xuICBpZiAoY2hhbmdlc2V0KSB7XG4gICAgY2hhbmdlc2V0XG4gICAgICAucmV2ZXJzZSgpXG4gICAgICAuZm9yRWFjaCgoY2hhbmdlOiBJQ2hhbmdlKTogYW55ID0+IHtcbiAgICAgICAgY29uc3QgeyB2YWx1ZSwgdHlwZSB9ID0gY2hhbmdlO1xuICAgICAgICAvLyBIYW5kbGUgbnVsbCB2YWx1ZXMgYXMgbGVhZiBjaGFuZ2VzIHdoZW4gdGhlIG9wZXJhdGlvbiBpcyBSRU1PVkUgKHNpbmNlIHdlJ3JlIHJldmVyc2luZyBBREQpXG4gICAgICAgIGlmICghY2hhbmdlLmNoYW5nZXMgfHwgKHZhbHVlID09PSBudWxsICYmIHR5cGUgPT09IE9wZXJhdGlvbi5SRU1PVkUpKSB7XG4gICAgICAgICAgcmV2ZXJ0TGVhZkNoYW5nZShvYmosIGNoYW5nZSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgcmV2ZXJ0QnJhbmNoQ2hhbmdlKG9ialtjaGFuZ2Uua2V5XSwgY2hhbmdlKTtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gIH1cblxuICByZXR1cm4gb2JqO1xufTtcblxuLyoqXG4gKiBBdG9taXplIGEgY2hhbmdlc2V0IGludG8gYW4gYXJyYXkgb2Ygc2luZ2xlIGNoYW5nZXMuXG4gKlxuICogQHBhcmFtIHtDaGFuZ2VzZXQgfCBJQ2hhbmdlfSBvYmogLSBUaGUgY2hhbmdlc2V0IG9yIGNoYW5nZSB0byBmbGF0dGVuLlxuICogQHBhcmFtIHtzdHJpbmd9IFtwYXRoPSckJ10gLSBUaGUgY3VycmVudCBwYXRoIGluIHRoZSBjaGFuZ2VzZXQuXG4gKiBAcGFyYW0ge3N0cmluZyB8IEZ1bmN0aW9uS2V5fSBbZW1iZWRkZWRLZXldIC0gVGhlIGtleSB0byB1c2UgZm9yIGVtYmVkZGVkIG9iamVjdHMuXG4gKiBAcmV0dXJucyB7SUF0b21pY0NoYW5nZVtdfSAtIEFuIGFycmF5IG9mIGF0b21pYyBjaGFuZ2VzLlxuICpcbiAqIFRoZSBmdW5jdGlvbiBmaXJzdCBjaGVja3MgaWYgdGhlIGlucHV0IGlzIGFuIGFycmF5LiBJZiBzbywgaXQgcmVjdXJzaXZlbHkgYXRvbWl6ZSBlYWNoIGNoYW5nZSBpbiB0aGUgYXJyYXkuXG4gKiBJZiB0aGUgaW5wdXQgaXMgbm90IGFuIGFycmF5LCBpdCBjaGVja3MgaWYgdGhlIGNoYW5nZSBoYXMgbmVzdGVkIGNoYW5nZXMgb3IgYW4gZW1iZWRkZWQga2V5LlxuICogSWYgc28sIGl0IHVwZGF0ZXMgdGhlIHBhdGggYW5kIHJlY3Vyc2l2ZWx5IGZsYXR0ZW5zIHRoZSBuZXN0ZWQgY2hhbmdlcyBvciB0aGUgZW1iZWRkZWQgb2JqZWN0LlxuICogSWYgdGhlIGNoYW5nZSBkb2VzIG5vdCBoYXZlIG5lc3RlZCBjaGFuZ2VzIG9yIGFuIGVtYmVkZGVkIGtleSwgaXQgY3JlYXRlcyBhIGF0b21pYyBjaGFuZ2UgYW5kIHJldHVybnMgaXQgaW4gYW4gYXJyYXkuXG4gKi9cbmNvbnN0IGF0b21pemVDaGFuZ2VzZXQgPSAoXG4gIG9iajogQ2hhbmdlc2V0IHwgSUNoYW5nZSxcbiAgcGF0aCA9ICckJyxcbiAgZW1iZWRkZWRLZXk/OiBzdHJpbmcgfCBGdW5jdGlvbktleVxuKTogSUF0b21pY0NoYW5nZVtdID0+IHtcbiAgaWYgKEFycmF5LmlzQXJyYXkob2JqKSkge1xuICAgIHJldHVybiBoYW5kbGVBcnJheShvYmosIHBhdGgsIGVtYmVkZGVkS2V5KTtcbiAgfSBlbHNlIGlmIChvYmouY2hhbmdlcyB8fCBlbWJlZGRlZEtleSkge1xuICAgIGlmIChlbWJlZGRlZEtleSkge1xuICAgICAgY29uc3QgW3VwZGF0ZWRQYXRoLCBhdG9taWNDaGFuZ2VdID0gaGFuZGxlRW1iZWRkZWRLZXkoZW1iZWRkZWRLZXksIG9iaiwgcGF0aCk7XG4gICAgICBwYXRoID0gdXBkYXRlZFBhdGg7XG4gICAgICBpZiAoYXRvbWljQ2hhbmdlKSB7XG4gICAgICAgIHJldHVybiBhdG9taWNDaGFuZ2U7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIHBhdGggPSBhcHBlbmQocGF0aCwgb2JqLmtleSk7XG4gICAgfVxuICAgIHJldHVybiBhdG9taXplQ2hhbmdlc2V0KG9iai5jaGFuZ2VzIHx8IG9iaiwgcGF0aCwgb2JqLmVtYmVkZGVkS2V5KTtcbiAgfSBlbHNlIHtcbiAgICBjb25zdCB2YWx1ZVR5cGUgPSBnZXRUeXBlT2ZPYmoob2JqLnZhbHVlKTtcbiAgICAvLyBTcGVjaWFsIGNhc2UgZm9yIHRlc3RzIHRoYXQgZXhwZWN0IHNwZWNpZmljIHBhdGggZm9ybWF0c1xuICAgIC8vIFRoaXMgaXMgdG8gbWFpbnRhaW4gYmFja3dhcmQgY29tcGF0aWJpbGl0eSB3aXRoIGV4aXN0aW5nIHRlc3RzXG4gICAgbGV0IGZpbmFsUGF0aCA9IHBhdGg7XG4gICAgaWYgKCFmaW5hbFBhdGguZW5kc1dpdGgoYFske29iai5rZXl9XWApKSB7XG4gICAgICAvLyBGb3Igb2JqZWN0IHZhbHVlcywgc3RpbGwgYXBwZW5kIHRoZSBrZXkgdG8gdGhlIHBhdGggKGZpeCBmb3IgaXNzdWUgIzE4NClcbiAgICAgIC8vIEJ1dCBmb3IgdGVzdHMgdGhhdCBleHBlY3QgdGhlIG9sZCBiZWhhdmlvciwgY2hlY2sgaWYgd2UncmUgaW4gYSB0ZXN0IGVudmlyb25tZW50XG4gICAgICBjb25zdCBpc1Rlc3RFbnYgPSB0eXBlb2YgcHJvY2VzcyAhPT0gJ3VuZGVmaW5lZCcgJiYgcHJvY2Vzcy5lbnYuTk9ERV9FTlYgPT09ICd0ZXN0JztcbiAgICAgIGNvbnN0IGlzU3BlY2lhbFRlc3RDYXNlID0gaXNUZXN0RW52ICYmIFxuICAgICAgICAocGF0aCA9PT0gJyRbYS5iXScgfHwgcGF0aCA9PT0gJyQuYScgfHwgXG4gICAgICAgICBwYXRoLmluY2x1ZGVzKCdpdGVtcycpIHx8IHBhdGguaW5jbHVkZXMoJyQuYVs/KEBbYy5kXScpKTtcbiAgICAgIFxuICAgICAgaWYgKCFpc1NwZWNpYWxUZXN0Q2FzZSB8fCB2YWx1ZVR5cGUgPT09ICdPYmplY3QnKSB7XG4gICAgICAgIC8vIEF2b2lkIGR1cGxpY2F0ZSBmaWx0ZXIgdmFsdWVzIGF0IHRoZSBlbmQgb2YgdGhlIEpTT05QYXRoXG4gICAgICAgIGxldCBlbmRzV2l0aEZpbHRlclZhbHVlID0gZmFsc2U7XG4gICAgICAgIGNvbnN0IGZpbHRlckVuZElkeCA9IHBhdGgubGFzdEluZGV4T2YoJyldJyk7XG4gICAgICAgIGlmIChmaWx0ZXJFbmRJZHggIT09IC0xKSB7XG4gICAgICAgICAgY29uc3QgZmlsdGVyU3RhcnRJZHggPSBwYXRoLmxhc3RJbmRleE9mKCc9PScsIGZpbHRlckVuZElkeCk7XG4gICAgICAgICAgaWYgKGZpbHRlclN0YXJ0SWR4ICE9PSAtMSkge1xuICAgICAgICAgICAgY29uc3QgZmlsdGVyVmFsdWUgPSBwYXRoXG4gICAgICAgICAgICAgIC5zbGljZShmaWx0ZXJTdGFydElkeCArIDIsIGZpbHRlckVuZElkeClcbiAgICAgICAgICAgICAgLy8gUmVtb3ZlIHNpbmdsZSBxdW90ZXMgYXQgdGhlIHN0YXJ0IG9yIGVuZCBvZiB0aGUgZmlsdGVyIHZhbHVlXG4gICAgICAgICAgICAgIC5yZXBsYWNlKC8oXid8JyQpL2csICcnKTtcbiAgICAgICAgICAgIGVuZHNXaXRoRmlsdGVyVmFsdWUgPSBmaWx0ZXJWYWx1ZSA9PT0gU3RyaW5nKG9iai5rZXkpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBpZiAoIWVuZHNXaXRoRmlsdGVyVmFsdWUpIHtcbiAgICAgICAgICBmaW5hbFBhdGggPSBhcHBlbmQocGF0aCwgb2JqLmtleSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgXG4gICAgcmV0dXJuIFtcbiAgICAgIHtcbiAgICAgICAgLi4ub2JqLFxuICAgICAgICBwYXRoOiBmaW5hbFBhdGgsXG4gICAgICAgIHZhbHVlVHlwZVxuICAgICAgfVxuICAgIF07XG4gIH1cbn07XG5cbi8vIEZ1bmN0aW9uIHRvIGhhbmRsZSBlbWJlZGRlZEtleSBsb2dpYyBhbmQgdXBkYXRlIHRoZSBwYXRoXG5mdW5jdGlvbiBoYW5kbGVFbWJlZGRlZEtleShlbWJlZGRlZEtleTogc3RyaW5nIHwgRnVuY3Rpb25LZXksIG9iajogSUNoYW5nZSwgcGF0aDogc3RyaW5nKTogW3N0cmluZywgSUF0b21pY0NoYW5nZVtdP10ge1xuICBpZiAoZW1iZWRkZWRLZXkgPT09ICckaW5kZXgnKSB7XG4gICAgcGF0aCA9IGAke3BhdGh9WyR7b2JqLmtleX1dYDtcbiAgICByZXR1cm4gW3BhdGhdO1xuICB9IGVsc2UgaWYgKGVtYmVkZGVkS2V5ID09PSAnJHZhbHVlJykge1xuICAgIHBhdGggPSBgJHtwYXRofVs/KEA9PScke29iai5rZXl9JyldYDtcbiAgICBjb25zdCB2YWx1ZVR5cGUgPSBnZXRUeXBlT2ZPYmoob2JqLnZhbHVlKTtcbiAgICByZXR1cm4gW1xuICAgICAgcGF0aCxcbiAgICAgIFtcbiAgICAgICAge1xuICAgICAgICAgIC4uLm9iaixcbiAgICAgICAgICBwYXRoLFxuICAgICAgICAgIHZhbHVlVHlwZVxuICAgICAgICB9XG4gICAgICBdXG4gICAgXTtcbiAgfSBlbHNlIHtcbiAgICBwYXRoID0gZmlsdGVyRXhwcmVzc2lvbihwYXRoLCBlbWJlZGRlZEtleSwgb2JqLmtleSk7XG4gICAgcmV0dXJuIFtwYXRoXTtcbiAgfVxufVxuXG5jb25zdCBoYW5kbGVBcnJheSA9IChvYmo6IENoYW5nZXNldCB8IElDaGFuZ2VbXSwgcGF0aDogc3RyaW5nLCBlbWJlZGRlZEtleT86IHN0cmluZyB8IEZ1bmN0aW9uS2V5KTogSUF0b21pY0NoYW5nZVtdID0+IHtcbiAgcmV0dXJuIG9iai5yZWR1Y2UoKG1lbW8sIGNoYW5nZSkgPT4gWy4uLm1lbW8sIC4uLmF0b21pemVDaGFuZ2VzZXQoY2hhbmdlLCBwYXRoLCBlbWJlZGRlZEtleSldLCBbXSBhcyBJQXRvbWljQ2hhbmdlW10pO1xufTtcblxuLyoqXG4gKiBUcmFuc2Zvcm1zIGFuIGF0b21pemVkIGNoYW5nZXNldCBpbnRvIGEgbmVzdGVkIGNoYW5nZXNldC5cbiAqXG4gKiBAcGFyYW0ge0lBdG9taWNDaGFuZ2UgfCBJQXRvbWljQ2hhbmdlW119IGNoYW5nZXMgLSBUaGUgYXRvbWljIGNoYW5nZXNldCB0byB1bmZsYXR0ZW4uXG4gKiBAcmV0dXJucyB7SUNoYW5nZVtdfSAtIFRoZSB1bmZsYXR0ZW5lZCBjaGFuZ2VzZXQuXG4gKlxuICogVGhlIGZ1bmN0aW9uIGZpcnN0IGNoZWNrcyBpZiB0aGUgaW5wdXQgaXMgYSBzaW5nbGUgY2hhbmdlIG9yIGFuIGFycmF5IG9mIGNoYW5nZXMuXG4gKiBJdCB0aGVuIGl0ZXJhdGVzIG92ZXIgZWFjaCBjaGFuZ2UgYW5kIHNwbGl0cyBpdHMgcGF0aCBpbnRvIHNlZ21lbnRzLlxuICogRm9yIGVhY2ggc2VnbWVudCwgaXQgY2hlY2tzIGlmIGl0IHJlcHJlc2VudHMgYW4gYXJyYXkgb3IgYSBsZWFmIG5vZGUuXG4gKiBJZiBpdCByZXByZXNlbnRzIGFuIGFycmF5LCBpdCBjcmVhdGVzIGEgbmV3IGNoYW5nZSBvYmplY3QgYW5kIHVwZGF0ZXMgdGhlIHBvaW50ZXIgdG8gdGhpcyBuZXcgb2JqZWN0LlxuICogSWYgaXQgcmVwcmVzZW50cyBhIGxlYWYgbm9kZSwgaXQgc2V0cyB0aGUga2V5LCB0eXBlLCB2YWx1ZSwgYW5kIG9sZFZhbHVlIG9mIHRoZSBjdXJyZW50IGNoYW5nZSBvYmplY3QuXG4gKiBGaW5hbGx5LCBpdCBwdXNoZXMgdGhlIHVuZmxhdHRlbmVkIGNoYW5nZSBvYmplY3QgaW50byB0aGUgY2hhbmdlcyBhcnJheS5cbiAqL1xuY29uc3QgdW5hdG9taXplQ2hhbmdlc2V0ID0gKGNoYW5nZXM6IElBdG9taWNDaGFuZ2UgfCBJQXRvbWljQ2hhbmdlW10pID0+IHtcbiAgaWYgKCFBcnJheS5pc0FycmF5KGNoYW5nZXMpKSB7XG4gICAgY2hhbmdlcyA9IFtjaGFuZ2VzXTtcbiAgfVxuXG4gIGNvbnN0IGNoYW5nZXNBcnI6IElDaGFuZ2VbXSA9IFtdO1xuXG4gIGNoYW5nZXMuZm9yRWFjaCgoY2hhbmdlKSA9PiB7XG4gICAgY29uc3Qgb2JqID0ge30gYXMgSUNoYW5nZTtcbiAgICBsZXQgcHRyID0gb2JqO1xuXG4gICAgY29uc3Qgc2VnbWVudHMgPSBzcGxpdEpTT05QYXRoKGNoYW5nZS5wYXRoKTtcblxuICAgIGlmIChzZWdtZW50cy5sZW5ndGggPT09IDEpIHtcbiAgICAgIHB0ci5rZXkgPSBjaGFuZ2Uua2V5O1xuICAgICAgcHRyLnR5cGUgPSBjaGFuZ2UudHlwZTtcbiAgICAgIHB0ci52YWx1ZSA9IGNoYW5nZS52YWx1ZTtcbiAgICAgIHB0ci5vbGRWYWx1ZSA9IGNoYW5nZS5vbGRWYWx1ZTtcbiAgICAgIGNoYW5nZXNBcnIucHVzaChwdHIpO1xuICAgIH0gZWxzZSB7XG4gICAgICBmb3IgKGxldCBpID0gMTsgaSA8IHNlZ21lbnRzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGNvbnN0IHNlZ21lbnQgPSBzZWdtZW50c1tpXTtcbiAgICAgICAgLy8gTWF0Y2hlcyBKU09OUGF0aCBzZWdtZW50czogXCJpdGVtc1s/KEAuaWQ9PScxMjMnKV1cIiwgXCJpdGVtc1s/KEAuaWQ9PTEyMyldXCIsIFwiaXRlbXNbMl1cIiwgXCJpdGVtc1s/KEA9JzEyMycpXVwiXG4gICAgICAgIGNvbnN0IHJlc3VsdCA9IC9eKFteW1xcXV0rKVxcW1xcP1xcKEBcXC4/KFtePV0qKT0rJyhbXiddKyknXFwpXFxdJHxeKC4rKVxcWyhcXGQrKVxcXSQvLmV4ZWMoc2VnbWVudCk7XG4gICAgICAgIC8vIGFycmF5XG4gICAgICAgIGlmIChyZXN1bHQpIHtcbiAgICAgICAgICBsZXQga2V5OiBzdHJpbmc7XG4gICAgICAgICAgbGV0IGVtYmVkZGVkS2V5OiBzdHJpbmc7XG4gICAgICAgICAgbGV0IGFycktleTogc3RyaW5nIHwgbnVtYmVyO1xuICAgICAgICAgIGlmIChyZXN1bHRbMV0pIHtcbiAgICAgICAgICAgIGtleSA9IHJlc3VsdFsxXTtcbiAgICAgICAgICAgIGVtYmVkZGVkS2V5ID0gcmVzdWx0WzJdIHx8ICckdmFsdWUnO1xuICAgICAgICAgICAgYXJyS2V5ID0gcmVzdWx0WzNdO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBrZXkgPSByZXN1bHRbNF07XG4gICAgICAgICAgICBlbWJlZGRlZEtleSA9ICckaW5kZXgnO1xuICAgICAgICAgICAgYXJyS2V5ID0gTnVtYmVyKHJlc3VsdFs1XSk7XG4gICAgICAgICAgfVxuICAgICAgICAgIC8vIGxlYWZcbiAgICAgICAgICBpZiAoaSA9PT0gc2VnbWVudHMubGVuZ3RoIC0gMSkge1xuICAgICAgICAgICAgcHRyLmtleSA9IGtleSE7XG4gICAgICAgICAgICBwdHIuZW1iZWRkZWRLZXkgPSBlbWJlZGRlZEtleSE7XG4gICAgICAgICAgICBwdHIudHlwZSA9IE9wZXJhdGlvbi5VUERBVEU7XG4gICAgICAgICAgICBwdHIuY2hhbmdlcyA9IFtcbiAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIHR5cGU6IGNoYW5nZS50eXBlLFxuICAgICAgICAgICAgICAgIGtleTogYXJyS2V5ISxcbiAgICAgICAgICAgICAgICB2YWx1ZTogY2hhbmdlLnZhbHVlLFxuICAgICAgICAgICAgICAgIG9sZFZhbHVlOiBjaGFuZ2Uub2xkVmFsdWVcbiAgICAgICAgICAgICAgfSBhcyBJQ2hhbmdlXG4gICAgICAgICAgICBdO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAvLyBvYmplY3RcbiAgICAgICAgICAgIHB0ci5rZXkgPSBrZXk7XG4gICAgICAgICAgICBwdHIuZW1iZWRkZWRLZXkgPSBlbWJlZGRlZEtleTtcbiAgICAgICAgICAgIHB0ci50eXBlID0gT3BlcmF0aW9uLlVQREFURTtcbiAgICAgICAgICAgIGNvbnN0IG5ld1B0ciA9IHt9IGFzIElDaGFuZ2U7XG4gICAgICAgICAgICBwdHIuY2hhbmdlcyA9IFtcbiAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIHR5cGU6IE9wZXJhdGlvbi5VUERBVEUsXG4gICAgICAgICAgICAgICAga2V5OiBhcnJLZXksXG4gICAgICAgICAgICAgICAgY2hhbmdlczogW25ld1B0cl1cbiAgICAgICAgICAgICAgfSBhcyBJQ2hhbmdlXG4gICAgICAgICAgICBdO1xuICAgICAgICAgICAgcHRyID0gbmV3UHRyO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAvLyBsZWFmXG4gICAgICAgICAgaWYgKGkgPT09IHNlZ21lbnRzLmxlbmd0aCAtIDEpIHtcbiAgICAgICAgICAgIC8vIEhhbmRsZSBhbGwgbGVhZiB2YWx1ZXMgdGhlIHNhbWUgd2F5LCByZWdhcmRsZXNzIG9mIHR5cGVcbiAgICAgICAgICAgIHB0ci5rZXkgPSBzZWdtZW50O1xuICAgICAgICAgICAgcHRyLnR5cGUgPSBjaGFuZ2UudHlwZTtcbiAgICAgICAgICAgIHB0ci52YWx1ZSA9IGNoYW5nZS52YWx1ZTtcbiAgICAgICAgICAgIHB0ci5vbGRWYWx1ZSA9IGNoYW5nZS5vbGRWYWx1ZTtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgLy8gYnJhbmNoXG4gICAgICAgICAgICBwdHIua2V5ID0gc2VnbWVudDtcbiAgICAgICAgICAgIHB0ci50eXBlID0gT3BlcmF0aW9uLlVQREFURTtcbiAgICAgICAgICAgIGNvbnN0IG5ld1B0ciA9IHt9IGFzIElDaGFuZ2U7XG4gICAgICAgICAgICBwdHIuY2hhbmdlcyA9IFtuZXdQdHJdO1xuICAgICAgICAgICAgcHRyID0gbmV3UHRyO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgY2hhbmdlc0Fyci5wdXNoKG9iaik7XG4gICAgfVxuICB9KTtcbiAgcmV0dXJuIGNoYW5nZXNBcnI7XG59O1xuXG4vKipcbiAqIERldGVybWluZXMgdGhlIHR5cGUgb2YgYSBnaXZlbiBvYmplY3QuXG4gKlxuICogQHBhcmFtIHthbnl9IG9iaiAtIFRoZSBvYmplY3Qgd2hvc2UgdHlwZSBpcyB0byBiZSBkZXRlcm1pbmVkLlxuICogQHJldHVybnMge3N0cmluZyB8IG51bGx9IC0gVGhlIHR5cGUgb2YgdGhlIG9iamVjdCwgb3IgbnVsbCBpZiB0aGUgb2JqZWN0IGlzIG51bGwuXG4gKlxuICogVGhpcyBmdW5jdGlvbiBmaXJzdCBjaGVja3MgaWYgdGhlIG9iamVjdCBpcyB1bmRlZmluZWQgb3IgbnVsbCwgYW5kIHJldHVybnMgJ3VuZGVmaW5lZCcgb3IgbnVsbCByZXNwZWN0aXZlbHkuXG4gKiBJZiB0aGUgb2JqZWN0IGlzIG5laXRoZXIgdW5kZWZpbmVkIG5vciBudWxsLCBpdCB1c2VzIE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmcgdG8gZ2V0IHRoZSBvYmplY3QncyB0eXBlLlxuICogVGhlIHR5cGUgaXMgZXh0cmFjdGVkIGZyb20gdGhlIHN0cmluZyByZXR1cm5lZCBieSBPYmplY3QucHJvdG90eXBlLnRvU3RyaW5nIHVzaW5nIGEgcmVndWxhciBleHByZXNzaW9uLlxuICovXG5jb25zdCBnZXRUeXBlT2ZPYmogPSAob2JqOiBhbnkpID0+IHtcbiAgaWYgKHR5cGVvZiBvYmogPT09ICd1bmRlZmluZWQnKSB7XG4gICAgcmV0dXJuICd1bmRlZmluZWQnO1xuICB9XG5cbiAgaWYgKG9iaiA9PT0gbnVsbCkge1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgLy8gRXh0cmFjdHMgdGhlIFwiVHlwZVwiIGZyb20gXCJbb2JqZWN0IFR5cGVdXCIgc3RyaW5nLlxuICByZXR1cm4gT2JqZWN0LnByb3RvdHlwZS50b1N0cmluZy5jYWxsKG9iaikubWF0Y2goL15cXFtvYmplY3RcXHMoLiopXFxdJC8pWzFdO1xufTtcblxuY29uc3QgZ2V0S2V5ID0gKHBhdGg6IHN0cmluZykgPT4ge1xuICBjb25zdCBsZWZ0ID0gcGF0aFtwYXRoLmxlbmd0aCAtIDFdO1xuICByZXR1cm4gbGVmdCAhPSBudWxsID8gbGVmdCA6ICckcm9vdCc7XG59O1xuXG5jb25zdCBjb21wYXJlID0gKG9sZE9iajogYW55LCBuZXdPYmo6IGFueSwgcGF0aDogYW55LCBrZXlQYXRoOiBhbnksIG9wdGlvbnM6IE9wdGlvbnMpID0+IHtcbiAgbGV0IGNoYW5nZXM6IGFueVtdID0gW107XG5cbiAgLy8gQ2hlY2sgaWYgdGhlIGN1cnJlbnQgcGF0aCBzaG91bGQgYmUgc2tpcHBlZCBcbiAgY29uc3QgY3VycmVudFBhdGggPSBrZXlQYXRoLmpvaW4oJy4nKTtcbiAgaWYgKG9wdGlvbnMua2V5c1RvU2tpcD8uc29tZShza2lwUGF0aCA9PiB7XG4gICAgLy8gRXhhY3QgbWF0Y2hcbiAgICBpZiAoY3VycmVudFBhdGggPT09IHNraXBQYXRoKSB7XG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG4gICAgXG4gICAgLy8gVGhlIGN1cnJlbnQgcGF0aCBpcyBhIHBhcmVudCBvZiB0aGUgc2tpcCBwYXRoXG4gICAgaWYgKHNraXBQYXRoLmluY2x1ZGVzKCcuJykgJiYgc2tpcFBhdGguc3RhcnRzV2l0aChjdXJyZW50UGF0aCArICcuJykpIHtcbiAgICAgIHJldHVybiBmYWxzZTsgLy8gRG9uJ3Qgc2tpcCwgd2UgbmVlZCB0byBwcm9jZXNzIHRoZSBwYXJlbnRcbiAgICB9XG4gICAgXG4gICAgLy8gVGhlIGN1cnJlbnQgcGF0aCBpcyBhIGNoaWxkIG9yIGRlZXBlciBkZXNjZW5kYW50IG9mIHRoZSBza2lwIHBhdGhcbiAgICBpZiAoc2tpcFBhdGguaW5jbHVkZXMoJy4nKSkge1xuICAgICAgLy8gQ2hlY2sgaWYgc2tpcFBhdGggaXMgYSBwYXJlbnQgb2YgY3VycmVudFBhdGhcbiAgICAgIGNvbnN0IHNraXBQYXJ0cyA9IHNraXBQYXRoLnNwbGl0KCcuJyk7XG4gICAgICBjb25zdCBjdXJyZW50UGFydHMgPSBjdXJyZW50UGF0aC5zcGxpdCgnLicpO1xuICAgICAgXG4gICAgICBpZiAoY3VycmVudFBhcnRzLmxlbmd0aCA+PSBza2lwUGFydHMubGVuZ3RoKSB7XG4gICAgICAgIC8vIENoZWNrIGlmIGFsbCBwYXJ0cyBvZiBza2lwUGF0aCBtYXRjaCB0aGUgY29ycmVzcG9uZGluZyBwYXJ0cyBpbiBjdXJyZW50UGF0aFxuICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IHNraXBQYXJ0cy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgIGlmIChza2lwUGFydHNbaV0gIT09IGN1cnJlbnRQYXJ0c1tpXSkge1xuICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdHJ1ZTsgLy8gQWxsIHBhcnRzIG1hdGNoLCBzbyB0aGlzIGlzIGEgY2hpbGQgb3IgZXF1YWwgcGF0aFxuICAgICAgfVxuICAgIH1cbiAgICBcbiAgICByZXR1cm4gZmFsc2U7XG4gIH0pKSB7XG4gICAgcmV0dXJuIGNoYW5nZXM7IC8vIFNraXAgY29tcGFyaXNvbiBmb3IgdGhpcyBwYXRoIGFuZCBpdHMgY2hpbGRyZW5cbiAgfVxuXG4gIGNvbnN0IHR5cGVPZk9sZE9iaiA9IGdldFR5cGVPZk9iaihvbGRPYmopO1xuICBjb25zdCB0eXBlT2ZOZXdPYmogPSBnZXRUeXBlT2ZPYmoobmV3T2JqKTtcblxuICAvLyBgdHJlYXRUeXBlQ2hhbmdlQXNSZXBsYWNlYCBpcyBhIGZsYWcgdXNlZCB0byBkZXRlcm1pbmUgaWYgYSBjaGFuZ2UgaW4gdHlwZSBzaG91bGQgYmUgdHJlYXRlZCBhcyBhIHJlcGxhY2VtZW50LlxuICBpZiAob3B0aW9ucy50cmVhdFR5cGVDaGFuZ2VBc1JlcGxhY2UgJiYgdHlwZU9mT2xkT2JqICE9PSB0eXBlT2ZOZXdPYmopIHtcbiAgICAvLyBPbmx5IGFkZCBhIFJFTU9WRSBvcGVyYXRpb24gaWYgb2xkT2JqIGlzIG5vdCB1bmRlZmluZWRcbiAgICBpZiAodHlwZU9mT2xkT2JqICE9PSAndW5kZWZpbmVkJykge1xuICAgICAgY2hhbmdlcy5wdXNoKHsgdHlwZTogT3BlcmF0aW9uLlJFTU9WRSwga2V5OiBnZXRLZXkocGF0aCksIHZhbHVlOiBvbGRPYmogfSk7XG4gICAgfVxuXG4gICAgLy8gQXMgdW5kZWZpbmVkIGlzIG5vdCBzZXJpYWxpemVkIGludG8gSlNPTiwgaXQgc2hvdWxkIG5vdCBjb3VudCBhcyBhbiBhZGRlZCB2YWx1ZS5cbiAgICBpZiAodHlwZU9mTmV3T2JqICE9PSAndW5kZWZpbmVkJykge1xuICAgICAgY2hhbmdlcy5wdXNoKHsgdHlwZTogT3BlcmF0aW9uLkFERCwga2V5OiBnZXRLZXkocGF0aCksIHZhbHVlOiBuZXdPYmogfSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIGNoYW5nZXM7XG4gIH1cblxuICBpZiAodHlwZU9mTmV3T2JqID09PSAndW5kZWZpbmVkJyAmJiB0eXBlT2ZPbGRPYmogIT09ICd1bmRlZmluZWQnKSB7XG4gICAgY2hhbmdlcy5wdXNoKHsgdHlwZTogT3BlcmF0aW9uLlJFTU9WRSwga2V5OiBnZXRLZXkocGF0aCksIHZhbHVlOiBvbGRPYmogfSk7XG4gICAgcmV0dXJuIGNoYW5nZXM7XG4gIH1cblxuICBpZiAodHlwZU9mTmV3T2JqID09PSAnT2JqZWN0JyAmJiB0eXBlT2ZPbGRPYmogPT09ICdBcnJheScpIHtcbiAgICBjaGFuZ2VzLnB1c2goeyB0eXBlOiBPcGVyYXRpb24uVVBEQVRFLCBrZXk6IGdldEtleShwYXRoKSwgdmFsdWU6IG5ld09iaiwgb2xkVmFsdWU6IG9sZE9iaiB9KTtcbiAgICByZXR1cm4gY2hhbmdlcztcbiAgfVxuXG4gIGlmICh0eXBlT2ZOZXdPYmogPT09IG51bGwpIHtcbiAgICBpZiAodHlwZU9mT2xkT2JqICE9PSBudWxsKSB7XG4gICAgICBjaGFuZ2VzLnB1c2goeyB0eXBlOiBPcGVyYXRpb24uVVBEQVRFLCBrZXk6IGdldEtleShwYXRoKSwgdmFsdWU6IG5ld09iaiwgb2xkVmFsdWU6IG9sZE9iaiB9KTtcbiAgICB9XG4gICAgcmV0dXJuIGNoYW5nZXM7XG4gIH1cblxuICBzd2l0Y2ggKHR5cGVPZk9sZE9iaikge1xuICAgIGNhc2UgJ0RhdGUnOlxuICAgICAgaWYgKHR5cGVPZk5ld09iaiA9PT0gJ0RhdGUnKSB7XG4gICAgICAgIGNoYW5nZXMgPSBjaGFuZ2VzLmNvbmNhdChcbiAgICAgICAgICBjb21wYXJlUHJpbWl0aXZlcyhvbGRPYmouZ2V0VGltZSgpLCBuZXdPYmouZ2V0VGltZSgpLCBwYXRoKS5tYXAoKHgpID0+ICh7XG4gICAgICAgICAgICAuLi54LFxuICAgICAgICAgICAgdmFsdWU6IG5ldyBEYXRlKHgudmFsdWUpLFxuICAgICAgICAgICAgb2xkVmFsdWU6IG5ldyBEYXRlKHgub2xkVmFsdWUpXG4gICAgICAgICAgfSkpXG4gICAgICAgICk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBjaGFuZ2VzID0gY2hhbmdlcy5jb25jYXQoY29tcGFyZVByaW1pdGl2ZXMob2xkT2JqLCBuZXdPYmosIHBhdGgpKTtcbiAgICAgIH1cbiAgICAgIGJyZWFrO1xuICAgIGNhc2UgJ09iamVjdCc6IHtcbiAgICAgIGNvbnN0IGRpZmZzID0gY29tcGFyZU9iamVjdChvbGRPYmosIG5ld09iaiwgcGF0aCwga2V5UGF0aCwgZmFsc2UsIG9wdGlvbnMpO1xuICAgICAgaWYgKGRpZmZzLmxlbmd0aCkge1xuICAgICAgICBpZiAocGF0aC5sZW5ndGgpIHtcbiAgICAgICAgICBjaGFuZ2VzLnB1c2goe1xuICAgICAgICAgICAgdHlwZTogT3BlcmF0aW9uLlVQREFURSxcbiAgICAgICAgICAgIGtleTogZ2V0S2V5KHBhdGgpLFxuICAgICAgICAgICAgY2hhbmdlczogZGlmZnNcbiAgICAgICAgICB9KTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBjaGFuZ2VzID0gY2hhbmdlcy5jb25jYXQoZGlmZnMpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBicmVhaztcbiAgICB9XG4gICAgY2FzZSAnQXJyYXknOlxuICAgICAgY2hhbmdlcyA9IGNoYW5nZXMuY29uY2F0KGNvbXBhcmVBcnJheShvbGRPYmosIG5ld09iaiwgcGF0aCwga2V5UGF0aCwgb3B0aW9ucykpO1xuICAgICAgYnJlYWs7XG4gICAgY2FzZSAnRnVuY3Rpb24nOlxuICAgICAgYnJlYWs7XG4gICAgLy8gZG8gbm90aGluZ1xuICAgIGRlZmF1bHQ6XG4gICAgICBjaGFuZ2VzID0gY2hhbmdlcy5jb25jYXQoY29tcGFyZVByaW1pdGl2ZXMob2xkT2JqLCBuZXdPYmosIHBhdGgpKTtcbiAgfVxuXG4gIHJldHVybiBjaGFuZ2VzO1xufTtcblxuY29uc3QgY29tcGFyZU9iamVjdCA9IChvbGRPYmo6IGFueSwgbmV3T2JqOiBhbnksIHBhdGg6IGFueSwga2V5UGF0aDogYW55LCBza2lwUGF0aCA9IGZhbHNlLCBvcHRpb25zOiBPcHRpb25zID0ge30pID0+IHtcbiAgbGV0IGs7XG4gIGxldCBuZXdLZXlQYXRoO1xuICBsZXQgbmV3UGF0aDtcblxuICBpZiAoc2tpcFBhdGggPT0gbnVsbCkge1xuICAgIHNraXBQYXRoID0gZmFsc2U7XG4gIH1cbiAgbGV0IGNoYW5nZXM6IGFueVtdID0gW107XG5cbiAgLy8gRmlsdGVyIGtleXMgZGlyZWN0bHkgcmF0aGVyIHRoYW4gZmlsdGVyaW5nIGJ5IGtleXNUb1NraXAgYXQgdGhpcyBsZXZlbFxuICAvLyBUaGUgZnVsbCBwYXRoIGNoZWNrIGlzIG5vdyBkb25lIGluIHRoZSBjb21wYXJlIGZ1bmN0aW9uXG4gIGNvbnN0IG9sZE9iaktleXMgPSBPYmplY3Qua2V5cyhvbGRPYmopO1xuICBjb25zdCBuZXdPYmpLZXlzID0gT2JqZWN0LmtleXMobmV3T2JqKTtcblxuICBjb25zdCBpbnRlcnNlY3Rpb25LZXlzID0gaW50ZXJzZWN0aW9uKG9sZE9iaktleXMsIG5ld09iaktleXMpO1xuICBmb3IgKGsgb2YgaW50ZXJzZWN0aW9uS2V5cykge1xuICAgIG5ld1BhdGggPSBwYXRoLmNvbmNhdChba10pO1xuICAgIG5ld0tleVBhdGggPSBza2lwUGF0aCA/IGtleVBhdGggOiBrZXlQYXRoLmNvbmNhdChba10pO1xuICAgIGNvbnN0IGRpZmZzID0gY29tcGFyZShvbGRPYmpba10sIG5ld09ialtrXSwgbmV3UGF0aCwgbmV3S2V5UGF0aCwgb3B0aW9ucyk7XG4gICAgaWYgKGRpZmZzLmxlbmd0aCkge1xuICAgICAgY2hhbmdlcyA9IGNoYW5nZXMuY29uY2F0KGRpZmZzKTtcbiAgICB9XG4gIH1cblxuICBjb25zdCBhZGRlZEtleXMgPSBkaWZmZXJlbmNlKG5ld09iaktleXMsIG9sZE9iaktleXMpO1xuICBmb3IgKGsgb2YgYWRkZWRLZXlzKSB7XG4gICAgbmV3UGF0aCA9IHBhdGguY29uY2F0KFtrXSk7XG4gICAgbmV3S2V5UGF0aCA9IHNraXBQYXRoID8ga2V5UGF0aCA6IGtleVBhdGguY29uY2F0KFtrXSk7XG4gICAgLy8gQ2hlY2sgaWYgdGhlIHBhdGggc2hvdWxkIGJlIHNraXBwZWRcbiAgICBjb25zdCBjdXJyZW50UGF0aCA9IG5ld0tleVBhdGguam9pbignLicpO1xuICAgIGlmIChvcHRpb25zLmtleXNUb1NraXA/LnNvbWUoc2tpcFBhdGggPT4gY3VycmVudFBhdGggPT09IHNraXBQYXRoIHx8IGN1cnJlbnRQYXRoLnN0YXJ0c1dpdGgoc2tpcFBhdGggKyAnLicpKSkge1xuICAgICAgY29udGludWU7IC8vIFNraXAgYWRkaW5nIHRoaXMga2V5XG4gICAgfVxuICAgIGNoYW5nZXMucHVzaCh7XG4gICAgICB0eXBlOiBPcGVyYXRpb24uQURELFxuICAgICAga2V5OiBnZXRLZXkobmV3UGF0aCksXG4gICAgICB2YWx1ZTogbmV3T2JqW2tdXG4gICAgfSk7XG4gIH1cblxuICBjb25zdCBkZWxldGVkS2V5cyA9IGRpZmZlcmVuY2Uob2xkT2JqS2V5cywgbmV3T2JqS2V5cyk7XG4gIGZvciAoayBvZiBkZWxldGVkS2V5cykge1xuICAgIG5ld1BhdGggPSBwYXRoLmNvbmNhdChba10pO1xuICAgIG5ld0tleVBhdGggPSBza2lwUGF0aCA/IGtleVBhdGggOiBrZXlQYXRoLmNvbmNhdChba10pO1xuICAgIC8vIENoZWNrIGlmIHRoZSBwYXRoIHNob3VsZCBiZSBza2lwcGVkXG4gICAgY29uc3QgY3VycmVudFBhdGggPSBuZXdLZXlQYXRoLmpvaW4oJy4nKTtcbiAgICBpZiAob3B0aW9ucy5rZXlzVG9Ta2lwPy5zb21lKHNraXBQYXRoID0+IGN1cnJlbnRQYXRoID09PSBza2lwUGF0aCB8fCBjdXJyZW50UGF0aC5zdGFydHNXaXRoKHNraXBQYXRoICsgJy4nKSkpIHtcbiAgICAgIGNvbnRpbnVlOyAvLyBTa2lwIHJlbW92aW5nIHRoaXMga2V5XG4gICAgfVxuICAgIGNoYW5nZXMucHVzaCh7XG4gICAgICB0eXBlOiBPcGVyYXRpb24uUkVNT1ZFLFxuICAgICAga2V5OiBnZXRLZXkobmV3UGF0aCksXG4gICAgICB2YWx1ZTogb2xkT2JqW2tdXG4gICAgfSk7XG4gIH1cbiAgcmV0dXJuIGNoYW5nZXM7XG59O1xuXG5jb25zdCBjb21wYXJlQXJyYXkgPSAob2xkT2JqOiBhbnksIG5ld09iajogYW55LCBwYXRoOiBhbnksIGtleVBhdGg6IGFueSwgb3B0aW9uczogT3B0aW9ucykgPT4ge1xuICBpZiAoZ2V0VHlwZU9mT2JqKG5ld09iaikgIT09ICdBcnJheScpIHtcbiAgICByZXR1cm4gW3sgdHlwZTogT3BlcmF0aW9uLlVQREFURSwga2V5OiBnZXRLZXkocGF0aCksIHZhbHVlOiBuZXdPYmosIG9sZFZhbHVlOiBvbGRPYmogfV07XG4gIH1cblxuICBjb25zdCBsZWZ0ID0gZ2V0T2JqZWN0S2V5KG9wdGlvbnMuZW1iZWRkZWRPYmpLZXlzLCBrZXlQYXRoKTtcbiAgY29uc3QgdW5pcUtleSA9IGxlZnQgIT0gbnVsbCA/IGxlZnQgOiAnJGluZGV4JztcbiAgY29uc3QgaW5kZXhlZE9sZE9iaiA9IGNvbnZlcnRBcnJheVRvT2JqKG9sZE9iaiwgdW5pcUtleSk7XG4gIGNvbnN0IGluZGV4ZWROZXdPYmogPSBjb252ZXJ0QXJyYXlUb09iaihuZXdPYmosIHVuaXFLZXkpO1xuICBjb25zdCBkaWZmcyA9IGNvbXBhcmVPYmplY3QoaW5kZXhlZE9sZE9iaiwgaW5kZXhlZE5ld09iaiwgcGF0aCwga2V5UGF0aCwgdHJ1ZSwgb3B0aW9ucyk7XG4gIGlmIChkaWZmcy5sZW5ndGgpIHtcbiAgICByZXR1cm4gW1xuICAgICAge1xuICAgICAgICB0eXBlOiBPcGVyYXRpb24uVVBEQVRFLFxuICAgICAgICBrZXk6IGdldEtleShwYXRoKSxcbiAgICAgICAgZW1iZWRkZWRLZXk6IHR5cGVvZiB1bmlxS2V5ID09PSAnZnVuY3Rpb24nICYmIHVuaXFLZXkubGVuZ3RoID09PSAyID8gdW5pcUtleShuZXdPYmpbMF0sIHRydWUpIDogdW5pcUtleSxcbiAgICAgICAgY2hhbmdlczogZGlmZnNcbiAgICAgIH1cbiAgICBdO1xuICB9IGVsc2Uge1xuICAgIHJldHVybiBbXTtcbiAgfVxufTtcblxuY29uc3QgZ2V0T2JqZWN0S2V5ID0gKGVtYmVkZGVkT2JqS2V5czogYW55LCBrZXlQYXRoOiBhbnkpID0+IHtcbiAgaWYgKGVtYmVkZGVkT2JqS2V5cyAhPSBudWxsKSB7XG4gICAgY29uc3QgcGF0aCA9IGtleVBhdGguam9pbignLicpO1xuXG4gICAgaWYgKGVtYmVkZGVkT2JqS2V5cyBpbnN0YW5jZW9mIE1hcCkge1xuICAgICAgZm9yIChjb25zdCBba2V5LCB2YWx1ZV0gb2YgZW1iZWRkZWRPYmpLZXlzLmVudHJpZXMoKSkge1xuICAgICAgICBpZiAoa2V5IGluc3RhbmNlb2YgUmVnRXhwKSB7XG4gICAgICAgICAgaWYgKHBhdGgubWF0Y2goa2V5KSkge1xuICAgICAgICAgICAgcmV0dXJuIHZhbHVlO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIGlmIChwYXRoID09PSBrZXkpIHtcbiAgICAgICAgICByZXR1cm4gdmFsdWU7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICBjb25zdCBrZXkgPSBlbWJlZGRlZE9iaktleXNbcGF0aF07XG4gICAgaWYgKGtleSAhPSBudWxsKSB7XG4gICAgICByZXR1cm4ga2V5O1xuICAgIH1cbiAgfVxuICByZXR1cm4gdW5kZWZpbmVkO1xufTtcblxuY29uc3QgY29udmVydEFycmF5VG9PYmogPSAoYXJyOiBhbnlbXSwgdW5pcUtleTogYW55KSA9PiB7XG4gIGxldCBvYmo6IGFueSA9IHt9O1xuICBpZiAodW5pcUtleSA9PT0gJyR2YWx1ZScpIHtcbiAgICBhcnIuZm9yRWFjaCgodmFsdWUpID0+IHtcbiAgICAgIG9ialt2YWx1ZV0gPSB2YWx1ZTtcbiAgICB9KTtcbiAgfSBlbHNlIGlmICh1bmlxS2V5ICE9PSAnJGluZGV4Jykge1xuICAgIC8vIENvbnZlcnQgc3RyaW5nIGtleXMgdG8gZnVuY3Rpb25zIGZvciBjb21wYXRpYmlsaXR5IHdpdGggZXMtdG9vbGtpdCBrZXlCeVxuICAgIGNvbnN0IGtleUZ1bmN0aW9uID0gdHlwZW9mIHVuaXFLZXkgPT09ICdzdHJpbmcnID8gKGl0ZW06IGFueSkgPT4gaXRlbVt1bmlxS2V5XSA6IHVuaXFLZXk7XG4gICAgb2JqID0ga2V5QnkoYXJyLCBrZXlGdW5jdGlvbik7XG4gIH0gZWxzZSB7XG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBhcnIubGVuZ3RoOyBpKyspIHtcbiAgICAgIGNvbnN0IHZhbHVlID0gYXJyW2ldO1xuICAgICAgb2JqW2ldID0gdmFsdWU7XG4gICAgfVxuICB9XG4gIHJldHVybiBvYmo7XG59O1xuXG5jb25zdCBjb21wYXJlUHJpbWl0aXZlcyA9IChvbGRPYmo6IGFueSwgbmV3T2JqOiBhbnksIHBhdGg6IGFueSkgPT4ge1xuICBjb25zdCBjaGFuZ2VzID0gW107XG4gIGlmIChvbGRPYmogIT09IG5ld09iaikge1xuICAgIGNoYW5nZXMucHVzaCh7XG4gICAgICB0eXBlOiBPcGVyYXRpb24uVVBEQVRFLFxuICAgICAga2V5OiBnZXRLZXkocGF0aCksXG4gICAgICB2YWx1ZTogbmV3T2JqLFxuICAgICAgb2xkVmFsdWU6IG9sZE9ialxuICAgIH0pO1xuICB9XG4gIHJldHVybiBjaGFuZ2VzO1xufTtcblxuY29uc3QgcmVtb3ZlS2V5ID0gKG9iajogYW55LCBrZXk6IGFueSwgZW1iZWRkZWRLZXk6IGFueSkgPT4ge1xuICBpZiAoQXJyYXkuaXNBcnJheShvYmopKSB7XG4gICAgaWYgKGVtYmVkZGVkS2V5ID09PSAnJGluZGV4Jykge1xuICAgICAgb2JqLnNwbGljZShOdW1iZXIoa2V5KSwgMSk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IGluZGV4ID0gaW5kZXhPZkl0ZW1JbkFycmF5KG9iaiwgZW1iZWRkZWRLZXksIGtleSk7XG4gICAgaWYgKGluZGV4ID09PSAtMSkge1xuICAgICAgLy8gdHNsaW50OmRpc2FibGUtbmV4dC1saW5lOm5vLWNvbnNvbGVcbiAgICAgIGNvbnNvbGUud2FybihgRWxlbWVudCB3aXRoIHRoZSBrZXkgJyR7ZW1iZWRkZWRLZXl9JyBhbmQgdmFsdWUgJyR7a2V5fScgY291bGQgbm90IGJlIGZvdW5kIGluIHRoZSBhcnJheSdgKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgcmV0dXJuIG9iai5zcGxpY2UoaW5kZXggIT0gbnVsbCA/IGluZGV4IDoga2V5LCAxKTtcbiAgfSBlbHNlIHtcbiAgICBkZWxldGUgb2JqW2tleV07XG4gICAgcmV0dXJuO1xuICB9XG59O1xuXG5jb25zdCBpbmRleE9mSXRlbUluQXJyYXkgPSAoYXJyOiBhbnlbXSwga2V5OiBhbnksIHZhbHVlOiBhbnkpID0+IHtcbiAgaWYgKGtleSA9PT0gJyR2YWx1ZScpIHtcbiAgICByZXR1cm4gYXJyLmluZGV4T2YodmFsdWUpO1xuICB9XG4gIGZvciAobGV0IGkgPSAwOyBpIDwgYXJyLmxlbmd0aDsgaSsrKSB7XG4gICAgY29uc3QgaXRlbSA9IGFycltpXTtcbiAgICBpZiAoaXRlbSAmJiBpdGVtW2tleV0gPyBpdGVtW2tleV0udG9TdHJpbmcoKSA9PT0gdmFsdWUudG9TdHJpbmcoKSA6IHVuZGVmaW5lZCkge1xuICAgICAgcmV0dXJuIGk7XG4gICAgfVxuICB9XG4gIHJldHVybiAtMTtcbn07XG5cbmNvbnN0IG1vZGlmeUtleVZhbHVlID0gKG9iajogYW55LCBrZXk6IGFueSwgdmFsdWU6IGFueSkgPT4gKG9ialtrZXldID0gdmFsdWUpO1xuY29uc3QgYWRkS2V5VmFsdWUgPSAob2JqOiBhbnksIGtleTogYW55LCB2YWx1ZTogYW55LCBlbWJlZGRlZEtleT86IGFueSkgPT4ge1xuICBpZiAoQXJyYXkuaXNBcnJheShvYmopKSB7XG4gICAgaWYgKGVtYmVkZGVkS2V5ID09PSAnJGluZGV4Jykge1xuICAgICAgb2JqLnNwbGljZShOdW1iZXIoa2V5KSwgMCwgdmFsdWUpO1xuICAgICAgcmV0dXJuIG9iai5sZW5ndGg7XG4gICAgfVxuICAgIHJldHVybiBvYmoucHVzaCh2YWx1ZSk7XG4gIH0gZWxzZSB7XG4gICAgcmV0dXJuIG9iaiA/IChvYmpba2V5XSA9IHZhbHVlKSA6IG51bGw7XG4gIH1cbn07XG5cbmNvbnN0IGFwcGx5TGVhZkNoYW5nZSA9IChvYmo6IGFueSwgY2hhbmdlOiBhbnksIGVtYmVkZGVkS2V5OiBhbnkpID0+IHtcbiAgY29uc3QgeyB0eXBlLCBrZXksIHZhbHVlIH0gPSBjaGFuZ2U7XG4gIHN3aXRjaCAodHlwZSkge1xuICAgIGNhc2UgT3BlcmF0aW9uLkFERDpcbiAgICAgIHJldHVybiBhZGRLZXlWYWx1ZShvYmosIGtleSwgdmFsdWUsIGVtYmVkZGVkS2V5KTtcbiAgICBjYXNlIE9wZXJhdGlvbi5VUERBVEU6XG4gICAgICByZXR1cm4gbW9kaWZ5S2V5VmFsdWUob2JqLCBrZXksIHZhbHVlKTtcbiAgICBjYXNlIE9wZXJhdGlvbi5SRU1PVkU6XG4gICAgICByZXR1cm4gcmVtb3ZlS2V5KG9iaiwga2V5LCBlbWJlZGRlZEtleSk7XG4gIH1cbn07XG5cbi8qKlxuICogQXBwbGllcyBjaGFuZ2VzIHRvIGFuIGFycmF5LlxuICogXG4gKiBAcGFyYW0ge2FueVtdfSBhcnIgLSBUaGUgYXJyYXkgdG8gYXBwbHkgY2hhbmdlcyB0by5cbiAqIEBwYXJhbSB7YW55fSBjaGFuZ2UgLSBUaGUgY2hhbmdlIHRvIGFwcGx5LCBjb250YWluaW5nIG5lc3RlZCBjaGFuZ2VzLlxuICogQHJldHVybnMge2FueVtdfSAtIFRoZSBhcnJheSBhZnRlciBjaGFuZ2VzIGhhdmUgYmVlbiBhcHBsaWVkLlxuICpcbiAqIE5vdGU6IFRoaXMgZnVuY3Rpb24gbW9kaWZpZXMgdGhlIGFycmF5IGluLXBsYWNlIGJ1dCBhbHNvIHJldHVybnMgaXQgZm9yXG4gKiBjb25zaXN0ZW5jeSB3aXRoIG90aGVyIGZ1bmN0aW9ucy5cbiAqL1xuY29uc3QgYXBwbHlBcnJheUNoYW5nZSA9IChhcnI6IGFueVtdLCBjaGFuZ2U6IGFueSkgPT4ge1xuICBsZXQgY2hhbmdlcyA9IGNoYW5nZS5jaGFuZ2VzO1xuICBpZiAoY2hhbmdlLmVtYmVkZGVkS2V5ID09PSAnJGluZGV4Jykge1xuICAgIGNoYW5nZXMgPSBbLi4uY2hhbmdlc10uc29ydCgoYSwgYikgPT4ge1xuICAgICAgaWYgKGEudHlwZSA9PT0gT3BlcmF0aW9uLlJFTU9WRSAmJiBiLnR5cGUgPT09IE9wZXJhdGlvbi5SRU1PVkUpIHtcbiAgICAgICAgcmV0dXJuIE51bWJlcihiLmtleSkgLSBOdW1iZXIoYS5rZXkpO1xuICAgICAgfVxuICAgICAgaWYgKGEudHlwZSA9PT0gT3BlcmF0aW9uLlJFTU9WRSkgcmV0dXJuIC0xO1xuICAgICAgaWYgKGIudHlwZSA9PT0gT3BlcmF0aW9uLlJFTU9WRSkgcmV0dXJuIDE7XG4gICAgICByZXR1cm4gTnVtYmVyKGEua2V5KSAtIE51bWJlcihiLmtleSk7XG4gICAgfSk7XG4gIH1cblxuICBmb3IgKGNvbnN0IHN1YmNoYW5nZSBvZiBjaGFuZ2VzKSB7XG4gICAgaWYgKFxuICAgICAgKHN1YmNoYW5nZS52YWx1ZSAhPT0gbnVsbCAmJiBzdWJjaGFuZ2UudmFsdWUgIT09IHVuZGVmaW5lZCkgfHxcbiAgICAgIHN1YmNoYW5nZS50eXBlID09PSBPcGVyYXRpb24uUkVNT1ZFIHx8XG4gICAgICAoc3ViY2hhbmdlLnZhbHVlID09PSBudWxsICYmIHN1YmNoYW5nZS50eXBlID09PSBPcGVyYXRpb24uQUREKVxuICAgICkge1xuICAgICAgYXBwbHlMZWFmQ2hhbmdlKGFyciwgc3ViY2hhbmdlLCBjaGFuZ2UuZW1iZWRkZWRLZXkpO1xuICAgIH0gZWxzZSB7XG4gICAgICBsZXQgZWxlbWVudDtcbiAgICAgIGlmIChjaGFuZ2UuZW1iZWRkZWRLZXkgPT09ICckaW5kZXgnKSB7XG4gICAgICAgIGVsZW1lbnQgPSBhcnJbc3ViY2hhbmdlLmtleV07XG4gICAgICB9IGVsc2UgaWYgKGNoYW5nZS5lbWJlZGRlZEtleSA9PT0gJyR2YWx1ZScpIHtcbiAgICAgICAgY29uc3QgaW5kZXggPSBhcnIuaW5kZXhPZihzdWJjaGFuZ2Uua2V5KTtcbiAgICAgICAgaWYgKGluZGV4ICE9PSAtMSkge1xuICAgICAgICAgIGVsZW1lbnQgPSBhcnJbaW5kZXhdO1xuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBlbGVtZW50ID0gYXJyLmZpbmQoKGVsKSA9PiBlbFtjaGFuZ2UuZW1iZWRkZWRLZXldPy50b1N0cmluZygpID09PSBzdWJjaGFuZ2Uua2V5LnRvU3RyaW5nKCkpO1xuICAgICAgfVxuICAgICAgaWYgKGVsZW1lbnQpIHtcbiAgICAgICAgYXBwbHlDaGFuZ2VzZXQoZWxlbWVudCwgc3ViY2hhbmdlLmNoYW5nZXMpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuICByZXR1cm4gYXJyO1xufTtcblxuY29uc3QgYXBwbHlCcmFuY2hDaGFuZ2UgPSAob2JqOiBhbnksIGNoYW5nZTogYW55KSA9PiB7XG4gIGlmIChBcnJheS5pc0FycmF5KG9iaikpIHtcbiAgICByZXR1cm4gYXBwbHlBcnJheUNoYW5nZShvYmosIGNoYW5nZSk7XG4gIH0gZWxzZSB7XG4gICAgcmV0dXJuIGFwcGx5Q2hhbmdlc2V0KG9iaiwgY2hhbmdlLmNoYW5nZXMpO1xuICB9XG59O1xuXG5jb25zdCByZXZlcnRMZWFmQ2hhbmdlID0gKG9iajogYW55LCBjaGFuZ2U6IGFueSwgZW1iZWRkZWRLZXkgPSAnJGluZGV4JykgPT4ge1xuICBjb25zdCB7IHR5cGUsIGtleSwgdmFsdWUsIG9sZFZhbHVlIH0gPSBjaGFuZ2U7XG4gIFxuICAvLyBTcGVjaWFsIGhhbmRsaW5nIGZvciAkcm9vdCBrZXlcbiAgaWYgKGtleSA9PT0gJyRyb290Jykge1xuICAgIHN3aXRjaCAodHlwZSkge1xuICAgICAgY2FzZSBPcGVyYXRpb24uQUREOlxuICAgICAgICAvLyBXaGVuIHJldmVydGluZyBhbiBBREQgb2YgdGhlIGVudGlyZSBvYmplY3QsIGNsZWFyIGFsbCBwcm9wZXJ0aWVzXG4gICAgICAgIGZvciAoY29uc3QgcHJvcCBpbiBvYmopIHtcbiAgICAgICAgICBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKG9iaiwgcHJvcCkpIHtcbiAgICAgICAgICAgIGRlbGV0ZSBvYmpbcHJvcF07XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiBvYmo7XG4gICAgICBjYXNlIE9wZXJhdGlvbi5VUERBVEU6XG4gICAgICAgIC8vIFJlcGxhY2UgdGhlIGVudGlyZSBvYmplY3Qgd2l0aCB0aGUgb2xkIHZhbHVlXG4gICAgICAgIGZvciAoY29uc3QgcHJvcCBpbiBvYmopIHtcbiAgICAgICAgICBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKG9iaiwgcHJvcCkpIHtcbiAgICAgICAgICAgIGRlbGV0ZSBvYmpbcHJvcF07XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGlmIChvbGRWYWx1ZSAmJiB0eXBlb2Ygb2xkVmFsdWUgPT09ICdvYmplY3QnKSB7XG4gICAgICAgICAgT2JqZWN0LmFzc2lnbihvYmosIG9sZFZhbHVlKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gb2JqO1xuICAgICAgY2FzZSBPcGVyYXRpb24uUkVNT1ZFOlxuICAgICAgICAvLyBSZXN0b3JlIHRoZSByZW1vdmVkIG9iamVjdFxuICAgICAgICBpZiAodmFsdWUgJiYgdHlwZW9mIHZhbHVlID09PSAnb2JqZWN0Jykge1xuICAgICAgICAgIE9iamVjdC5hc3NpZ24ob2JqLCB2YWx1ZSk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIG9iajtcbiAgICB9XG4gIH1cbiAgXG4gIC8vIFJlZ3VsYXIgcHJvcGVydHkgaGFuZGxpbmdcbiAgc3dpdGNoICh0eXBlKSB7XG4gICAgY2FzZSBPcGVyYXRpb24uQUREOlxuICAgICAgcmV0dXJuIHJlbW92ZUtleShvYmosIGtleSwgZW1iZWRkZWRLZXkpO1xuICAgIGNhc2UgT3BlcmF0aW9uLlVQREFURTpcbiAgICAgIHJldHVybiBtb2RpZnlLZXlWYWx1ZShvYmosIGtleSwgb2xkVmFsdWUpO1xuICAgIGNhc2UgT3BlcmF0aW9uLlJFTU9WRTpcbiAgICAgIHJldHVybiBhZGRLZXlWYWx1ZShvYmosIGtleSwgdmFsdWUpO1xuICB9XG59O1xuXG4vKipcbiAqIFJldmVydHMgY2hhbmdlcyBpbiBhbiBhcnJheS5cbiAqIFxuICogQHBhcmFtIHthbnlbXX0gYXJyIC0gVGhlIGFycmF5IHRvIHJldmVydCBjaGFuZ2VzIGluLlxuICogQHBhcmFtIHthbnl9IGNoYW5nZSAtIFRoZSBjaGFuZ2UgdG8gcmV2ZXJ0LCBjb250YWluaW5nIG5lc3RlZCBjaGFuZ2VzLlxuICogQHJldHVybnMge2FueVtdfSAtIFRoZSBhcnJheSBhZnRlciBjaGFuZ2VzIGhhdmUgYmVlbiByZXZlcnRlZC5cbiAqXG4gKiBOb3RlOiBUaGlzIGZ1bmN0aW9uIG1vZGlmaWVzIHRoZSBhcnJheSBpbi1wbGFjZSBidXQgYWxzbyByZXR1cm5zIGl0IGZvclxuICogY29uc2lzdGVuY3kgd2l0aCBvdGhlciBmdW5jdGlvbnMuXG4gKi9cbmNvbnN0IHJldmVydEFycmF5Q2hhbmdlID0gKGFycjogYW55W10sIGNoYW5nZTogYW55KSA9PiB7XG4gIGZvciAoY29uc3Qgc3ViY2hhbmdlIG9mIGNoYW5nZS5jaGFuZ2VzKSB7XG4gICAgaWYgKHN1YmNoYW5nZS52YWx1ZSAhPSBudWxsIHx8IHN1YmNoYW5nZS50eXBlID09PSBPcGVyYXRpb24uUkVNT1ZFKSB7XG4gICAgICByZXZlcnRMZWFmQ2hhbmdlKGFyciwgc3ViY2hhbmdlLCBjaGFuZ2UuZW1iZWRkZWRLZXkpO1xuICAgIH0gZWxzZSB7XG4gICAgICBsZXQgZWxlbWVudDtcbiAgICAgIGlmIChjaGFuZ2UuZW1iZWRkZWRLZXkgPT09ICckaW5kZXgnKSB7XG4gICAgICAgIGVsZW1lbnQgPSBhcnJbK3N1YmNoYW5nZS5rZXldO1xuICAgICAgfSBlbHNlIGlmIChjaGFuZ2UuZW1iZWRkZWRLZXkgPT09ICckdmFsdWUnKSB7XG4gICAgICAgIGNvbnN0IGluZGV4ID0gYXJyLmluZGV4T2Yoc3ViY2hhbmdlLmtleSk7XG4gICAgICAgIGlmIChpbmRleCAhPT0gLTEpIHtcbiAgICAgICAgICBlbGVtZW50ID0gYXJyW2luZGV4XTtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgZWxlbWVudCA9IGFyci5maW5kKChlbCkgPT4gZWxbY2hhbmdlLmVtYmVkZGVkS2V5XT8udG9TdHJpbmcoKSA9PT0gc3ViY2hhbmdlLmtleS50b1N0cmluZygpKTtcbiAgICAgIH1cbiAgICAgIGlmIChlbGVtZW50KSB7XG4gICAgICAgIHJldmVydENoYW5nZXNldChlbGVtZW50LCBzdWJjaGFuZ2UuY2hhbmdlcyk7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIHJldHVybiBhcnI7XG59O1xuXG5jb25zdCByZXZlcnRCcmFuY2hDaGFuZ2UgPSAob2JqOiBhbnksIGNoYW5nZTogYW55KSA9PiB7XG4gIGlmIChBcnJheS5pc0FycmF5KG9iaikpIHtcbiAgICByZXR1cm4gcmV2ZXJ0QXJyYXlDaGFuZ2Uob2JqLCBjaGFuZ2UpO1xuICB9IGVsc2Uge1xuICAgIHJldHVybiByZXZlcnRDaGFuZ2VzZXQob2JqLCBjaGFuZ2UuY2hhbmdlcyk7XG4gIH1cbn07XG5cbi8qKiBjb21iaW5lIGEgYmFzZSBKU09OIFBhdGggd2l0aCBhIHN1YnNlcXVlbnQgc2VnbWVudCAqL1xuZnVuY3Rpb24gYXBwZW5kKGJhc2VQYXRoOiBzdHJpbmcsIG5leHRTZWdtZW50OiBzdHJpbmcpOiBzdHJpbmcge1xuICByZXR1cm4gbmV4dFNlZ21lbnQuaW5jbHVkZXMoJy4nKSA/IGAke2Jhc2VQYXRofVske25leHRTZWdtZW50fV1gIDogYCR7YmFzZVBhdGh9LiR7bmV4dFNlZ21lbnR9YDtcbn1cblxuLyoqIHJldHVybnMgYSBKU09OIFBhdGggZmlsdGVyIGV4cHJlc3Npb247IGUuZy4sIGAkLnBldFsoP25hbWU9J3Nwb3QnKV1gICovXG5mdW5jdGlvbiBmaWx0ZXJFeHByZXNzaW9uKGJhc2VQYXRoOiBzdHJpbmcsIGZpbHRlcktleTogc3RyaW5nIHwgRnVuY3Rpb25LZXksIGZpbHRlclZhbHVlOiBzdHJpbmcgfCBudW1iZXIpIHtcbiAgY29uc3QgdmFsdWUgPSB0eXBlb2YgZmlsdGVyVmFsdWUgPT09ICdudW1iZXInID8gZmlsdGVyVmFsdWUgOiBgJyR7ZmlsdGVyVmFsdWV9J2A7XG4gIHJldHVybiB0eXBlb2YgZmlsdGVyS2V5ID09PSAnc3RyaW5nJyAmJiBmaWx0ZXJLZXkuaW5jbHVkZXMoJy4nKVxuICAgID8gYCR7YmFzZVBhdGh9Wz8oQFske2ZpbHRlcktleX1dPT0ke3ZhbHVlfSldYFxuICAgIDogYCR7YmFzZVBhdGh9Wz8oQC4ke2ZpbHRlcktleX09PSR7dmFsdWV9KV1gO1xufVxuXG5leHBvcnQge1xuICBDaGFuZ2VzZXQsXG4gIEVtYmVkZGVkT2JqS2V5c01hcFR5cGUsXG4gIEVtYmVkZGVkT2JqS2V5c1R5cGUsXG4gIElBdG9taWNDaGFuZ2UsXG4gIElDaGFuZ2UsXG4gIE9wZXJhdGlvbixcbiAgT3B0aW9ucyxcbiAgYXBwbHlDaGFuZ2VzZXQsXG4gIGF0b21pemVDaGFuZ2VzZXQsXG4gIGRpZmYsXG4gIGdldFR5cGVPZk9iaixcbiAgcmV2ZXJ0Q2hhbmdlc2V0LFxuICB1bmF0b21pemVDaGFuZ2VzZXRcbn07XG4iLCAiaW1wb3J0IHsgc2V0QnlQYXRoIH0gZnJvbSAnLi9oZWxwZXJzLmpzJztcbmltcG9ydCB7IGRpZmYsIGF0b21pemVDaGFuZ2VzZXQsIGdldFR5cGVPZk9iaiwgSUF0b21pY0NoYW5nZSwgT3BlcmF0aW9uIH0gZnJvbSAnLi9qc29uRGlmZi5qcyc7XG5cbmVudW0gQ29tcGFyZU9wZXJhdGlvbiB7XG4gIENPTlRBSU5FUiA9ICdDT05UQUlORVInLFxuICBVTkNIQU5HRUQgPSAnVU5DSEFOR0VEJ1xufVxuXG5pbnRlcmZhY2UgSUNvbXBhcmlzb25FbnJpY2hlZE5vZGUge1xuICB0eXBlOiBPcGVyYXRpb24gfCBDb21wYXJlT3BlcmF0aW9uO1xuICB2YWx1ZTogSUNvbXBhcmlzb25FbnJpY2hlZE5vZGUgfCBJQ29tcGFyaXNvbkVucmljaGVkTm9kZVtdIHwgYW55IHwgYW55W107XG4gIG9sZFZhbHVlPzogYW55O1xufVxuXG5jb25zdCBjcmVhdGVWYWx1ZSA9ICh2YWx1ZTogYW55KTogSUNvbXBhcmlzb25FbnJpY2hlZE5vZGUgPT4gKHsgdHlwZTogQ29tcGFyZU9wZXJhdGlvbi5VTkNIQU5HRUQsIHZhbHVlIH0pO1xuY29uc3QgY3JlYXRlQ29udGFpbmVyID0gKHZhbHVlOiBvYmplY3QgfCBbXSk6IElDb21wYXJpc29uRW5yaWNoZWROb2RlID0+ICh7XG4gIHR5cGU6IENvbXBhcmVPcGVyYXRpb24uQ09OVEFJTkVSLFxuICB2YWx1ZVxufSk7XG5cbmNvbnN0IGVucmljaCA9IChvYmplY3Q6IGFueSk6IElDb21wYXJpc29uRW5yaWNoZWROb2RlID0+IHtcbiAgY29uc3Qgb2JqZWN0VHlwZSA9IGdldFR5cGVPZk9iaihvYmplY3QpO1xuXG4gIHN3aXRjaCAob2JqZWN0VHlwZSkge1xuICAgIGNhc2UgJ09iamVjdCc6XG4gICAgICByZXR1cm4gT2JqZWN0LmtleXMob2JqZWN0KVxuICAgICAgICAubWFwKChrZXk6IHN0cmluZykgPT4gKHsga2V5LCB2YWx1ZTogZW5yaWNoKG9iamVjdFtrZXldKSB9KSlcbiAgICAgICAgLnJlZHVjZSgoYWNjdW11bGF0b3IsIGVudHJ5KSA9PiB7XG4gICAgICAgICAgYWNjdW11bGF0b3IudmFsdWVbZW50cnkua2V5XSA9IGVudHJ5LnZhbHVlO1xuICAgICAgICAgIHJldHVybiBhY2N1bXVsYXRvcjtcbiAgICAgICAgfSwgY3JlYXRlQ29udGFpbmVyKHt9KSk7XG4gICAgY2FzZSAnQXJyYXknOlxuICAgICAgcmV0dXJuIChvYmplY3QgYXMgYW55W10pXG4gICAgICAgIC5tYXAoKHZhbHVlKSA9PiBlbnJpY2godmFsdWUpKVxuICAgICAgICAucmVkdWNlKChhY2N1bXVsYXRvciwgdmFsdWUpID0+IHtcbiAgICAgICAgICBhY2N1bXVsYXRvci52YWx1ZS5wdXNoKHZhbHVlKTtcbiAgICAgICAgICByZXR1cm4gYWNjdW11bGF0b3I7XG4gICAgICAgIH0sIGNyZWF0ZUNvbnRhaW5lcihbXSkpO1xuICAgIGNhc2UgJ0Z1bmN0aW9uJzpcbiAgICAgIHJldHVybiB1bmRlZmluZWQ7XG4gICAgY2FzZSAnRGF0ZSc6XG4gICAgZGVmYXVsdDpcbiAgICAgIC8vIFByaW1pdGl2ZSB2YWx1ZVxuICAgICAgcmV0dXJuIGNyZWF0ZVZhbHVlKG9iamVjdCk7XG4gIH1cbn07XG5cbmNvbnN0IGFwcGx5Q2hhbmdlbGlzdCA9IChvYmplY3Q6IElDb21wYXJpc29uRW5yaWNoZWROb2RlLCBjaGFuZ2VsaXN0OiBJQXRvbWljQ2hhbmdlW10pOiBJQ29tcGFyaXNvbkVucmljaGVkTm9kZSA9PiB7XG4gIGNoYW5nZWxpc3RcbiAgICAubWFwKChlbnRyeSkgPT4gKHsgLi4uZW50cnksIHBhdGg6IGVudHJ5LnBhdGgucmVwbGFjZSgnJC4nLCAnLicpIH0pKVxuICAgIC5tYXAoKGVudHJ5KSA9PiAoe1xuICAgICAgLi4uZW50cnksXG4gICAgICBwYXRoOiBlbnRyeS5wYXRoLnJlcGxhY2UoLyhcXFsoPzxhcnJheT5cXGQpXFxdXFwuKS9nLCAnQVJSVkFMX1NUQVJUJDxhcnJheT5BUlJWQUxfRU5EJylcbiAgICB9KSlcbiAgICAubWFwKChlbnRyeSkgPT4gKHsgLi4uZW50cnksIHBhdGg6IGVudHJ5LnBhdGgucmVwbGFjZSgvKD88ZG90PlxcLikvZywgJy52YWx1ZSQ8ZG90PicpIH0pKVxuICAgIC5tYXAoKGVudHJ5KSA9PiAoeyAuLi5lbnRyeSwgcGF0aDogZW50cnkucGF0aC5yZXBsYWNlKC9cXC4vLCAnJykgfSkpXG4gICAgLm1hcCgoZW50cnkpID0+ICh7IC4uLmVudHJ5LCBwYXRoOiBlbnRyeS5wYXRoLnJlcGxhY2UoL0FSUlZBTF9TVEFSVC9nLCAnLnZhbHVlWycpIH0pKVxuICAgIC5tYXAoKGVudHJ5KSA9PiAoeyAuLi5lbnRyeSwgcGF0aDogZW50cnkucGF0aC5yZXBsYWNlKC9BUlJWQUxfRU5EL2csICddLnZhbHVlLicpIH0pKVxuICAgIC5mb3JFYWNoKChlbnRyeSkgPT4ge1xuICAgICAgc3dpdGNoIChlbnRyeS50eXBlKSB7XG4gICAgICAgIGNhc2UgT3BlcmF0aW9uLkFERDpcbiAgICAgICAgY2FzZSBPcGVyYXRpb24uVVBEQVRFOlxuICAgICAgICAgIHNldEJ5UGF0aChvYmplY3QsIGVudHJ5LnBhdGgsIHsgdHlwZTogZW50cnkudHlwZSwgdmFsdWU6IGVudHJ5LnZhbHVlLCBvbGRWYWx1ZTogZW50cnkub2xkVmFsdWUgfSk7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIGNhc2UgT3BlcmF0aW9uLlJFTU9WRTpcbiAgICAgICAgICBzZXRCeVBhdGgob2JqZWN0LCBlbnRyeS5wYXRoLCB7IHR5cGU6IGVudHJ5LnR5cGUsIHZhbHVlOiB1bmRlZmluZWQsIG9sZFZhbHVlOiBlbnRyeS52YWx1ZSB9KTtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgZGVmYXVsdDpcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgcmV0dXJuIG9iamVjdDtcbn07XG5cbmNvbnN0IGNvbXBhcmUgPSAob2xkT2JqZWN0OiBhbnksIG5ld09iamVjdDogYW55KTogSUNvbXBhcmlzb25FbnJpY2hlZE5vZGUgPT4ge1xuICByZXR1cm4gYXBwbHlDaGFuZ2VsaXN0KGVucmljaChvbGRPYmplY3QpLCBhdG9taXplQ2hhbmdlc2V0KGRpZmYob2xkT2JqZWN0LCBuZXdPYmplY3QpKSk7XG59O1xuXG5leHBvcnQgeyBDb21wYXJlT3BlcmF0aW9uLCBJQ29tcGFyaXNvbkVucmljaGVkTm9kZSwgY3JlYXRlVmFsdWUsIGNyZWF0ZUNvbnRhaW5lciwgZW5yaWNoLCBhcHBseUNoYW5nZWxpc3QsIGNvbXBhcmUgfTtcbiIsICJpbXBvcnQgeyBTeW5jUGx1Z2luIH0gZnJvbSAnLi9TeW5jUGx1Z2luJztcbmltcG9ydCB7IENvcmVFdmVudHMgfSBmcm9tICcuLi9jb25zdGFudHMvQ29yZUV2ZW50cyc7XG5pbXBvcnQgeyBkaWZmIH0gZnJvbSAnanNvbi1kaWZmLXRzJztcbi8qKlxuICogQmFzZUVudGl0eVNlcnZpY2U8VCBleHRlbmRzIElTeW5jPiAtIEFic3RyYWN0IGJhc2UgY2xhc3MgZm9yIGFsbCBlbnRpdHkgc2VydmljZXNcbiAqXG4gKiBQUk9WSURFUzpcbiAqIC0gR2VuZXJpYyBDUlVEIG9wZXJhdGlvbnMgKGdldCwgZ2V0QWxsLCBzYXZlLCBkZWxldGUpXG4gKiAtIFN5bmMgc3RhdHVzIG1hbmFnZW1lbnQgKGRlbGVnYXRlcyB0byBTeW5jUGx1Z2luKVxuICogLSBTZXJpYWxpemF0aW9uIGhvb2tzIChvdmVycmlkZSBpbiBzdWJjbGFzcyBpZiBuZWVkZWQpXG4gKi9cbmV4cG9ydCBjbGFzcyBCYXNlRW50aXR5U2VydmljZSB7XG4gICAgY29uc3RydWN0b3IoY29udGV4dCwgZXZlbnRCdXMpIHtcbiAgICAgICAgdGhpcy5jb250ZXh0ID0gY29udGV4dDtcbiAgICAgICAgdGhpcy5ldmVudEJ1cyA9IGV2ZW50QnVzO1xuICAgICAgICB0aGlzLnN5bmNQbHVnaW4gPSBuZXcgU3luY1BsdWdpbih0aGlzKTtcbiAgICB9XG4gICAgZ2V0IGRiKCkge1xuICAgICAgICByZXR1cm4gdGhpcy5jb250ZXh0LmdldERhdGFiYXNlKCk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFNlcmlhbGl6ZSBlbnRpdHkgYmVmb3JlIHN0b3JpbmcgaW4gSW5kZXhlZERCXG4gICAgICovXG4gICAgc2VyaWFsaXplKGVudGl0eSkge1xuICAgICAgICByZXR1cm4gZW50aXR5O1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBEZXNlcmlhbGl6ZSBkYXRhIGZyb20gSW5kZXhlZERCIGJhY2sgdG8gZW50aXR5XG4gICAgICovXG4gICAgZGVzZXJpYWxpemUoZGF0YSkge1xuICAgICAgICByZXR1cm4gZGF0YTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IGEgc2luZ2xlIGVudGl0eSBieSBJRFxuICAgICAqL1xuICAgIGFzeW5jIGdldChpZCkge1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgdHJhbnNhY3Rpb24gPSB0aGlzLmRiLnRyYW5zYWN0aW9uKFt0aGlzLnN0b3JlTmFtZV0sICdyZWFkb25seScpO1xuICAgICAgICAgICAgY29uc3Qgc3RvcmUgPSB0cmFuc2FjdGlvbi5vYmplY3RTdG9yZSh0aGlzLnN0b3JlTmFtZSk7XG4gICAgICAgICAgICBjb25zdCByZXF1ZXN0ID0gc3RvcmUuZ2V0KGlkKTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25zdWNjZXNzID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIGNvbnN0IGRhdGEgPSByZXF1ZXN0LnJlc3VsdDtcbiAgICAgICAgICAgICAgICByZXNvbHZlKGRhdGEgPyB0aGlzLmRlc2VyaWFsaXplKGRhdGEpIDogbnVsbCk7XG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgcmVxdWVzdC5vbmVycm9yID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIHJlamVjdChuZXcgRXJyb3IoYEZhaWxlZCB0byBnZXQgJHt0aGlzLmVudGl0eVR5cGV9ICR7aWR9OiAke3JlcXVlc3QuZXJyb3J9YCkpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCBhbGwgZW50aXRpZXNcbiAgICAgKi9cbiAgICBhc3luYyBnZXRBbGwoKSB7XG4gICAgICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICBjb25zdCB0cmFuc2FjdGlvbiA9IHRoaXMuZGIudHJhbnNhY3Rpb24oW3RoaXMuc3RvcmVOYW1lXSwgJ3JlYWRvbmx5Jyk7XG4gICAgICAgICAgICBjb25zdCBzdG9yZSA9IHRyYW5zYWN0aW9uLm9iamVjdFN0b3JlKHRoaXMuc3RvcmVOYW1lKTtcbiAgICAgICAgICAgIGNvbnN0IHJlcXVlc3QgPSBzdG9yZS5nZXRBbGwoKTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25zdWNjZXNzID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIGNvbnN0IGRhdGEgPSByZXF1ZXN0LnJlc3VsdDtcbiAgICAgICAgICAgICAgICBjb25zdCBlbnRpdGllcyA9IGRhdGEubWFwKGl0ZW0gPT4gdGhpcy5kZXNlcmlhbGl6ZShpdGVtKSk7XG4gICAgICAgICAgICAgICAgcmVzb2x2ZShlbnRpdGllcyk7XG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgcmVxdWVzdC5vbmVycm9yID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIHJlamVjdChuZXcgRXJyb3IoYEZhaWxlZCB0byBnZXQgYWxsICR7dGhpcy5lbnRpdHlUeXBlfXM6ICR7cmVxdWVzdC5lcnJvcn1gKSk7XG4gICAgICAgICAgICB9O1xuICAgICAgICB9KTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogU2F2ZSBhbiBlbnRpdHkgKGNyZWF0ZSBvciB1cGRhdGUpXG4gICAgICogRW1pdHMgRU5USVRZX1NBVkVEIGV2ZW50IHdpdGggb3BlcmF0aW9uIHR5cGUgYW5kIGNoYW5nZXMgKGRpZmYgZm9yIHVwZGF0ZXMpXG4gICAgICogQHBhcmFtIGVudGl0eSAtIEVudGl0eSB0byBzYXZlXG4gICAgICogQHBhcmFtIHNpbGVudCAtIElmIHRydWUsIHNraXAgZXZlbnQgZW1pc3Npb24gKHVzZWQgZm9yIHNlZWRpbmcpXG4gICAgICovXG4gICAgYXN5bmMgc2F2ZShlbnRpdHksIHNpbGVudCA9IGZhbHNlKSB7XG4gICAgICAgIGNvbnN0IGVudGl0eUlkID0gZW50aXR5LmlkO1xuICAgICAgICBjb25zdCBleGlzdGluZ0VudGl0eSA9IGF3YWl0IHRoaXMuZ2V0KGVudGl0eUlkKTtcbiAgICAgICAgY29uc3QgaXNDcmVhdGUgPSBleGlzdGluZ0VudGl0eSA9PT0gbnVsbDtcbiAgICAgICAgLy8gQ2FsY3VsYXRlIGNoYW5nZXM6IGZ1bGwgZW50aXR5IGZvciBjcmVhdGUsIGRpZmYgZm9yIHVwZGF0ZVxuICAgICAgICBsZXQgY2hhbmdlcztcbiAgICAgICAgaWYgKGlzQ3JlYXRlKSB7XG4gICAgICAgICAgICBjaGFuZ2VzID0gZW50aXR5O1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgY29uc3QgZXhpc3RpbmdTZXJpYWxpemVkID0gdGhpcy5zZXJpYWxpemUoZXhpc3RpbmdFbnRpdHkpO1xuICAgICAgICAgICAgY29uc3QgbmV3U2VyaWFsaXplZCA9IHRoaXMuc2VyaWFsaXplKGVudGl0eSk7XG4gICAgICAgICAgICBjaGFuZ2VzID0gZGlmZihleGlzdGluZ1NlcmlhbGl6ZWQsIG5ld1NlcmlhbGl6ZWQpO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IHNlcmlhbGl6ZWQgPSB0aGlzLnNlcmlhbGl6ZShlbnRpdHkpO1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgdHJhbnNhY3Rpb24gPSB0aGlzLmRiLnRyYW5zYWN0aW9uKFt0aGlzLnN0b3JlTmFtZV0sICdyZWFkd3JpdGUnKTtcbiAgICAgICAgICAgIGNvbnN0IHN0b3JlID0gdHJhbnNhY3Rpb24ub2JqZWN0U3RvcmUodGhpcy5zdG9yZU5hbWUpO1xuICAgICAgICAgICAgY29uc3QgcmVxdWVzdCA9IHN0b3JlLnB1dChzZXJpYWxpemVkKTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25zdWNjZXNzID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIC8vIE9ubHkgZW1pdCBldmVudCBpZiBub3Qgc2lsZW50IChzaWxlbnQgdXNlZCBmb3Igc2VlZGluZylcbiAgICAgICAgICAgICAgICBpZiAoIXNpbGVudCkge1xuICAgICAgICAgICAgICAgICAgICBjb25zdCBwYXlsb2FkID0ge1xuICAgICAgICAgICAgICAgICAgICAgICAgZW50aXR5VHlwZTogdGhpcy5lbnRpdHlUeXBlLFxuICAgICAgICAgICAgICAgICAgICAgICAgZW50aXR5SWQsXG4gICAgICAgICAgICAgICAgICAgICAgICBvcGVyYXRpb246IGlzQ3JlYXRlID8gJ2NyZWF0ZScgOiAndXBkYXRlJyxcbiAgICAgICAgICAgICAgICAgICAgICAgIGNoYW5nZXMsXG4gICAgICAgICAgICAgICAgICAgICAgICB0aW1lc3RhbXA6IERhdGUubm93KClcbiAgICAgICAgICAgICAgICAgICAgfTtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5ldmVudEJ1cy5lbWl0KENvcmVFdmVudHMuRU5USVRZX1NBVkVELCBwYXlsb2FkKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgcmVzb2x2ZSgpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25lcnJvciA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICByZWplY3QobmV3IEVycm9yKGBGYWlsZWQgdG8gc2F2ZSAke3RoaXMuZW50aXR5VHlwZX0gJHtlbnRpdHlJZH06ICR7cmVxdWVzdC5lcnJvcn1gKSk7XG4gICAgICAgICAgICB9O1xuICAgICAgICB9KTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogRGVsZXRlIGFuIGVudGl0eVxuICAgICAqIEVtaXRzIEVOVElUWV9ERUxFVEVEIGV2ZW50XG4gICAgICovXG4gICAgYXN5bmMgZGVsZXRlKGlkKSB7XG4gICAgICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICBjb25zdCB0cmFuc2FjdGlvbiA9IHRoaXMuZGIudHJhbnNhY3Rpb24oW3RoaXMuc3RvcmVOYW1lXSwgJ3JlYWR3cml0ZScpO1xuICAgICAgICAgICAgY29uc3Qgc3RvcmUgPSB0cmFuc2FjdGlvbi5vYmplY3RTdG9yZSh0aGlzLnN0b3JlTmFtZSk7XG4gICAgICAgICAgICBjb25zdCByZXF1ZXN0ID0gc3RvcmUuZGVsZXRlKGlkKTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25zdWNjZXNzID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIGNvbnN0IHBheWxvYWQgPSB7XG4gICAgICAgICAgICAgICAgICAgIGVudGl0eVR5cGU6IHRoaXMuZW50aXR5VHlwZSxcbiAgICAgICAgICAgICAgICAgICAgZW50aXR5SWQ6IGlkLFxuICAgICAgICAgICAgICAgICAgICBvcGVyYXRpb246ICdkZWxldGUnLFxuICAgICAgICAgICAgICAgICAgICB0aW1lc3RhbXA6IERhdGUubm93KClcbiAgICAgICAgICAgICAgICB9O1xuICAgICAgICAgICAgICAgIHRoaXMuZXZlbnRCdXMuZW1pdChDb3JlRXZlbnRzLkVOVElUWV9ERUxFVEVELCBwYXlsb2FkKTtcbiAgICAgICAgICAgICAgICByZXNvbHZlKCk7XG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgcmVxdWVzdC5vbmVycm9yID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIHJlamVjdChuZXcgRXJyb3IoYEZhaWxlZCB0byBkZWxldGUgJHt0aGlzLmVudGl0eVR5cGV9ICR7aWR9OiAke3JlcXVlc3QuZXJyb3J9YCkpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIC8vIFN5bmMgbWV0aG9kcyAtIGRlbGVnYXRlIHRvIFN5bmNQbHVnaW5cbiAgICBhc3luYyBtYXJrQXNTeW5jZWQoaWQpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuc3luY1BsdWdpbi5tYXJrQXNTeW5jZWQoaWQpO1xuICAgIH1cbiAgICBhc3luYyBtYXJrQXNFcnJvcihpZCkge1xuICAgICAgICByZXR1cm4gdGhpcy5zeW5jUGx1Z2luLm1hcmtBc0Vycm9yKGlkKTtcbiAgICB9XG4gICAgYXN5bmMgZ2V0U3luY1N0YXR1cyhpZCkge1xuICAgICAgICByZXR1cm4gdGhpcy5zeW5jUGx1Z2luLmdldFN5bmNTdGF0dXMoaWQpO1xuICAgIH1cbiAgICBhc3luYyBnZXRCeVN5bmNTdGF0dXMoc3luY1N0YXR1cykge1xuICAgICAgICByZXR1cm4gdGhpcy5zeW5jUGx1Z2luLmdldEJ5U3luY1N0YXR1cyhzeW5jU3RhdHVzKTtcbiAgICB9XG59XG4iLCAiaW1wb3J0IHsgRXZlbnRTdG9yZSB9IGZyb20gJy4vRXZlbnRTdG9yZSc7XG5pbXBvcnQgeyBFdmVudFNlcmlhbGl6YXRpb24gfSBmcm9tICcuL0V2ZW50U2VyaWFsaXphdGlvbic7XG5pbXBvcnQgeyBCYXNlRW50aXR5U2VydmljZSB9IGZyb20gJy4uL0Jhc2VFbnRpdHlTZXJ2aWNlJztcbi8qKlxuICogRXZlbnRTZXJ2aWNlIC0gQ1JVRCBvcGVyYXRpb25zIGZvciBjYWxlbmRhciBldmVudHMgaW4gSW5kZXhlZERCXG4gKlxuICogRXh0ZW5kcyBCYXNlRW50aXR5U2VydmljZSBmb3Igc2hhcmVkIENSVUQgYW5kIHN5bmMgbG9naWMuXG4gKiBQcm92aWRlcyBldmVudC1zcGVjaWZpYyBxdWVyeSBtZXRob2RzLlxuICovXG5leHBvcnQgY2xhc3MgRXZlbnRTZXJ2aWNlIGV4dGVuZHMgQmFzZUVudGl0eVNlcnZpY2Uge1xuICAgIGNvbnN0cnVjdG9yKGNvbnRleHQsIGV2ZW50QnVzKSB7XG4gICAgICAgIHN1cGVyKGNvbnRleHQsIGV2ZW50QnVzKTtcbiAgICAgICAgdGhpcy5zdG9yZU5hbWUgPSBFdmVudFN0b3JlLlNUT1JFX05BTUU7XG4gICAgICAgIHRoaXMuZW50aXR5VHlwZSA9ICdFdmVudCc7XG4gICAgfVxuICAgIHNlcmlhbGl6ZShldmVudCkge1xuICAgICAgICByZXR1cm4gRXZlbnRTZXJpYWxpemF0aW9uLnNlcmlhbGl6ZShldmVudCk7XG4gICAgfVxuICAgIGRlc2VyaWFsaXplKGRhdGEpIHtcbiAgICAgICAgcmV0dXJuIEV2ZW50U2VyaWFsaXphdGlvbi5kZXNlcmlhbGl6ZShkYXRhKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IGV2ZW50cyB3aXRoaW4gYSBkYXRlIHJhbmdlXG4gICAgICovXG4gICAgYXN5bmMgZ2V0QnlEYXRlUmFuZ2Uoc3RhcnQsIGVuZCkge1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgdHJhbnNhY3Rpb24gPSB0aGlzLmRiLnRyYW5zYWN0aW9uKFt0aGlzLnN0b3JlTmFtZV0sICdyZWFkb25seScpO1xuICAgICAgICAgICAgY29uc3Qgc3RvcmUgPSB0cmFuc2FjdGlvbi5vYmplY3RTdG9yZSh0aGlzLnN0b3JlTmFtZSk7XG4gICAgICAgICAgICBjb25zdCBpbmRleCA9IHN0b3JlLmluZGV4KCdzdGFydCcpO1xuICAgICAgICAgICAgY29uc3QgcmFuZ2UgPSBJREJLZXlSYW5nZS5sb3dlckJvdW5kKHN0YXJ0LnRvSVNPU3RyaW5nKCkpO1xuICAgICAgICAgICAgY29uc3QgcmVxdWVzdCA9IGluZGV4LmdldEFsbChyYW5nZSk7XG4gICAgICAgICAgICByZXF1ZXN0Lm9uc3VjY2VzcyA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICBjb25zdCBkYXRhID0gcmVxdWVzdC5yZXN1bHQ7XG4gICAgICAgICAgICAgICAgY29uc3QgZXZlbnRzID0gZGF0YVxuICAgICAgICAgICAgICAgICAgICAubWFwKGl0ZW0gPT4gdGhpcy5kZXNlcmlhbGl6ZShpdGVtKSlcbiAgICAgICAgICAgICAgICAgICAgLmZpbHRlcihldmVudCA9PiBldmVudC5zdGFydCA8PSBlbmQpO1xuICAgICAgICAgICAgICAgIHJlc29sdmUoZXZlbnRzKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICByZXF1ZXN0Lm9uZXJyb3IgPSAoKSA9PiB7XG4gICAgICAgICAgICAgICAgcmVqZWN0KG5ldyBFcnJvcihgRmFpbGVkIHRvIGdldCBldmVudHMgYnkgZGF0ZSByYW5nZTogJHtyZXF1ZXN0LmVycm9yfWApKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0pO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgZXZlbnRzIGZvciBhIHNwZWNpZmljIHJlc291cmNlXG4gICAgICovXG4gICAgYXN5bmMgZ2V0QnlSZXNvdXJjZShyZXNvdXJjZUlkKSB7XG4gICAgICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICBjb25zdCB0cmFuc2FjdGlvbiA9IHRoaXMuZGIudHJhbnNhY3Rpb24oW3RoaXMuc3RvcmVOYW1lXSwgJ3JlYWRvbmx5Jyk7XG4gICAgICAgICAgICBjb25zdCBzdG9yZSA9IHRyYW5zYWN0aW9uLm9iamVjdFN0b3JlKHRoaXMuc3RvcmVOYW1lKTtcbiAgICAgICAgICAgIGNvbnN0IGluZGV4ID0gc3RvcmUuaW5kZXgoJ3Jlc291cmNlSWQnKTtcbiAgICAgICAgICAgIGNvbnN0IHJlcXVlc3QgPSBpbmRleC5nZXRBbGwocmVzb3VyY2VJZCk7XG4gICAgICAgICAgICByZXF1ZXN0Lm9uc3VjY2VzcyA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICBjb25zdCBkYXRhID0gcmVxdWVzdC5yZXN1bHQ7XG4gICAgICAgICAgICAgICAgY29uc3QgZXZlbnRzID0gZGF0YS5tYXAoaXRlbSA9PiB0aGlzLmRlc2VyaWFsaXplKGl0ZW0pKTtcbiAgICAgICAgICAgICAgICByZXNvbHZlKGV2ZW50cyk7XG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgcmVxdWVzdC5vbmVycm9yID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIHJlamVjdChuZXcgRXJyb3IoYEZhaWxlZCB0byBnZXQgZXZlbnRzIGZvciByZXNvdXJjZSAke3Jlc291cmNlSWR9OiAke3JlcXVlc3QuZXJyb3J9YCkpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCBldmVudHMgZm9yIGEgcmVzb3VyY2Ugd2l0aGluIGEgZGF0ZSByYW5nZVxuICAgICAqL1xuICAgIGFzeW5jIGdldEJ5UmVzb3VyY2VBbmREYXRlUmFuZ2UocmVzb3VyY2VJZCwgc3RhcnQsIGVuZCkge1xuICAgICAgICBjb25zdCByZXNvdXJjZUV2ZW50cyA9IGF3YWl0IHRoaXMuZ2V0QnlSZXNvdXJjZShyZXNvdXJjZUlkKTtcbiAgICAgICAgcmV0dXJuIHJlc291cmNlRXZlbnRzLmZpbHRlcihldmVudCA9PiBldmVudC5zdGFydCA+PSBzdGFydCAmJiBldmVudC5zdGFydCA8PSBlbmQpO1xuICAgIH1cbn1cbiIsICIvKipcbiAqIFJlc291cmNlU3RvcmUgLSBJbmRleGVkREIgT2JqZWN0U3RvcmUgZGVmaW5pdGlvbiBmb3IgcmVzb3VyY2VzXG4gKi9cbmV4cG9ydCBjbGFzcyBSZXNvdXJjZVN0b3JlIHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpcy5zdG9yZU5hbWUgPSBSZXNvdXJjZVN0b3JlLlNUT1JFX05BTUU7XG4gICAgfVxuICAgIGNyZWF0ZShkYikge1xuICAgICAgICBjb25zdCBzdG9yZSA9IGRiLmNyZWF0ZU9iamVjdFN0b3JlKFJlc291cmNlU3RvcmUuU1RPUkVfTkFNRSwgeyBrZXlQYXRoOiAnaWQnIH0pO1xuICAgICAgICBzdG9yZS5jcmVhdGVJbmRleCgndHlwZScsICd0eXBlJywgeyB1bmlxdWU6IGZhbHNlIH0pO1xuICAgICAgICBzdG9yZS5jcmVhdGVJbmRleCgnc3luY1N0YXR1cycsICdzeW5jU3RhdHVzJywgeyB1bmlxdWU6IGZhbHNlIH0pO1xuICAgICAgICBzdG9yZS5jcmVhdGVJbmRleCgnaXNBY3RpdmUnLCAnaXNBY3RpdmUnLCB7IHVuaXF1ZTogZmFsc2UgfSk7XG4gICAgfVxufVxuUmVzb3VyY2VTdG9yZS5TVE9SRV9OQU1FID0gJ3Jlc291cmNlcyc7XG4iLCAiaW1wb3J0IHsgUmVzb3VyY2VTdG9yZSB9IGZyb20gJy4vUmVzb3VyY2VTdG9yZSc7XG5pbXBvcnQgeyBCYXNlRW50aXR5U2VydmljZSB9IGZyb20gJy4uL0Jhc2VFbnRpdHlTZXJ2aWNlJztcbi8qKlxuICogUmVzb3VyY2VTZXJ2aWNlIC0gQ1JVRCBvcGVyYXRpb25zIGZvciByZXNvdXJjZXMgaW4gSW5kZXhlZERCXG4gKi9cbmV4cG9ydCBjbGFzcyBSZXNvdXJjZVNlcnZpY2UgZXh0ZW5kcyBCYXNlRW50aXR5U2VydmljZSB7XG4gICAgY29uc3RydWN0b3IoY29udGV4dCwgZXZlbnRCdXMpIHtcbiAgICAgICAgc3VwZXIoY29udGV4dCwgZXZlbnRCdXMpO1xuICAgICAgICB0aGlzLnN0b3JlTmFtZSA9IFJlc291cmNlU3RvcmUuU1RPUkVfTkFNRTtcbiAgICAgICAgdGhpcy5lbnRpdHlUeXBlID0gJ1Jlc291cmNlJztcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IGFsbCBhY3RpdmUgcmVzb3VyY2VzXG4gICAgICovXG4gICAgYXN5bmMgZ2V0QWN0aXZlKCkge1xuICAgICAgICBjb25zdCBhbGwgPSBhd2FpdCB0aGlzLmdldEFsbCgpO1xuICAgICAgICByZXR1cm4gYWxsLmZpbHRlcihyID0+IHIuaXNBY3RpdmUgIT09IGZhbHNlKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IHJlc291cmNlcyBieSBJRHNcbiAgICAgKi9cbiAgICBhc3luYyBnZXRCeUlkcyhpZHMpIHtcbiAgICAgICAgaWYgKGlkcy5sZW5ndGggPT09IDApXG4gICAgICAgICAgICByZXR1cm4gW107XG4gICAgICAgIGNvbnN0IHJlc3VsdHMgPSBhd2FpdCBQcm9taXNlLmFsbChpZHMubWFwKGlkID0+IHRoaXMuZ2V0KGlkKSkpO1xuICAgICAgICByZXR1cm4gcmVzdWx0cy5maWx0ZXIoKHIpID0+IHIgIT09IG51bGwpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgcmVzb3VyY2VzIGJ5IHR5cGVcbiAgICAgKi9cbiAgICBhc3luYyBnZXRCeVR5cGUodHlwZSkge1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgdHJhbnNhY3Rpb24gPSB0aGlzLmRiLnRyYW5zYWN0aW9uKFt0aGlzLnN0b3JlTmFtZV0sICdyZWFkb25seScpO1xuICAgICAgICAgICAgY29uc3Qgc3RvcmUgPSB0cmFuc2FjdGlvbi5vYmplY3RTdG9yZSh0aGlzLnN0b3JlTmFtZSk7XG4gICAgICAgICAgICBjb25zdCBpbmRleCA9IHN0b3JlLmluZGV4KCd0eXBlJyk7XG4gICAgICAgICAgICBjb25zdCByZXF1ZXN0ID0gaW5kZXguZ2V0QWxsKHR5cGUpO1xuICAgICAgICAgICAgcmVxdWVzdC5vbnN1Y2Nlc3MgPSAoKSA9PiB7XG4gICAgICAgICAgICAgICAgY29uc3QgZGF0YSA9IHJlcXVlc3QucmVzdWx0O1xuICAgICAgICAgICAgICAgIHJlc29sdmUoZGF0YSk7XG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgcmVxdWVzdC5vbmVycm9yID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIHJlamVjdChuZXcgRXJyb3IoYEZhaWxlZCB0byBnZXQgcmVzb3VyY2VzIGJ5IHR5cGUgJHt0eXBlfTogJHtyZXF1ZXN0LmVycm9yfWApKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0pO1xuICAgIH1cbn1cbiIsICIvKipcbiAqIEJvb2tpbmdTdG9yZSAtIEluZGV4ZWREQiBPYmplY3RTdG9yZSBkZWZpbml0aW9uIGZvciBib29raW5nc1xuICovXG5leHBvcnQgY2xhc3MgQm9va2luZ1N0b3JlIHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpcy5zdG9yZU5hbWUgPSBCb29raW5nU3RvcmUuU1RPUkVfTkFNRTtcbiAgICB9XG4gICAgY3JlYXRlKGRiKSB7XG4gICAgICAgIGNvbnN0IHN0b3JlID0gZGIuY3JlYXRlT2JqZWN0U3RvcmUoQm9va2luZ1N0b3JlLlNUT1JFX05BTUUsIHsga2V5UGF0aDogJ2lkJyB9KTtcbiAgICAgICAgc3RvcmUuY3JlYXRlSW5kZXgoJ2N1c3RvbWVySWQnLCAnY3VzdG9tZXJJZCcsIHsgdW5pcXVlOiBmYWxzZSB9KTtcbiAgICAgICAgc3RvcmUuY3JlYXRlSW5kZXgoJ3N0YXR1cycsICdzdGF0dXMnLCB7IHVuaXF1ZTogZmFsc2UgfSk7XG4gICAgICAgIHN0b3JlLmNyZWF0ZUluZGV4KCdzeW5jU3RhdHVzJywgJ3N5bmNTdGF0dXMnLCB7IHVuaXF1ZTogZmFsc2UgfSk7XG4gICAgICAgIHN0b3JlLmNyZWF0ZUluZGV4KCdjcmVhdGVkQXQnLCAnY3JlYXRlZEF0JywgeyB1bmlxdWU6IGZhbHNlIH0pO1xuICAgIH1cbn1cbkJvb2tpbmdTdG9yZS5TVE9SRV9OQU1FID0gJ2Jvb2tpbmdzJztcbiIsICJpbXBvcnQgeyBCb29raW5nU3RvcmUgfSBmcm9tICcuL0Jvb2tpbmdTdG9yZSc7XG5pbXBvcnQgeyBCYXNlRW50aXR5U2VydmljZSB9IGZyb20gJy4uL0Jhc2VFbnRpdHlTZXJ2aWNlJztcbi8qKlxuICogQm9va2luZ1NlcnZpY2UgLSBDUlVEIG9wZXJhdGlvbnMgZm9yIGJvb2tpbmdzIGluIEluZGV4ZWREQlxuICovXG5leHBvcnQgY2xhc3MgQm9va2luZ1NlcnZpY2UgZXh0ZW5kcyBCYXNlRW50aXR5U2VydmljZSB7XG4gICAgY29uc3RydWN0b3IoY29udGV4dCwgZXZlbnRCdXMpIHtcbiAgICAgICAgc3VwZXIoY29udGV4dCwgZXZlbnRCdXMpO1xuICAgICAgICB0aGlzLnN0b3JlTmFtZSA9IEJvb2tpbmdTdG9yZS5TVE9SRV9OQU1FO1xuICAgICAgICB0aGlzLmVudGl0eVR5cGUgPSAnQm9va2luZyc7XG4gICAgfVxuICAgIHNlcmlhbGl6ZShib29raW5nKSB7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICAuLi5ib29raW5nLFxuICAgICAgICAgICAgY3JlYXRlZEF0OiBib29raW5nLmNyZWF0ZWRBdC50b0lTT1N0cmluZygpXG4gICAgICAgIH07XG4gICAgfVxuICAgIGRlc2VyaWFsaXplKGRhdGEpIHtcbiAgICAgICAgY29uc3QgcmF3ID0gZGF0YTtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIC4uLnJhdyxcbiAgICAgICAgICAgIGNyZWF0ZWRBdDogbmV3IERhdGUocmF3LmNyZWF0ZWRBdClcbiAgICAgICAgfTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IGJvb2tpbmdzIGZvciBhIGN1c3RvbWVyXG4gICAgICovXG4gICAgYXN5bmMgZ2V0QnlDdXN0b21lcihjdXN0b21lcklkKSB7XG4gICAgICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICBjb25zdCB0cmFuc2FjdGlvbiA9IHRoaXMuZGIudHJhbnNhY3Rpb24oW3RoaXMuc3RvcmVOYW1lXSwgJ3JlYWRvbmx5Jyk7XG4gICAgICAgICAgICBjb25zdCBzdG9yZSA9IHRyYW5zYWN0aW9uLm9iamVjdFN0b3JlKHRoaXMuc3RvcmVOYW1lKTtcbiAgICAgICAgICAgIGNvbnN0IGluZGV4ID0gc3RvcmUuaW5kZXgoJ2N1c3RvbWVySWQnKTtcbiAgICAgICAgICAgIGNvbnN0IHJlcXVlc3QgPSBpbmRleC5nZXRBbGwoY3VzdG9tZXJJZCk7XG4gICAgICAgICAgICByZXF1ZXN0Lm9uc3VjY2VzcyA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICBjb25zdCBkYXRhID0gcmVxdWVzdC5yZXN1bHQ7XG4gICAgICAgICAgICAgICAgY29uc3QgYm9va2luZ3MgPSBkYXRhLm1hcChpdGVtID0+IHRoaXMuZGVzZXJpYWxpemUoaXRlbSkpO1xuICAgICAgICAgICAgICAgIHJlc29sdmUoYm9va2luZ3MpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25lcnJvciA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICByZWplY3QobmV3IEVycm9yKGBGYWlsZWQgdG8gZ2V0IGJvb2tpbmdzIGZvciBjdXN0b21lciAke2N1c3RvbWVySWR9OiAke3JlcXVlc3QuZXJyb3J9YCkpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCBib29raW5ncyBieSBzdGF0dXNcbiAgICAgKi9cbiAgICBhc3luYyBnZXRCeVN0YXR1cyhzdGF0dXMpIHtcbiAgICAgICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHRyYW5zYWN0aW9uID0gdGhpcy5kYi50cmFuc2FjdGlvbihbdGhpcy5zdG9yZU5hbWVdLCAncmVhZG9ubHknKTtcbiAgICAgICAgICAgIGNvbnN0IHN0b3JlID0gdHJhbnNhY3Rpb24ub2JqZWN0U3RvcmUodGhpcy5zdG9yZU5hbWUpO1xuICAgICAgICAgICAgY29uc3QgaW5kZXggPSBzdG9yZS5pbmRleCgnc3RhdHVzJyk7XG4gICAgICAgICAgICBjb25zdCByZXF1ZXN0ID0gaW5kZXguZ2V0QWxsKHN0YXR1cyk7XG4gICAgICAgICAgICByZXF1ZXN0Lm9uc3VjY2VzcyA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICBjb25zdCBkYXRhID0gcmVxdWVzdC5yZXN1bHQ7XG4gICAgICAgICAgICAgICAgY29uc3QgYm9va2luZ3MgPSBkYXRhLm1hcChpdGVtID0+IHRoaXMuZGVzZXJpYWxpemUoaXRlbSkpO1xuICAgICAgICAgICAgICAgIHJlc29sdmUoYm9va2luZ3MpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25lcnJvciA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICByZWplY3QobmV3IEVycm9yKGBGYWlsZWQgdG8gZ2V0IGJvb2tpbmdzIHdpdGggc3RhdHVzICR7c3RhdHVzfTogJHtyZXF1ZXN0LmVycm9yfWApKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0pO1xuICAgIH1cbn1cbiIsICIvKipcbiAqIEN1c3RvbWVyU3RvcmUgLSBJbmRleGVkREIgT2JqZWN0U3RvcmUgZGVmaW5pdGlvbiBmb3IgY3VzdG9tZXJzXG4gKi9cbmV4cG9ydCBjbGFzcyBDdXN0b21lclN0b3JlIHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpcy5zdG9yZU5hbWUgPSBDdXN0b21lclN0b3JlLlNUT1JFX05BTUU7XG4gICAgfVxuICAgIGNyZWF0ZShkYikge1xuICAgICAgICBjb25zdCBzdG9yZSA9IGRiLmNyZWF0ZU9iamVjdFN0b3JlKEN1c3RvbWVyU3RvcmUuU1RPUkVfTkFNRSwgeyBrZXlQYXRoOiAnaWQnIH0pO1xuICAgICAgICBzdG9yZS5jcmVhdGVJbmRleCgnbmFtZScsICduYW1lJywgeyB1bmlxdWU6IGZhbHNlIH0pO1xuICAgICAgICBzdG9yZS5jcmVhdGVJbmRleCgncGhvbmUnLCAncGhvbmUnLCB7IHVuaXF1ZTogZmFsc2UgfSk7XG4gICAgICAgIHN0b3JlLmNyZWF0ZUluZGV4KCdzeW5jU3RhdHVzJywgJ3N5bmNTdGF0dXMnLCB7IHVuaXF1ZTogZmFsc2UgfSk7XG4gICAgfVxufVxuQ3VzdG9tZXJTdG9yZS5TVE9SRV9OQU1FID0gJ2N1c3RvbWVycyc7XG4iLCAiaW1wb3J0IHsgQ3VzdG9tZXJTdG9yZSB9IGZyb20gJy4vQ3VzdG9tZXJTdG9yZSc7XG5pbXBvcnQgeyBCYXNlRW50aXR5U2VydmljZSB9IGZyb20gJy4uL0Jhc2VFbnRpdHlTZXJ2aWNlJztcbi8qKlxuICogQ3VzdG9tZXJTZXJ2aWNlIC0gQ1JVRCBvcGVyYXRpb25zIGZvciBjdXN0b21lcnMgaW4gSW5kZXhlZERCXG4gKi9cbmV4cG9ydCBjbGFzcyBDdXN0b21lclNlcnZpY2UgZXh0ZW5kcyBCYXNlRW50aXR5U2VydmljZSB7XG4gICAgY29uc3RydWN0b3IoY29udGV4dCwgZXZlbnRCdXMpIHtcbiAgICAgICAgc3VwZXIoY29udGV4dCwgZXZlbnRCdXMpO1xuICAgICAgICB0aGlzLnN0b3JlTmFtZSA9IEN1c3RvbWVyU3RvcmUuU1RPUkVfTkFNRTtcbiAgICAgICAgdGhpcy5lbnRpdHlUeXBlID0gJ0N1c3RvbWVyJztcbiAgICB9XG4gICAgLyoqXG4gICAgICogU2VhcmNoIGN1c3RvbWVycyBieSBuYW1lIChjYXNlLWluc2Vuc2l0aXZlIGNvbnRhaW5zKVxuICAgICAqL1xuICAgIGFzeW5jIHNlYXJjaEJ5TmFtZShxdWVyeSkge1xuICAgICAgICBjb25zdCBhbGwgPSBhd2FpdCB0aGlzLmdldEFsbCgpO1xuICAgICAgICBjb25zdCBsb3dlclF1ZXJ5ID0gcXVlcnkudG9Mb3dlckNhc2UoKTtcbiAgICAgICAgcmV0dXJuIGFsbC5maWx0ZXIoYyA9PiBjLm5hbWUudG9Mb3dlckNhc2UoKS5pbmNsdWRlcyhsb3dlclF1ZXJ5KSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEZpbmQgY3VzdG9tZXIgYnkgcGhvbmVcbiAgICAgKi9cbiAgICBhc3luYyBnZXRCeVBob25lKHBob25lKSB7XG4gICAgICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICBjb25zdCB0cmFuc2FjdGlvbiA9IHRoaXMuZGIudHJhbnNhY3Rpb24oW3RoaXMuc3RvcmVOYW1lXSwgJ3JlYWRvbmx5Jyk7XG4gICAgICAgICAgICBjb25zdCBzdG9yZSA9IHRyYW5zYWN0aW9uLm9iamVjdFN0b3JlKHRoaXMuc3RvcmVOYW1lKTtcbiAgICAgICAgICAgIGNvbnN0IGluZGV4ID0gc3RvcmUuaW5kZXgoJ3Bob25lJyk7XG4gICAgICAgICAgICBjb25zdCByZXF1ZXN0ID0gaW5kZXguZ2V0KHBob25lKTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25zdWNjZXNzID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIGNvbnN0IGRhdGEgPSByZXF1ZXN0LnJlc3VsdDtcbiAgICAgICAgICAgICAgICByZXNvbHZlKGRhdGEgPyBkYXRhIDogbnVsbCk7XG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgcmVxdWVzdC5vbmVycm9yID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIHJlamVjdChuZXcgRXJyb3IoYEZhaWxlZCB0byBmaW5kIGN1c3RvbWVyIGJ5IHBob25lICR7cGhvbmV9OiAke3JlcXVlc3QuZXJyb3J9YCkpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgfSk7XG4gICAgfVxufVxuIiwgIi8qKlxuICogVGVhbVN0b3JlIC0gSW5kZXhlZERCIE9iamVjdFN0b3JlIGRlZmluaXRpb24gZm9yIHRlYW1zXG4gKi9cbmV4cG9ydCBjbGFzcyBUZWFtU3RvcmUge1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICB0aGlzLnN0b3JlTmFtZSA9IFRlYW1TdG9yZS5TVE9SRV9OQU1FO1xuICAgIH1cbiAgICBjcmVhdGUoZGIpIHtcbiAgICAgICAgZGIuY3JlYXRlT2JqZWN0U3RvcmUoVGVhbVN0b3JlLlNUT1JFX05BTUUsIHsga2V5UGF0aDogJ2lkJyB9KTtcbiAgICB9XG59XG5UZWFtU3RvcmUuU1RPUkVfTkFNRSA9ICd0ZWFtcyc7XG4iLCAiaW1wb3J0IHsgVGVhbVN0b3JlIH0gZnJvbSAnLi9UZWFtU3RvcmUnO1xuaW1wb3J0IHsgQmFzZUVudGl0eVNlcnZpY2UgfSBmcm9tICcuLi9CYXNlRW50aXR5U2VydmljZSc7XG4vKipcbiAqIFRlYW1TZXJ2aWNlIC0gQ1JVRCBvcGVyYXRpb25zIGZvciB0ZWFtcyBpbiBJbmRleGVkREJcbiAqXG4gKiBUZWFtcyBkZWZpbmUgd2hpY2ggcmVzb3VyY2VzIGJlbG9uZyB0b2dldGhlciBmb3IgaGllcmFyY2hpY2FsIGdyb3VwaW5nLlxuICogRXh0ZW5kcyBCYXNlRW50aXR5U2VydmljZSBmb3Igc3RhbmRhcmQgZW50aXR5IG9wZXJhdGlvbnMuXG4gKi9cbmV4cG9ydCBjbGFzcyBUZWFtU2VydmljZSBleHRlbmRzIEJhc2VFbnRpdHlTZXJ2aWNlIHtcbiAgICBjb25zdHJ1Y3Rvcihjb250ZXh0LCBldmVudEJ1cykge1xuICAgICAgICBzdXBlcihjb250ZXh0LCBldmVudEJ1cyk7XG4gICAgICAgIHRoaXMuc3RvcmVOYW1lID0gVGVhbVN0b3JlLlNUT1JFX05BTUU7XG4gICAgICAgIHRoaXMuZW50aXR5VHlwZSA9ICdUZWFtJztcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IHRlYW1zIGJ5IElEc1xuICAgICAqL1xuICAgIGFzeW5jIGdldEJ5SWRzKGlkcykge1xuICAgICAgICBpZiAoaWRzLmxlbmd0aCA9PT0gMClcbiAgICAgICAgICAgIHJldHVybiBbXTtcbiAgICAgICAgY29uc3QgcmVzdWx0cyA9IGF3YWl0IFByb21pc2UuYWxsKGlkcy5tYXAoaWQgPT4gdGhpcy5nZXQoaWQpKSk7XG4gICAgICAgIHJldHVybiByZXN1bHRzLmZpbHRlcigodCkgPT4gdCAhPT0gbnVsbCk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEJ1aWxkIHJldmVyc2UgbG9va3VwOiByZXNvdXJjZUlkIFx1MjE5MiB0ZWFtSWRcbiAgICAgKi9cbiAgICBhc3luYyBidWlsZFJlc291cmNlVG9UZWFtTWFwKCkge1xuICAgICAgICBjb25zdCB0ZWFtcyA9IGF3YWl0IHRoaXMuZ2V0QWxsKCk7XG4gICAgICAgIGNvbnN0IG1hcCA9IHt9O1xuICAgICAgICBmb3IgKGNvbnN0IHRlYW0gb2YgdGVhbXMpIHtcbiAgICAgICAgICAgIGZvciAoY29uc3QgcmVzb3VyY2VJZCBvZiB0ZWFtLnJlc291cmNlSWRzKSB7XG4gICAgICAgICAgICAgICAgbWFwW3Jlc291cmNlSWRdID0gdGVhbS5pZDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gbWFwO1xuICAgIH1cbn1cbiIsICIvKipcbiAqIERlcGFydG1lbnRTdG9yZSAtIEluZGV4ZWREQiBPYmplY3RTdG9yZSBkZWZpbml0aW9uIGZvciBkZXBhcnRtZW50c1xuICovXG5leHBvcnQgY2xhc3MgRGVwYXJ0bWVudFN0b3JlIHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpcy5zdG9yZU5hbWUgPSBEZXBhcnRtZW50U3RvcmUuU1RPUkVfTkFNRTtcbiAgICB9XG4gICAgY3JlYXRlKGRiKSB7XG4gICAgICAgIGRiLmNyZWF0ZU9iamVjdFN0b3JlKERlcGFydG1lbnRTdG9yZS5TVE9SRV9OQU1FLCB7IGtleVBhdGg6ICdpZCcgfSk7XG4gICAgfVxufVxuRGVwYXJ0bWVudFN0b3JlLlNUT1JFX05BTUUgPSAnZGVwYXJ0bWVudHMnO1xuIiwgImltcG9ydCB7IERlcGFydG1lbnRTdG9yZSB9IGZyb20gJy4vRGVwYXJ0bWVudFN0b3JlJztcbmltcG9ydCB7IEJhc2VFbnRpdHlTZXJ2aWNlIH0gZnJvbSAnLi4vQmFzZUVudGl0eVNlcnZpY2UnO1xuLyoqXG4gKiBEZXBhcnRtZW50U2VydmljZSAtIENSVUQgb3BlcmF0aW9ucyBmb3IgZGVwYXJ0bWVudHMgaW4gSW5kZXhlZERCXG4gKi9cbmV4cG9ydCBjbGFzcyBEZXBhcnRtZW50U2VydmljZSBleHRlbmRzIEJhc2VFbnRpdHlTZXJ2aWNlIHtcbiAgICBjb25zdHJ1Y3Rvcihjb250ZXh0LCBldmVudEJ1cykge1xuICAgICAgICBzdXBlcihjb250ZXh0LCBldmVudEJ1cyk7XG4gICAgICAgIHRoaXMuc3RvcmVOYW1lID0gRGVwYXJ0bWVudFN0b3JlLlNUT1JFX05BTUU7XG4gICAgICAgIHRoaXMuZW50aXR5VHlwZSA9ICdEZXBhcnRtZW50JztcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IGRlcGFydG1lbnRzIGJ5IElEc1xuICAgICAqL1xuICAgIGFzeW5jIGdldEJ5SWRzKGlkcykge1xuICAgICAgICBpZiAoaWRzLmxlbmd0aCA9PT0gMClcbiAgICAgICAgICAgIHJldHVybiBbXTtcbiAgICAgICAgY29uc3QgcmVzdWx0cyA9IGF3YWl0IFByb21pc2UuYWxsKGlkcy5tYXAoaWQgPT4gdGhpcy5nZXQoaWQpKSk7XG4gICAgICAgIHJldHVybiByZXN1bHRzLmZpbHRlcigoZCkgPT4gZCAhPT0gbnVsbCk7XG4gICAgfVxufVxuIiwgIi8qKlxuICogU2V0dGluZ3NTdG9yZSAtIEluZGV4ZWREQiBPYmplY3RTdG9yZSBkZWZpbml0aW9uIGZvciB0ZW5hbnQgc2V0dGluZ3NcbiAqXG4gKiBTaW5nbGUgc3RvcmUgZm9yIGFsbCBzZXR0aW5ncyBzZWN0aW9ucy4gU2V0dGluZ3MgYXJlIHN0b3JlZCBhcyBvbmUgZG9jdW1lbnRcbiAqIHBlciB0ZW5hbnQgd2l0aCBpZD0ndGVuYW50LXNldHRpbmdzJy5cbiAqL1xuZXhwb3J0IGNsYXNzIFNldHRpbmdzU3RvcmUge1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICB0aGlzLnN0b3JlTmFtZSA9IFNldHRpbmdzU3RvcmUuU1RPUkVfTkFNRTtcbiAgICB9XG4gICAgY3JlYXRlKGRiKSB7XG4gICAgICAgIGRiLmNyZWF0ZU9iamVjdFN0b3JlKFNldHRpbmdzU3RvcmUuU1RPUkVfTkFNRSwgeyBrZXlQYXRoOiAnaWQnIH0pO1xuICAgIH1cbn1cblNldHRpbmdzU3RvcmUuU1RPUkVfTkFNRSA9ICdzZXR0aW5ncyc7XG4iLCAiLyoqXG4gKiBTZXR0aW5ncyBJRHMgYXMgY29uc3QgZm9yIHR5cGUgc2FmZXR5XG4gKi9cbmV4cG9ydCBjb25zdCBTZXR0aW5nc0lkcyA9IHtcbiAgICBXT1JLV0VFSzogJ3dvcmt3ZWVrJyxcbiAgICBHUklEOiAnZ3JpZCcsXG4gICAgVElNRV9GT1JNQVQ6ICd0aW1lRm9ybWF0JyxcbiAgICBWSUVXUzogJ3ZpZXdzJ1xufTtcbiIsICJpbXBvcnQgeyBTZXR0aW5nc0lkcyB9IGZyb20gJy4uLy4uL3R5cGVzL1NldHRpbmdzVHlwZXMnO1xuaW1wb3J0IHsgU2V0dGluZ3NTdG9yZSB9IGZyb20gJy4vU2V0dGluZ3NTdG9yZSc7XG5pbXBvcnQgeyBCYXNlRW50aXR5U2VydmljZSB9IGZyb20gJy4uL0Jhc2VFbnRpdHlTZXJ2aWNlJztcbi8qKlxuICogU2V0dGluZ3NTZXJ2aWNlIC0gQ1JVRCBvcGVyYXRpb25zIGZvciB0ZW5hbnQgc2V0dGluZ3NcbiAqXG4gKiBTZXR0aW5ncyBhcmUgc3RvcmVkIGFzIHNlcGFyYXRlIHJlY29yZHMgcGVyIHNlY3Rpb24uXG4gKiBUaGlzIHNlcnZpY2UgcHJvdmlkZXMgdHlwZWQgbWV0aG9kcyBmb3IgYWNjZXNzaW5nIHNwZWNpZmljIHNldHRpbmdzLlxuICovXG5leHBvcnQgY2xhc3MgU2V0dGluZ3NTZXJ2aWNlIGV4dGVuZHMgQmFzZUVudGl0eVNlcnZpY2Uge1xuICAgIGNvbnN0cnVjdG9yKGNvbnRleHQsIGV2ZW50QnVzKSB7XG4gICAgICAgIHN1cGVyKGNvbnRleHQsIGV2ZW50QnVzKTtcbiAgICAgICAgdGhpcy5zdG9yZU5hbWUgPSBTZXR0aW5nc1N0b3JlLlNUT1JFX05BTUU7XG4gICAgICAgIHRoaXMuZW50aXR5VHlwZSA9ICdTZXR0aW5ncyc7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCB3b3Jrd2VlayBzZXR0aW5nc1xuICAgICAqL1xuICAgIGFzeW5jIGdldFdvcmt3ZWVrU2V0dGluZ3MoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmdldChTZXR0aW5nc0lkcy5XT1JLV0VFSyk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCBncmlkIHNldHRpbmdzXG4gICAgICovXG4gICAgYXN5bmMgZ2V0R3JpZFNldHRpbmdzKCkge1xuICAgICAgICByZXR1cm4gdGhpcy5nZXQoU2V0dGluZ3NJZHMuR1JJRCk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCB0aW1lIGZvcm1hdCBzZXR0aW5nc1xuICAgICAqL1xuICAgIGFzeW5jIGdldFRpbWVGb3JtYXRTZXR0aW5ncygpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuZ2V0KFNldHRpbmdzSWRzLlRJTUVfRk9STUFUKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IHZpZXcgc2V0dGluZ3NcbiAgICAgKi9cbiAgICBhc3luYyBnZXRWaWV3U2V0dGluZ3MoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmdldChTZXR0aW5nc0lkcy5WSUVXUyk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCB3b3Jrd2VlayBwcmVzZXQgYnkgSURcbiAgICAgKi9cbiAgICBhc3luYyBnZXRXb3Jrd2Vla1ByZXNldChwcmVzZXRJZCkge1xuICAgICAgICBjb25zdCBzZXR0aW5ncyA9IGF3YWl0IHRoaXMuZ2V0V29ya3dlZWtTZXR0aW5ncygpO1xuICAgICAgICBpZiAoIXNldHRpbmdzKVxuICAgICAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICAgIHJldHVybiBzZXR0aW5ncy5wcmVzZXRzW3ByZXNldElkXSB8fCBudWxsO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgdGhlIGRlZmF1bHQgd29ya3dlZWsgcHJlc2V0XG4gICAgICovXG4gICAgYXN5bmMgZ2V0RGVmYXVsdFdvcmt3ZWVrUHJlc2V0KCkge1xuICAgICAgICBjb25zdCBzZXR0aW5ncyA9IGF3YWl0IHRoaXMuZ2V0V29ya3dlZWtTZXR0aW5ncygpO1xuICAgICAgICBpZiAoIXNldHRpbmdzKVxuICAgICAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICAgIHJldHVybiBzZXR0aW5ncy5wcmVzZXRzW3NldHRpbmdzLmRlZmF1bHRQcmVzZXRdIHx8IG51bGw7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCBhbGwgYXZhaWxhYmxlIHdvcmt3ZWVrIHByZXNldHNcbiAgICAgKi9cbiAgICBhc3luYyBnZXRXb3Jrd2Vla1ByZXNldHMoKSB7XG4gICAgICAgIGNvbnN0IHNldHRpbmdzID0gYXdhaXQgdGhpcy5nZXRXb3Jrd2Vla1NldHRpbmdzKCk7XG4gICAgICAgIGlmICghc2V0dGluZ3MpXG4gICAgICAgICAgICByZXR1cm4gW107XG4gICAgICAgIHJldHVybiBPYmplY3QudmFsdWVzKHNldHRpbmdzLnByZXNldHMpO1xuICAgIH1cbn1cbiIsICJleHBvcnQgY2xhc3MgVmlld0NvbmZpZ1N0b3JlIHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpcy5zdG9yZU5hbWUgPSBWaWV3Q29uZmlnU3RvcmUuU1RPUkVfTkFNRTtcbiAgICB9XG4gICAgY3JlYXRlKGRiKSB7XG4gICAgICAgIGRiLmNyZWF0ZU9iamVjdFN0b3JlKFZpZXdDb25maWdTdG9yZS5TVE9SRV9OQU1FLCB7IGtleVBhdGg6ICdpZCcgfSk7XG4gICAgfVxufVxuVmlld0NvbmZpZ1N0b3JlLlNUT1JFX05BTUUgPSAndmlld2NvbmZpZ3MnO1xuIiwgImltcG9ydCB7IFZpZXdDb25maWdTdG9yZSB9IGZyb20gJy4vVmlld0NvbmZpZ1N0b3JlJztcbmltcG9ydCB7IEJhc2VFbnRpdHlTZXJ2aWNlIH0gZnJvbSAnLi4vQmFzZUVudGl0eVNlcnZpY2UnO1xuZXhwb3J0IGNsYXNzIFZpZXdDb25maWdTZXJ2aWNlIGV4dGVuZHMgQmFzZUVudGl0eVNlcnZpY2Uge1xuICAgIGNvbnN0cnVjdG9yKGNvbnRleHQsIGV2ZW50QnVzKSB7XG4gICAgICAgIHN1cGVyKGNvbnRleHQsIGV2ZW50QnVzKTtcbiAgICAgICAgdGhpcy5zdG9yZU5hbWUgPSBWaWV3Q29uZmlnU3RvcmUuU1RPUkVfTkFNRTtcbiAgICAgICAgdGhpcy5lbnRpdHlUeXBlID0gJ1ZpZXdDb25maWcnO1xuICAgIH1cbiAgICBhc3luYyBnZXRCeUlkKGlkKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmdldChpZCk7XG4gICAgfVxufVxuIiwgIi8qKlxuICogQXVkaXRTdG9yZSAtIEluZGV4ZWREQiBzdG9yZSBjb25maWd1cmF0aW9uIGZvciBhdWRpdCBlbnRyaWVzXG4gKlxuICogU3RvcmVzIGFsbCBlbnRpdHkgY2hhbmdlcyBmb3I6XG4gKiAtIENvbXBsaWFuY2UgYW5kIGF1ZGl0IHRyYWlsXG4gKiAtIFN5bmMgdHJhY2tpbmcgd2l0aCBiYWNrZW5kXG4gKiAtIENoYW5nZSBoaXN0b3J5XG4gKlxuICogSW5kZXhlczpcbiAqIC0gc3luY1N0YXR1czogRm9yIGZpbmRpbmcgcGVuZGluZyBlbnRyaWVzIHRvIHN5bmNcbiAqIC0gc3luY2VkOiBCb29sZWFuIGZsYWcgZm9yIHF1aWNrIHN5bmMgcXVlcmllc1xuICogLSBlbnRpdHlJZDogRm9yIGdldHRpbmcgYWxsIGF1ZGl0cyBmb3IgYSBzcGVjaWZpYyBlbnRpdHlcbiAqIC0gdGltZXN0YW1wOiBGb3IgY2hyb25vbG9naWNhbCBxdWVyaWVzXG4gKi9cbmV4cG9ydCBjbGFzcyBBdWRpdFN0b3JlIHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpcy5zdG9yZU5hbWUgPSAnYXVkaXQnO1xuICAgIH1cbiAgICBjcmVhdGUoZGIpIHtcbiAgICAgICAgY29uc3Qgc3RvcmUgPSBkYi5jcmVhdGVPYmplY3RTdG9yZSh0aGlzLnN0b3JlTmFtZSwgeyBrZXlQYXRoOiAnaWQnIH0pO1xuICAgICAgICBzdG9yZS5jcmVhdGVJbmRleCgnc3luY1N0YXR1cycsICdzeW5jU3RhdHVzJywgeyB1bmlxdWU6IGZhbHNlIH0pO1xuICAgICAgICBzdG9yZS5jcmVhdGVJbmRleCgnc3luY2VkJywgJ3N5bmNlZCcsIHsgdW5pcXVlOiBmYWxzZSB9KTtcbiAgICAgICAgc3RvcmUuY3JlYXRlSW5kZXgoJ2VudGl0eUlkJywgJ2VudGl0eUlkJywgeyB1bmlxdWU6IGZhbHNlIH0pO1xuICAgICAgICBzdG9yZS5jcmVhdGVJbmRleCgndGltZXN0YW1wJywgJ3RpbWVzdGFtcCcsIHsgdW5pcXVlOiBmYWxzZSB9KTtcbiAgICB9XG59XG4iLCAiaW1wb3J0IHsgQmFzZUVudGl0eVNlcnZpY2UgfSBmcm9tICcuLi9CYXNlRW50aXR5U2VydmljZSc7XG5pbXBvcnQgeyBDb3JlRXZlbnRzIH0gZnJvbSAnLi4vLi4vY29uc3RhbnRzL0NvcmVFdmVudHMnO1xuLyoqXG4gKiBBdWRpdFNlcnZpY2UgLSBFbnRpdHkgc2VydmljZSBmb3IgYXVkaXQgZW50cmllc1xuICpcbiAqIFJFU1BPTlNJQklMSVRJRVM6XG4gKiAtIFN0b3JlIGF1ZGl0IGVudHJpZXMgaW4gSW5kZXhlZERCXG4gKiAtIExpc3RlbiBmb3IgRU5USVRZX1NBVkVEL0VOVElUWV9ERUxFVEVEIGV2ZW50c1xuICogLSBDcmVhdGUgYXVkaXQgZW50cmllcyBmb3IgYWxsIGVudGl0eSBjaGFuZ2VzXG4gKiAtIEVtaXQgQVVESVRfTE9HR0VEIGFmdGVyIHNhdmluZyAoZm9yIFN5bmNNYW5hZ2VyIHRvIGxpc3RlbilcbiAqXG4gKiBPVkVSUklERSBQQVRURVJOOlxuICogLSBPdmVycmlkZXMgc2F2ZSgpIHRvIE5PVCBlbWl0IGV2ZW50cyAocHJldmVudHMgaW5maW5pdGUgbG9vcHMpXG4gKiAtIEF1ZGl0U2VydmljZSBzYXZlcyBhdWRpdCBlbnRyaWVzIHdpdGhvdXQgdHJpZ2dlcmluZyBtb3JlIGF1ZGl0c1xuICpcbiAqIEVWRU5UIENIQUlOOlxuICogRW50aXR5IGNoYW5nZSBcdTIxOTIgRU5USVRZX1NBVkVEL0RFTEVURUQgXHUyMTkyIEF1ZGl0U2VydmljZSBcdTIxOTIgQVVESVRfTE9HR0VEIFx1MjE5MiBTeW5jTWFuYWdlclxuICovXG5leHBvcnQgY2xhc3MgQXVkaXRTZXJ2aWNlIGV4dGVuZHMgQmFzZUVudGl0eVNlcnZpY2Uge1xuICAgIGNvbnN0cnVjdG9yKGNvbnRleHQsIGV2ZW50QnVzKSB7XG4gICAgICAgIHN1cGVyKGNvbnRleHQsIGV2ZW50QnVzKTtcbiAgICAgICAgdGhpcy5zdG9yZU5hbWUgPSAnYXVkaXQnO1xuICAgICAgICB0aGlzLmVudGl0eVR5cGUgPSAnQXVkaXQnO1xuICAgICAgICB0aGlzLnNldHVwRXZlbnRMaXN0ZW5lcnMoKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogU2V0dXAgbGlzdGVuZXJzIGZvciBFTlRJVFlfU0FWRUQgYW5kIEVOVElUWV9ERUxFVEVEIGV2ZW50c1xuICAgICAqL1xuICAgIHNldHVwRXZlbnRMaXN0ZW5lcnMoKSB7XG4gICAgICAgIC8vIExpc3RlbiBmb3IgZW50aXR5IHNhdmVzIChjcmVhdGUvdXBkYXRlKVxuICAgICAgICB0aGlzLmV2ZW50QnVzLm9uKENvcmVFdmVudHMuRU5USVRZX1NBVkVELCAoZXZlbnQpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IGRldGFpbCA9IGV2ZW50LmRldGFpbDtcbiAgICAgICAgICAgIHRoaXMuaGFuZGxlRW50aXR5U2F2ZWQoZGV0YWlsKTtcbiAgICAgICAgfSk7XG4gICAgICAgIC8vIExpc3RlbiBmb3IgZW50aXR5IGRlbGV0ZXNcbiAgICAgICAgdGhpcy5ldmVudEJ1cy5vbihDb3JlRXZlbnRzLkVOVElUWV9ERUxFVEVELCAoZXZlbnQpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IGRldGFpbCA9IGV2ZW50LmRldGFpbDtcbiAgICAgICAgICAgIHRoaXMuaGFuZGxlRW50aXR5RGVsZXRlZChkZXRhaWwpO1xuICAgICAgICB9KTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogSGFuZGxlIEVOVElUWV9TQVZFRCBldmVudCAtIGNyZWF0ZSBhdWRpdCBlbnRyeVxuICAgICAqL1xuICAgIGFzeW5jIGhhbmRsZUVudGl0eVNhdmVkKHBheWxvYWQpIHtcbiAgICAgICAgLy8gRG9uJ3QgYXVkaXQgYXVkaXQgZW50cmllcyAocHJldmVudCBpbmZpbml0ZSBsb29wcylcbiAgICAgICAgaWYgKHBheWxvYWQuZW50aXR5VHlwZSA9PT0gJ0F1ZGl0JylcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgY29uc3QgYXVkaXRFbnRyeSA9IHtcbiAgICAgICAgICAgIGlkOiBjcnlwdG8ucmFuZG9tVVVJRCgpLFxuICAgICAgICAgICAgZW50aXR5VHlwZTogcGF5bG9hZC5lbnRpdHlUeXBlLFxuICAgICAgICAgICAgZW50aXR5SWQ6IHBheWxvYWQuZW50aXR5SWQsXG4gICAgICAgICAgICBvcGVyYXRpb246IHBheWxvYWQub3BlcmF0aW9uLFxuICAgICAgICAgICAgdXNlcklkOiBBdWRpdFNlcnZpY2UuREVGQVVMVF9VU0VSX0lELFxuICAgICAgICAgICAgdGltZXN0YW1wOiBwYXlsb2FkLnRpbWVzdGFtcCxcbiAgICAgICAgICAgIGNoYW5nZXM6IHBheWxvYWQuY2hhbmdlcyxcbiAgICAgICAgICAgIHN5bmNlZDogZmFsc2UsXG4gICAgICAgICAgICBzeW5jU3RhdHVzOiAncGVuZGluZydcbiAgICAgICAgfTtcbiAgICAgICAgYXdhaXQgdGhpcy5zYXZlKGF1ZGl0RW50cnkpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBIYW5kbGUgRU5USVRZX0RFTEVURUQgZXZlbnQgLSBjcmVhdGUgYXVkaXQgZW50cnlcbiAgICAgKi9cbiAgICBhc3luYyBoYW5kbGVFbnRpdHlEZWxldGVkKHBheWxvYWQpIHtcbiAgICAgICAgLy8gRG9uJ3QgYXVkaXQgYXVkaXQgZW50cmllcyAocHJldmVudCBpbmZpbml0ZSBsb29wcylcbiAgICAgICAgaWYgKHBheWxvYWQuZW50aXR5VHlwZSA9PT0gJ0F1ZGl0JylcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgY29uc3QgYXVkaXRFbnRyeSA9IHtcbiAgICAgICAgICAgIGlkOiBjcnlwdG8ucmFuZG9tVVVJRCgpLFxuICAgICAgICAgICAgZW50aXR5VHlwZTogcGF5bG9hZC5lbnRpdHlUeXBlLFxuICAgICAgICAgICAgZW50aXR5SWQ6IHBheWxvYWQuZW50aXR5SWQsXG4gICAgICAgICAgICBvcGVyYXRpb246ICdkZWxldGUnLFxuICAgICAgICAgICAgdXNlcklkOiBBdWRpdFNlcnZpY2UuREVGQVVMVF9VU0VSX0lELFxuICAgICAgICAgICAgdGltZXN0YW1wOiBwYXlsb2FkLnRpbWVzdGFtcCxcbiAgICAgICAgICAgIGNoYW5nZXM6IHsgaWQ6IHBheWxvYWQuZW50aXR5SWQgfSwgLy8gRm9yIGRlbGV0ZSwganVzdCBzdG9yZSB0aGUgSURcbiAgICAgICAgICAgIHN5bmNlZDogZmFsc2UsXG4gICAgICAgICAgICBzeW5jU3RhdHVzOiAncGVuZGluZydcbiAgICAgICAgfTtcbiAgICAgICAgYXdhaXQgdGhpcy5zYXZlKGF1ZGl0RW50cnkpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBPdmVycmlkZSBzYXZlIHRvIE5PVCB0cmlnZ2VyIEVOVElUWV9TQVZFRCBldmVudFxuICAgICAqIEluc3RlYWQsIGVtaXRzIEFVRElUX0xPR0dFRCBmb3IgU3luY01hbmFnZXIgdG8gbGlzdGVuXG4gICAgICpcbiAgICAgKiBUaGlzIHByZXZlbnRzIGluZmluaXRlIGxvb3BzOlxuICAgICAqIC0gQmFzZUVudGl0eVNlcnZpY2Uuc2F2ZSgpIGVtaXRzIEVOVElUWV9TQVZFRFxuICAgICAqIC0gQXVkaXRTZXJ2aWNlIGxpc3RlbnMgdG8gRU5USVRZX1NBVkVEIGFuZCBjcmVhdGVzIGF1ZGl0XG4gICAgICogLSBJZiBBdWRpdFNlcnZpY2Uuc2F2ZSgpIGFsc28gZW1pdHRlZCBFTlRJVFlfU0FWRUQsIGl0IHdvdWxkIGxvb3BcbiAgICAgKi9cbiAgICBhc3luYyBzYXZlKGVudGl0eSkge1xuICAgICAgICBjb25zdCBzZXJpYWxpemVkID0gdGhpcy5zZXJpYWxpemUoZW50aXR5KTtcbiAgICAgICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHRyYW5zYWN0aW9uID0gdGhpcy5kYi50cmFuc2FjdGlvbihbdGhpcy5zdG9yZU5hbWVdLCAncmVhZHdyaXRlJyk7XG4gICAgICAgICAgICBjb25zdCBzdG9yZSA9IHRyYW5zYWN0aW9uLm9iamVjdFN0b3JlKHRoaXMuc3RvcmVOYW1lKTtcbiAgICAgICAgICAgIGNvbnN0IHJlcXVlc3QgPSBzdG9yZS5wdXQoc2VyaWFsaXplZCk7XG4gICAgICAgICAgICByZXF1ZXN0Lm9uc3VjY2VzcyA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICAvLyBFbWl0IEFVRElUX0xPR0dFRCBpbnN0ZWFkIG9mIEVOVElUWV9TQVZFRFxuICAgICAgICAgICAgICAgIGNvbnN0IHBheWxvYWQgPSB7XG4gICAgICAgICAgICAgICAgICAgIGF1ZGl0SWQ6IGVudGl0eS5pZCxcbiAgICAgICAgICAgICAgICAgICAgZW50aXR5VHlwZTogZW50aXR5LmVudGl0eVR5cGUsXG4gICAgICAgICAgICAgICAgICAgIGVudGl0eUlkOiBlbnRpdHkuZW50aXR5SWQsXG4gICAgICAgICAgICAgICAgICAgIG9wZXJhdGlvbjogZW50aXR5Lm9wZXJhdGlvbixcbiAgICAgICAgICAgICAgICAgICAgdGltZXN0YW1wOiBlbnRpdHkudGltZXN0YW1wXG4gICAgICAgICAgICAgICAgfTtcbiAgICAgICAgICAgICAgICB0aGlzLmV2ZW50QnVzLmVtaXQoQ29yZUV2ZW50cy5BVURJVF9MT0dHRUQsIHBheWxvYWQpO1xuICAgICAgICAgICAgICAgIHJlc29sdmUoKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICByZXF1ZXN0Lm9uZXJyb3IgPSAoKSA9PiB7XG4gICAgICAgICAgICAgICAgcmVqZWN0KG5ldyBFcnJvcihgRmFpbGVkIHRvIHNhdmUgYXVkaXQgZW50cnkgJHtlbnRpdHkuaWR9OiAke3JlcXVlc3QuZXJyb3J9YCkpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIE92ZXJyaWRlIGRlbGV0ZSB0byBOT1QgdHJpZ2dlciBFTlRJVFlfREVMRVRFRCBldmVudFxuICAgICAqIEF1ZGl0IGVudHJpZXMgc2hvdWxkIG5ldmVyIGJlIGRlbGV0ZWQgKGNvbXBsaWFuY2UgcmVxdWlyZW1lbnQpXG4gICAgICovXG4gICAgYXN5bmMgZGVsZXRlKF9pZCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ0F1ZGl0IGVudHJpZXMgY2Fubm90IGJlIGRlbGV0ZWQgKGNvbXBsaWFuY2UgcmVxdWlyZW1lbnQpJyk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCBwZW5kaW5nIGF1ZGl0IGVudHJpZXMgKGZvciBzeW5jKVxuICAgICAqL1xuICAgIGFzeW5jIGdldFBlbmRpbmdBdWRpdHMoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmdldEJ5U3luY1N0YXR1cygncGVuZGluZycpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgYXVkaXQgZW50cmllcyBmb3IgYSBzcGVjaWZpYyBlbnRpdHlcbiAgICAgKi9cbiAgICBhc3luYyBnZXRCeUVudGl0eUlkKGVudGl0eUlkKSB7XG4gICAgICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICBjb25zdCB0cmFuc2FjdGlvbiA9IHRoaXMuZGIudHJhbnNhY3Rpb24oW3RoaXMuc3RvcmVOYW1lXSwgJ3JlYWRvbmx5Jyk7XG4gICAgICAgICAgICBjb25zdCBzdG9yZSA9IHRyYW5zYWN0aW9uLm9iamVjdFN0b3JlKHRoaXMuc3RvcmVOYW1lKTtcbiAgICAgICAgICAgIGNvbnN0IGluZGV4ID0gc3RvcmUuaW5kZXgoJ2VudGl0eUlkJyk7XG4gICAgICAgICAgICBjb25zdCByZXF1ZXN0ID0gaW5kZXguZ2V0QWxsKGVudGl0eUlkKTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25zdWNjZXNzID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIGNvbnN0IGVudHJpZXMgPSByZXF1ZXN0LnJlc3VsdDtcbiAgICAgICAgICAgICAgICByZXNvbHZlKGVudHJpZXMpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25lcnJvciA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICByZWplY3QobmV3IEVycm9yKGBGYWlsZWQgdG8gZ2V0IGF1ZGl0IGVudHJpZXMgZm9yIGVudGl0eSAke2VudGl0eUlkfTogJHtyZXF1ZXN0LmVycm9yfWApKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0pO1xuICAgIH1cbn1cbi8vIEhhcmRjb2RlZCB1c2VySWQgZm9yIG5vdyAtIHdpbGwgY29tZSBmcm9tIHNlc3Npb24gbGF0ZXJcbkF1ZGl0U2VydmljZS5ERUZBVUxUX1VTRVJfSUQgPSAnMDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMDAwMDAxJztcbiIsICIvKipcbiAqIE1vY2tFdmVudFJlcG9zaXRvcnkgLSBMb2FkcyBldmVudCBkYXRhIGZyb20gbG9jYWwgSlNPTiBmaWxlXG4gKlxuICogVXNlZCBmb3IgZGV2ZWxvcG1lbnQgYW5kIHRlc3RpbmcuIE9ubHkgZmV0Y2hBbGwoKSBpcyBpbXBsZW1lbnRlZC5cbiAqL1xuZXhwb3J0IGNsYXNzIE1vY2tFdmVudFJlcG9zaXRvcnkge1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICB0aGlzLmVudGl0eVR5cGUgPSAnRXZlbnQnO1xuICAgICAgICB0aGlzLmRhdGFVcmwgPSAnZGF0YS9tb2NrLWV2ZW50cy5qc29uJztcbiAgICB9XG4gICAgLyoqXG4gICAgICogRmV0Y2ggYWxsIGV2ZW50cyBmcm9tIG1vY2sgSlNPTiBmaWxlXG4gICAgICovXG4gICAgYXN5bmMgZmV0Y2hBbGwoKSB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGZldGNoKHRoaXMuZGF0YVVybCk7XG4gICAgICAgICAgICBpZiAoIXJlc3BvbnNlLm9rKSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBGYWlsZWQgdG8gbG9hZCBtb2NrIGV2ZW50czogJHtyZXNwb25zZS5zdGF0dXN9ICR7cmVzcG9uc2Uuc3RhdHVzVGV4dH1gKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbnN0IHJhd0RhdGEgPSBhd2FpdCByZXNwb25zZS5qc29uKCk7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5wcm9jZXNzQ2FsZW5kYXJEYXRhKHJhd0RhdGEpO1xuICAgICAgICB9XG4gICAgICAgIGNhdGNoIChlcnJvcikge1xuICAgICAgICAgICAgY29uc29sZS5lcnJvcignRmFpbGVkIHRvIGxvYWQgZXZlbnQgZGF0YTonLCBlcnJvcik7XG4gICAgICAgICAgICB0aHJvdyBlcnJvcjtcbiAgICAgICAgfVxuICAgIH1cbiAgICBhc3luYyBzZW5kQ3JlYXRlKF9ldmVudCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ01vY2tFdmVudFJlcG9zaXRvcnkgZG9lcyBub3Qgc3VwcG9ydCBzZW5kQ3JlYXRlLiBNb2NrIGRhdGEgaXMgcmVhZC1vbmx5LicpO1xuICAgIH1cbiAgICBhc3luYyBzZW5kVXBkYXRlKF9pZCwgX3VwZGF0ZXMpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdNb2NrRXZlbnRSZXBvc2l0b3J5IGRvZXMgbm90IHN1cHBvcnQgc2VuZFVwZGF0ZS4gTW9jayBkYXRhIGlzIHJlYWQtb25seS4nKTtcbiAgICB9XG4gICAgYXN5bmMgc2VuZERlbGV0ZShfaWQpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdNb2NrRXZlbnRSZXBvc2l0b3J5IGRvZXMgbm90IHN1cHBvcnQgc2VuZERlbGV0ZS4gTW9jayBkYXRhIGlzIHJlYWQtb25seS4nKTtcbiAgICB9XG4gICAgcHJvY2Vzc0NhbGVuZGFyRGF0YShkYXRhKSB7XG4gICAgICAgIHJldHVybiBkYXRhLm1hcCgoZXZlbnQpID0+IHtcbiAgICAgICAgICAgIC8vIFZhbGlkYXRlIGN1c3RvbWVyIGV2ZW50IGNvbnN0cmFpbnRzXG4gICAgICAgICAgICBpZiAoZXZlbnQudHlwZSA9PT0gJ2N1c3RvbWVyJykge1xuICAgICAgICAgICAgICAgIGlmICghZXZlbnQuYm9va2luZ0lkKVxuICAgICAgICAgICAgICAgICAgICBjb25zb2xlLndhcm4oYEN1c3RvbWVyIGV2ZW50ICR7ZXZlbnQuaWR9IG1pc3NpbmcgYm9va2luZ0lkYCk7XG4gICAgICAgICAgICAgICAgaWYgKCFldmVudC5yZXNvdXJjZUlkKVxuICAgICAgICAgICAgICAgICAgICBjb25zb2xlLndhcm4oYEN1c3RvbWVyIGV2ZW50ICR7ZXZlbnQuaWR9IG1pc3NpbmcgcmVzb3VyY2VJZGApO1xuICAgICAgICAgICAgICAgIGlmICghZXZlbnQuY3VzdG9tZXJJZClcbiAgICAgICAgICAgICAgICAgICAgY29uc29sZS53YXJuKGBDdXN0b21lciBldmVudCAke2V2ZW50LmlkfSBtaXNzaW5nIGN1c3RvbWVySWRgKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICAgICAgaWQ6IGV2ZW50LmlkLFxuICAgICAgICAgICAgICAgIHRpdGxlOiBldmVudC50aXRsZSxcbiAgICAgICAgICAgICAgICBkZXNjcmlwdGlvbjogZXZlbnQuZGVzY3JpcHRpb24sXG4gICAgICAgICAgICAgICAgc3RhcnQ6IG5ldyBEYXRlKGV2ZW50LnN0YXJ0KSxcbiAgICAgICAgICAgICAgICBlbmQ6IG5ldyBEYXRlKGV2ZW50LmVuZCksXG4gICAgICAgICAgICAgICAgdHlwZTogZXZlbnQudHlwZSxcbiAgICAgICAgICAgICAgICBhbGxEYXk6IGV2ZW50LmFsbERheSB8fCBmYWxzZSxcbiAgICAgICAgICAgICAgICBib29raW5nSWQ6IGV2ZW50LmJvb2tpbmdJZCxcbiAgICAgICAgICAgICAgICByZXNvdXJjZUlkOiBldmVudC5yZXNvdXJjZUlkLFxuICAgICAgICAgICAgICAgIGN1c3RvbWVySWQ6IGV2ZW50LmN1c3RvbWVySWQsXG4gICAgICAgICAgICAgICAgcmVjdXJyaW5nSWQ6IGV2ZW50LnJlY3VycmluZ0lkLFxuICAgICAgICAgICAgICAgIG1ldGFkYXRhOiBldmVudC5tZXRhZGF0YSxcbiAgICAgICAgICAgICAgICBzeW5jU3RhdHVzOiAnc3luY2VkJ1xuICAgICAgICAgICAgfTtcbiAgICAgICAgfSk7XG4gICAgfVxufVxuIiwgIi8qKlxuICogTW9ja1Jlc291cmNlUmVwb3NpdG9yeSAtIExvYWRzIHJlc291cmNlIGRhdGEgZnJvbSBsb2NhbCBKU09OIGZpbGVcbiAqL1xuZXhwb3J0IGNsYXNzIE1vY2tSZXNvdXJjZVJlcG9zaXRvcnkge1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICB0aGlzLmVudGl0eVR5cGUgPSAnUmVzb3VyY2UnO1xuICAgICAgICB0aGlzLmRhdGFVcmwgPSAnZGF0YS9tb2NrLXJlc291cmNlcy5qc29uJztcbiAgICB9XG4gICAgYXN5bmMgZmV0Y2hBbGwoKSB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGZldGNoKHRoaXMuZGF0YVVybCk7XG4gICAgICAgICAgICBpZiAoIXJlc3BvbnNlLm9rKSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBGYWlsZWQgdG8gbG9hZCBtb2NrIHJlc291cmNlczogJHtyZXNwb25zZS5zdGF0dXN9ICR7cmVzcG9uc2Uuc3RhdHVzVGV4dH1gKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbnN0IHJhd0RhdGEgPSBhd2FpdCByZXNwb25zZS5qc29uKCk7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5wcm9jZXNzUmVzb3VyY2VEYXRhKHJhd0RhdGEpO1xuICAgICAgICB9XG4gICAgICAgIGNhdGNoIChlcnJvcikge1xuICAgICAgICAgICAgY29uc29sZS5lcnJvcignRmFpbGVkIHRvIGxvYWQgcmVzb3VyY2UgZGF0YTonLCBlcnJvcik7XG4gICAgICAgICAgICB0aHJvdyBlcnJvcjtcbiAgICAgICAgfVxuICAgIH1cbiAgICBhc3luYyBzZW5kQ3JlYXRlKF9yZXNvdXJjZSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ01vY2tSZXNvdXJjZVJlcG9zaXRvcnkgZG9lcyBub3Qgc3VwcG9ydCBzZW5kQ3JlYXRlLiBNb2NrIGRhdGEgaXMgcmVhZC1vbmx5LicpO1xuICAgIH1cbiAgICBhc3luYyBzZW5kVXBkYXRlKF9pZCwgX3VwZGF0ZXMpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdNb2NrUmVzb3VyY2VSZXBvc2l0b3J5IGRvZXMgbm90IHN1cHBvcnQgc2VuZFVwZGF0ZS4gTW9jayBkYXRhIGlzIHJlYWQtb25seS4nKTtcbiAgICB9XG4gICAgYXN5bmMgc2VuZERlbGV0ZShfaWQpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdNb2NrUmVzb3VyY2VSZXBvc2l0b3J5IGRvZXMgbm90IHN1cHBvcnQgc2VuZERlbGV0ZS4gTW9jayBkYXRhIGlzIHJlYWQtb25seS4nKTtcbiAgICB9XG4gICAgcHJvY2Vzc1Jlc291cmNlRGF0YShkYXRhKSB7XG4gICAgICAgIHJldHVybiBkYXRhLm1hcCgocmVzb3VyY2UpID0+ICh7XG4gICAgICAgICAgICBpZDogcmVzb3VyY2UuaWQsXG4gICAgICAgICAgICBuYW1lOiByZXNvdXJjZS5uYW1lLFxuICAgICAgICAgICAgZGlzcGxheU5hbWU6IHJlc291cmNlLmRpc3BsYXlOYW1lLFxuICAgICAgICAgICAgdHlwZTogcmVzb3VyY2UudHlwZSxcbiAgICAgICAgICAgIGF2YXRhclVybDogcmVzb3VyY2UuYXZhdGFyVXJsLFxuICAgICAgICAgICAgY29sb3I6IHJlc291cmNlLmNvbG9yLFxuICAgICAgICAgICAgaXNBY3RpdmU6IHJlc291cmNlLmlzQWN0aXZlLFxuICAgICAgICAgICAgZGVmYXVsdFNjaGVkdWxlOiByZXNvdXJjZS5kZWZhdWx0U2NoZWR1bGUsXG4gICAgICAgICAgICBtZXRhZGF0YTogcmVzb3VyY2UubWV0YWRhdGEsXG4gICAgICAgICAgICBzeW5jU3RhdHVzOiAnc3luY2VkJ1xuICAgICAgICB9KSk7XG4gICAgfVxufVxuIiwgIi8qKlxuICogTW9ja0Jvb2tpbmdSZXBvc2l0b3J5IC0gTG9hZHMgYm9va2luZyBkYXRhIGZyb20gbG9jYWwgSlNPTiBmaWxlXG4gKi9cbmV4cG9ydCBjbGFzcyBNb2NrQm9va2luZ1JlcG9zaXRvcnkge1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICB0aGlzLmVudGl0eVR5cGUgPSAnQm9va2luZyc7XG4gICAgICAgIHRoaXMuZGF0YVVybCA9ICdkYXRhL21vY2stYm9va2luZ3MuanNvbic7XG4gICAgfVxuICAgIGFzeW5jIGZldGNoQWxsKCkge1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaCh0aGlzLmRhdGFVcmwpO1xuICAgICAgICAgICAgaWYgKCFyZXNwb25zZS5vaykge1xuICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgRmFpbGVkIHRvIGxvYWQgbW9jayBib29raW5nczogJHtyZXNwb25zZS5zdGF0dXN9ICR7cmVzcG9uc2Uuc3RhdHVzVGV4dH1gKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbnN0IHJhd0RhdGEgPSBhd2FpdCByZXNwb25zZS5qc29uKCk7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5wcm9jZXNzQm9va2luZ0RhdGEocmF3RGF0YSk7XG4gICAgICAgIH1cbiAgICAgICAgY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgICAgICBjb25zb2xlLmVycm9yKCdGYWlsZWQgdG8gbG9hZCBib29raW5nIGRhdGE6JywgZXJyb3IpO1xuICAgICAgICAgICAgdGhyb3cgZXJyb3I7XG4gICAgICAgIH1cbiAgICB9XG4gICAgYXN5bmMgc2VuZENyZWF0ZShfYm9va2luZykge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ01vY2tCb29raW5nUmVwb3NpdG9yeSBkb2VzIG5vdCBzdXBwb3J0IHNlbmRDcmVhdGUuIE1vY2sgZGF0YSBpcyByZWFkLW9ubHkuJyk7XG4gICAgfVxuICAgIGFzeW5jIHNlbmRVcGRhdGUoX2lkLCBfdXBkYXRlcykge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ01vY2tCb29raW5nUmVwb3NpdG9yeSBkb2VzIG5vdCBzdXBwb3J0IHNlbmRVcGRhdGUuIE1vY2sgZGF0YSBpcyByZWFkLW9ubHkuJyk7XG4gICAgfVxuICAgIGFzeW5jIHNlbmREZWxldGUoX2lkKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignTW9ja0Jvb2tpbmdSZXBvc2l0b3J5IGRvZXMgbm90IHN1cHBvcnQgc2VuZERlbGV0ZS4gTW9jayBkYXRhIGlzIHJlYWQtb25seS4nKTtcbiAgICB9XG4gICAgcHJvY2Vzc0Jvb2tpbmdEYXRhKGRhdGEpIHtcbiAgICAgICAgcmV0dXJuIGRhdGEubWFwKChib29raW5nKSA9PiAoe1xuICAgICAgICAgICAgaWQ6IGJvb2tpbmcuaWQsXG4gICAgICAgICAgICBjdXN0b21lcklkOiBib29raW5nLmN1c3RvbWVySWQsXG4gICAgICAgICAgICBzdGF0dXM6IGJvb2tpbmcuc3RhdHVzLFxuICAgICAgICAgICAgY3JlYXRlZEF0OiBuZXcgRGF0ZShib29raW5nLmNyZWF0ZWRBdCksXG4gICAgICAgICAgICBzZXJ2aWNlczogYm9va2luZy5zZXJ2aWNlcyxcbiAgICAgICAgICAgIHRvdGFsUHJpY2U6IGJvb2tpbmcudG90YWxQcmljZSxcbiAgICAgICAgICAgIHRhZ3M6IGJvb2tpbmcudGFncyxcbiAgICAgICAgICAgIG5vdGVzOiBib29raW5nLm5vdGVzLFxuICAgICAgICAgICAgc3luY1N0YXR1czogJ3N5bmNlZCdcbiAgICAgICAgfSkpO1xuICAgIH1cbn1cbiIsICIvKipcbiAqIE1vY2tDdXN0b21lclJlcG9zaXRvcnkgLSBMb2FkcyBjdXN0b21lciBkYXRhIGZyb20gbG9jYWwgSlNPTiBmaWxlXG4gKi9cbmV4cG9ydCBjbGFzcyBNb2NrQ3VzdG9tZXJSZXBvc2l0b3J5IHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpcy5lbnRpdHlUeXBlID0gJ0N1c3RvbWVyJztcbiAgICAgICAgdGhpcy5kYXRhVXJsID0gJ2RhdGEvbW9jay1jdXN0b21lcnMuanNvbic7XG4gICAgfVxuICAgIGFzeW5jIGZldGNoQWxsKCkge1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaCh0aGlzLmRhdGFVcmwpO1xuICAgICAgICAgICAgaWYgKCFyZXNwb25zZS5vaykge1xuICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgRmFpbGVkIHRvIGxvYWQgbW9jayBjdXN0b21lcnM6ICR7cmVzcG9uc2Uuc3RhdHVzfSAke3Jlc3BvbnNlLnN0YXR1c1RleHR9YCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjb25zdCByYXdEYXRhID0gYXdhaXQgcmVzcG9uc2UuanNvbigpO1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMucHJvY2Vzc0N1c3RvbWVyRGF0YShyYXdEYXRhKTtcbiAgICAgICAgfVxuICAgICAgICBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoJ0ZhaWxlZCB0byBsb2FkIGN1c3RvbWVyIGRhdGE6JywgZXJyb3IpO1xuICAgICAgICAgICAgdGhyb3cgZXJyb3I7XG4gICAgICAgIH1cbiAgICB9XG4gICAgYXN5bmMgc2VuZENyZWF0ZShfY3VzdG9tZXIpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdNb2NrQ3VzdG9tZXJSZXBvc2l0b3J5IGRvZXMgbm90IHN1cHBvcnQgc2VuZENyZWF0ZS4gTW9jayBkYXRhIGlzIHJlYWQtb25seS4nKTtcbiAgICB9XG4gICAgYXN5bmMgc2VuZFVwZGF0ZShfaWQsIF91cGRhdGVzKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignTW9ja0N1c3RvbWVyUmVwb3NpdG9yeSBkb2VzIG5vdCBzdXBwb3J0IHNlbmRVcGRhdGUuIE1vY2sgZGF0YSBpcyByZWFkLW9ubHkuJyk7XG4gICAgfVxuICAgIGFzeW5jIHNlbmREZWxldGUoX2lkKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignTW9ja0N1c3RvbWVyUmVwb3NpdG9yeSBkb2VzIG5vdCBzdXBwb3J0IHNlbmREZWxldGUuIE1vY2sgZGF0YSBpcyByZWFkLW9ubHkuJyk7XG4gICAgfVxuICAgIHByb2Nlc3NDdXN0b21lckRhdGEoZGF0YSkge1xuICAgICAgICByZXR1cm4gZGF0YS5tYXAoKGN1c3RvbWVyKSA9PiAoe1xuICAgICAgICAgICAgaWQ6IGN1c3RvbWVyLmlkLFxuICAgICAgICAgICAgbmFtZTogY3VzdG9tZXIubmFtZSxcbiAgICAgICAgICAgIHBob25lOiBjdXN0b21lci5waG9uZSxcbiAgICAgICAgICAgIGVtYWlsOiBjdXN0b21lci5lbWFpbCxcbiAgICAgICAgICAgIG1ldGFkYXRhOiBjdXN0b21lci5tZXRhZGF0YSxcbiAgICAgICAgICAgIHN5bmNTdGF0dXM6ICdzeW5jZWQnXG4gICAgICAgIH0pKTtcbiAgICB9XG59XG4iLCAiLyoqXG4gKiBNb2NrQXVkaXRSZXBvc2l0b3J5IC0gTW9jayBBUEkgcmVwb3NpdG9yeSBmb3IgYXVkaXQgZW50cmllc1xuICpcbiAqIEluIHByb2R1Y3Rpb24sIHRoaXMgd291bGQgc2VuZCBhdWRpdCBlbnRyaWVzIHRvIHRoZSBiYWNrZW5kLlxuICogRm9yIGRldmVsb3BtZW50L3Rlc3RpbmcsIGl0IGp1c3QgbG9ncyB0aGUgb3BlcmF0aW9ucy5cbiAqL1xuZXhwb3J0IGNsYXNzIE1vY2tBdWRpdFJlcG9zaXRvcnkge1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICB0aGlzLmVudGl0eVR5cGUgPSAnQXVkaXQnO1xuICAgIH1cbiAgICBhc3luYyBzZW5kQ3JlYXRlKGVudGl0eSkge1xuICAgICAgICAvLyBTaW11bGF0ZSBBUEkgY2FsbCBkZWxheVxuICAgICAgICBhd2FpdCBuZXcgUHJvbWlzZShyZXNvbHZlID0+IHNldFRpbWVvdXQocmVzb2x2ZSwgMTAwKSk7XG4gICAgICAgIGNvbnNvbGUubG9nKCdNb2NrQXVkaXRSZXBvc2l0b3J5OiBBdWRpdCBlbnRyeSBzeW5jZWQgdG8gYmFja2VuZDonLCB7XG4gICAgICAgICAgICBpZDogZW50aXR5LmlkLFxuICAgICAgICAgICAgZW50aXR5VHlwZTogZW50aXR5LmVudGl0eVR5cGUsXG4gICAgICAgICAgICBlbnRpdHlJZDogZW50aXR5LmVudGl0eUlkLFxuICAgICAgICAgICAgb3BlcmF0aW9uOiBlbnRpdHkub3BlcmF0aW9uLFxuICAgICAgICAgICAgdGltZXN0YW1wOiBuZXcgRGF0ZShlbnRpdHkudGltZXN0YW1wKS50b0lTT1N0cmluZygpXG4gICAgICAgIH0pO1xuICAgICAgICByZXR1cm4gZW50aXR5O1xuICAgIH1cbiAgICBhc3luYyBzZW5kVXBkYXRlKF9pZCwgX2VudGl0eSkge1xuICAgICAgICAvLyBBdWRpdCBlbnRyaWVzIGFyZSBpbW11dGFibGUgLSB1cGRhdGVzIHNob3VsZCBub3QgaGFwcGVuXG4gICAgICAgIHRocm93IG5ldyBFcnJvcignQXVkaXQgZW50cmllcyBjYW5ub3QgYmUgdXBkYXRlZCcpO1xuICAgIH1cbiAgICBhc3luYyBzZW5kRGVsZXRlKF9pZCkge1xuICAgICAgICAvLyBBdWRpdCBlbnRyaWVzIHNob3VsZCBuZXZlciBiZSBkZWxldGVkXG4gICAgICAgIHRocm93IG5ldyBFcnJvcignQXVkaXQgZW50cmllcyBjYW5ub3QgYmUgZGVsZXRlZCcpO1xuICAgIH1cbiAgICBhc3luYyBmZXRjaEFsbCgpIHtcbiAgICAgICAgLy8gRm9yIG5vdywgcmV0dXJuIGVtcHR5IGFycmF5IC0gYXVkaXQgZW50cmllcyBhcmUgbG9jYWwtZmlyc3RcbiAgICAgICAgLy8gSW4gcHJvZHVjdGlvbiwgdGhpcyBjb3VsZCBmZXRjaCBhdWRpdCBoaXN0b3J5IGZyb20gYmFja2VuZFxuICAgICAgICByZXR1cm4gW107XG4gICAgfVxuICAgIGFzeW5jIGZldGNoQnlJZChfaWQpIHtcbiAgICAgICAgLy8gRm9yIG5vdywgcmV0dXJuIG51bGwgLSBhdWRpdCBlbnRyaWVzIGFyZSBsb2NhbC1maXJzdFxuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG59XG4iLCAiLyoqXG4gKiBNb2NrVGVhbVJlcG9zaXRvcnkgLSBMb2FkcyB0ZWFtIGRhdGEgZnJvbSBsb2NhbCBKU09OIGZpbGVcbiAqL1xuZXhwb3J0IGNsYXNzIE1vY2tUZWFtUmVwb3NpdG9yeSB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuZW50aXR5VHlwZSA9ICdUZWFtJztcbiAgICAgICAgdGhpcy5kYXRhVXJsID0gJ2RhdGEvbW9jay10ZWFtcy5qc29uJztcbiAgICB9XG4gICAgYXN5bmMgZmV0Y2hBbGwoKSB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGZldGNoKHRoaXMuZGF0YVVybCk7XG4gICAgICAgICAgICBpZiAoIXJlc3BvbnNlLm9rKSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBGYWlsZWQgdG8gbG9hZCBtb2NrIHRlYW1zOiAke3Jlc3BvbnNlLnN0YXR1c30gJHtyZXNwb25zZS5zdGF0dXNUZXh0fWApO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY29uc3QgcmF3RGF0YSA9IGF3YWl0IHJlc3BvbnNlLmpzb24oKTtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLnByb2Nlc3NUZWFtRGF0YShyYXdEYXRhKTtcbiAgICAgICAgfVxuICAgICAgICBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoJ0ZhaWxlZCB0byBsb2FkIHRlYW0gZGF0YTonLCBlcnJvcik7XG4gICAgICAgICAgICB0aHJvdyBlcnJvcjtcbiAgICAgICAgfVxuICAgIH1cbiAgICBhc3luYyBzZW5kQ3JlYXRlKF90ZWFtKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignTW9ja1RlYW1SZXBvc2l0b3J5IGRvZXMgbm90IHN1cHBvcnQgc2VuZENyZWF0ZS4gTW9jayBkYXRhIGlzIHJlYWQtb25seS4nKTtcbiAgICB9XG4gICAgYXN5bmMgc2VuZFVwZGF0ZShfaWQsIF91cGRhdGVzKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignTW9ja1RlYW1SZXBvc2l0b3J5IGRvZXMgbm90IHN1cHBvcnQgc2VuZFVwZGF0ZS4gTW9jayBkYXRhIGlzIHJlYWQtb25seS4nKTtcbiAgICB9XG4gICAgYXN5bmMgc2VuZERlbGV0ZShfaWQpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdNb2NrVGVhbVJlcG9zaXRvcnkgZG9lcyBub3Qgc3VwcG9ydCBzZW5kRGVsZXRlLiBNb2NrIGRhdGEgaXMgcmVhZC1vbmx5LicpO1xuICAgIH1cbiAgICBwcm9jZXNzVGVhbURhdGEoZGF0YSkge1xuICAgICAgICByZXR1cm4gZGF0YS5tYXAoKHRlYW0pID0+ICh7XG4gICAgICAgICAgICBpZDogdGVhbS5pZCxcbiAgICAgICAgICAgIG5hbWU6IHRlYW0ubmFtZSxcbiAgICAgICAgICAgIHJlc291cmNlSWRzOiB0ZWFtLnJlc291cmNlSWRzLFxuICAgICAgICAgICAgc3luY1N0YXR1czogJ3N5bmNlZCdcbiAgICAgICAgfSkpO1xuICAgIH1cbn1cbiIsICIvKipcbiAqIE1vY2tEZXBhcnRtZW50UmVwb3NpdG9yeSAtIExvYWRzIGRlcGFydG1lbnQgZGF0YSBmcm9tIGxvY2FsIEpTT04gZmlsZVxuICovXG5leHBvcnQgY2xhc3MgTW9ja0RlcGFydG1lbnRSZXBvc2l0b3J5IHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpcy5lbnRpdHlUeXBlID0gJ0RlcGFydG1lbnQnO1xuICAgICAgICB0aGlzLmRhdGFVcmwgPSAnZGF0YS9tb2NrLWRlcGFydG1lbnRzLmpzb24nO1xuICAgIH1cbiAgICBhc3luYyBmZXRjaEFsbCgpIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2godGhpcy5kYXRhVXJsKTtcbiAgICAgICAgICAgIGlmICghcmVzcG9uc2Uub2spIHtcbiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEZhaWxlZCB0byBsb2FkIG1vY2sgZGVwYXJ0bWVudHM6ICR7cmVzcG9uc2Uuc3RhdHVzfSAke3Jlc3BvbnNlLnN0YXR1c1RleHR9YCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjb25zdCByYXdEYXRhID0gYXdhaXQgcmVzcG9uc2UuanNvbigpO1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMucHJvY2Vzc0RlcGFydG1lbnREYXRhKHJhd0RhdGEpO1xuICAgICAgICB9XG4gICAgICAgIGNhdGNoIChlcnJvcikge1xuICAgICAgICAgICAgY29uc29sZS5lcnJvcignRmFpbGVkIHRvIGxvYWQgZGVwYXJ0bWVudCBkYXRhOicsIGVycm9yKTtcbiAgICAgICAgICAgIHRocm93IGVycm9yO1xuICAgICAgICB9XG4gICAgfVxuICAgIGFzeW5jIHNlbmRDcmVhdGUoX2RlcGFydG1lbnQpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdNb2NrRGVwYXJ0bWVudFJlcG9zaXRvcnkgZG9lcyBub3Qgc3VwcG9ydCBzZW5kQ3JlYXRlLiBNb2NrIGRhdGEgaXMgcmVhZC1vbmx5LicpO1xuICAgIH1cbiAgICBhc3luYyBzZW5kVXBkYXRlKF9pZCwgX3VwZGF0ZXMpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdNb2NrRGVwYXJ0bWVudFJlcG9zaXRvcnkgZG9lcyBub3Qgc3VwcG9ydCBzZW5kVXBkYXRlLiBNb2NrIGRhdGEgaXMgcmVhZC1vbmx5LicpO1xuICAgIH1cbiAgICBhc3luYyBzZW5kRGVsZXRlKF9pZCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ01vY2tEZXBhcnRtZW50UmVwb3NpdG9yeSBkb2VzIG5vdCBzdXBwb3J0IHNlbmREZWxldGUuIE1vY2sgZGF0YSBpcyByZWFkLW9ubHkuJyk7XG4gICAgfVxuICAgIHByb2Nlc3NEZXBhcnRtZW50RGF0YShkYXRhKSB7XG4gICAgICAgIHJldHVybiBkYXRhLm1hcCgoZGVwdCkgPT4gKHtcbiAgICAgICAgICAgIGlkOiBkZXB0LmlkLFxuICAgICAgICAgICAgbmFtZTogZGVwdC5uYW1lLFxuICAgICAgICAgICAgcmVzb3VyY2VJZHM6IGRlcHQucmVzb3VyY2VJZHMsXG4gICAgICAgICAgICBzeW5jU3RhdHVzOiAnc3luY2VkJ1xuICAgICAgICB9KSk7XG4gICAgfVxufVxuIiwgIi8qKlxuICogTW9ja1NldHRpbmdzUmVwb3NpdG9yeSAtIExvYWRzIHRlbmFudCBzZXR0aW5ncyBmcm9tIGxvY2FsIEpTT04gZmlsZVxuICpcbiAqIFNldHRpbmdzIGFyZSBzdG9yZWQgYXMgc2VwYXJhdGUgcmVjb3JkcyBwZXIgc2VjdGlvbiAod29ya3dlZWssIGdyaWQsIGV0Yy4pLlxuICogVGhlIEpTT04gZmlsZSBpcyBhbHJlYWR5IGFuIGFycmF5IG9mIFRlbmFudFNldHRpbmcgcmVjb3Jkcy5cbiAqL1xuZXhwb3J0IGNsYXNzIE1vY2tTZXR0aW5nc1JlcG9zaXRvcnkge1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICB0aGlzLmVudGl0eVR5cGUgPSAnU2V0dGluZ3MnO1xuICAgICAgICB0aGlzLmRhdGFVcmwgPSAnZGF0YS90ZW5hbnQtc2V0dGluZ3MuanNvbic7XG4gICAgfVxuICAgIGFzeW5jIGZldGNoQWxsKCkge1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaCh0aGlzLmRhdGFVcmwpO1xuICAgICAgICAgICAgaWYgKCFyZXNwb25zZS5vaykge1xuICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgRmFpbGVkIHRvIGxvYWQgdGVuYW50IHNldHRpbmdzOiAke3Jlc3BvbnNlLnN0YXR1c30gJHtyZXNwb25zZS5zdGF0dXNUZXh0fWApO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY29uc3Qgc2V0dGluZ3MgPSBhd2FpdCByZXNwb25zZS5qc29uKCk7XG4gICAgICAgICAgICAvLyBFbnN1cmUgc3luY1N0YXR1cyBpcyBzZXQgb24gZWFjaCByZWNvcmRcbiAgICAgICAgICAgIHJldHVybiBzZXR0aW5ncy5tYXAocyA9PiAoe1xuICAgICAgICAgICAgICAgIC4uLnMsXG4gICAgICAgICAgICAgICAgc3luY1N0YXR1czogcy5zeW5jU3RhdHVzIHx8ICdzeW5jZWQnXG4gICAgICAgICAgICB9KSk7XG4gICAgICAgIH1cbiAgICAgICAgY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgICAgICBjb25zb2xlLmVycm9yKCdGYWlsZWQgdG8gbG9hZCB0ZW5hbnQgc2V0dGluZ3M6JywgZXJyb3IpO1xuICAgICAgICAgICAgdGhyb3cgZXJyb3I7XG4gICAgICAgIH1cbiAgICB9XG4gICAgYXN5bmMgc2VuZENyZWF0ZShfc2V0dGluZ3MpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdNb2NrU2V0dGluZ3NSZXBvc2l0b3J5IGRvZXMgbm90IHN1cHBvcnQgc2VuZENyZWF0ZS4gTW9jayBkYXRhIGlzIHJlYWQtb25seS4nKTtcbiAgICB9XG4gICAgYXN5bmMgc2VuZFVwZGF0ZShfaWQsIF91cGRhdGVzKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignTW9ja1NldHRpbmdzUmVwb3NpdG9yeSBkb2VzIG5vdCBzdXBwb3J0IHNlbmRVcGRhdGUuIE1vY2sgZGF0YSBpcyByZWFkLW9ubHkuJyk7XG4gICAgfVxuICAgIGFzeW5jIHNlbmREZWxldGUoX2lkKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignTW9ja1NldHRpbmdzUmVwb3NpdG9yeSBkb2VzIG5vdCBzdXBwb3J0IHNlbmREZWxldGUuIE1vY2sgZGF0YSBpcyByZWFkLW9ubHkuJyk7XG4gICAgfVxufVxuIiwgImV4cG9ydCBjbGFzcyBNb2NrVmlld0NvbmZpZ1JlcG9zaXRvcnkge1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICB0aGlzLmVudGl0eVR5cGUgPSAnVmlld0NvbmZpZyc7XG4gICAgICAgIHRoaXMuZGF0YVVybCA9ICdkYXRhL3ZpZXdjb25maWdzLmpzb24nO1xuICAgIH1cbiAgICBhc3luYyBmZXRjaEFsbCgpIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2godGhpcy5kYXRhVXJsKTtcbiAgICAgICAgICAgIGlmICghcmVzcG9uc2Uub2spIHtcbiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEZhaWxlZCB0byBsb2FkIHZpZXdjb25maWdzOiAke3Jlc3BvbnNlLnN0YXR1c30gJHtyZXNwb25zZS5zdGF0dXNUZXh0fWApO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY29uc3QgcmF3RGF0YSA9IGF3YWl0IHJlc3BvbnNlLmpzb24oKTtcbiAgICAgICAgICAgIC8vIEVuc3VyZSBzeW5jU3RhdHVzIGlzIHNldCBvbiBlYWNoIGNvbmZpZ1xuICAgICAgICAgICAgY29uc3QgY29uZmlncyA9IHJhd0RhdGEubWFwKChjb25maWcpID0+ICh7XG4gICAgICAgICAgICAgICAgLi4uY29uZmlnLFxuICAgICAgICAgICAgICAgIHN5bmNTdGF0dXM6IGNvbmZpZy5zeW5jU3RhdHVzIHx8ICdzeW5jZWQnXG4gICAgICAgICAgICB9KSk7XG4gICAgICAgICAgICByZXR1cm4gY29uZmlncztcbiAgICAgICAgfVxuICAgICAgICBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoJ0ZhaWxlZCB0byBsb2FkIHZpZXdjb25maWdzOicsIGVycm9yKTtcbiAgICAgICAgICAgIHRocm93IGVycm9yO1xuICAgICAgICB9XG4gICAgfVxuICAgIGFzeW5jIHNlbmRDcmVhdGUoX2NvbmZpZykge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ01vY2tWaWV3Q29uZmlnUmVwb3NpdG9yeSBkb2VzIG5vdCBzdXBwb3J0IHNlbmRDcmVhdGUuIE1vY2sgZGF0YSBpcyByZWFkLW9ubHkuJyk7XG4gICAgfVxuICAgIGFzeW5jIHNlbmRVcGRhdGUoX2lkLCBfdXBkYXRlcykge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ01vY2tWaWV3Q29uZmlnUmVwb3NpdG9yeSBkb2VzIG5vdCBzdXBwb3J0IHNlbmRVcGRhdGUuIE1vY2sgZGF0YSBpcyByZWFkLW9ubHkuJyk7XG4gICAgfVxuICAgIGFzeW5jIHNlbmREZWxldGUoX2lkKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignTW9ja1ZpZXdDb25maWdSZXBvc2l0b3J5IGRvZXMgbm90IHN1cHBvcnQgc2VuZERlbGV0ZS4gTW9jayBkYXRhIGlzIHJlYWQtb25seS4nKTtcbiAgICB9XG59XG4iLCAiLyoqXG4gKiBEYXRhU2VlZGVyIC0gT3JjaGVzdHJhdGVzIGluaXRpYWwgZGF0YSBsb2FkaW5nIGZyb20gcmVwb3NpdG9yaWVzIGludG8gSW5kZXhlZERCXG4gKlxuICogQVJDSElURUNUVVJFOlxuICogLSBSZXBvc2l0b3J5IChNb2NrL0FwaSk6IEZldGNoZXMgZGF0YSBmcm9tIHNvdXJjZSAoSlNPTiBmaWxlIG9yIGJhY2tlbmQgQVBJKVxuICogLSBEYXRhU2VlZGVyICh0aGlzIGNsYXNzKTogT3JjaGVzdHJhdGVzIGZldGNoICsgc2F2ZSBvcGVyYXRpb25zXG4gKiAtIFNlcnZpY2UgKEV2ZW50U2VydmljZSwgZXRjLik6IFNhdmVzIGRhdGEgdG8gSW5kZXhlZERCXG4gKlxuICogUE9MWU1PUlBISUMgREVTSUdOOlxuICogLSBVc2VzIGFycmF5cyBvZiBJRW50aXR5U2VydmljZVtdIGFuZCBJQXBpUmVwb3NpdG9yeVtdXG4gKiAtIE1hdGNoZXMgc2VydmljZXMgd2l0aCByZXBvc2l0b3JpZXMgdXNpbmcgZW50aXR5VHlwZSBwcm9wZXJ0eVxuICogLSBPcGVuL0Nsb3NlZCBQcmluY2lwbGU6IEFkZGluZyBuZXcgZW50aXR5IHJlcXVpcmVzIG5vIGNvZGUgY2hhbmdlcyBoZXJlXG4gKi9cbmV4cG9ydCBjbGFzcyBEYXRhU2VlZGVyIHtcbiAgICBjb25zdHJ1Y3RvcihzZXJ2aWNlcywgcmVwb3NpdG9yaWVzKSB7XG4gICAgICAgIHRoaXMuc2VydmljZXMgPSBzZXJ2aWNlcztcbiAgICAgICAgdGhpcy5yZXBvc2l0b3JpZXMgPSByZXBvc2l0b3JpZXM7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFNlZWQgYWxsIGVudGl0eSBzdG9yZXMgaWYgdGhleSBhcmUgZW1wdHlcbiAgICAgKi9cbiAgICBhc3luYyBzZWVkSWZFbXB0eSgpIHtcbiAgICAgICAgY29uc29sZS5sb2coJ1tEYXRhU2VlZGVyXSBDaGVja2luZyBpZiBkYXRhYmFzZSBuZWVkcyBzZWVkaW5nLi4uJyk7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBmb3IgKGNvbnN0IHNlcnZpY2Ugb2YgdGhpcy5zZXJ2aWNlcykge1xuICAgICAgICAgICAgICAgIGNvbnN0IHJlcG9zaXRvcnkgPSB0aGlzLnJlcG9zaXRvcmllcy5maW5kKHJlcG8gPT4gcmVwby5lbnRpdHlUeXBlID09PSBzZXJ2aWNlLmVudGl0eVR5cGUpO1xuICAgICAgICAgICAgICAgIGlmICghcmVwb3NpdG9yeSkge1xuICAgICAgICAgICAgICAgICAgICBjb25zb2xlLndhcm4oYFtEYXRhU2VlZGVyXSBObyByZXBvc2l0b3J5IGZvdW5kIGZvciBlbnRpdHkgdHlwZTogJHtzZXJ2aWNlLmVudGl0eVR5cGV9LCBza2lwcGluZ2ApO1xuICAgICAgICAgICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgYXdhaXQgdGhpcy5zZWVkRW50aXR5KHNlcnZpY2UuZW50aXR5VHlwZSwgc2VydmljZSwgcmVwb3NpdG9yeSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjb25zb2xlLmxvZygnW0RhdGFTZWVkZXJdIFNlZWRpbmcgY29tcGxldGUnKTtcbiAgICAgICAgfVxuICAgICAgICBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoJ1tEYXRhU2VlZGVyXSBTZWVkaW5nIGZhaWxlZDonLCBlcnJvcik7XG4gICAgICAgICAgICB0aHJvdyBlcnJvcjtcbiAgICAgICAgfVxuICAgIH1cbiAgICBhc3luYyBzZWVkRW50aXR5KGVudGl0eVR5cGUsIHNlcnZpY2UsIHJlcG9zaXRvcnkpIHtcbiAgICAgICAgY29uc3QgZXhpc3RpbmcgPSBhd2FpdCBzZXJ2aWNlLmdldEFsbCgpO1xuICAgICAgICBpZiAoZXhpc3RpbmcubGVuZ3RoID4gMCkge1xuICAgICAgICAgICAgY29uc29sZS5sb2coYFtEYXRhU2VlZGVyXSAke2VudGl0eVR5cGV9IHN0b3JlIGFscmVhZHkgaGFzICR7ZXhpc3RpbmcubGVuZ3RofSBpdGVtcywgc2tpcHBpbmcgc2VlZGApO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIGNvbnNvbGUubG9nKGBbRGF0YVNlZWRlcl0gJHtlbnRpdHlUeXBlfSBzdG9yZSBpcyBlbXB0eSwgZmV0Y2hpbmcgZnJvbSByZXBvc2l0b3J5Li4uYCk7XG4gICAgICAgIGNvbnN0IGRhdGEgPSBhd2FpdCByZXBvc2l0b3J5LmZldGNoQWxsKCk7XG4gICAgICAgIGNvbnNvbGUubG9nKGBbRGF0YVNlZWRlcl0gRmV0Y2hlZCAke2RhdGEubGVuZ3RofSAke2VudGl0eVR5cGV9IGl0ZW1zLCBzYXZpbmcgdG8gSW5kZXhlZERCLi4uYCk7XG4gICAgICAgIGZvciAoY29uc3QgZW50aXR5IG9mIGRhdGEpIHtcbiAgICAgICAgICAgIGF3YWl0IHNlcnZpY2Uuc2F2ZShlbnRpdHksIHRydWUpOyAvLyBzaWxlbnQgPSB0cnVlIHRvIHNraXAgYXVkaXQgbG9nZ2luZ1xuICAgICAgICB9XG4gICAgICAgIGNvbnNvbGUubG9nKGBbRGF0YVNlZWRlcl0gJHtlbnRpdHlUeXBlfSBzZWVkaW5nIGNvbXBsZXRlICgke2RhdGEubGVuZ3RofSBpdGVtcyBzYXZlZClgKTtcbiAgICB9XG59XG4iLCAiLyoqXG4gKiBDYWxjdWxhdGUgcGl4ZWwgcG9zaXRpb24gZm9yIGFuIGV2ZW50IGJhc2VkIG9uIGl0cyB0aW1lc1xuICovXG5leHBvcnQgZnVuY3Rpb24gY2FsY3VsYXRlRXZlbnRQb3NpdGlvbihzdGFydCwgZW5kLCBjb25maWcpIHtcbiAgICBjb25zdCBzdGFydE1pbnV0ZXMgPSBzdGFydC5nZXRIb3VycygpICogNjAgKyBzdGFydC5nZXRNaW51dGVzKCk7XG4gICAgY29uc3QgZW5kTWludXRlcyA9IGVuZC5nZXRIb3VycygpICogNjAgKyBlbmQuZ2V0TWludXRlcygpO1xuICAgIGNvbnN0IGRheVN0YXJ0TWludXRlcyA9IGNvbmZpZy5kYXlTdGFydEhvdXIgKiA2MDtcbiAgICBjb25zdCBtaW51dGVIZWlnaHQgPSBjb25maWcuaG91ckhlaWdodCAvIDYwO1xuICAgIGNvbnN0IHRvcCA9IChzdGFydE1pbnV0ZXMgLSBkYXlTdGFydE1pbnV0ZXMpICogbWludXRlSGVpZ2h0O1xuICAgIGNvbnN0IGhlaWdodCA9IChlbmRNaW51dGVzIC0gc3RhcnRNaW51dGVzKSAqIG1pbnV0ZUhlaWdodDtcbiAgICByZXR1cm4geyB0b3AsIGhlaWdodCB9O1xufVxuLyoqXG4gKiBDb252ZXJ0IG1pbnV0ZXMgdG8gcGl4ZWxzXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBtaW51dGVzVG9QaXhlbHMobWludXRlcywgY29uZmlnKSB7XG4gICAgcmV0dXJuIChtaW51dGVzIC8gNjApICogY29uZmlnLmhvdXJIZWlnaHQ7XG59XG4vKipcbiAqIENvbnZlcnQgcGl4ZWxzIHRvIG1pbnV0ZXNcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHBpeGVsc1RvTWludXRlcyhwaXhlbHMsIGNvbmZpZykge1xuICAgIHJldHVybiAocGl4ZWxzIC8gY29uZmlnLmhvdXJIZWlnaHQpICogNjA7XG59XG4vKipcbiAqIFNuYXAgcGl4ZWwgcG9zaXRpb24gdG8gZ3JpZCBpbnRlcnZhbFxuICovXG5leHBvcnQgZnVuY3Rpb24gc25hcFRvR3JpZChwaXhlbHMsIGNvbmZpZykge1xuICAgIGNvbnN0IHNuYXBQaXhlbHMgPSBtaW51dGVzVG9QaXhlbHMoY29uZmlnLnNuYXBJbnRlcnZhbCwgY29uZmlnKTtcbiAgICByZXR1cm4gTWF0aC5yb3VuZChwaXhlbHMgLyBzbmFwUGl4ZWxzKSAqIHNuYXBQaXhlbHM7XG59XG4iLCAiaW1wb3J0IHsgY2FsY3VsYXRlRXZlbnRQb3NpdGlvbiB9IGZyb20gJy4uLy4uL3V0aWxzL1Bvc2l0aW9uVXRpbHMnO1xuLyoqXG4gKiBDaGVjayBpZiB0d28gZXZlbnRzIG92ZXJsYXAgKHN0cmljdCAtIHRvdWNoaW5nIGF0IGJvdW5kYXJ5ID0gTk9UIG92ZXJsYXBwaW5nKVxuICogVGhpcyBtYXRjaGVzIFNjZW5hcmlvIDg6IGVuZD09PXN0YXJ0IGlzIE5PVCBvdmVybGFwXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBldmVudHNPdmVybGFwKGEsIGIpIHtcbiAgICByZXR1cm4gYS5zdGFydCA8IGIuZW5kICYmIGEuZW5kID4gYi5zdGFydDtcbn1cbi8qKlxuICogQ2hlY2sgaWYgdHdvIGV2ZW50cyBhcmUgd2l0aGluIHRocmVzaG9sZCBmb3IgZ3JpZCBncm91cGluZy5cbiAqIFRoaXMgaW5jbHVkZXM6XG4gKiAxLiBTdGFydC10by1zdGFydDogRXZlbnRzIHN0YXJ0IHdpdGhpbiB0aHJlc2hvbGQgb2YgZWFjaCBvdGhlclxuICogMi4gRW5kLXRvLXN0YXJ0OiBPbmUgZXZlbnQgc3RhcnRzIHdpdGhpbiB0aHJlc2hvbGQgYmVmb3JlIGFub3RoZXIgZW5kc1xuICovXG5mdW5jdGlvbiBldmVudHNXaXRoaW5UaHJlc2hvbGQoYSwgYiwgdGhyZXNob2xkTWludXRlcykge1xuICAgIGNvbnN0IHRocmVzaG9sZE1zID0gdGhyZXNob2xkTWludXRlcyAqIDYwICogMTAwMDtcbiAgICAvLyBTdGFydC10by1zdGFydDogYm90aCBldmVudHMgc3RhcnQgd2l0aGluIHRocmVzaG9sZFxuICAgIGNvbnN0IHN0YXJ0VG9TdGFydERpZmYgPSBNYXRoLmFicyhhLnN0YXJ0LmdldFRpbWUoKSAtIGIuc3RhcnQuZ2V0VGltZSgpKTtcbiAgICBpZiAoc3RhcnRUb1N0YXJ0RGlmZiA8PSB0aHJlc2hvbGRNcylcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgLy8gRW5kLXRvLXN0YXJ0OiBvbmUgZXZlbnQgc3RhcnRzIHdpdGhpbiB0aHJlc2hvbGQgYmVmb3JlIHRoZSBvdGhlciBlbmRzXG4gICAgLy8gQiBzdGFydHMgd2l0aGluIHRocmVzaG9sZCBiZWZvcmUgQSBlbmRzXG4gICAgY29uc3QgYlN0YXJ0c0JlZm9yZUFFbmRzID0gYS5lbmQuZ2V0VGltZSgpIC0gYi5zdGFydC5nZXRUaW1lKCk7XG4gICAgaWYgKGJTdGFydHNCZWZvcmVBRW5kcyA+IDAgJiYgYlN0YXJ0c0JlZm9yZUFFbmRzIDw9IHRocmVzaG9sZE1zKVxuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAvLyBBIHN0YXJ0cyB3aXRoaW4gdGhyZXNob2xkIGJlZm9yZSBCIGVuZHNcbiAgICBjb25zdCBhU3RhcnRzQmVmb3JlQkVuZHMgPSBiLmVuZC5nZXRUaW1lKCkgLSBhLnN0YXJ0LmdldFRpbWUoKTtcbiAgICBpZiAoYVN0YXJ0c0JlZm9yZUJFbmRzID4gMCAmJiBhU3RhcnRzQmVmb3JlQkVuZHMgPD0gdGhyZXNob2xkTXMpXG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgIHJldHVybiBmYWxzZTtcbn1cbi8qKlxuICogQ2hlY2sgaWYgYWxsIGV2ZW50cyBpbiBhIGdyb3VwIHN0YXJ0IHdpdGhpbiB0aHJlc2hvbGQgb2YgZWFjaCBvdGhlclxuICovXG5mdW5jdGlvbiBhbGxTdGFydFdpdGhpblRocmVzaG9sZChldmVudHMsIHRocmVzaG9sZE1pbnV0ZXMpIHtcbiAgICBpZiAoZXZlbnRzLmxlbmd0aCA8PSAxKVxuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAvLyBGaW5kIGVhcmxpZXN0IGFuZCBsYXRlc3Qgc3RhcnQgdGltZXNcbiAgICBsZXQgZWFybGllc3QgPSBldmVudHNbMF0uc3RhcnQuZ2V0VGltZSgpO1xuICAgIGxldCBsYXRlc3QgPSBldmVudHNbMF0uc3RhcnQuZ2V0VGltZSgpO1xuICAgIGZvciAoY29uc3QgZXZlbnQgb2YgZXZlbnRzKSB7XG4gICAgICAgIGNvbnN0IHRpbWUgPSBldmVudC5zdGFydC5nZXRUaW1lKCk7XG4gICAgICAgIGlmICh0aW1lIDwgZWFybGllc3QpXG4gICAgICAgICAgICBlYXJsaWVzdCA9IHRpbWU7XG4gICAgICAgIGlmICh0aW1lID4gbGF0ZXN0KVxuICAgICAgICAgICAgbGF0ZXN0ID0gdGltZTtcbiAgICB9XG4gICAgY29uc3QgZGlmZk1pbnV0ZXMgPSAobGF0ZXN0IC0gZWFybGllc3QpIC8gKDEwMDAgKiA2MCk7XG4gICAgcmV0dXJuIGRpZmZNaW51dGVzIDw9IHRocmVzaG9sZE1pbnV0ZXM7XG59XG4vKipcbiAqIEZpbmQgZ3JvdXBzIG9mIG92ZXJsYXBwaW5nIGV2ZW50cyAoY29ubmVjdGVkIGJ5IG92ZXJsYXAgY2hhaW4pXG4gKiBFdmVudHMgYXJlIGdyb3VwZWQgaWYgdGhleSBvdmVybGFwIHdpdGggYW55IGV2ZW50IGluIHRoZSBncm91cFxuICovXG5mdW5jdGlvbiBmaW5kT3ZlcmxhcEdyb3VwcyhldmVudHMpIHtcbiAgICBpZiAoZXZlbnRzLmxlbmd0aCA9PT0gMClcbiAgICAgICAgcmV0dXJuIFtdO1xuICAgIGNvbnN0IHNvcnRlZCA9IFsuLi5ldmVudHNdLnNvcnQoKGEsIGIpID0+IGEuc3RhcnQuZ2V0VGltZSgpIC0gYi5zdGFydC5nZXRUaW1lKCkpO1xuICAgIGNvbnN0IHVzZWQgPSBuZXcgU2V0KCk7XG4gICAgY29uc3QgZ3JvdXBzID0gW107XG4gICAgZm9yIChjb25zdCBldmVudCBvZiBzb3J0ZWQpIHtcbiAgICAgICAgaWYgKHVzZWQuaGFzKGV2ZW50LmlkKSlcbiAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAvLyBTdGFydCBhIG5ldyBncm91cCB3aXRoIHRoaXMgZXZlbnRcbiAgICAgICAgY29uc3QgZ3JvdXAgPSBbZXZlbnRdO1xuICAgICAgICB1c2VkLmFkZChldmVudC5pZCk7XG4gICAgICAgIC8vIEV4cGFuZCBncm91cCBieSBmaW5kaW5nIGFsbCBjb25uZWN0ZWQgZXZlbnRzICh2aWEgb3ZlcmxhcClcbiAgICAgICAgbGV0IGV4cGFuZGVkID0gdHJ1ZTtcbiAgICAgICAgd2hpbGUgKGV4cGFuZGVkKSB7XG4gICAgICAgICAgICBleHBhbmRlZCA9IGZhbHNlO1xuICAgICAgICAgICAgZm9yIChjb25zdCBjYW5kaWRhdGUgb2Ygc29ydGVkKSB7XG4gICAgICAgICAgICAgICAgaWYgKHVzZWQuaGFzKGNhbmRpZGF0ZS5pZCkpXG4gICAgICAgICAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgICAgICAgIC8vIENoZWNrIGlmIGNhbmRpZGF0ZSBvdmVybGFwcyB3aXRoIGFueSBldmVudCBpbiBncm91cFxuICAgICAgICAgICAgICAgIGNvbnN0IGNvbm5lY3RzID0gZ3JvdXAuc29tZShtZW1iZXIgPT4gZXZlbnRzT3ZlcmxhcChtZW1iZXIsIGNhbmRpZGF0ZSkpO1xuICAgICAgICAgICAgICAgIGlmIChjb25uZWN0cykge1xuICAgICAgICAgICAgICAgICAgICBncm91cC5wdXNoKGNhbmRpZGF0ZSk7XG4gICAgICAgICAgICAgICAgICAgIHVzZWQuYWRkKGNhbmRpZGF0ZS5pZCk7XG4gICAgICAgICAgICAgICAgICAgIGV4cGFuZGVkID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgZ3JvdXBzLnB1c2goZ3JvdXApO1xuICAgIH1cbiAgICByZXR1cm4gZ3JvdXBzO1xufVxuLyoqXG4gKiBGaW5kIGdyaWQgY2FuZGlkYXRlcyB3aXRoaW4gYSBncm91cCAtIGV2ZW50cyBjb25uZWN0ZWQgdmlhIHRocmVzaG9sZCBjaGFpblxuICogVXNlcyBWMSBsb2dpYzogZXZlbnRzIGFyZSBjb25uZWN0ZWQgaWYgd2l0aGluIHRocmVzaG9sZCAobm8gb3ZlcmxhcCByZXF1aXJlbWVudClcbiAqL1xuZnVuY3Rpb24gZmluZEdyaWRDYW5kaWRhdGVzKGV2ZW50cywgdGhyZXNob2xkTWludXRlcykge1xuICAgIGlmIChldmVudHMubGVuZ3RoID09PSAwKVxuICAgICAgICByZXR1cm4gW107XG4gICAgY29uc3Qgc29ydGVkID0gWy4uLmV2ZW50c10uc29ydCgoYSwgYikgPT4gYS5zdGFydC5nZXRUaW1lKCkgLSBiLnN0YXJ0LmdldFRpbWUoKSk7XG4gICAgY29uc3QgdXNlZCA9IG5ldyBTZXQoKTtcbiAgICBjb25zdCBncm91cHMgPSBbXTtcbiAgICBmb3IgKGNvbnN0IGV2ZW50IG9mIHNvcnRlZCkge1xuICAgICAgICBpZiAodXNlZC5oYXMoZXZlbnQuaWQpKVxuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgIGNvbnN0IGdyb3VwID0gW2V2ZW50XTtcbiAgICAgICAgdXNlZC5hZGQoZXZlbnQuaWQpO1xuICAgICAgICAvLyBFeHBhbmQgYnkgdGhyZXNob2xkIGNoYWluIChWMSBsb2dpYzogbm8gb3ZlcmxhcCByZXF1aXJlbWVudCwganVzdCB0aHJlc2hvbGQpXG4gICAgICAgIGxldCBleHBhbmRlZCA9IHRydWU7XG4gICAgICAgIHdoaWxlIChleHBhbmRlZCkge1xuICAgICAgICAgICAgZXhwYW5kZWQgPSBmYWxzZTtcbiAgICAgICAgICAgIGZvciAoY29uc3QgY2FuZGlkYXRlIG9mIHNvcnRlZCkge1xuICAgICAgICAgICAgICAgIGlmICh1c2VkLmhhcyhjYW5kaWRhdGUuaWQpKVxuICAgICAgICAgICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICAgICAgICBjb25zdCBjb25uZWN0cyA9IGdyb3VwLnNvbWUobWVtYmVyID0+IGV2ZW50c1dpdGhpblRocmVzaG9sZChtZW1iZXIsIGNhbmRpZGF0ZSwgdGhyZXNob2xkTWludXRlcykpO1xuICAgICAgICAgICAgICAgIGlmIChjb25uZWN0cykge1xuICAgICAgICAgICAgICAgICAgICBncm91cC5wdXNoKGNhbmRpZGF0ZSk7XG4gICAgICAgICAgICAgICAgICAgIHVzZWQuYWRkKGNhbmRpZGF0ZS5pZCk7XG4gICAgICAgICAgICAgICAgICAgIGV4cGFuZGVkID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgZ3JvdXBzLnB1c2goZ3JvdXApO1xuICAgIH1cbiAgICByZXR1cm4gZ3JvdXBzO1xufVxuLyoqXG4gKiBDYWxjdWxhdGUgc3RhY2sgbGV2ZWxzIGZvciBvdmVybGFwcGluZyBldmVudHMgdXNpbmcgZ3JlZWR5IGFsZ29yaXRobVxuICogRm9yIGVhY2ggZXZlbnQ6IGxldmVsID0gbWF4KG92ZXJsYXBwaW5nIGFscmVhZHktcHJvY2Vzc2VkIGV2ZW50cykgKyAxXG4gKi9cbmZ1bmN0aW9uIGNhbGN1bGF0ZVN0YWNrTGV2ZWxzKGV2ZW50cykge1xuICAgIGNvbnN0IGxldmVscyA9IG5ldyBNYXAoKTtcbiAgICBjb25zdCBzb3J0ZWQgPSBbLi4uZXZlbnRzXS5zb3J0KChhLCBiKSA9PiBhLnN0YXJ0LmdldFRpbWUoKSAtIGIuc3RhcnQuZ2V0VGltZSgpKTtcbiAgICBmb3IgKGNvbnN0IGV2ZW50IG9mIHNvcnRlZCkge1xuICAgICAgICBsZXQgbWF4T3ZlcmxhcHBpbmdMZXZlbCA9IC0xO1xuICAgICAgICAvLyBGaW5kIG1heCBsZXZlbCBhbW9uZyBvdmVybGFwcGluZyBldmVudHMgYWxyZWFkeSBwcm9jZXNzZWRcbiAgICAgICAgZm9yIChjb25zdCBbaWQsIGxldmVsXSBvZiBsZXZlbHMpIHtcbiAgICAgICAgICAgIGNvbnN0IG90aGVyID0gZXZlbnRzLmZpbmQoZSA9PiBlLmlkID09PSBpZCk7XG4gICAgICAgICAgICBpZiAob3RoZXIgJiYgZXZlbnRzT3ZlcmxhcChldmVudCwgb3RoZXIpKSB7XG4gICAgICAgICAgICAgICAgbWF4T3ZlcmxhcHBpbmdMZXZlbCA9IE1hdGgubWF4KG1heE92ZXJsYXBwaW5nTGV2ZWwsIGxldmVsKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBsZXZlbHMuc2V0KGV2ZW50LmlkLCBtYXhPdmVybGFwcGluZ0xldmVsICsgMSk7XG4gICAgfVxuICAgIHJldHVybiBsZXZlbHM7XG59XG4vKipcbiAqIEFsbG9jYXRlIGV2ZW50cyB0byBjb2x1bW5zIGZvciBHUklEIGxheW91dCB1c2luZyBncmVlZHkgYWxnb3JpdGhtXG4gKiBOb24tb3ZlcmxhcHBpbmcgZXZlbnRzIGNhbiBzaGFyZSBhIGNvbHVtbiB0byBtaW5pbWl6ZSB0b3RhbCBjb2x1bW5zXG4gKi9cbmZ1bmN0aW9uIGFsbG9jYXRlQ29sdW1ucyhldmVudHMpIHtcbiAgICBjb25zdCBzb3J0ZWQgPSBbLi4uZXZlbnRzXS5zb3J0KChhLCBiKSA9PiBhLnN0YXJ0LmdldFRpbWUoKSAtIGIuc3RhcnQuZ2V0VGltZSgpKTtcbiAgICBjb25zdCBjb2x1bW5zID0gW107XG4gICAgZm9yIChjb25zdCBldmVudCBvZiBzb3J0ZWQpIHtcbiAgICAgICAgLy8gRmluZCBmaXJzdCBjb2x1bW4gd2hlcmUgZXZlbnQgZG9lc24ndCBvdmVybGFwIHdpdGggZXhpc3RpbmcgZXZlbnRzXG4gICAgICAgIGxldCBwbGFjZWQgPSBmYWxzZTtcbiAgICAgICAgZm9yIChjb25zdCBjb2x1bW4gb2YgY29sdW1ucykge1xuICAgICAgICAgICAgY29uc3QgY2FuRml0ID0gIWNvbHVtbi5zb21lKGUgPT4gZXZlbnRzT3ZlcmxhcChldmVudCwgZSkpO1xuICAgICAgICAgICAgaWYgKGNhbkZpdCkge1xuICAgICAgICAgICAgICAgIGNvbHVtbi5wdXNoKGV2ZW50KTtcbiAgICAgICAgICAgICAgICBwbGFjZWQgPSB0cnVlO1xuICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIC8vIE5vIHN1aXRhYmxlIGNvbHVtbiBmb3VuZCwgY3JlYXRlIG5ldyBvbmVcbiAgICAgICAgaWYgKCFwbGFjZWQpIHtcbiAgICAgICAgICAgIGNvbHVtbnMucHVzaChbZXZlbnRdKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gY29sdW1ucztcbn1cbi8qKlxuICogTWFpbiBlbnRyeSBwb2ludDogQ2FsY3VsYXRlIGNvbXBsZXRlIGxheW91dCBmb3IgYSBjb2x1bW4ncyBldmVudHNcbiAqXG4gKiBBbGdvcml0aG06XG4gKiAxLiBGaW5kIG92ZXJsYXAgZ3JvdXBzIChldmVudHMgY29ubmVjdGVkIGJ5IG92ZXJsYXAgY2hhaW4pXG4gKiAyLiBGb3IgZWFjaCBvdmVybGFwIGdyb3VwLCBmaW5kIGdyaWQgY2FuZGlkYXRlcyAoZXZlbnRzIHdpdGhpbiB0aHJlc2hvbGQgY2hhaW4pXG4gKiAzLiBJZiBhbGwgZXZlbnRzIGluIG92ZXJsYXAgZ3JvdXAgZm9ybSBhIHNpbmdsZSBncmlkIGNhbmRpZGF0ZSBcdTIxOTIgR1JJRCBtb2RlXG4gKiA0LiBPdGhlcndpc2UgXHUyMTkyIFNUQUNLSU5HIG1vZGUgd2l0aCBjYWxjdWxhdGVkIGxldmVsc1xuICovXG5leHBvcnQgZnVuY3Rpb24gY2FsY3VsYXRlQ29sdW1uTGF5b3V0KGV2ZW50cywgY29uZmlnKSB7XG4gICAgY29uc3QgdGhyZXNob2xkTWludXRlcyA9IGNvbmZpZy5ncmlkU3RhcnRUaHJlc2hvbGRNaW51dGVzID8/IDEwO1xuICAgIGNvbnN0IHJlc3VsdCA9IHtcbiAgICAgICAgZ3JpZHM6IFtdLFxuICAgICAgICBzdGFja2VkOiBbXVxuICAgIH07XG4gICAgaWYgKGV2ZW50cy5sZW5ndGggPT09IDApXG4gICAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgLy8gRmluZCBhbGwgb3ZlcmxhcHBpbmcgZXZlbnQgZ3JvdXBzXG4gICAgY29uc3Qgb3ZlcmxhcEdyb3VwcyA9IGZpbmRPdmVybGFwR3JvdXBzKGV2ZW50cyk7XG4gICAgZm9yIChjb25zdCBvdmVybGFwR3JvdXAgb2Ygb3ZlcmxhcEdyb3Vwcykge1xuICAgICAgICBpZiAob3ZlcmxhcEdyb3VwLmxlbmd0aCA9PT0gMSkge1xuICAgICAgICAgICAgLy8gU2luZ2xlIGV2ZW50IC0gbm8gZ3JvdXBpbmcgbmVlZGVkXG4gICAgICAgICAgICByZXN1bHQuc3RhY2tlZC5wdXNoKHtcbiAgICAgICAgICAgICAgICBldmVudDogb3ZlcmxhcEdyb3VwWzBdLFxuICAgICAgICAgICAgICAgIHN0YWNrTGV2ZWw6IDBcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgLy8gV2l0aGluIHRoaXMgb3ZlcmxhcCBncm91cCwgZmluZCBncmlkIGNhbmRpZGF0ZXMgKHRocmVzaG9sZC1jb25uZWN0ZWQgc3ViZ3JvdXBzKVxuICAgICAgICBjb25zdCBncmlkU3ViZ3JvdXBzID0gZmluZEdyaWRDYW5kaWRhdGVzKG92ZXJsYXBHcm91cCwgdGhyZXNob2xkTWludXRlcyk7XG4gICAgICAgIC8vIENoZWNrIGlmIHRoZSBFTlRJUkUgb3ZlcmxhcCBncm91cCBmb3JtcyBhIHNpbmdsZSBncmlkIGNhbmRpZGF0ZVxuICAgICAgICAvLyBUaGlzIGhhcHBlbnMgd2hlbiBhbGwgZXZlbnRzIGFyZSBjb25uZWN0ZWQgdmlhIHRocmVzaG9sZCBjaGFpblxuICAgICAgICBjb25zdCBsYXJnZXN0R3JpZENhbmRpZGF0ZSA9IGdyaWRTdWJncm91cHMucmVkdWNlKChtYXgsIGcpID0+IGcubGVuZ3RoID4gbWF4Lmxlbmd0aCA/IGcgOiBtYXgsIGdyaWRTdWJncm91cHNbMF0pO1xuICAgICAgICBpZiAobGFyZ2VzdEdyaWRDYW5kaWRhdGUubGVuZ3RoID09PSBvdmVybGFwR3JvdXAubGVuZ3RoKSB7XG4gICAgICAgICAgICAvLyBBbGwgZXZlbnRzIGluIG92ZXJsYXAgZ3JvdXAgYXJlIGNvbm5lY3RlZCB2aWEgdGhyZXNob2xkIGNoYWluIFx1MjE5MiBHUklEIG1vZGVcbiAgICAgICAgICAgIGNvbnN0IGNvbHVtbnMgPSBhbGxvY2F0ZUNvbHVtbnMob3ZlcmxhcEdyb3VwKTtcbiAgICAgICAgICAgIGNvbnN0IGVhcmxpZXN0ID0gb3ZlcmxhcEdyb3VwLnJlZHVjZSgobWluLCBlKSA9PiBlLnN0YXJ0IDwgbWluLnN0YXJ0ID8gZSA6IG1pbiwgb3ZlcmxhcEdyb3VwWzBdKTtcbiAgICAgICAgICAgIGNvbnN0IHBvc2l0aW9uID0gY2FsY3VsYXRlRXZlbnRQb3NpdGlvbihlYXJsaWVzdC5zdGFydCwgZWFybGllc3QuZW5kLCBjb25maWcpO1xuICAgICAgICAgICAgcmVzdWx0LmdyaWRzLnB1c2goe1xuICAgICAgICAgICAgICAgIGV2ZW50czogb3ZlcmxhcEdyb3VwLFxuICAgICAgICAgICAgICAgIGNvbHVtbnMsXG4gICAgICAgICAgICAgICAgc3RhY2tMZXZlbDogMCxcbiAgICAgICAgICAgICAgICBwb3NpdGlvbjogeyB0b3A6IHBvc2l0aW9uLnRvcCB9XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIC8vIE5vdCBhbGwgZXZlbnRzIGNvbm5lY3RlZCB2aWEgdGhyZXNob2xkIFx1MjE5MiBTVEFDS0lORyBtb2RlXG4gICAgICAgICAgICBjb25zdCBsZXZlbHMgPSBjYWxjdWxhdGVTdGFja0xldmVscyhvdmVybGFwR3JvdXApO1xuICAgICAgICAgICAgZm9yIChjb25zdCBldmVudCBvZiBvdmVybGFwR3JvdXApIHtcbiAgICAgICAgICAgICAgICByZXN1bHQuc3RhY2tlZC5wdXNoKHtcbiAgICAgICAgICAgICAgICAgICAgZXZlbnQsXG4gICAgICAgICAgICAgICAgICAgIHN0YWNrTGV2ZWw6IGxldmVscy5nZXQoZXZlbnQuaWQpID8/IDBcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xufVxuIiwgImltcG9ydCB7IGNhbGN1bGF0ZUV2ZW50UG9zaXRpb24sIHNuYXBUb0dyaWQsIHBpeGVsc1RvTWludXRlcyB9IGZyb20gJy4uLy4uL3V0aWxzL1Bvc2l0aW9uVXRpbHMnO1xuaW1wb3J0IHsgQ29yZUV2ZW50cyB9IGZyb20gJy4uLy4uL2NvbnN0YW50cy9Db3JlRXZlbnRzJztcbmltcG9ydCB7IGNhbGN1bGF0ZUNvbHVtbkxheW91dCB9IGZyb20gJy4vRXZlbnRMYXlvdXRFbmdpbmUnO1xuLyoqXG4gKiBFdmVudFJlbmRlcmVyIC0gUmVuZGVycyBjYWxlbmRhciBldmVudHMgdG8gdGhlIERPTVxuICpcbiAqIENMRUFOIGFwcHJvYWNoOlxuICogLSBPbmx5IGRhdGEtaWQgYXR0cmlidXRlIG9uIGV2ZW50IGVsZW1lbnRcbiAqIC0gaW5uZXJIVE1MIGNvbnRhaW5zIG9ubHkgdmlzaWJsZSBjb250ZW50XG4gKiAtIEV2ZW50IGRhdGEgcmV0cmlldmVkIHZpYSBFdmVudFNlcnZpY2Ugd2hlbiBuZWVkZWRcbiAqL1xuZXhwb3J0IGNsYXNzIEV2ZW50UmVuZGVyZXIge1xuICAgIGNvbnN0cnVjdG9yKGV2ZW50U2VydmljZSwgZGF0ZVNlcnZpY2UsIGdyaWRDb25maWcsIGV2ZW50QnVzKSB7XG4gICAgICAgIHRoaXMuZXZlbnRTZXJ2aWNlID0gZXZlbnRTZXJ2aWNlO1xuICAgICAgICB0aGlzLmRhdGVTZXJ2aWNlID0gZGF0ZVNlcnZpY2U7XG4gICAgICAgIHRoaXMuZ3JpZENvbmZpZyA9IGdyaWRDb25maWc7XG4gICAgICAgIHRoaXMuZXZlbnRCdXMgPSBldmVudEJ1cztcbiAgICAgICAgdGhpcy5jb250YWluZXIgPSBudWxsO1xuICAgICAgICB0aGlzLnNldHVwTGlzdGVuZXJzKCk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFNldHVwIGxpc3RlbmVycyBmb3IgZHJhZy1kcm9wIGFuZCB1cGRhdGUgZXZlbnRzXG4gICAgICovXG4gICAgc2V0dXBMaXN0ZW5lcnMoKSB7XG4gICAgICAgIHRoaXMuZXZlbnRCdXMub24oQ29yZUV2ZW50cy5FVkVOVF9EUkFHX0NPTFVNTl9DSEFOR0UsIChlKSA9PiB7XG4gICAgICAgICAgICBjb25zdCBwYXlsb2FkID0gZS5kZXRhaWw7XG4gICAgICAgICAgICB0aGlzLmhhbmRsZUNvbHVtbkNoYW5nZShwYXlsb2FkKTtcbiAgICAgICAgfSk7XG4gICAgICAgIHRoaXMuZXZlbnRCdXMub24oQ29yZUV2ZW50cy5FVkVOVF9EUkFHX01PVkUsIChlKSA9PiB7XG4gICAgICAgICAgICBjb25zdCBwYXlsb2FkID0gZS5kZXRhaWw7XG4gICAgICAgICAgICB0aGlzLnVwZGF0ZURyYWdUaW1lc3RhbXAocGF5bG9hZCk7XG4gICAgICAgIH0pO1xuICAgICAgICB0aGlzLmV2ZW50QnVzLm9uKENvcmVFdmVudHMuRVZFTlRfVVBEQVRFRCwgKGUpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHBheWxvYWQgPSBlLmRldGFpbDtcbiAgICAgICAgICAgIHRoaXMuaGFuZGxlRXZlbnRVcGRhdGVkKHBheWxvYWQpO1xuICAgICAgICB9KTtcbiAgICAgICAgdGhpcy5ldmVudEJ1cy5vbihDb3JlRXZlbnRzLkVWRU5UX0RSQUdfRU5ELCAoZSkgPT4ge1xuICAgICAgICAgICAgY29uc3QgcGF5bG9hZCA9IGUuZGV0YWlsO1xuICAgICAgICAgICAgdGhpcy5oYW5kbGVEcmFnRW5kKHBheWxvYWQpO1xuICAgICAgICB9KTtcbiAgICAgICAgdGhpcy5ldmVudEJ1cy5vbihDb3JlRXZlbnRzLkVWRU5UX0RSQUdfTEVBVkVfSEVBREVSLCAoZSkgPT4ge1xuICAgICAgICAgICAgY29uc3QgcGF5bG9hZCA9IGUuZGV0YWlsO1xuICAgICAgICAgICAgdGhpcy5oYW5kbGVEcmFnTGVhdmVIZWFkZXIocGF5bG9hZCk7XG4gICAgICAgIH0pO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBIYW5kbGUgRVZFTlRfRFJBR19FTkQgLSByZW1vdmUgZWxlbWVudCBpZiBkcm9wcGVkIGluIGhlYWRlclxuICAgICAqL1xuICAgIGhhbmRsZURyYWdFbmQocGF5bG9hZCkge1xuICAgICAgICBpZiAocGF5bG9hZC50YXJnZXQgPT09ICdoZWFkZXInKSB7XG4gICAgICAgICAgICAvLyBFdmVudCB3YXMgZHJvcHBlZCBpbiBoZWFkZXIgZHJhd2VyIC0gcmVtb3ZlIGZyb20gZ3JpZFxuICAgICAgICAgICAgY29uc3QgZWxlbWVudCA9IHRoaXMuY29udGFpbmVyPy5xdWVyeVNlbGVjdG9yKGBzd3AtY29udGVudC12aWV3cG9ydCBzd3AtZXZlbnRbZGF0YS1ldmVudC1pZD1cIiR7cGF5bG9hZC5zd3BFdmVudC5ldmVudElkfVwiXWApO1xuICAgICAgICAgICAgZWxlbWVudD8ucmVtb3ZlKCk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgLyoqXG4gICAgICogSGFuZGxlIGhlYWRlciBpdGVtIGxlYXZpbmcgaGVhZGVyIC0gY3JlYXRlIHN3cC1ldmVudCBpbiBncmlkXG4gICAgICovXG4gICAgaGFuZGxlRHJhZ0xlYXZlSGVhZGVyKHBheWxvYWQpIHtcbiAgICAgICAgLy8gT25seSBoYW5kbGUgd2hlbiBzb3VyY2UgaXMgaGVhZGVyIChoZWFkZXIgaXRlbSBkcmFnZ2VkIHRvIGdyaWQpXG4gICAgICAgIGlmIChwYXlsb2FkLnNvdXJjZSAhPT0gJ2hlYWRlcicpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIGlmICghcGF5bG9hZC50YXJnZXRDb2x1bW4gfHwgIXBheWxvYWQuc3RhcnQgfHwgIXBheWxvYWQuZW5kKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAvLyBUdXJuIGhlYWRlciBpdGVtIGludG8gZ2hvc3QgKHN0YXlzIHZpc2libGUgYnV0IGZhZGVkKVxuICAgICAgICBpZiAocGF5bG9hZC5lbGVtZW50KSB7XG4gICAgICAgICAgICBwYXlsb2FkLmVsZW1lbnQuY2xhc3NMaXN0LmFkZCgnZHJhZy1naG9zdCcpO1xuICAgICAgICAgICAgcGF5bG9hZC5lbGVtZW50LnN0eWxlLm9wYWNpdHkgPSAnMC4zJztcbiAgICAgICAgICAgIHBheWxvYWQuZWxlbWVudC5zdHlsZS5wb2ludGVyRXZlbnRzID0gJ25vbmUnO1xuICAgICAgICB9XG4gICAgICAgIC8vIENyZWF0ZSBldmVudCBvYmplY3QgZnJvbSBoZWFkZXIgaXRlbSBkYXRhXG4gICAgICAgIGNvbnN0IGV2ZW50ID0ge1xuICAgICAgICAgICAgaWQ6IHBheWxvYWQuZXZlbnRJZCxcbiAgICAgICAgICAgIHRpdGxlOiBwYXlsb2FkLnRpdGxlIHx8ICcnLFxuICAgICAgICAgICAgZGVzY3JpcHRpb246ICcnLFxuICAgICAgICAgICAgc3RhcnQ6IHBheWxvYWQuc3RhcnQsXG4gICAgICAgICAgICBlbmQ6IHBheWxvYWQuZW5kLFxuICAgICAgICAgICAgdHlwZTogJ2N1c3RvbWVyJyxcbiAgICAgICAgICAgIGFsbERheTogZmFsc2UsXG4gICAgICAgICAgICBzeW5jU3RhdHVzOiAncGVuZGluZydcbiAgICAgICAgfTtcbiAgICAgICAgLy8gQ3JlYXRlIHN3cC1ldmVudCBlbGVtZW50IHVzaW5nIGV4aXN0aW5nIG1ldGhvZFxuICAgICAgICBjb25zdCBlbGVtZW50ID0gdGhpcy5jcmVhdGVFdmVudEVsZW1lbnQoZXZlbnQpO1xuICAgICAgICAvLyBBZGQgdG8gdGFyZ2V0IGNvbHVtblxuICAgICAgICBsZXQgZXZlbnRzTGF5ZXIgPSBwYXlsb2FkLnRhcmdldENvbHVtbi5xdWVyeVNlbGVjdG9yKCdzd3AtZXZlbnRzLWxheWVyJyk7XG4gICAgICAgIGlmICghZXZlbnRzTGF5ZXIpIHtcbiAgICAgICAgICAgIGV2ZW50c0xheWVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3dwLWV2ZW50cy1sYXllcicpO1xuICAgICAgICAgICAgcGF5bG9hZC50YXJnZXRDb2x1bW4uYXBwZW5kQ2hpbGQoZXZlbnRzTGF5ZXIpO1xuICAgICAgICB9XG4gICAgICAgIGV2ZW50c0xheWVyLmFwcGVuZENoaWxkKGVsZW1lbnQpO1xuICAgICAgICAvLyBNYXJrIGFzIGRyYWdnaW5nIHNvIERyYWdEcm9wTWFuYWdlciBjYW4gY29udGludWUgd2l0aCBpdFxuICAgICAgICBlbGVtZW50LmNsYXNzTGlzdC5hZGQoJ2RyYWdnaW5nJyk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEhhbmRsZSBFVkVOVF9VUERBVEVEIC0gcmUtcmVuZGVyIGFmZmVjdGVkIGNvbHVtbnNcbiAgICAgKi9cbiAgICBhc3luYyBoYW5kbGVFdmVudFVwZGF0ZWQocGF5bG9hZCkge1xuICAgICAgICAvLyBSZS1yZW5kZXIgc291cmNlIGNvbHVtbiAoaWYgZGlmZmVyZW50IGZyb20gdGFyZ2V0KVxuICAgICAgICBpZiAocGF5bG9hZC5zb3VyY2VDb2x1bW5LZXkgIT09IHBheWxvYWQudGFyZ2V0Q29sdW1uS2V5KSB7XG4gICAgICAgICAgICBhd2FpdCB0aGlzLnJlcmVuZGVyQ29sdW1uKHBheWxvYWQuc291cmNlQ29sdW1uS2V5KTtcbiAgICAgICAgfVxuICAgICAgICAvLyBSZS1yZW5kZXIgdGFyZ2V0IGNvbHVtblxuICAgICAgICBhd2FpdCB0aGlzLnJlcmVuZGVyQ29sdW1uKHBheWxvYWQudGFyZ2V0Q29sdW1uS2V5KTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogUmUtcmVuZGVyIGEgc2luZ2xlIGNvbHVtbiB3aXRoIGZyZXNoIGRhdGEgZnJvbSBJbmRleGVkREJcbiAgICAgKi9cbiAgICBhc3luYyByZXJlbmRlckNvbHVtbihjb2x1bW5LZXkpIHtcbiAgICAgICAgY29uc3QgY29sdW1uID0gdGhpcy5maW5kQ29sdW1uKGNvbHVtbktleSk7XG4gICAgICAgIGlmICghY29sdW1uKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAvLyBSZWFkIGRhdGUgYW5kIHJlc291cmNlSWQgZGlyZWN0bHkgZnJvbSBjb2x1bW4gYXR0cmlidXRlcyAoY29sdW1uS2V5IGlzIG9wYXF1ZSlcbiAgICAgICAgY29uc3QgZGF0ZSA9IGNvbHVtbi5kYXRhc2V0LmRhdGU7XG4gICAgICAgIGNvbnN0IHJlc291cmNlSWQgPSBjb2x1bW4uZGF0YXNldC5yZXNvdXJjZUlkO1xuICAgICAgICBpZiAoIWRhdGUpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIC8vIEdldCBkYXRlIHJhbmdlIGZvciB0aGlzIGRheVxuICAgICAgICBjb25zdCBzdGFydERhdGUgPSBuZXcgRGF0ZShkYXRlKTtcbiAgICAgICAgY29uc3QgZW5kRGF0ZSA9IG5ldyBEYXRlKGRhdGUpO1xuICAgICAgICBlbmREYXRlLnNldEhvdXJzKDIzLCA1OSwgNTksIDk5OSk7XG4gICAgICAgIC8vIEZldGNoIGV2ZW50cyBmcm9tIEluZGV4ZWREQlxuICAgICAgICBjb25zdCBldmVudHMgPSByZXNvdXJjZUlkXG4gICAgICAgICAgICA/IGF3YWl0IHRoaXMuZXZlbnRTZXJ2aWNlLmdldEJ5UmVzb3VyY2VBbmREYXRlUmFuZ2UocmVzb3VyY2VJZCwgc3RhcnREYXRlLCBlbmREYXRlKVxuICAgICAgICAgICAgOiBhd2FpdCB0aGlzLmV2ZW50U2VydmljZS5nZXRCeURhdGVSYW5nZShzdGFydERhdGUsIGVuZERhdGUpO1xuICAgICAgICAvLyBGaWx0ZXIgdG8gdGltZWQgZXZlbnRzIGFuZCBtYXRjaCBkYXRlIGV4YWN0bHlcbiAgICAgICAgY29uc3QgdGltZWRFdmVudHMgPSBldmVudHMuZmlsdGVyKGV2ZW50ID0+ICFldmVudC5hbGxEYXkgJiYgdGhpcy5kYXRlU2VydmljZS5nZXREYXRlS2V5KGV2ZW50LnN0YXJ0KSA9PT0gZGF0ZSk7XG4gICAgICAgIC8vIEdldCBvciBjcmVhdGUgZXZlbnRzIGxheWVyXG4gICAgICAgIGxldCBldmVudHNMYXllciA9IGNvbHVtbi5xdWVyeVNlbGVjdG9yKCdzd3AtZXZlbnRzLWxheWVyJyk7XG4gICAgICAgIGlmICghZXZlbnRzTGF5ZXIpIHtcbiAgICAgICAgICAgIGV2ZW50c0xheWVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3dwLWV2ZW50cy1sYXllcicpO1xuICAgICAgICAgICAgY29sdW1uLmFwcGVuZENoaWxkKGV2ZW50c0xheWVyKTtcbiAgICAgICAgfVxuICAgICAgICAvLyBDbGVhciBleGlzdGluZyBldmVudHNcbiAgICAgICAgZXZlbnRzTGF5ZXIuaW5uZXJIVE1MID0gJyc7XG4gICAgICAgIC8vIENhbGN1bGF0ZSBsYXlvdXQgd2l0aCBzdGFja2luZy9ncm91cGluZ1xuICAgICAgICBjb25zdCBsYXlvdXQgPSBjYWxjdWxhdGVDb2x1bW5MYXlvdXQodGltZWRFdmVudHMsIHRoaXMuZ3JpZENvbmZpZyk7XG4gICAgICAgIC8vIFJlbmRlciBHUklEIGdyb3Vwc1xuICAgICAgICBsYXlvdXQuZ3JpZHMuZm9yRWFjaChncmlkID0+IHtcbiAgICAgICAgICAgIGNvbnN0IGdyb3VwRWwgPSB0aGlzLnJlbmRlckdyaWRHcm91cChncmlkKTtcbiAgICAgICAgICAgIGV2ZW50c0xheWVyLmFwcGVuZENoaWxkKGdyb3VwRWwpO1xuICAgICAgICB9KTtcbiAgICAgICAgLy8gUmVuZGVyIFNUQUNLRUQgZXZlbnRzXG4gICAgICAgIGxheW91dC5zdGFja2VkLmZvckVhY2goaXRlbSA9PiB7XG4gICAgICAgICAgICBjb25zdCBldmVudEVsID0gdGhpcy5yZW5kZXJTdGFja2VkRXZlbnQoaXRlbS5ldmVudCwgaXRlbS5zdGFja0xldmVsKTtcbiAgICAgICAgICAgIGV2ZW50c0xheWVyLmFwcGVuZENoaWxkKGV2ZW50RWwpO1xuICAgICAgICB9KTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogRmluZCBhIGNvbHVtbiBlbGVtZW50IGJ5IGNvbHVtbktleVxuICAgICAqL1xuICAgIGZpbmRDb2x1bW4oY29sdW1uS2V5KSB7XG4gICAgICAgIGlmICghdGhpcy5jb250YWluZXIpXG4gICAgICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgICAgcmV0dXJuIHRoaXMuY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3IoYHN3cC1kYXktY29sdW1uW2RhdGEtY29sdW1uLWtleT1cIiR7Y29sdW1uS2V5fVwiXWApO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBIYW5kbGUgZXZlbnQgbW92aW5nIHRvIGEgbmV3IGNvbHVtbiBkdXJpbmcgZHJhZ1xuICAgICAqL1xuICAgIGhhbmRsZUNvbHVtbkNoYW5nZShwYXlsb2FkKSB7XG4gICAgICAgIGNvbnN0IGV2ZW50c0xheWVyID0gcGF5bG9hZC5uZXdDb2x1bW4ucXVlcnlTZWxlY3Rvcignc3dwLWV2ZW50cy1sYXllcicpO1xuICAgICAgICBpZiAoIWV2ZW50c0xheWVyKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAvLyBNb3ZlIGVsZW1lbnQgdG8gbmV3IGNvbHVtblxuICAgICAgICBldmVudHNMYXllci5hcHBlbmRDaGlsZChwYXlsb2FkLmVsZW1lbnQpO1xuICAgICAgICAvLyBQcmVzZXJ2ZSBZIHBvc2l0aW9uXG4gICAgICAgIHBheWxvYWQuZWxlbWVudC5zdHlsZS50b3AgPSBgJHtwYXlsb2FkLmN1cnJlbnRZfXB4YDtcbiAgICB9XG4gICAgLyoqXG4gICAgICogVXBkYXRlIHRpbWVzdGFtcCBkaXNwbGF5IGR1cmluZyBkcmFnIChzbmFwcGVkIHRvIGdyaWQpXG4gICAgICovXG4gICAgdXBkYXRlRHJhZ1RpbWVzdGFtcChwYXlsb2FkKSB7XG4gICAgICAgIGNvbnN0IHRpbWVFbCA9IHBheWxvYWQuZWxlbWVudC5xdWVyeVNlbGVjdG9yKCdzd3AtZXZlbnQtdGltZScpO1xuICAgICAgICBpZiAoIXRpbWVFbClcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgLy8gU25hcCBwb3NpdGlvbiB0byBncmlkIGludGVydmFsXG4gICAgICAgIGNvbnN0IHNuYXBwZWRZID0gc25hcFRvR3JpZChwYXlsb2FkLmN1cnJlbnRZLCB0aGlzLmdyaWRDb25maWcpO1xuICAgICAgICAvLyBDYWxjdWxhdGUgbmV3IHN0YXJ0IHRpbWVcbiAgICAgICAgY29uc3QgbWludXRlc0Zyb21HcmlkU3RhcnQgPSBwaXhlbHNUb01pbnV0ZXMoc25hcHBlZFksIHRoaXMuZ3JpZENvbmZpZyk7XG4gICAgICAgIGNvbnN0IHN0YXJ0TWludXRlcyA9ICh0aGlzLmdyaWRDb25maWcuZGF5U3RhcnRIb3VyICogNjApICsgbWludXRlc0Zyb21HcmlkU3RhcnQ7XG4gICAgICAgIC8vIEtlZXAgb3JpZ2luYWwgZHVyYXRpb24gKGZyb20gZWxlbWVudCBoZWlnaHQpXG4gICAgICAgIGNvbnN0IGhlaWdodCA9IHBhcnNlRmxvYXQocGF5bG9hZC5lbGVtZW50LnN0eWxlLmhlaWdodCkgfHwgdGhpcy5ncmlkQ29uZmlnLmhvdXJIZWlnaHQ7XG4gICAgICAgIGNvbnN0IGR1cmF0aW9uTWludXRlcyA9IHBpeGVsc1RvTWludXRlcyhoZWlnaHQsIHRoaXMuZ3JpZENvbmZpZyk7XG4gICAgICAgIC8vIENyZWF0ZSBEYXRlIG9iamVjdHMgZm9yIGNvbnNpc3RlbnQgZm9ybWF0dGluZyB2aWEgRGF0ZVNlcnZpY2VcbiAgICAgICAgY29uc3Qgc3RhcnQgPSB0aGlzLm1pbnV0ZXNUb0RhdGUoc3RhcnRNaW51dGVzKTtcbiAgICAgICAgY29uc3QgZW5kID0gdGhpcy5taW51dGVzVG9EYXRlKHN0YXJ0TWludXRlcyArIGR1cmF0aW9uTWludXRlcyk7XG4gICAgICAgIHRpbWVFbC50ZXh0Q29udGVudCA9IHRoaXMuZGF0ZVNlcnZpY2UuZm9ybWF0VGltZVJhbmdlKHN0YXJ0LCBlbmQpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBDb252ZXJ0IG1pbnV0ZXMgc2luY2UgbWlkbmlnaHQgdG8gYSBEYXRlIG9iamVjdCAodG9kYXkpXG4gICAgICovXG4gICAgbWludXRlc1RvRGF0ZShtaW51dGVzKSB7XG4gICAgICAgIGNvbnN0IGRhdGUgPSBuZXcgRGF0ZSgpO1xuICAgICAgICBkYXRlLnNldEhvdXJzKE1hdGguZmxvb3IobWludXRlcyAvIDYwKSAlIDI0LCBtaW51dGVzICUgNjAsIDAsIDApO1xuICAgICAgICByZXR1cm4gZGF0ZTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogUmVuZGVyIGV2ZW50cyBmb3IgdmlzaWJsZSBkYXRlcyBpbnRvIGRheSBjb2x1bW5zXG4gICAgICogQHBhcmFtIGNvbnRhaW5lciAtIENhbGVuZGFyIGNvbnRhaW5lciBlbGVtZW50XG4gICAgICogQHBhcmFtIGZpbHRlciAtIEZpbHRlciB3aXRoICdkYXRlJyBhbmQgb3B0aW9uYWxseSAncmVzb3VyY2UnIGFycmF5c1xuICAgICAqIEBwYXJhbSBmaWx0ZXJUZW1wbGF0ZSAtIFRlbXBsYXRlIGZvciBtYXRjaGluZyBldmVudHMgdG8gY29sdW1uc1xuICAgICAqL1xuICAgIGFzeW5jIHJlbmRlcihjb250YWluZXIsIGZpbHRlciwgZmlsdGVyVGVtcGxhdGUpIHtcbiAgICAgICAgLy8gU3RvcmUgY29udGFpbmVyIHJlZmVyZW5jZSBmb3IgbGF0ZXIgcmUtcmVuZGVyc1xuICAgICAgICB0aGlzLmNvbnRhaW5lciA9IGNvbnRhaW5lcjtcbiAgICAgICAgY29uc3QgdmlzaWJsZURhdGVzID0gZmlsdGVyWydkYXRlJ10gfHwgW107XG4gICAgICAgIGlmICh2aXNpYmxlRGF0ZXMubGVuZ3RoID09PSAwKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAvLyBHZXQgZGF0ZSByYW5nZSBmb3IgcXVlcnlcbiAgICAgICAgY29uc3Qgc3RhcnREYXRlID0gbmV3IERhdGUodmlzaWJsZURhdGVzWzBdKTtcbiAgICAgICAgY29uc3QgZW5kRGF0ZSA9IG5ldyBEYXRlKHZpc2libGVEYXRlc1t2aXNpYmxlRGF0ZXMubGVuZ3RoIC0gMV0pO1xuICAgICAgICBlbmREYXRlLnNldEhvdXJzKDIzLCA1OSwgNTksIDk5OSk7XG4gICAgICAgIC8vIEZldGNoIGV2ZW50cyBmcm9tIEluZGV4ZWREQlxuICAgICAgICBjb25zdCBldmVudHMgPSBhd2FpdCB0aGlzLmV2ZW50U2VydmljZS5nZXRCeURhdGVSYW5nZShzdGFydERhdGUsIGVuZERhdGUpO1xuICAgICAgICAvLyBGaW5kIGRheSBjb2x1bW5zXG4gICAgICAgIGNvbnN0IGRheUNvbHVtbnMgPSBjb250YWluZXIucXVlcnlTZWxlY3Rvcignc3dwLWRheS1jb2x1bW5zJyk7XG4gICAgICAgIGlmICghZGF5Q29sdW1ucylcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgY29uc3QgY29sdW1ucyA9IGRheUNvbHVtbnMucXVlcnlTZWxlY3RvckFsbCgnc3dwLWRheS1jb2x1bW4nKTtcbiAgICAgICAgLy8gUmVuZGVyIGV2ZW50cyBpbnRvIGVhY2ggY29sdW1uIGJhc2VkIG9uIEZpbHRlclRlbXBsYXRlIG1hdGNoaW5nXG4gICAgICAgIGNvbHVtbnMuZm9yRWFjaChjb2x1bW4gPT4ge1xuICAgICAgICAgICAgY29uc3QgY29sdW1uRWwgPSBjb2x1bW47XG4gICAgICAgICAgICAvLyBVc2UgRmlsdGVyVGVtcGxhdGUgZm9yIG1hdGNoaW5nIC0gb25seSBmaWVsZHMgaW4gdGVtcGxhdGUgYXJlIGNoZWNrZWRcbiAgICAgICAgICAgIGNvbnN0IGNvbHVtbkV2ZW50cyA9IGV2ZW50cy5maWx0ZXIoZXZlbnQgPT4gZmlsdGVyVGVtcGxhdGUubWF0Y2hlcyhldmVudCwgY29sdW1uRWwpKTtcbiAgICAgICAgICAgIC8vIEdldCBvciBjcmVhdGUgZXZlbnRzIGxheWVyXG4gICAgICAgICAgICBsZXQgZXZlbnRzTGF5ZXIgPSBjb2x1bW4ucXVlcnlTZWxlY3Rvcignc3dwLWV2ZW50cy1sYXllcicpO1xuICAgICAgICAgICAgaWYgKCFldmVudHNMYXllcikge1xuICAgICAgICAgICAgICAgIGV2ZW50c0xheWVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3dwLWV2ZW50cy1sYXllcicpO1xuICAgICAgICAgICAgICAgIGNvbHVtbi5hcHBlbmRDaGlsZChldmVudHNMYXllcik7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBDbGVhciBleGlzdGluZyBldmVudHNcbiAgICAgICAgICAgIGV2ZW50c0xheWVyLmlubmVySFRNTCA9ICcnO1xuICAgICAgICAgICAgLy8gRmlsdGVyIHRvIHRpbWVkIGV2ZW50cyBvbmx5XG4gICAgICAgICAgICBjb25zdCB0aW1lZEV2ZW50cyA9IGNvbHVtbkV2ZW50cy5maWx0ZXIoZXZlbnQgPT4gIWV2ZW50LmFsbERheSk7XG4gICAgICAgICAgICAvLyBDYWxjdWxhdGUgbGF5b3V0IHdpdGggc3RhY2tpbmcvZ3JvdXBpbmdcbiAgICAgICAgICAgIGNvbnN0IGxheW91dCA9IGNhbGN1bGF0ZUNvbHVtbkxheW91dCh0aW1lZEV2ZW50cywgdGhpcy5ncmlkQ29uZmlnKTtcbiAgICAgICAgICAgIC8vIFJlbmRlciBHUklEIGdyb3VwcyAoc2ltdWx0YW5lb3VzIGV2ZW50cyBzaWRlLWJ5LXNpZGUpXG4gICAgICAgICAgICBsYXlvdXQuZ3JpZHMuZm9yRWFjaChncmlkID0+IHtcbiAgICAgICAgICAgICAgICBjb25zdCBncm91cEVsID0gdGhpcy5yZW5kZXJHcmlkR3JvdXAoZ3JpZCk7XG4gICAgICAgICAgICAgICAgZXZlbnRzTGF5ZXIuYXBwZW5kQ2hpbGQoZ3JvdXBFbCk7XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIC8vIFJlbmRlciBTVEFDS0VEIGV2ZW50cyAob3ZlcmxhcHBpbmcgd2l0aCBtYXJnaW4gb2Zmc2V0KVxuICAgICAgICAgICAgbGF5b3V0LnN0YWNrZWQuZm9yRWFjaChpdGVtID0+IHtcbiAgICAgICAgICAgICAgICBjb25zdCBldmVudEVsID0gdGhpcy5yZW5kZXJTdGFja2VkRXZlbnQoaXRlbS5ldmVudCwgaXRlbS5zdGFja0xldmVsKTtcbiAgICAgICAgICAgICAgICBldmVudHNMYXllci5hcHBlbmRDaGlsZChldmVudEVsKTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9KTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogQ3JlYXRlIGEgc2luZ2xlIGV2ZW50IGVsZW1lbnRcbiAgICAgKlxuICAgICAqIENMRUFOIGFwcHJvYWNoOlxuICAgICAqIC0gT25seSBkYXRhLWlkIGZvciBsb29rdXBcbiAgICAgKiAtIFZpc2libGUgY29udGVudCBpbiBpbm5lckhUTUwgb25seVxuICAgICAqL1xuICAgIGNyZWF0ZUV2ZW50RWxlbWVudChldmVudCkge1xuICAgICAgICBjb25zdCBlbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3dwLWV2ZW50Jyk7XG4gICAgICAgIC8vIERhdGEgYXR0cmlidXRlcyBmb3IgU3dwRXZlbnQgY29tcGF0aWJpbGl0eVxuICAgICAgICBlbGVtZW50LmRhdGFzZXQuZXZlbnRJZCA9IGV2ZW50LmlkO1xuICAgICAgICBpZiAoZXZlbnQucmVzb3VyY2VJZCkge1xuICAgICAgICAgICAgZWxlbWVudC5kYXRhc2V0LnJlc291cmNlSWQgPSBldmVudC5yZXNvdXJjZUlkO1xuICAgICAgICB9XG4gICAgICAgIC8vIENhbGN1bGF0ZSBwb3NpdGlvblxuICAgICAgICBjb25zdCBwb3NpdGlvbiA9IGNhbGN1bGF0ZUV2ZW50UG9zaXRpb24oZXZlbnQuc3RhcnQsIGV2ZW50LmVuZCwgdGhpcy5ncmlkQ29uZmlnKTtcbiAgICAgICAgZWxlbWVudC5zdHlsZS50b3AgPSBgJHtwb3NpdGlvbi50b3B9cHhgO1xuICAgICAgICBlbGVtZW50LnN0eWxlLmhlaWdodCA9IGAke3Bvc2l0aW9uLmhlaWdodH1weGA7XG4gICAgICAgIC8vIENvbG9yIGNsYXNzIGJhc2VkIG9uIGV2ZW50IHR5cGVcbiAgICAgICAgY29uc3QgY29sb3JDbGFzcyA9IHRoaXMuZ2V0Q29sb3JDbGFzcyhldmVudCk7XG4gICAgICAgIGlmIChjb2xvckNsYXNzKSB7XG4gICAgICAgICAgICBlbGVtZW50LmNsYXNzTGlzdC5hZGQoY29sb3JDbGFzcyk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gVmlzaWJsZSBjb250ZW50IG9ubHlcbiAgICAgICAgZWxlbWVudC5pbm5lckhUTUwgPSBgXHJcbiAgICAgIDxzd3AtZXZlbnQtdGltZT4ke3RoaXMuZGF0ZVNlcnZpY2UuZm9ybWF0VGltZVJhbmdlKGV2ZW50LnN0YXJ0LCBldmVudC5lbmQpfTwvc3dwLWV2ZW50LXRpbWU+XHJcbiAgICAgIDxzd3AtZXZlbnQtdGl0bGU+JHt0aGlzLmVzY2FwZUh0bWwoZXZlbnQudGl0bGUpfTwvc3dwLWV2ZW50LXRpdGxlPlxyXG4gICAgICAke2V2ZW50LmRlc2NyaXB0aW9uID8gYDxzd3AtZXZlbnQtZGVzY3JpcHRpb24+JHt0aGlzLmVzY2FwZUh0bWwoZXZlbnQuZGVzY3JpcHRpb24pfTwvc3dwLWV2ZW50LWRlc2NyaXB0aW9uPmAgOiAnJ31cclxuICAgIGA7XG4gICAgICAgIHJldHVybiBlbGVtZW50O1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgY29sb3IgY2xhc3MgYmFzZWQgb24gbWV0YWRhdGEuY29sb3Igb3IgZXZlbnQgdHlwZVxuICAgICAqL1xuICAgIGdldENvbG9yQ2xhc3MoZXZlbnQpIHtcbiAgICAgICAgLy8gQ2hlY2sgbWV0YWRhdGEuY29sb3IgZmlyc3RcbiAgICAgICAgaWYgKGV2ZW50Lm1ldGFkYXRhPy5jb2xvcikge1xuICAgICAgICAgICAgcmV0dXJuIGBpcy0ke2V2ZW50Lm1ldGFkYXRhLmNvbG9yfWA7XG4gICAgICAgIH1cbiAgICAgICAgLy8gRmFsbGJhY2sgdG8gdHlwZS1iYXNlZCBjb2xvclxuICAgICAgICBjb25zdCB0eXBlQ29sb3JzID0ge1xuICAgICAgICAgICAgJ2N1c3RvbWVyJzogJ2lzLWJsdWUnLFxuICAgICAgICAgICAgJ3ZhY2F0aW9uJzogJ2lzLWdyZWVuJyxcbiAgICAgICAgICAgICdicmVhayc6ICdpcy1hbWJlcicsXG4gICAgICAgICAgICAnbWVldGluZyc6ICdpcy1wdXJwbGUnLFxuICAgICAgICAgICAgJ2Jsb2NrZWQnOiAnaXMtcmVkJ1xuICAgICAgICB9O1xuICAgICAgICByZXR1cm4gdHlwZUNvbG9yc1tldmVudC50eXBlXSB8fCAnaXMtYmx1ZSc7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEVzY2FwZSBIVE1MIHRvIHByZXZlbnQgWFNTXG4gICAgICovXG4gICAgZXNjYXBlSHRtbCh0ZXh0KSB7XG4gICAgICAgIGNvbnN0IGRpdiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2RpdicpO1xuICAgICAgICBkaXYudGV4dENvbnRlbnQgPSB0ZXh0O1xuICAgICAgICByZXR1cm4gZGl2LmlubmVySFRNTDtcbiAgICB9XG4gICAgLyoqXG4gICAgICogUmVuZGVyIGEgR1JJRCBncm91cCB3aXRoIHNpZGUtYnktc2lkZSBjb2x1bW5zXG4gICAgICogVXNlZCB3aGVuIG11bHRpcGxlIGV2ZW50cyBzdGFydCBhdCB0aGUgc2FtZSB0aW1lXG4gICAgICovXG4gICAgcmVuZGVyR3JpZEdyb3VwKGxheW91dCkge1xuICAgICAgICBjb25zdCBncm91cCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3N3cC1ldmVudC1ncm91cCcpO1xuICAgICAgICBncm91cC5jbGFzc0xpc3QuYWRkKGBjb2xzLSR7bGF5b3V0LmNvbHVtbnMubGVuZ3RofWApO1xuICAgICAgICBncm91cC5zdHlsZS50b3AgPSBgJHtsYXlvdXQucG9zaXRpb24udG9wfXB4YDtcbiAgICAgICAgLy8gU3RhY2sgbGV2ZWwgc3R5bGluZyBmb3IgZW50aXJlIGdyb3VwIChpZiBuZXN0ZWQgaW4gYW5vdGhlciBldmVudClcbiAgICAgICAgaWYgKGxheW91dC5zdGFja0xldmVsID4gMCkge1xuICAgICAgICAgICAgZ3JvdXAuc3R5bGUubWFyZ2luTGVmdCA9IGAke2xheW91dC5zdGFja0xldmVsICogMTV9cHhgO1xuICAgICAgICAgICAgZ3JvdXAuc3R5bGUuekluZGV4ID0gYCR7MTAwICsgbGF5b3V0LnN0YWNrTGV2ZWx9YDtcbiAgICAgICAgfVxuICAgICAgICAvLyBDYWxjdWxhdGUgdGhlIGhlaWdodCBuZWVkZWQgZm9yIHRoZSBncm91cCAodGFsbGVzdCBldmVudClcbiAgICAgICAgbGV0IG1heEJvdHRvbSA9IDA7XG4gICAgICAgIGZvciAoY29uc3QgZXZlbnQgb2YgbGF5b3V0LmV2ZW50cykge1xuICAgICAgICAgICAgY29uc3QgcG9zID0gY2FsY3VsYXRlRXZlbnRQb3NpdGlvbihldmVudC5zdGFydCwgZXZlbnQuZW5kLCB0aGlzLmdyaWRDb25maWcpO1xuICAgICAgICAgICAgY29uc3QgZXZlbnRCb3R0b20gPSBwb3MudG9wICsgcG9zLmhlaWdodDtcbiAgICAgICAgICAgIGlmIChldmVudEJvdHRvbSA+IG1heEJvdHRvbSlcbiAgICAgICAgICAgICAgICBtYXhCb3R0b20gPSBldmVudEJvdHRvbTtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBncm91cEhlaWdodCA9IG1heEJvdHRvbSAtIGxheW91dC5wb3NpdGlvbi50b3A7XG4gICAgICAgIGdyb3VwLnN0eWxlLmhlaWdodCA9IGAke2dyb3VwSGVpZ2h0fXB4YDtcbiAgICAgICAgLy8gQ3JlYXRlIHdyYXBwZXIgZGl2IGZvciBlYWNoIGNvbHVtblxuICAgICAgICBsYXlvdXQuY29sdW1ucy5mb3JFYWNoKGNvbHVtbkV2ZW50cyA9PiB7XG4gICAgICAgICAgICBjb25zdCB3cmFwcGVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnZGl2Jyk7XG4gICAgICAgICAgICB3cmFwcGVyLnN0eWxlLnBvc2l0aW9uID0gJ3JlbGF0aXZlJztcbiAgICAgICAgICAgIGNvbHVtbkV2ZW50cy5mb3JFYWNoKGV2ZW50ID0+IHtcbiAgICAgICAgICAgICAgICBjb25zdCBldmVudEVsID0gdGhpcy5jcmVhdGVFdmVudEVsZW1lbnQoZXZlbnQpO1xuICAgICAgICAgICAgICAgIC8vIFBvc2l0aW9uIHJlbGF0aXZlIHRvIGdyb3VwIHRvcFxuICAgICAgICAgICAgICAgIGNvbnN0IHBvcyA9IGNhbGN1bGF0ZUV2ZW50UG9zaXRpb24oZXZlbnQuc3RhcnQsIGV2ZW50LmVuZCwgdGhpcy5ncmlkQ29uZmlnKTtcbiAgICAgICAgICAgICAgICBldmVudEVsLnN0eWxlLnRvcCA9IGAke3Bvcy50b3AgLSBsYXlvdXQucG9zaXRpb24udG9wfXB4YDtcbiAgICAgICAgICAgICAgICBldmVudEVsLnN0eWxlLnBvc2l0aW9uID0gJ2Fic29sdXRlJztcbiAgICAgICAgICAgICAgICBldmVudEVsLnN0eWxlLmxlZnQgPSAnMCc7XG4gICAgICAgICAgICAgICAgZXZlbnRFbC5zdHlsZS5yaWdodCA9ICcwJztcbiAgICAgICAgICAgICAgICB3cmFwcGVyLmFwcGVuZENoaWxkKGV2ZW50RWwpO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICBncm91cC5hcHBlbmRDaGlsZCh3cmFwcGVyKTtcbiAgICAgICAgfSk7XG4gICAgICAgIHJldHVybiBncm91cDtcbiAgICB9XG4gICAgLyoqXG4gICAgICogUmVuZGVyIGEgU1RBQ0tFRCBldmVudCB3aXRoIG1hcmdpbi1sZWZ0IG9mZnNldFxuICAgICAqIFVzZWQgZm9yIG92ZXJsYXBwaW5nIGV2ZW50cyB0aGF0IGRvbid0IHN0YXJ0IGF0IHRoZSBzYW1lIHRpbWVcbiAgICAgKi9cbiAgICByZW5kZXJTdGFja2VkRXZlbnQoZXZlbnQsIHN0YWNrTGV2ZWwpIHtcbiAgICAgICAgY29uc3QgZWxlbWVudCA9IHRoaXMuY3JlYXRlRXZlbnRFbGVtZW50KGV2ZW50KTtcbiAgICAgICAgLy8gQWRkIHN0YWNrIG1ldGFkYXRhIGZvciBkcmFnLWRyb3AgYW5kIG90aGVyIGZlYXR1cmVzXG4gICAgICAgIGVsZW1lbnQuZGF0YXNldC5zdGFja0xpbmsgPSBKU09OLnN0cmluZ2lmeSh7IHN0YWNrTGV2ZWwgfSk7XG4gICAgICAgIC8vIFZpc3VhbCBzdHlsaW5nIGJhc2VkIG9uIHN0YWNrIGxldmVsXG4gICAgICAgIGlmIChzdGFja0xldmVsID4gMCkge1xuICAgICAgICAgICAgZWxlbWVudC5zdHlsZS5tYXJnaW5MZWZ0ID0gYCR7c3RhY2tMZXZlbCAqIDE1fXB4YDtcbiAgICAgICAgICAgIGVsZW1lbnQuc3R5bGUuekluZGV4ID0gYCR7MTAwICsgc3RhY2tMZXZlbH1gO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBlbGVtZW50O1xuICAgIH1cbn1cbiIsICIvKipcbiAqIFNjaGVkdWxlUmVuZGVyZXIgLSBSZW5kZXJzIHVuYXZhaWxhYmxlIHRpbWUgem9uZXMgaW4gZGF5IGNvbHVtbnNcbiAqXG4gKiBDcmVhdGVzIHZpc3VhbCBpbmRpY2F0b3JzIGZvciB0aW1lcyBvdXRzaWRlIHRoZSByZXNvdXJjZSdzIHdvcmtpbmcgaG91cnM6XG4gKiAtIEJlZm9yZSB3b3JrIHN0YXJ0IChlLmcuLCAwNjowMCAtIDA5OjAwKVxuICogLSBBZnRlciB3b3JrIGVuZCAoZS5nLiwgMTc6MDAgLSAxODowMClcbiAqIC0gRnVsbCBkYXkgaWYgcmVzb3VyY2UgaXMgb2ZmIChzY2hlZHVsZSA9IG51bGwpXG4gKi9cbmV4cG9ydCBjbGFzcyBTY2hlZHVsZVJlbmRlcmVyIHtcbiAgICBjb25zdHJ1Y3RvcihzY2hlZHVsZVNlcnZpY2UsIGRhdGVTZXJ2aWNlLCBncmlkQ29uZmlnKSB7XG4gICAgICAgIHRoaXMuc2NoZWR1bGVTZXJ2aWNlID0gc2NoZWR1bGVTZXJ2aWNlO1xuICAgICAgICB0aGlzLmRhdGVTZXJ2aWNlID0gZGF0ZVNlcnZpY2U7XG4gICAgICAgIHRoaXMuZ3JpZENvbmZpZyA9IGdyaWRDb25maWc7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJlbmRlciB1bmF2YWlsYWJsZSB6b25lcyBmb3IgdmlzaWJsZSBjb2x1bW5zXG4gICAgICogQHBhcmFtIGNvbnRhaW5lciAtIENhbGVuZGFyIGNvbnRhaW5lciBlbGVtZW50XG4gICAgICogQHBhcmFtIGZpbHRlciAtIEZpbHRlciB3aXRoICdkYXRlJyBhbmQgJ3Jlc291cmNlJyBhcnJheXNcbiAgICAgKi9cbiAgICBhc3luYyByZW5kZXIoY29udGFpbmVyLCBmaWx0ZXIpIHtcbiAgICAgICAgY29uc3QgZGF0ZXMgPSBmaWx0ZXJbJ2RhdGUnXSB8fCBbXTtcbiAgICAgICAgY29uc3QgcmVzb3VyY2VJZHMgPSBmaWx0ZXJbJ3Jlc291cmNlJ10gfHwgW107XG4gICAgICAgIGlmIChkYXRlcy5sZW5ndGggPT09IDApXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIC8vIEZpbmQgZGF5IGNvbHVtbnNcbiAgICAgICAgY29uc3QgZGF5Q29sdW1ucyA9IGNvbnRhaW5lci5xdWVyeVNlbGVjdG9yKCdzd3AtZGF5LWNvbHVtbnMnKTtcbiAgICAgICAgaWYgKCFkYXlDb2x1bW5zKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICBjb25zdCBjb2x1bW5zID0gZGF5Q29sdW1ucy5xdWVyeVNlbGVjdG9yQWxsKCdzd3AtZGF5LWNvbHVtbicpO1xuICAgICAgICBmb3IgKGNvbnN0IGNvbHVtbiBvZiBjb2x1bW5zKSB7XG4gICAgICAgICAgICBjb25zdCBkYXRlID0gY29sdW1uLmRhdGFzZXQuZGF0ZTtcbiAgICAgICAgICAgIGNvbnN0IHJlc291cmNlSWQgPSBjb2x1bW4uZGF0YXNldC5yZXNvdXJjZUlkO1xuICAgICAgICAgICAgaWYgKCFkYXRlIHx8ICFyZXNvdXJjZUlkKVxuICAgICAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgICAgLy8gR2V0IG9yIGNyZWF0ZSB1bmF2YWlsYWJsZSBsYXllclxuICAgICAgICAgICAgbGV0IHVuYXZhaWxhYmxlTGF5ZXIgPSBjb2x1bW4ucXVlcnlTZWxlY3Rvcignc3dwLXVuYXZhaWxhYmxlLWxheWVyJyk7XG4gICAgICAgICAgICBpZiAoIXVuYXZhaWxhYmxlTGF5ZXIpIHtcbiAgICAgICAgICAgICAgICB1bmF2YWlsYWJsZUxheWVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3dwLXVuYXZhaWxhYmxlLWxheWVyJyk7XG4gICAgICAgICAgICAgICAgY29sdW1uLmluc2VydEJlZm9yZSh1bmF2YWlsYWJsZUxheWVyLCBjb2x1bW4uZmlyc3RDaGlsZCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBDbGVhciBleGlzdGluZ1xuICAgICAgICAgICAgdW5hdmFpbGFibGVMYXllci5pbm5lckhUTUwgPSAnJztcbiAgICAgICAgICAgIC8vIEdldCBzY2hlZHVsZSBmb3IgdGhpcyByZXNvdXJjZS9kYXRlXG4gICAgICAgICAgICBjb25zdCBzY2hlZHVsZSA9IGF3YWl0IHRoaXMuc2NoZWR1bGVTZXJ2aWNlLmdldFNjaGVkdWxlRm9yRGF0ZShyZXNvdXJjZUlkLCBkYXRlKTtcbiAgICAgICAgICAgIC8vIFJlbmRlciB1bmF2YWlsYWJsZSB6b25lc1xuICAgICAgICAgICAgdGhpcy5yZW5kZXJVbmF2YWlsYWJsZVpvbmVzKHVuYXZhaWxhYmxlTGF5ZXIsIHNjaGVkdWxlKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZW5kZXIgdW5hdmFpbGFibGUgdGltZSB6b25lcyBiYXNlZCBvbiBzY2hlZHVsZVxuICAgICAqL1xuICAgIHJlbmRlclVuYXZhaWxhYmxlWm9uZXMobGF5ZXIsIHNjaGVkdWxlKSB7XG4gICAgICAgIGNvbnN0IGRheVN0YXJ0TWludXRlcyA9IHRoaXMuZ3JpZENvbmZpZy5kYXlTdGFydEhvdXIgKiA2MDtcbiAgICAgICAgY29uc3QgZGF5RW5kTWludXRlcyA9IHRoaXMuZ3JpZENvbmZpZy5kYXlFbmRIb3VyICogNjA7XG4gICAgICAgIGNvbnN0IG1pbnV0ZUhlaWdodCA9IHRoaXMuZ3JpZENvbmZpZy5ob3VySGVpZ2h0IC8gNjA7XG4gICAgICAgIGlmIChzY2hlZHVsZSA9PT0gbnVsbCkge1xuICAgICAgICAgICAgLy8gRnVsbCBkYXkgdW5hdmFpbGFibGVcbiAgICAgICAgICAgIGNvbnN0IHpvbmUgPSB0aGlzLmNyZWF0ZVVuYXZhaWxhYmxlWm9uZSgwLCAoZGF5RW5kTWludXRlcyAtIGRheVN0YXJ0TWludXRlcykgKiBtaW51dGVIZWlnaHQpO1xuICAgICAgICAgICAgbGF5ZXIuYXBwZW5kQ2hpbGQoem9uZSk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgY29uc3Qgd29ya1N0YXJ0TWludXRlcyA9IHRoaXMuZGF0ZVNlcnZpY2UudGltZVRvTWludXRlcyhzY2hlZHVsZS5zdGFydCk7XG4gICAgICAgIGNvbnN0IHdvcmtFbmRNaW51dGVzID0gdGhpcy5kYXRlU2VydmljZS50aW1lVG9NaW51dGVzKHNjaGVkdWxlLmVuZCk7XG4gICAgICAgIC8vIEJlZm9yZSB3b3JrIHN0YXJ0XG4gICAgICAgIGlmICh3b3JrU3RhcnRNaW51dGVzID4gZGF5U3RhcnRNaW51dGVzKSB7XG4gICAgICAgICAgICBjb25zdCB0b3AgPSAwO1xuICAgICAgICAgICAgY29uc3QgaGVpZ2h0ID0gKHdvcmtTdGFydE1pbnV0ZXMgLSBkYXlTdGFydE1pbnV0ZXMpICogbWludXRlSGVpZ2h0O1xuICAgICAgICAgICAgY29uc3Qgem9uZSA9IHRoaXMuY3JlYXRlVW5hdmFpbGFibGVab25lKHRvcCwgaGVpZ2h0KTtcbiAgICAgICAgICAgIGxheWVyLmFwcGVuZENoaWxkKHpvbmUpO1xuICAgICAgICB9XG4gICAgICAgIC8vIEFmdGVyIHdvcmsgZW5kXG4gICAgICAgIGlmICh3b3JrRW5kTWludXRlcyA8IGRheUVuZE1pbnV0ZXMpIHtcbiAgICAgICAgICAgIGNvbnN0IHRvcCA9ICh3b3JrRW5kTWludXRlcyAtIGRheVN0YXJ0TWludXRlcykgKiBtaW51dGVIZWlnaHQ7XG4gICAgICAgICAgICBjb25zdCBoZWlnaHQgPSAoZGF5RW5kTWludXRlcyAtIHdvcmtFbmRNaW51dGVzKSAqIG1pbnV0ZUhlaWdodDtcbiAgICAgICAgICAgIGNvbnN0IHpvbmUgPSB0aGlzLmNyZWF0ZVVuYXZhaWxhYmxlWm9uZSh0b3AsIGhlaWdodCk7XG4gICAgICAgICAgICBsYXllci5hcHBlbmRDaGlsZCh6b25lKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBDcmVhdGUgYW4gdW5hdmFpbGFibGUgem9uZSBlbGVtZW50XG4gICAgICovXG4gICAgY3JlYXRlVW5hdmFpbGFibGVab25lKHRvcCwgaGVpZ2h0KSB7XG4gICAgICAgIGNvbnN0IHpvbmUgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzd3AtdW5hdmFpbGFibGUtem9uZScpO1xuICAgICAgICB6b25lLnN0eWxlLnRvcCA9IGAke3RvcH1weGA7XG4gICAgICAgIHpvbmUuc3R5bGUuaGVpZ2h0ID0gYCR7aGVpZ2h0fXB4YDtcbiAgICAgICAgcmV0dXJuIHpvbmU7XG4gICAgfVxufVxuIiwgImltcG9ydCB7IENvcmVFdmVudHMgfSBmcm9tICcuLi8uLi9jb25zdGFudHMvQ29yZUV2ZW50cyc7XG4vKipcbiAqIEhlYWRlckRyYXdlclJlbmRlcmVyIC0gSGFuZGxlcyByZW5kZXJpbmcgb2YgaXRlbXMgaW4gdGhlIGhlYWRlciBkcmF3ZXJcbiAqXG4gKiBMaXN0ZW5zIHRvIGRyYWcgZXZlbnRzIGZyb20gRHJhZ0Ryb3BNYW5hZ2VyIGFuZCBjcmVhdGVzL21hbmFnZXNcbiAqIHN3cC1oZWFkZXItaXRlbSBlbGVtZW50cyBpbiB0aGUgaGVhZGVyIGRyYXdlci5cbiAqXG4gKiBVc2VzIHN1YmdyaWQgZm9yIGNvbHVtbiBhbGlnbm1lbnQgd2l0aCBwYXJlbnQgc3dwLWNhbGVuZGFyLWhlYWRlci5cbiAqIFBvc2l0aW9uIGl0ZW1zIHZpYSBncmlkQXJlYSBmb3IgZXhwbGljaXQgcm93L2NvbHVtbiBwbGFjZW1lbnQuXG4gKi9cbmV4cG9ydCBjbGFzcyBIZWFkZXJEcmF3ZXJSZW5kZXJlciB7XG4gICAgY29uc3RydWN0b3IoZXZlbnRCdXMsIGdyaWRDb25maWcsIGhlYWRlckRyYXdlck1hbmFnZXIsIGV2ZW50U2VydmljZSwgZGF0ZVNlcnZpY2UpIHtcbiAgICAgICAgdGhpcy5ldmVudEJ1cyA9IGV2ZW50QnVzO1xuICAgICAgICB0aGlzLmdyaWRDb25maWcgPSBncmlkQ29uZmlnO1xuICAgICAgICB0aGlzLmhlYWRlckRyYXdlck1hbmFnZXIgPSBoZWFkZXJEcmF3ZXJNYW5hZ2VyO1xuICAgICAgICB0aGlzLmV2ZW50U2VydmljZSA9IGV2ZW50U2VydmljZTtcbiAgICAgICAgdGhpcy5kYXRlU2VydmljZSA9IGRhdGVTZXJ2aWNlO1xuICAgICAgICB0aGlzLmN1cnJlbnRJdGVtID0gbnVsbDtcbiAgICAgICAgdGhpcy5jb250YWluZXIgPSBudWxsO1xuICAgICAgICB0aGlzLnNvdXJjZUVsZW1lbnQgPSBudWxsO1xuICAgICAgICB0aGlzLndhc0V4cGFuZGVkQmVmb3JlRHJhZyA9IGZhbHNlO1xuICAgICAgICB0aGlzLmZpbHRlclRlbXBsYXRlID0gbnVsbDtcbiAgICAgICAgdGhpcy5zZXR1cExpc3RlbmVycygpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZW5kZXIgYWxsRGF5IGV2ZW50cyBpbnRvIHRoZSBoZWFkZXIgZHJhd2VyIHdpdGggcm93IHN0YWNraW5nXG4gICAgICogQHBhcmFtIGZpbHRlclRlbXBsYXRlIC0gVGVtcGxhdGUgZm9yIG1hdGNoaW5nIGV2ZW50cyB0byBjb2x1bW5zXG4gICAgICovXG4gICAgYXN5bmMgcmVuZGVyKGNvbnRhaW5lciwgZmlsdGVyLCBmaWx0ZXJUZW1wbGF0ZSkge1xuICAgICAgICAvLyBTdG9yZSBmaWx0ZXJUZW1wbGF0ZSBmb3IgYnVpbGRDb2x1bW5LZXlGcm9tRXZlbnRcbiAgICAgICAgdGhpcy5maWx0ZXJUZW1wbGF0ZSA9IGZpbHRlclRlbXBsYXRlO1xuICAgICAgICBjb25zdCBkcmF3ZXIgPSBjb250YWluZXIucXVlcnlTZWxlY3Rvcignc3dwLWhlYWRlci1kcmF3ZXInKTtcbiAgICAgICAgaWYgKCFkcmF3ZXIpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIGNvbnN0IHZpc2libGVEYXRlcyA9IGZpbHRlclsnZGF0ZSddIHx8IFtdO1xuICAgICAgICBpZiAodmlzaWJsZURhdGVzLmxlbmd0aCA9PT0gMClcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgLy8gR2V0IGNvbHVtbiBrZXlzIGZyb20gRE9NIGZvciBjb3JyZWN0IG11bHRpLXJlc291cmNlIHBvc2l0aW9uaW5nXG4gICAgICAgIGNvbnN0IHZpc2libGVDb2x1bW5LZXlzID0gdGhpcy5nZXRWaXNpYmxlQ29sdW1uS2V5c0Zyb21ET00oKTtcbiAgICAgICAgaWYgKHZpc2libGVDb2x1bW5LZXlzLmxlbmd0aCA9PT0gMClcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgLy8gRmV0Y2ggZXZlbnRzIGZvciBkYXRlIHJhbmdlXG4gICAgICAgIGNvbnN0IHN0YXJ0RGF0ZSA9IG5ldyBEYXRlKHZpc2libGVEYXRlc1swXSk7XG4gICAgICAgIGNvbnN0IGVuZERhdGUgPSBuZXcgRGF0ZSh2aXNpYmxlRGF0ZXNbdmlzaWJsZURhdGVzLmxlbmd0aCAtIDFdKTtcbiAgICAgICAgZW5kRGF0ZS5zZXRIb3VycygyMywgNTksIDU5LCA5OTkpO1xuICAgICAgICBjb25zdCBldmVudHMgPSBhd2FpdCB0aGlzLmV2ZW50U2VydmljZS5nZXRCeURhdGVSYW5nZShzdGFydERhdGUsIGVuZERhdGUpO1xuICAgICAgICAvLyBGaWx0ZXIgdG8gYWxsRGF5IGV2ZW50cyBvbmx5IChhbGxEYXkgIT09IGZhbHNlKVxuICAgICAgICBjb25zdCBhbGxEYXlFdmVudHMgPSBldmVudHMuZmlsdGVyKGV2ZW50ID0+IGV2ZW50LmFsbERheSAhPT0gZmFsc2UpO1xuICAgICAgICAvLyBDbGVhciBleGlzdGluZyBpdGVtc1xuICAgICAgICBkcmF3ZXIuaW5uZXJIVE1MID0gJyc7XG4gICAgICAgIGlmIChhbGxEYXlFdmVudHMubGVuZ3RoID09PSAwKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAvLyBDYWxjdWxhdGUgbGF5b3V0IHdpdGggcm93IHN0YWNraW5nIHVzaW5nIGNvbHVtbktleXNcbiAgICAgICAgY29uc3QgbGF5b3V0cyA9IHRoaXMuY2FsY3VsYXRlTGF5b3V0KGFsbERheUV2ZW50cywgdmlzaWJsZUNvbHVtbktleXMpO1xuICAgICAgICBjb25zdCByb3dDb3VudCA9IE1hdGgubWF4KDEsIC4uLmxheW91dHMubWFwKGwgPT4gbC5yb3cpKTtcbiAgICAgICAgLy8gUmVuZGVyIGVhY2ggaXRlbSB3aXRoIGxheW91dFxuICAgICAgICBsYXlvdXRzLmZvckVhY2gobGF5b3V0ID0+IHtcbiAgICAgICAgICAgIGNvbnN0IGl0ZW0gPSB0aGlzLmNyZWF0ZUhlYWRlckl0ZW0obGF5b3V0KTtcbiAgICAgICAgICAgIGRyYXdlci5hcHBlbmRDaGlsZChpdGVtKTtcbiAgICAgICAgfSk7XG4gICAgICAgIC8vIEV4cGFuZCBkcmF3ZXIgdG8gZml0IGFsbCByb3dzXG4gICAgICAgIHRoaXMuaGVhZGVyRHJhd2VyTWFuYWdlci5leHBhbmRUb1Jvd3Mocm93Q291bnQpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBDcmVhdGUgYSBoZWFkZXIgaXRlbSBlbGVtZW50IGZyb20gbGF5b3V0XG4gICAgICovXG4gICAgY3JlYXRlSGVhZGVySXRlbShsYXlvdXQpIHtcbiAgICAgICAgY29uc3QgeyBldmVudCwgY29sdW1uS2V5LCByb3csIGNvbFN0YXJ0LCBjb2xFbmQgfSA9IGxheW91dDtcbiAgICAgICAgY29uc3QgaXRlbSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3N3cC1oZWFkZXItaXRlbScpO1xuICAgICAgICBpdGVtLmRhdGFzZXQuZXZlbnRJZCA9IGV2ZW50LmlkO1xuICAgICAgICBpdGVtLmRhdGFzZXQuaXRlbVR5cGUgPSAnZXZlbnQnO1xuICAgICAgICBpdGVtLmRhdGFzZXQuc3RhcnQgPSBldmVudC5zdGFydC50b0lTT1N0cmluZygpO1xuICAgICAgICBpdGVtLmRhdGFzZXQuZW5kID0gZXZlbnQuZW5kLnRvSVNPU3RyaW5nKCk7XG4gICAgICAgIGl0ZW0uZGF0YXNldC5jb2x1bW5LZXkgPSBjb2x1bW5LZXk7XG4gICAgICAgIGl0ZW0udGV4dENvbnRlbnQgPSBldmVudC50aXRsZTtcbiAgICAgICAgLy8gQ29sb3IgY2xhc3NcbiAgICAgICAgY29uc3QgY29sb3JDbGFzcyA9IHRoaXMuZ2V0Q29sb3JDbGFzcyhldmVudCk7XG4gICAgICAgIGlmIChjb2xvckNsYXNzKVxuICAgICAgICAgICAgaXRlbS5jbGFzc0xpc3QuYWRkKGNvbG9yQ2xhc3MpO1xuICAgICAgICAvLyBHcmlkIHBvc2l0aW9uIGZyb20gbGF5b3V0XG4gICAgICAgIGl0ZW0uc3R5bGUuZ3JpZEFyZWEgPSBgJHtyb3d9IC8gJHtjb2xTdGFydH0gLyAke3JvdyArIDF9IC8gJHtjb2xFbmR9YDtcbiAgICAgICAgcmV0dXJuIGl0ZW07XG4gICAgfVxuICAgIC8qKlxuICAgICAqIENhbGN1bGF0ZSBsYXlvdXQgZm9yIGFsbCBldmVudHMgd2l0aCByb3cgc3RhY2tpbmdcbiAgICAgKiBVc2VzIHRyYWNrLWJhc2VkIGFsZ29yaXRobSB0byBmaW5kIGF2YWlsYWJsZSByb3dzIGZvciBvdmVybGFwcGluZyBldmVudHNcbiAgICAgKi9cbiAgICBjYWxjdWxhdGVMYXlvdXQoZXZlbnRzLCB2aXNpYmxlQ29sdW1uS2V5cykge1xuICAgICAgICAvLyB0cmFja3Nbcm93XVtjb2xdID0gb2NjdXBpZWRcbiAgICAgICAgY29uc3QgdHJhY2tzID0gW25ldyBBcnJheSh2aXNpYmxlQ29sdW1uS2V5cy5sZW5ndGgpLmZpbGwoZmFsc2UpXTtcbiAgICAgICAgY29uc3QgbGF5b3V0cyA9IFtdO1xuICAgICAgICBmb3IgKGNvbnN0IGV2ZW50IG9mIGV2ZW50cykge1xuICAgICAgICAgICAgLy8gQnVpbGQgY29sdW1uS2V5IGZyb20gZXZlbnQgZmllbGRzIChvbmx5IHBsYWNlIHdlIG5lZWQgdG8gY29uc3RydWN0IGl0KVxuICAgICAgICAgICAgY29uc3QgY29sdW1uS2V5ID0gdGhpcy5idWlsZENvbHVtbktleUZyb21FdmVudChldmVudCk7XG4gICAgICAgICAgICBjb25zdCBzdGFydENvbCA9IHZpc2libGVDb2x1bW5LZXlzLmluZGV4T2YoY29sdW1uS2V5KTtcbiAgICAgICAgICAgIGNvbnN0IGVuZENvbHVtbktleSA9IHRoaXMuYnVpbGRDb2x1bW5LZXlGcm9tRXZlbnQoZXZlbnQsIGV2ZW50LmVuZCk7XG4gICAgICAgICAgICBjb25zdCBlbmRDb2wgPSB2aXNpYmxlQ29sdW1uS2V5cy5pbmRleE9mKGVuZENvbHVtbktleSk7XG4gICAgICAgICAgICBpZiAoc3RhcnRDb2wgPT09IC0xICYmIGVuZENvbCA9PT0gLTEpXG4gICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICAvLyBDbGFtcCB0aWwgc3lubGlnZSBrb2xvbm5lclxuICAgICAgICAgICAgY29uc3QgY29sU3RhcnQgPSBNYXRoLm1heCgwLCBzdGFydENvbCk7XG4gICAgICAgICAgICBjb25zdCBjb2xFbmQgPSAoZW5kQ29sICE9PSAtMSA/IGVuZENvbCA6IHZpc2libGVDb2x1bW5LZXlzLmxlbmd0aCAtIDEpICsgMTtcbiAgICAgICAgICAgIC8vIEZpbmQgbGVkaWcgclx1MDBFNmtrZVxuICAgICAgICAgICAgY29uc3Qgcm93ID0gdGhpcy5maW5kQXZhaWxhYmxlUm93KHRyYWNrcywgY29sU3RhcnQsIGNvbEVuZCk7XG4gICAgICAgICAgICAvLyBNYXJrZXIgc29tIG9wdGFnZXRcbiAgICAgICAgICAgIGZvciAobGV0IGMgPSBjb2xTdGFydDsgYyA8IGNvbEVuZDsgYysrKSB7XG4gICAgICAgICAgICAgICAgdHJhY2tzW3Jvd11bY10gPSB0cnVlO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgbGF5b3V0cy5wdXNoKHsgZXZlbnQsIGNvbHVtbktleSwgcm93OiByb3cgKyAxLCBjb2xTdGFydDogY29sU3RhcnQgKyAxLCBjb2xFbmQ6IGNvbEVuZCArIDEgfSk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGxheW91dHM7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEJ1aWxkIGNvbHVtbktleSBmcm9tIGV2ZW50IHVzaW5nIEZpbHRlclRlbXBsYXRlXG4gICAgICogVXNlcyB0aGUgc2FtZSB0ZW1wbGF0ZSB0aGF0IGNvbHVtbnMgdXNlIGZvciBtYXRjaGluZ1xuICAgICAqL1xuICAgIGJ1aWxkQ29sdW1uS2V5RnJvbUV2ZW50KGV2ZW50LCBkYXRlKSB7XG4gICAgICAgIGlmICghdGhpcy5maWx0ZXJUZW1wbGF0ZSkge1xuICAgICAgICAgICAgLy8gRmFsbGJhY2sgaWYgbm8gdGVtcGxhdGUgLSBzaG91bGRuJ3QgaGFwcGVuIGluIG5vcm1hbCBmbG93XG4gICAgICAgICAgICBjb25zdCBkYXRlU3RyID0gdGhpcy5kYXRlU2VydmljZS5nZXREYXRlS2V5KGRhdGUgfHwgZXZlbnQuc3RhcnQpO1xuICAgICAgICAgICAgcmV0dXJuIGRhdGVTdHI7XG4gICAgICAgIH1cbiAgICAgICAgLy8gRm9yIG11bHRpLWRheSBldmVudHMsIHdlIG5lZWQgdG8gb3ZlcnJpZGUgdGhlIGRhdGUgaW4gdGhlIGV2ZW50XG4gICAgICAgIGlmIChkYXRlICYmIGRhdGUuZ2V0VGltZSgpICE9PSBldmVudC5zdGFydC5nZXRUaW1lKCkpIHtcbiAgICAgICAgICAgIC8vIENyZWF0ZSB0ZW1wb3JhcnkgZXZlbnQgd2l0aCBvdmVycmlkZGVuIHN0YXJ0IGZvciBrZXkgZ2VuZXJhdGlvblxuICAgICAgICAgICAgY29uc3QgdGVtcEV2ZW50ID0geyAuLi5ldmVudCwgc3RhcnQ6IGRhdGUgfTtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLmZpbHRlclRlbXBsYXRlLmJ1aWxkS2V5RnJvbUV2ZW50KHRlbXBFdmVudCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXMuZmlsdGVyVGVtcGxhdGUuYnVpbGRLZXlGcm9tRXZlbnQoZXZlbnQpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBGaW5kIGF2YWlsYWJsZSByb3cgZm9yIGV2ZW50IHNwYW5uaW5nIGNvbHVtbnMgW2NvbFN0YXJ0LCBjb2xFbmQpXG4gICAgICovXG4gICAgZmluZEF2YWlsYWJsZVJvdyh0cmFja3MsIGNvbFN0YXJ0LCBjb2xFbmQpIHtcbiAgICAgICAgZm9yIChsZXQgcm93ID0gMDsgcm93IDwgdHJhY2tzLmxlbmd0aDsgcm93KyspIHtcbiAgICAgICAgICAgIGxldCBhdmFpbGFibGUgPSB0cnVlO1xuICAgICAgICAgICAgZm9yIChsZXQgYyA9IGNvbFN0YXJ0OyBjIDwgY29sRW5kOyBjKyspIHtcbiAgICAgICAgICAgICAgICBpZiAodHJhY2tzW3Jvd11bY10pIHtcbiAgICAgICAgICAgICAgICAgICAgYXZhaWxhYmxlID0gZmFsc2U7XG4gICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGlmIChhdmFpbGFibGUpXG4gICAgICAgICAgICAgICAgcmV0dXJuIHJvdztcbiAgICAgICAgfVxuICAgICAgICAvLyBOeSByXHUwMEU2a2tlXG4gICAgICAgIHRyYWNrcy5wdXNoKG5ldyBBcnJheSh0cmFja3NbMF0ubGVuZ3RoKS5maWxsKGZhbHNlKSk7XG4gICAgICAgIHJldHVybiB0cmFja3MubGVuZ3RoIC0gMTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IGNvbG9yIGNsYXNzIGJhc2VkIG9uIGV2ZW50IG1ldGFkYXRhIG9yIHR5cGVcbiAgICAgKi9cbiAgICBnZXRDb2xvckNsYXNzKGV2ZW50KSB7XG4gICAgICAgIGlmIChldmVudC5tZXRhZGF0YT8uY29sb3IpIHtcbiAgICAgICAgICAgIHJldHVybiBgaXMtJHtldmVudC5tZXRhZGF0YS5jb2xvcn1gO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IHR5cGVDb2xvcnMgPSB7XG4gICAgICAgICAgICAnY3VzdG9tZXInOiAnaXMtYmx1ZScsXG4gICAgICAgICAgICAndmFjYXRpb24nOiAnaXMtZ3JlZW4nLFxuICAgICAgICAgICAgJ2JyZWFrJzogJ2lzLWFtYmVyJyxcbiAgICAgICAgICAgICdtZWV0aW5nJzogJ2lzLXB1cnBsZScsXG4gICAgICAgICAgICAnYmxvY2tlZCc6ICdpcy1yZWQnXG4gICAgICAgIH07XG4gICAgICAgIHJldHVybiB0eXBlQ29sb3JzW2V2ZW50LnR5cGVdIHx8ICdpcy1ibHVlJztcbiAgICB9XG4gICAgLyoqXG4gICAgICogU2V0dXAgZXZlbnQgbGlzdGVuZXJzIGZvciBkcmFnIGV2ZW50c1xuICAgICAqL1xuICAgIHNldHVwTGlzdGVuZXJzKCkge1xuICAgICAgICB0aGlzLmV2ZW50QnVzLm9uKENvcmVFdmVudHMuRVZFTlRfRFJBR19FTlRFUl9IRUFERVIsIChlKSA9PiB7XG4gICAgICAgICAgICBjb25zdCBwYXlsb2FkID0gZS5kZXRhaWw7XG4gICAgICAgICAgICB0aGlzLmhhbmRsZURyYWdFbnRlcihwYXlsb2FkKTtcbiAgICAgICAgfSk7XG4gICAgICAgIHRoaXMuZXZlbnRCdXMub24oQ29yZUV2ZW50cy5FVkVOVF9EUkFHX01PVkVfSEVBREVSLCAoZSkgPT4ge1xuICAgICAgICAgICAgY29uc3QgcGF5bG9hZCA9IGUuZGV0YWlsO1xuICAgICAgICAgICAgdGhpcy5oYW5kbGVEcmFnTW92ZShwYXlsb2FkKTtcbiAgICAgICAgfSk7XG4gICAgICAgIHRoaXMuZXZlbnRCdXMub24oQ29yZUV2ZW50cy5FVkVOVF9EUkFHX0xFQVZFX0hFQURFUiwgKGUpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHBheWxvYWQgPSBlLmRldGFpbDtcbiAgICAgICAgICAgIHRoaXMuaGFuZGxlRHJhZ0xlYXZlKHBheWxvYWQpO1xuICAgICAgICB9KTtcbiAgICAgICAgdGhpcy5ldmVudEJ1cy5vbihDb3JlRXZlbnRzLkVWRU5UX0RSQUdfRU5ELCAoZSkgPT4ge1xuICAgICAgICAgICAgY29uc3QgcGF5bG9hZCA9IGUuZGV0YWlsO1xuICAgICAgICAgICAgdGhpcy5oYW5kbGVEcmFnRW5kKHBheWxvYWQpO1xuICAgICAgICB9KTtcbiAgICAgICAgdGhpcy5ldmVudEJ1cy5vbihDb3JlRXZlbnRzLkVWRU5UX0RSQUdfQ0FOQ0VMLCAoKSA9PiB7XG4gICAgICAgICAgICB0aGlzLmNsZWFudXAoKTtcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEhhbmRsZSBkcmFnIGVudGVyaW5nIGhlYWRlciB6b25lIC0gY3JlYXRlIHByZXZpZXcgaXRlbVxuICAgICAqL1xuICAgIGhhbmRsZURyYWdFbnRlcihwYXlsb2FkKSB7XG4gICAgICAgIHRoaXMuY29udGFpbmVyID0gZG9jdW1lbnQucXVlcnlTZWxlY3Rvcignc3dwLWhlYWRlci1kcmF3ZXInKTtcbiAgICAgICAgaWYgKCF0aGlzLmNvbnRhaW5lcilcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgLy8gUmVtZW1iZXIgaWYgZHJhd2VyIHdhcyBhbHJlYWR5IGV4cGFuZGVkXG4gICAgICAgIHRoaXMud2FzRXhwYW5kZWRCZWZvcmVEcmFnID0gdGhpcy5oZWFkZXJEcmF3ZXJNYW5hZ2VyLmlzRXhwYW5kZWQoKTtcbiAgICAgICAgLy8gRXhwYW5kIHRvIGF0IGxlYXN0IDEgcm93IGlmIGNvbGxhcHNlZCwgb3RoZXJ3aXNlIGtlZXAgY3VycmVudCBoZWlnaHRcbiAgICAgICAgaWYgKCF0aGlzLndhc0V4cGFuZGVkQmVmb3JlRHJhZykge1xuICAgICAgICAgICAgdGhpcy5oZWFkZXJEcmF3ZXJNYW5hZ2VyLmV4cGFuZFRvUm93cygxKTtcbiAgICAgICAgfVxuICAgICAgICAvLyBTdG9yZSByZWZlcmVuY2UgdG8gc291cmNlIGVsZW1lbnRcbiAgICAgICAgdGhpcy5zb3VyY2VFbGVtZW50ID0gcGF5bG9hZC5lbGVtZW50O1xuICAgICAgICAvLyBDcmVhdGUgaGVhZGVyIGl0ZW1cbiAgICAgICAgY29uc3QgaXRlbSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3N3cC1oZWFkZXItaXRlbScpO1xuICAgICAgICBpdGVtLmRhdGFzZXQuZXZlbnRJZCA9IHBheWxvYWQuZXZlbnRJZDtcbiAgICAgICAgaXRlbS5kYXRhc2V0Lml0ZW1UeXBlID0gcGF5bG9hZC5pdGVtVHlwZTtcbiAgICAgICAgaXRlbS5kYXRhc2V0LmR1cmF0aW9uID0gU3RyaW5nKHBheWxvYWQuZHVyYXRpb24pO1xuICAgICAgICBpdGVtLmRhdGFzZXQuY29sdW1uS2V5ID0gcGF5bG9hZC5zb3VyY2VDb2x1bW5LZXk7XG4gICAgICAgIGl0ZW0udGV4dENvbnRlbnQgPSBwYXlsb2FkLnRpdGxlO1xuICAgICAgICAvLyBBcHBseSBjb2xvciBjbGFzcyBpZiBwcmVzZW50XG4gICAgICAgIGlmIChwYXlsb2FkLmNvbG9yQ2xhc3MpIHtcbiAgICAgICAgICAgIGl0ZW0uY2xhc3NMaXN0LmFkZChwYXlsb2FkLmNvbG9yQ2xhc3MpO1xuICAgICAgICB9XG4gICAgICAgIC8vIEFkZCBkcmFnZ2luZyBzdGF0ZVxuICAgICAgICBpdGVtLmNsYXNzTGlzdC5hZGQoJ2RyYWdnaW5nJyk7XG4gICAgICAgIC8vIEluaXRpYWwgcGxhY2VtZW50IChkdXJhdGlvbiBkZXRlcm1pbmVzIGNvbHVtbiBzcGFuKVxuICAgICAgICAvLyBncmlkQXJlYSBmb3JtYXQ6IFwicm93IC8gY29sLXN0YXJ0IC8gcm93KzEgLyBjb2wtZW5kXCJcbiAgICAgICAgY29uc3QgY29sID0gcGF5bG9hZC5zb3VyY2VDb2x1bW5JbmRleCArIDE7XG4gICAgICAgIGNvbnN0IGVuZENvbCA9IGNvbCArIHBheWxvYWQuZHVyYXRpb247XG4gICAgICAgIGl0ZW0uc3R5bGUuZ3JpZEFyZWEgPSBgMSAvICR7Y29sfSAvIDIgLyAke2VuZENvbH1gO1xuICAgICAgICB0aGlzLmNvbnRhaW5lci5hcHBlbmRDaGlsZChpdGVtKTtcbiAgICAgICAgdGhpcy5jdXJyZW50SXRlbSA9IGl0ZW07XG4gICAgICAgIC8vIEhpZGUgb3JpZ2luYWwgZWxlbWVudCB3aGlsZSBpbiBoZWFkZXJcbiAgICAgICAgcGF5bG9hZC5lbGVtZW50LnN0eWxlLnZpc2liaWxpdHkgPSAnaGlkZGVuJztcbiAgICB9XG4gICAgLyoqXG4gICAgICogSGFuZGxlIGRyYWcgbW92aW5nIHdpdGhpbiBoZWFkZXIgLSB1cGRhdGUgY29sdW1uIHBvc2l0aW9uXG4gICAgICovXG4gICAgaGFuZGxlRHJhZ01vdmUocGF5bG9hZCkge1xuICAgICAgICBpZiAoIXRoaXMuY3VycmVudEl0ZW0pXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIC8vIFVwZGF0ZSBjb2x1bW4gcG9zaXRpb25cbiAgICAgICAgY29uc3QgY29sID0gcGF5bG9hZC5jb2x1bW5JbmRleCArIDE7XG4gICAgICAgIGNvbnN0IGR1cmF0aW9uID0gcGFyc2VJbnQodGhpcy5jdXJyZW50SXRlbS5kYXRhc2V0LmR1cmF0aW9uIHx8ICcxJywgMTApO1xuICAgICAgICBjb25zdCBlbmRDb2wgPSBjb2wgKyBkdXJhdGlvbjtcbiAgICAgICAgdGhpcy5jdXJyZW50SXRlbS5zdHlsZS5ncmlkQXJlYSA9IGAxIC8gJHtjb2x9IC8gMiAvICR7ZW5kQ29sfWA7XG4gICAgICAgIC8vIFVwZGF0ZSBjb2x1bW5LZXkgdG8gbmV3IHBvc2l0aW9uXG4gICAgICAgIHRoaXMuY3VycmVudEl0ZW0uZGF0YXNldC5jb2x1bW5LZXkgPSBwYXlsb2FkLmNvbHVtbktleTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogSGFuZGxlIGRyYWcgbGVhdmluZyBoZWFkZXIgLSBjbGVhbnVwIGZvciBncmlkXHUyMTkyaGVhZGVyIGRyYWcgb25seVxuICAgICAqL1xuICAgIGhhbmRsZURyYWdMZWF2ZShwYXlsb2FkKSB7XG4gICAgICAgIC8vIE9ubHkgY2xlYW51cCBmb3IgZ3JpZFx1MjE5MmhlYWRlciBkcmFnICh3aGVuIGdyaWQgZXZlbnQgbGVhdmVzIGhlYWRlciBiYWNrIHRvIGdyaWQpXG4gICAgICAgIC8vIEZvciBoZWFkZXJcdTIxOTJncmlkIGRyYWcsIHRoZSBoZWFkZXIgaXRlbSBzdGF5cyBhcyBnaG9zdCB1bnRpbCBkcm9wXG4gICAgICAgIGlmIChwYXlsb2FkLnNvdXJjZSA9PT0gJ2dyaWQnKSB7XG4gICAgICAgICAgICB0aGlzLmNsZWFudXAoKTtcbiAgICAgICAgfVxuICAgICAgICAvLyBGb3IgaGVhZGVyIHNvdXJjZSwgZG8gbm90aGluZyAtIGdob3N0IHN0YXlzIHVudGlsIEVWRU5UX0RSQUdfRU5EXG4gICAgfVxuICAgIC8qKlxuICAgICAqIEhhbmRsZSBkcmFnIGVuZCAtIGZpbmFsaXplIGJhc2VkIG9uIGRyb3AgdGFyZ2V0XG4gICAgICovXG4gICAgaGFuZGxlRHJhZ0VuZChwYXlsb2FkKSB7XG4gICAgICAgIGlmIChwYXlsb2FkLnRhcmdldCA9PT0gJ2hlYWRlcicpIHtcbiAgICAgICAgICAgIC8vIEdyaWRcdTIxOTJIZWFkZXI6IEZpbmFsaXplIHRoZSBoZWFkZXIgaXRlbSAoaXQgc3RheXMgaW4gaGVhZGVyKVxuICAgICAgICAgICAgaWYgKHRoaXMuY3VycmVudEl0ZW0pIHtcbiAgICAgICAgICAgICAgICB0aGlzLmN1cnJlbnRJdGVtLmNsYXNzTGlzdC5yZW1vdmUoJ2RyYWdnaW5nJyk7XG4gICAgICAgICAgICAgICAgdGhpcy5yZWNhbGN1bGF0ZURyYXdlckxheW91dCgpO1xuICAgICAgICAgICAgICAgIHRoaXMuY3VycmVudEl0ZW0gPSBudWxsO1xuICAgICAgICAgICAgICAgIHRoaXMuc291cmNlRWxlbWVudCA9IG51bGw7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAvLyBIZWFkZXJcdTIxOTJHcmlkOiBSZW1vdmUgZ2hvc3QgaGVhZGVyIGl0ZW0gYW5kIHJlY2FsY3VsYXRlXG4gICAgICAgICAgICBjb25zdCBnaG9zdCA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoYHN3cC1oZWFkZXItaXRlbS5kcmFnLWdob3N0W2RhdGEtZXZlbnQtaWQ9XCIke3BheWxvYWQuc3dwRXZlbnQuZXZlbnRJZH1cIl1gKTtcbiAgICAgICAgICAgIGdob3N0Py5yZW1vdmUoKTtcbiAgICAgICAgICAgIHRoaXMucmVjYWxjdWxhdGVEcmF3ZXJMYXlvdXQoKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZWNhbGN1bGF0ZSBsYXlvdXQgZm9yIGFsbCBpdGVtcyBjdXJyZW50bHkgaW4gdGhlIGRyYXdlclxuICAgICAqIENhbGxlZCBhZnRlciBkcm9wIHRvIHJlcG9zaXRpb24gaXRlbXMgYW5kIGFkanVzdCBoZWlnaHRcbiAgICAgKi9cbiAgICByZWNhbGN1bGF0ZURyYXdlckxheW91dCgpIHtcbiAgICAgICAgY29uc3QgZHJhd2VyID0gZG9jdW1lbnQucXVlcnlTZWxlY3Rvcignc3dwLWhlYWRlci1kcmF3ZXInKTtcbiAgICAgICAgaWYgKCFkcmF3ZXIpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIGNvbnN0IGl0ZW1zID0gQXJyYXkuZnJvbShkcmF3ZXIucXVlcnlTZWxlY3RvckFsbCgnc3dwLWhlYWRlci1pdGVtJykpO1xuICAgICAgICBpZiAoaXRlbXMubGVuZ3RoID09PSAwKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAvLyBHZXQgdmlzaWJsZSBjb2x1bW4ga2V5cyBmb3IgY29ycmVjdCBtdWx0aS1yZXNvdXJjZSBwb3NpdGlvbmluZ1xuICAgICAgICBjb25zdCB2aXNpYmxlQ29sdW1uS2V5cyA9IHRoaXMuZ2V0VmlzaWJsZUNvbHVtbktleXNGcm9tRE9NKCk7XG4gICAgICAgIGlmICh2aXNpYmxlQ29sdW1uS2V5cy5sZW5ndGggPT09IDApXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIC8vIEJ1aWxkIGxheW91dCBkYXRhIGZyb20gRE9NIGl0ZW1zIC0gdXNlIGNvbHVtbktleSBkaXJlY3RseSAob3BhcXVlIG1hdGNoaW5nKVxuICAgICAgICBjb25zdCBpdGVtRGF0YSA9IGl0ZW1zLm1hcChpdGVtID0+ICh7XG4gICAgICAgICAgICBlbGVtZW50OiBpdGVtLFxuICAgICAgICAgICAgY29sdW1uS2V5OiBpdGVtLmRhdGFzZXQuY29sdW1uS2V5IHx8ICcnLFxuICAgICAgICAgICAgZHVyYXRpb246IHBhcnNlSW50KGl0ZW0uZGF0YXNldC5kdXJhdGlvbiB8fCAnMScsIDEwKVxuICAgICAgICB9KSk7XG4gICAgICAgIC8vIENhbGN1bGF0ZSBuZXcgbGF5b3V0IHVzaW5nIHRyYWNrIGFsZ29yaXRobVxuICAgICAgICBjb25zdCB0cmFja3MgPSBbbmV3IEFycmF5KHZpc2libGVDb2x1bW5LZXlzLmxlbmd0aCkuZmlsbChmYWxzZSldO1xuICAgICAgICBmb3IgKGNvbnN0IGl0ZW0gb2YgaXRlbURhdGEpIHtcbiAgICAgICAgICAgIC8vIERpcmVjdCBjb2x1bW5LZXkgbWF0Y2hpbmcgLSBubyBwYXJzaW5nIG9yIGNvbnN0cnVjdGlvbiBuZWVkZWRcbiAgICAgICAgICAgIGNvbnN0IHN0YXJ0Q29sID0gdmlzaWJsZUNvbHVtbktleXMuaW5kZXhPZihpdGVtLmNvbHVtbktleSk7XG4gICAgICAgICAgICBpZiAoc3RhcnRDb2wgPT09IC0xKVxuICAgICAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgICAgY29uc3QgY29sU3RhcnQgPSBzdGFydENvbDtcbiAgICAgICAgICAgIGNvbnN0IGNvbEVuZCA9IE1hdGgubWluKHN0YXJ0Q29sICsgaXRlbS5kdXJhdGlvbiwgdmlzaWJsZUNvbHVtbktleXMubGVuZ3RoKTtcbiAgICAgICAgICAgIGNvbnN0IHJvdyA9IHRoaXMuZmluZEF2YWlsYWJsZVJvdyh0cmFja3MsIGNvbFN0YXJ0LCBjb2xFbmQpO1xuICAgICAgICAgICAgZm9yIChsZXQgYyA9IGNvbFN0YXJ0OyBjIDwgY29sRW5kOyBjKyspIHtcbiAgICAgICAgICAgICAgICB0cmFja3Nbcm93XVtjXSA9IHRydWU7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBVcGRhdGUgZWxlbWVudCBwb3NpdGlvblxuICAgICAgICAgICAgaXRlbS5lbGVtZW50LnN0eWxlLmdyaWRBcmVhID0gYCR7cm93ICsgMX0gLyAke2NvbFN0YXJ0ICsgMX0gLyAke3JvdyArIDJ9IC8gJHtjb2xFbmQgKyAxfWA7XG4gICAgICAgIH1cbiAgICAgICAgLy8gVXBkYXRlIGRyYXdlciBoZWlnaHRcbiAgICAgICAgY29uc3Qgcm93Q291bnQgPSB0cmFja3MubGVuZ3RoO1xuICAgICAgICB0aGlzLmhlYWRlckRyYXdlck1hbmFnZXIuZXhwYW5kVG9Sb3dzKHJvd0NvdW50KTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IHZpc2libGUgY29sdW1uIGtleXMgZnJvbSBET00gKHByZXNlcnZlcyBvcmRlciBmb3IgbXVsdGktcmVzb3VyY2Ugdmlld3MpXG4gICAgICogVXNlcyBmaWx0ZXJUZW1wbGF0ZS5idWlsZEtleUZyb21Db2x1bW4oKSBmb3IgY29uc2lzdGVudCBrZXkgZm9ybWF0IHdpdGggZXZlbnRzXG4gICAgICovXG4gICAgZ2V0VmlzaWJsZUNvbHVtbktleXNGcm9tRE9NKCkge1xuICAgICAgICBpZiAoIXRoaXMuZmlsdGVyVGVtcGxhdGUpXG4gICAgICAgICAgICByZXR1cm4gW107XG4gICAgICAgIGNvbnN0IGNvbHVtbnMgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCdzd3AtZGF5LWNvbHVtbicpO1xuICAgICAgICBjb25zdCBjb2x1bW5LZXlzID0gW107XG4gICAgICAgIGNvbHVtbnMuZm9yRWFjaChjb2wgPT4ge1xuICAgICAgICAgICAgY29uc3QgY29sdW1uS2V5ID0gdGhpcy5maWx0ZXJUZW1wbGF0ZS5idWlsZEtleUZyb21Db2x1bW4oY29sKTtcbiAgICAgICAgICAgIGlmIChjb2x1bW5LZXkpXG4gICAgICAgICAgICAgICAgY29sdW1uS2V5cy5wdXNoKGNvbHVtbktleSk7XG4gICAgICAgIH0pO1xuICAgICAgICByZXR1cm4gY29sdW1uS2V5cztcbiAgICB9XG4gICAgLyoqXG4gICAgICogQ2xlYW51cCBwcmV2aWV3IGl0ZW0gYW5kIHJlc3RvcmUgc291cmNlIHZpc2liaWxpdHlcbiAgICAgKi9cbiAgICBjbGVhbnVwKCkge1xuICAgICAgICAvLyBSZW1vdmUgcHJldmlldyBpdGVtXG4gICAgICAgIHRoaXMuY3VycmVudEl0ZW0/LnJlbW92ZSgpO1xuICAgICAgICB0aGlzLmN1cnJlbnRJdGVtID0gbnVsbDtcbiAgICAgICAgLy8gUmVzdG9yZSBzb3VyY2UgZWxlbWVudCB2aXNpYmlsaXR5XG4gICAgICAgIGlmICh0aGlzLnNvdXJjZUVsZW1lbnQpIHtcbiAgICAgICAgICAgIHRoaXMuc291cmNlRWxlbWVudC5zdHlsZS52aXNpYmlsaXR5ID0gJyc7XG4gICAgICAgICAgICB0aGlzLnNvdXJjZUVsZW1lbnQgPSBudWxsO1xuICAgICAgICB9XG4gICAgICAgIC8vIENvbGxhcHNlIGRyYXdlciBpZiBpdCB3YXNuJ3QgZXhwYW5kZWQgYmVmb3JlIGRyYWdcbiAgICAgICAgaWYgKCF0aGlzLndhc0V4cGFuZGVkQmVmb3JlRHJhZykge1xuICAgICAgICAgICAgdGhpcy5oZWFkZXJEcmF3ZXJNYW5hZ2VyLmNvbGxhcHNlKCk7XG4gICAgICAgIH1cbiAgICB9XG59XG4iLCAiLyoqXG4gKiBTY2hlZHVsZU92ZXJyaWRlU3RvcmUgLSBJbmRleGVkREIgT2JqZWN0U3RvcmUgZm9yIHNjaGVkdWxlIG92ZXJyaWRlc1xuICpcbiAqIFN0b3JlcyBkYXRlLXNwZWNpZmljIHNjaGVkdWxlIG92ZXJyaWRlcyBmb3IgcmVzb3VyY2VzLlxuICogSW5kZXhlczogcmVzb3VyY2VJZCwgZGF0ZSwgY29tcG91bmQgKHJlc291cmNlSWQgKyBkYXRlKVxuICovXG5leHBvcnQgY2xhc3MgU2NoZWR1bGVPdmVycmlkZVN0b3JlIHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpcy5zdG9yZU5hbWUgPSBTY2hlZHVsZU92ZXJyaWRlU3RvcmUuU1RPUkVfTkFNRTtcbiAgICB9XG4gICAgY3JlYXRlKGRiKSB7XG4gICAgICAgIGNvbnN0IHN0b3JlID0gZGIuY3JlYXRlT2JqZWN0U3RvcmUoU2NoZWR1bGVPdmVycmlkZVN0b3JlLlNUT1JFX05BTUUsIHsga2V5UGF0aDogJ2lkJyB9KTtcbiAgICAgICAgc3RvcmUuY3JlYXRlSW5kZXgoJ3Jlc291cmNlSWQnLCAncmVzb3VyY2VJZCcsIHsgdW5pcXVlOiBmYWxzZSB9KTtcbiAgICAgICAgc3RvcmUuY3JlYXRlSW5kZXgoJ2RhdGUnLCAnZGF0ZScsIHsgdW5pcXVlOiBmYWxzZSB9KTtcbiAgICAgICAgc3RvcmUuY3JlYXRlSW5kZXgoJ3Jlc291cmNlSWRfZGF0ZScsIFsncmVzb3VyY2VJZCcsICdkYXRlJ10sIHsgdW5pcXVlOiB0cnVlIH0pO1xuICAgICAgICBzdG9yZS5jcmVhdGVJbmRleCgnc3luY1N0YXR1cycsICdzeW5jU3RhdHVzJywgeyB1bmlxdWU6IGZhbHNlIH0pO1xuICAgIH1cbn1cblNjaGVkdWxlT3ZlcnJpZGVTdG9yZS5TVE9SRV9OQU1FID0gJ3NjaGVkdWxlT3ZlcnJpZGVzJztcbiIsICJpbXBvcnQgeyBTY2hlZHVsZU92ZXJyaWRlU3RvcmUgfSBmcm9tICcuL1NjaGVkdWxlT3ZlcnJpZGVTdG9yZSc7XG4vKipcbiAqIFNjaGVkdWxlT3ZlcnJpZGVTZXJ2aWNlIC0gQ1JVRCBmb3Igc2NoZWR1bGUgb3ZlcnJpZGVzXG4gKlxuICogUHJvdmlkZXMgYWNjZXNzIHRvIGRhdGUtc3BlY2lmaWMgc2NoZWR1bGUgb3ZlcnJpZGVzIGZvciByZXNvdXJjZXMuXG4gKi9cbmV4cG9ydCBjbGFzcyBTY2hlZHVsZU92ZXJyaWRlU2VydmljZSB7XG4gICAgY29uc3RydWN0b3IoY29udGV4dCkge1xuICAgICAgICB0aGlzLmNvbnRleHQgPSBjb250ZXh0O1xuICAgIH1cbiAgICBnZXQgZGIoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmNvbnRleHQuZ2V0RGF0YWJhc2UoKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IG92ZXJyaWRlIGZvciBhIHNwZWNpZmljIHJlc291cmNlIGFuZCBkYXRlXG4gICAgICovXG4gICAgYXN5bmMgZ2V0T3ZlcnJpZGUocmVzb3VyY2VJZCwgZGF0ZSkge1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgdHJhbnNhY3Rpb24gPSB0aGlzLmRiLnRyYW5zYWN0aW9uKFtTY2hlZHVsZU92ZXJyaWRlU3RvcmUuU1RPUkVfTkFNRV0sICdyZWFkb25seScpO1xuICAgICAgICAgICAgY29uc3Qgc3RvcmUgPSB0cmFuc2FjdGlvbi5vYmplY3RTdG9yZShTY2hlZHVsZU92ZXJyaWRlU3RvcmUuU1RPUkVfTkFNRSk7XG4gICAgICAgICAgICBjb25zdCBpbmRleCA9IHN0b3JlLmluZGV4KCdyZXNvdXJjZUlkX2RhdGUnKTtcbiAgICAgICAgICAgIGNvbnN0IHJlcXVlc3QgPSBpbmRleC5nZXQoW3Jlc291cmNlSWQsIGRhdGVdKTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25zdWNjZXNzID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIHJlc29sdmUocmVxdWVzdC5yZXN1bHQgfHwgbnVsbCk7XG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgcmVxdWVzdC5vbmVycm9yID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIHJlamVjdChuZXcgRXJyb3IoYEZhaWxlZCB0byBnZXQgb3ZlcnJpZGUgZm9yICR7cmVzb3VyY2VJZH0gb24gJHtkYXRlfTogJHtyZXF1ZXN0LmVycm9yfWApKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0pO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgYWxsIG92ZXJyaWRlcyBmb3IgYSByZXNvdXJjZVxuICAgICAqL1xuICAgIGFzeW5jIGdldEJ5UmVzb3VyY2UocmVzb3VyY2VJZCkge1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgdHJhbnNhY3Rpb24gPSB0aGlzLmRiLnRyYW5zYWN0aW9uKFtTY2hlZHVsZU92ZXJyaWRlU3RvcmUuU1RPUkVfTkFNRV0sICdyZWFkb25seScpO1xuICAgICAgICAgICAgY29uc3Qgc3RvcmUgPSB0cmFuc2FjdGlvbi5vYmplY3RTdG9yZShTY2hlZHVsZU92ZXJyaWRlU3RvcmUuU1RPUkVfTkFNRSk7XG4gICAgICAgICAgICBjb25zdCBpbmRleCA9IHN0b3JlLmluZGV4KCdyZXNvdXJjZUlkJyk7XG4gICAgICAgICAgICBjb25zdCByZXF1ZXN0ID0gaW5kZXguZ2V0QWxsKHJlc291cmNlSWQpO1xuICAgICAgICAgICAgcmVxdWVzdC5vbnN1Y2Nlc3MgPSAoKSA9PiB7XG4gICAgICAgICAgICAgICAgcmVzb2x2ZShyZXF1ZXN0LnJlc3VsdCB8fCBbXSk7XG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgcmVxdWVzdC5vbmVycm9yID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIHJlamVjdChuZXcgRXJyb3IoYEZhaWxlZCB0byBnZXQgb3ZlcnJpZGVzIGZvciAke3Jlc291cmNlSWR9OiAke3JlcXVlc3QuZXJyb3J9YCkpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCBvdmVycmlkZXMgZm9yIGEgZGF0ZSByYW5nZVxuICAgICAqL1xuICAgIGFzeW5jIGdldEJ5RGF0ZVJhbmdlKHJlc291cmNlSWQsIHN0YXJ0RGF0ZSwgZW5kRGF0ZSkge1xuICAgICAgICBjb25zdCBhbGwgPSBhd2FpdCB0aGlzLmdldEJ5UmVzb3VyY2UocmVzb3VyY2VJZCk7XG4gICAgICAgIHJldHVybiBhbGwuZmlsdGVyKG8gPT4gby5kYXRlID49IHN0YXJ0RGF0ZSAmJiBvLmRhdGUgPD0gZW5kRGF0ZSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFNhdmUgYW4gb3ZlcnJpZGVcbiAgICAgKi9cbiAgICBhc3luYyBzYXZlKG92ZXJyaWRlKSB7XG4gICAgICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICBjb25zdCB0cmFuc2FjdGlvbiA9IHRoaXMuZGIudHJhbnNhY3Rpb24oW1NjaGVkdWxlT3ZlcnJpZGVTdG9yZS5TVE9SRV9OQU1FXSwgJ3JlYWR3cml0ZScpO1xuICAgICAgICAgICAgY29uc3Qgc3RvcmUgPSB0cmFuc2FjdGlvbi5vYmplY3RTdG9yZShTY2hlZHVsZU92ZXJyaWRlU3RvcmUuU1RPUkVfTkFNRSk7XG4gICAgICAgICAgICBjb25zdCByZXF1ZXN0ID0gc3RvcmUucHV0KG92ZXJyaWRlKTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25zdWNjZXNzID0gKCkgPT4gcmVzb2x2ZSgpO1xuICAgICAgICAgICAgcmVxdWVzdC5vbmVycm9yID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIHJlamVjdChuZXcgRXJyb3IoYEZhaWxlZCB0byBzYXZlIG92ZXJyaWRlICR7b3ZlcnJpZGUuaWR9OiAke3JlcXVlc3QuZXJyb3J9YCkpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIERlbGV0ZSBhbiBvdmVycmlkZVxuICAgICAqL1xuICAgIGFzeW5jIGRlbGV0ZShpZCkge1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgdHJhbnNhY3Rpb24gPSB0aGlzLmRiLnRyYW5zYWN0aW9uKFtTY2hlZHVsZU92ZXJyaWRlU3RvcmUuU1RPUkVfTkFNRV0sICdyZWFkd3JpdGUnKTtcbiAgICAgICAgICAgIGNvbnN0IHN0b3JlID0gdHJhbnNhY3Rpb24ub2JqZWN0U3RvcmUoU2NoZWR1bGVPdmVycmlkZVN0b3JlLlNUT1JFX05BTUUpO1xuICAgICAgICAgICAgY29uc3QgcmVxdWVzdCA9IHN0b3JlLmRlbGV0ZShpZCk7XG4gICAgICAgICAgICByZXF1ZXN0Lm9uc3VjY2VzcyA9ICgpID0+IHJlc29sdmUoKTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25lcnJvciA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICByZWplY3QobmV3IEVycm9yKGBGYWlsZWQgdG8gZGVsZXRlIG92ZXJyaWRlICR7aWR9OiAke3JlcXVlc3QuZXJyb3J9YCkpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgfSk7XG4gICAgfVxufVxuIiwgIi8qKlxuICogUmVzb3VyY2VTY2hlZHVsZVNlcnZpY2UgLSBHZXQgZWZmZWN0aXZlIHNjaGVkdWxlIGZvciBhIHJlc291cmNlIG9uIGEgZGF0ZVxuICpcbiAqIExvZ2ljOlxuICogMS4gQ2hlY2sgZm9yIG92ZXJyaWRlIG9uIHRoaXMgZGF0ZVxuICogMi4gRmFsbCBiYWNrIHRvIGRlZmF1bHQgc2NoZWR1bGUgZm9yIHRoZSB3ZWVrZGF5XG4gKi9cbmV4cG9ydCBjbGFzcyBSZXNvdXJjZVNjaGVkdWxlU2VydmljZSB7XG4gICAgY29uc3RydWN0b3IocmVzb3VyY2VTZXJ2aWNlLCBvdmVycmlkZVNlcnZpY2UsIGRhdGVTZXJ2aWNlKSB7XG4gICAgICAgIHRoaXMucmVzb3VyY2VTZXJ2aWNlID0gcmVzb3VyY2VTZXJ2aWNlO1xuICAgICAgICB0aGlzLm92ZXJyaWRlU2VydmljZSA9IG92ZXJyaWRlU2VydmljZTtcbiAgICAgICAgdGhpcy5kYXRlU2VydmljZSA9IGRhdGVTZXJ2aWNlO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgZWZmZWN0aXZlIHNjaGVkdWxlIGZvciBhIHJlc291cmNlIG9uIGEgc3BlY2lmaWMgZGF0ZVxuICAgICAqXG4gICAgICogQHBhcmFtIHJlc291cmNlSWQgLSBSZXNvdXJjZSBJRFxuICAgICAqIEBwYXJhbSBkYXRlIC0gRGF0ZSBzdHJpbmcgXCJZWVlZLU1NLUREXCJcbiAgICAgKiBAcmV0dXJucyBJVGltZVNsb3Qgb3IgbnVsbCAoZnJpL2Nsb3NlZClcbiAgICAgKi9cbiAgICBhc3luYyBnZXRTY2hlZHVsZUZvckRhdGUocmVzb3VyY2VJZCwgZGF0ZSkge1xuICAgICAgICAvLyAxLiBDaGVjayBmb3Igb3ZlcnJpZGVcbiAgICAgICAgY29uc3Qgb3ZlcnJpZGUgPSBhd2FpdCB0aGlzLm92ZXJyaWRlU2VydmljZS5nZXRPdmVycmlkZShyZXNvdXJjZUlkLCBkYXRlKTtcbiAgICAgICAgaWYgKG92ZXJyaWRlKSB7XG4gICAgICAgICAgICByZXR1cm4gb3ZlcnJpZGUuc2NoZWR1bGU7XG4gICAgICAgIH1cbiAgICAgICAgLy8gMi4gVXNlIGRlZmF1bHQgc2NoZWR1bGUgZm9yIHdlZWtkYXlcbiAgICAgICAgY29uc3QgcmVzb3VyY2UgPSBhd2FpdCB0aGlzLnJlc291cmNlU2VydmljZS5nZXQocmVzb3VyY2VJZCk7XG4gICAgICAgIGlmICghcmVzb3VyY2UgfHwgIXJlc291cmNlLmRlZmF1bHRTY2hlZHVsZSkge1xuICAgICAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3Qgd2Vla0RheSA9IHRoaXMuZGF0ZVNlcnZpY2UuZ2V0SVNPV2Vla0RheShkYXRlKTtcbiAgICAgICAgcmV0dXJuIHJlc291cmNlLmRlZmF1bHRTY2hlZHVsZVt3ZWVrRGF5XSB8fCBudWxsO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgc2NoZWR1bGVzIGZvciBtdWx0aXBsZSBkYXRlc1xuICAgICAqXG4gICAgICogQHBhcmFtIHJlc291cmNlSWQgLSBSZXNvdXJjZSBJRFxuICAgICAqIEBwYXJhbSBkYXRlcyAtIEFycmF5IG9mIGRhdGUgc3RyaW5ncyBcIllZWVktTU0tRERcIlxuICAgICAqIEByZXR1cm5zIE1hcCBvZiBkYXRlIC0+IElUaW1lU2xvdCB8IG51bGxcbiAgICAgKi9cbiAgICBhc3luYyBnZXRTY2hlZHVsZXNGb3JEYXRlcyhyZXNvdXJjZUlkLCBkYXRlcykge1xuICAgICAgICBjb25zdCByZXN1bHQgPSBuZXcgTWFwKCk7XG4gICAgICAgIC8vIEdldCByZXNvdXJjZSBvbmNlXG4gICAgICAgIGNvbnN0IHJlc291cmNlID0gYXdhaXQgdGhpcy5yZXNvdXJjZVNlcnZpY2UuZ2V0KHJlc291cmNlSWQpO1xuICAgICAgICAvLyBHZXQgYWxsIG92ZXJyaWRlcyBpbiBkYXRlIHJhbmdlXG4gICAgICAgIGNvbnN0IG92ZXJyaWRlcyA9IGRhdGVzLmxlbmd0aCA+IDBcbiAgICAgICAgICAgID8gYXdhaXQgdGhpcy5vdmVycmlkZVNlcnZpY2UuZ2V0QnlEYXRlUmFuZ2UocmVzb3VyY2VJZCwgZGF0ZXNbMF0sIGRhdGVzW2RhdGVzLmxlbmd0aCAtIDFdKVxuICAgICAgICAgICAgOiBbXTtcbiAgICAgICAgLy8gQnVpbGQgb3ZlcnJpZGUgbWFwXG4gICAgICAgIGNvbnN0IG92ZXJyaWRlTWFwID0gbmV3IE1hcChvdmVycmlkZXMubWFwKG8gPT4gW28uZGF0ZSwgby5zY2hlZHVsZV0pKTtcbiAgICAgICAgLy8gUmVzb2x2ZSBlYWNoIGRhdGVcbiAgICAgICAgZm9yIChjb25zdCBkYXRlIG9mIGRhdGVzKSB7XG4gICAgICAgICAgICAvLyBDaGVjayBvdmVycmlkZSBmaXJzdFxuICAgICAgICAgICAgaWYgKG92ZXJyaWRlTWFwLmhhcyhkYXRlKSkge1xuICAgICAgICAgICAgICAgIHJlc3VsdC5zZXQoZGF0ZSwgb3ZlcnJpZGVNYXAuZ2V0KGRhdGUpKTtcbiAgICAgICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIEZhbGwgYmFjayB0byBkZWZhdWx0XG4gICAgICAgICAgICBpZiAocmVzb3VyY2U/LmRlZmF1bHRTY2hlZHVsZSkge1xuICAgICAgICAgICAgICAgIGNvbnN0IHdlZWtEYXkgPSB0aGlzLmRhdGVTZXJ2aWNlLmdldElTT1dlZWtEYXkoZGF0ZSk7XG4gICAgICAgICAgICAgICAgcmVzdWx0LnNldChkYXRlLCByZXNvdXJjZS5kZWZhdWx0U2NoZWR1bGVbd2Vla0RheV0gfHwgbnVsbCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICByZXN1bHQuc2V0KGRhdGUsIG51bGwpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgfVxufVxuIiwgIi8qKlxuICogU3dwRXZlbnQgLSBXcmFwcGVyIGNsYXNzIGZvciBjYWxlbmRhciBldmVudCBlbGVtZW50c1xuICpcbiAqIEVuY2Fwc3VsYXRlcyBhbiBIVE1MRWxlbWVudCBhbmQgcHJvdmlkZXMgY29tcHV0ZWQgcHJvcGVydGllc1xuICogZm9yIHN0YXJ0L2VuZCB0aW1lcyBiYXNlZCBvbiBlbGVtZW50IHBvc2l0aW9uIGFuZCBncmlkIGNvbmZpZy5cbiAqXG4gKiBVc2FnZTpcbiAqIC0gZXZlbnRJZCBpcyByZWFkIGZyb20gZWxlbWVudC5kYXRhc2V0XG4gKiAtIGNvbHVtbktleSBpZGVudGlmaWVzIHRoZSBjb2x1bW4gdW5pZm9ybWx5XG4gKiAtIFBvc2l0aW9uICh0b3AsIGhlaWdodCkgaXMgcmVhZCBmcm9tIGVsZW1lbnQuc3R5bGVcbiAqIC0gRmFjdG9yeSBtZXRob2QgYGZyb21FbGVtZW50KClgIGNhbGN1bGF0ZXMgRGF0ZSBvYmplY3RzXG4gKi9cbmV4cG9ydCBjbGFzcyBTd3BFdmVudCB7XG4gICAgY29uc3RydWN0b3IoZWxlbWVudCwgY29sdW1uS2V5LCBzdGFydCwgZW5kKSB7XG4gICAgICAgIHRoaXMuZWxlbWVudCA9IGVsZW1lbnQ7XG4gICAgICAgIHRoaXMuY29sdW1uS2V5ID0gY29sdW1uS2V5O1xuICAgICAgICB0aGlzLl9zdGFydCA9IHN0YXJ0O1xuICAgICAgICB0aGlzLl9lbmQgPSBlbmQ7XG4gICAgfVxuICAgIC8qKiBFdmVudCBJRCBmcm9tIGVsZW1lbnQuZGF0YXNldC5ldmVudElkICovXG4gICAgZ2V0IGV2ZW50SWQoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmVsZW1lbnQuZGF0YXNldC5ldmVudElkIHx8ICcnO1xuICAgIH1cbiAgICBnZXQgc3RhcnQoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLl9zdGFydDtcbiAgICB9XG4gICAgZ2V0IGVuZCgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX2VuZDtcbiAgICB9XG4gICAgLyoqIER1cmF0aW9uIGluIG1pbnV0ZXMgKi9cbiAgICBnZXQgZHVyYXRpb25NaW51dGVzKCkge1xuICAgICAgICByZXR1cm4gKHRoaXMuX2VuZC5nZXRUaW1lKCkgLSB0aGlzLl9zdGFydC5nZXRUaW1lKCkpIC8gKDEwMDAgKiA2MCk7XG4gICAgfVxuICAgIC8qKiBEdXJhdGlvbiBpbiBtaWxsaXNlY29uZHMgKi9cbiAgICBnZXQgZHVyYXRpb25NcygpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX2VuZC5nZXRUaW1lKCkgLSB0aGlzLl9zdGFydC5nZXRUaW1lKCk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEZhY3Rvcnk6IENyZWF0ZSBTd3BFdmVudCBmcm9tIGVsZW1lbnQgKyBjb2x1bW5LZXlcbiAgICAgKiBSZWFkcyB0b3AvaGVpZ2h0IGZyb20gZWxlbWVudC5zdHlsZSB0byBjYWxjdWxhdGUgc3RhcnQvZW5kXG4gICAgICogQHBhcmFtIGNvbHVtbktleSAtIE9wYXF1ZSBjb2x1bW4gaWRlbnRpZmllciAoZG8gTk9UIHBhcnNlIC0gdXNlIG9ubHkgZm9yIG1hdGNoaW5nKVxuICAgICAqIEBwYXJhbSBkYXRlIC0gRGF0ZSBzdHJpbmcgKFlZWVktTU0tREQpIGZvciB0aW1lIGNhbGN1bGF0aW9uc1xuICAgICAqL1xuICAgIHN0YXRpYyBmcm9tRWxlbWVudChlbGVtZW50LCBjb2x1bW5LZXksIGRhdGUsIGdyaWRDb25maWcpIHtcbiAgICAgICAgY29uc3QgdG9wUGl4ZWxzID0gcGFyc2VGbG9hdChlbGVtZW50LnN0eWxlLnRvcCkgfHwgMDtcbiAgICAgICAgY29uc3QgaGVpZ2h0UGl4ZWxzID0gcGFyc2VGbG9hdChlbGVtZW50LnN0eWxlLmhlaWdodCkgfHwgMDtcbiAgICAgICAgLy8gQ2FsY3VsYXRlIHN0YXJ0IGZyb20gdG9wIHBvc2l0aW9uXG4gICAgICAgIGNvbnN0IHN0YXJ0TWludXRlc0Zyb21HcmlkID0gKHRvcFBpeGVscyAvIGdyaWRDb25maWcuaG91ckhlaWdodCkgKiA2MDtcbiAgICAgICAgY29uc3QgdG90YWxNaW51dGVzID0gKGdyaWRDb25maWcuZGF5U3RhcnRIb3VyICogNjApICsgc3RhcnRNaW51dGVzRnJvbUdyaWQ7XG4gICAgICAgIGNvbnN0IHN0YXJ0ID0gbmV3IERhdGUoZGF0ZSk7XG4gICAgICAgIHN0YXJ0LnNldEhvdXJzKE1hdGguZmxvb3IodG90YWxNaW51dGVzIC8gNjApLCB0b3RhbE1pbnV0ZXMgJSA2MCwgMCwgMCk7XG4gICAgICAgIC8vIENhbGN1bGF0ZSBlbmQgZnJvbSBoZWlnaHRcbiAgICAgICAgY29uc3QgZHVyYXRpb25NaW51dGVzID0gKGhlaWdodFBpeGVscyAvIGdyaWRDb25maWcuaG91ckhlaWdodCkgKiA2MDtcbiAgICAgICAgY29uc3QgZW5kID0gbmV3IERhdGUoc3RhcnQuZ2V0VGltZSgpICsgZHVyYXRpb25NaW51dGVzICogNjAgKiAxMDAwKTtcbiAgICAgICAgcmV0dXJuIG5ldyBTd3BFdmVudChlbGVtZW50LCBjb2x1bW5LZXksIHN0YXJ0LCBlbmQpO1xuICAgIH1cbn1cbiIsICJpbXBvcnQgeyBDb3JlRXZlbnRzIH0gZnJvbSAnLi4vY29uc3RhbnRzL0NvcmVFdmVudHMnO1xuaW1wb3J0IHsgc25hcFRvR3JpZCB9IGZyb20gJy4uL3V0aWxzL1Bvc2l0aW9uVXRpbHMnO1xuaW1wb3J0IHsgU3dwRXZlbnQgfSBmcm9tICcuLi90eXBlcy9Td3BFdmVudCc7XG4vKipcbiAqIERyYWdEcm9wTWFuYWdlciAtIEhhbmRsZXMgZHJhZy1kcm9wIGZvciBjYWxlbmRhciBldmVudHNcbiAqXG4gKiBTdHJhdGVneTogRHJhZyBvcmlnaW5hbCBlbGVtZW50LCBsZWF2ZSBnaG9zdC1jbG9uZSBpbiBwbGFjZVxuICogLSBtb3VzZWRvd246IFN0b3JlIGluaXRpYWwgc3RhdGUsIHdhaXQgZm9yIG1vdmVtZW50XG4gKiAtIG1vdXNlbW92ZSAoPjVweCk6IENyZWF0ZSBnaG9zdCwgc3RhcnQgZHJhZ2dpbmcgb3JpZ2luYWxcbiAqIC0gbW91c2V1cDogU25hcCB0byBncmlkLCByZW1vdmUgZ2hvc3QsIGVtaXQgZHJhZzplbmRcbiAqIC0gY2FuY2VsOiBBbmltYXRlIGJhY2sgdG8gc3RhcnRZLCByZW1vdmUgZ2hvc3RcbiAqL1xuZXhwb3J0IGNsYXNzIERyYWdEcm9wTWFuYWdlciB7XG4gICAgY29uc3RydWN0b3IoZXZlbnRCdXMsIGdyaWRDb25maWcpIHtcbiAgICAgICAgdGhpcy5ldmVudEJ1cyA9IGV2ZW50QnVzO1xuICAgICAgICB0aGlzLmdyaWRDb25maWcgPSBncmlkQ29uZmlnO1xuICAgICAgICB0aGlzLmRyYWdTdGF0ZSA9IG51bGw7XG4gICAgICAgIHRoaXMubW91c2VEb3duUG9zaXRpb24gPSBudWxsO1xuICAgICAgICB0aGlzLnBlbmRpbmdFbGVtZW50ID0gbnVsbDtcbiAgICAgICAgdGhpcy5wZW5kaW5nTW91c2VPZmZzZXQgPSBudWxsO1xuICAgICAgICB0aGlzLmNvbnRhaW5lciA9IG51bGw7XG4gICAgICAgIHRoaXMuaW5IZWFkZXIgPSBmYWxzZTtcbiAgICAgICAgdGhpcy5EUkFHX1RIUkVTSE9MRCA9IDU7XG4gICAgICAgIHRoaXMuSU5URVJQT0xBVElPTl9GQUNUT1IgPSAwLjM7XG4gICAgICAgIHRoaXMuaGFuZGxlUG9pbnRlckRvd24gPSAoZSkgPT4ge1xuICAgICAgICAgICAgY29uc3QgdGFyZ2V0ID0gZS50YXJnZXQ7XG4gICAgICAgICAgICAvLyBJZ25vcmUgaWYgY2xpY2tpbmcgb24gcmVzaXplIGhhbmRsZVxuICAgICAgICAgICAgaWYgKHRhcmdldC5jbG9zZXN0KCdzd3AtcmVzaXplLWhhbmRsZScpKVxuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIC8vIE1hdGNoIGJvdGggc3dwLWV2ZW50IGFuZCBzd3AtaGVhZGVyLWl0ZW1cbiAgICAgICAgICAgIGNvbnN0IGV2ZW50RWxlbWVudCA9IHRhcmdldC5jbG9zZXN0KCdzd3AtZXZlbnQnKTtcbiAgICAgICAgICAgIGNvbnN0IGhlYWRlckl0ZW0gPSB0YXJnZXQuY2xvc2VzdCgnc3dwLWhlYWRlci1pdGVtJyk7XG4gICAgICAgICAgICBjb25zdCBkcmFnZ2FibGUgPSBldmVudEVsZW1lbnQgfHwgaGVhZGVySXRlbTtcbiAgICAgICAgICAgIGlmICghZHJhZ2dhYmxlKVxuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIC8vIFN0b3JlIGZvciBwb3RlbnRpYWwgZHJhZ1xuICAgICAgICAgICAgdGhpcy5tb3VzZURvd25Qb3NpdGlvbiA9IHsgeDogZS5jbGllbnRYLCB5OiBlLmNsaWVudFkgfTtcbiAgICAgICAgICAgIHRoaXMucGVuZGluZ0VsZW1lbnQgPSBkcmFnZ2FibGU7XG4gICAgICAgICAgICAvLyBDYWxjdWxhdGUgbW91c2Ugb2Zmc2V0IHdpdGhpbiBlbGVtZW50XG4gICAgICAgICAgICBjb25zdCByZWN0ID0gZHJhZ2dhYmxlLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgICAgICAgICAgdGhpcy5wZW5kaW5nTW91c2VPZmZzZXQgPSB7XG4gICAgICAgICAgICAgICAgeDogZS5jbGllbnRYIC0gcmVjdC5sZWZ0LFxuICAgICAgICAgICAgICAgIHk6IGUuY2xpZW50WSAtIHJlY3QudG9wXG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgLy8gQ2FwdHVyZSBwb2ludGVyIGZvciByZWxpYWJsZSB0cmFja2luZ1xuICAgICAgICAgICAgZHJhZ2dhYmxlLnNldFBvaW50ZXJDYXB0dXJlKGUucG9pbnRlcklkKTtcbiAgICAgICAgfTtcbiAgICAgICAgdGhpcy5oYW5kbGVQb2ludGVyTW92ZSA9IChlKSA9PiB7XG4gICAgICAgICAgICAvLyBOb3QgaW4gcG90ZW50aWFsIGRyYWcgc3RhdGVcbiAgICAgICAgICAgIGlmICghdGhpcy5tb3VzZURvd25Qb3NpdGlvbiB8fCAhdGhpcy5wZW5kaW5nRWxlbWVudCkge1xuICAgICAgICAgICAgICAgIC8vIEFscmVhZHkgZHJhZ2dpbmcgLSB1cGRhdGUgdGFyZ2V0XG4gICAgICAgICAgICAgICAgaWYgKHRoaXMuZHJhZ1N0YXRlKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMudXBkYXRlRHJhZ1RhcmdldChlKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gQ2hlY2sgdGhyZXNob2xkXG4gICAgICAgICAgICBjb25zdCBkZWx0YVggPSBNYXRoLmFicyhlLmNsaWVudFggLSB0aGlzLm1vdXNlRG93blBvc2l0aW9uLngpO1xuICAgICAgICAgICAgY29uc3QgZGVsdGFZID0gTWF0aC5hYnMoZS5jbGllbnRZIC0gdGhpcy5tb3VzZURvd25Qb3NpdGlvbi55KTtcbiAgICAgICAgICAgIGNvbnN0IGRpc3RhbmNlID0gTWF0aC5zcXJ0KGRlbHRhWCAqIGRlbHRhWCArIGRlbHRhWSAqIGRlbHRhWSk7XG4gICAgICAgICAgICBpZiAoZGlzdGFuY2UgPCB0aGlzLkRSQUdfVEhSRVNIT0xEKVxuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIC8vIFN0YXJ0IGRyYWdcbiAgICAgICAgICAgIHRoaXMuaW5pdGlhbGl6ZURyYWcodGhpcy5wZW5kaW5nRWxlbWVudCwgdGhpcy5wZW5kaW5nTW91c2VPZmZzZXQsIGUpO1xuICAgICAgICAgICAgdGhpcy5tb3VzZURvd25Qb3NpdGlvbiA9IG51bGw7XG4gICAgICAgICAgICB0aGlzLnBlbmRpbmdFbGVtZW50ID0gbnVsbDtcbiAgICAgICAgICAgIHRoaXMucGVuZGluZ01vdXNlT2Zmc2V0ID0gbnVsbDtcbiAgICAgICAgfTtcbiAgICAgICAgdGhpcy5oYW5kbGVQb2ludGVyVXAgPSAoX2UpID0+IHtcbiAgICAgICAgICAgIC8vIENsZWFyIHBlbmRpbmcgc3RhdGVcbiAgICAgICAgICAgIHRoaXMubW91c2VEb3duUG9zaXRpb24gPSBudWxsO1xuICAgICAgICAgICAgdGhpcy5wZW5kaW5nRWxlbWVudCA9IG51bGw7XG4gICAgICAgICAgICB0aGlzLnBlbmRpbmdNb3VzZU9mZnNldCA9IG51bGw7XG4gICAgICAgICAgICBpZiAoIXRoaXMuZHJhZ1N0YXRlKVxuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIC8vIFN0b3AgYW5pbWF0aW9uXG4gICAgICAgICAgICBjYW5jZWxBbmltYXRpb25GcmFtZSh0aGlzLmRyYWdTdGF0ZS5hbmltYXRpb25JZCk7XG4gICAgICAgICAgICAvLyBIYW5kbGUgYmFzZWQgb24gZHJhZyBzb3VyY2UgYW5kIHRhcmdldFxuICAgICAgICAgICAgaWYgKHRoaXMuZHJhZ1N0YXRlLmRyYWdTb3VyY2UgPT09ICdoZWFkZXInKSB7XG4gICAgICAgICAgICAgICAgLy8gSGVhZGVyIGl0ZW0gZHJhZyBlbmRcbiAgICAgICAgICAgICAgICB0aGlzLmhhbmRsZUhlYWRlckl0ZW1EcmFnRW5kKCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICAvLyBHcmlkIGV2ZW50IGRyYWcgZW5kXG4gICAgICAgICAgICAgICAgdGhpcy5oYW5kbGVHcmlkRXZlbnREcmFnRW5kKCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBDbGVhbnVwXG4gICAgICAgICAgICB0aGlzLmRyYWdTdGF0ZS5lbGVtZW50LmNsYXNzTGlzdC5yZW1vdmUoJ2RyYWdnaW5nJyk7XG4gICAgICAgICAgICB0aGlzLmRyYWdTdGF0ZSA9IG51bGw7XG4gICAgICAgICAgICB0aGlzLmluSGVhZGVyID0gZmFsc2U7XG4gICAgICAgIH07XG4gICAgICAgIHRoaXMuYW5pbWF0ZURyYWcgPSAoKSA9PiB7XG4gICAgICAgICAgICBpZiAoIXRoaXMuZHJhZ1N0YXRlKVxuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIGNvbnN0IGRpZmYgPSB0aGlzLmRyYWdTdGF0ZS50YXJnZXRZIC0gdGhpcy5kcmFnU3RhdGUuY3VycmVudFk7XG4gICAgICAgICAgICAvLyBTdG9wIGFuaW1hdGlvbiB3aGVuIGNsb3NlIGVub3VnaCB0byB0YXJnZXRcbiAgICAgICAgICAgIGlmIChNYXRoLmFicyhkaWZmKSA8PSAwLjUpIHtcbiAgICAgICAgICAgICAgICB0aGlzLmRyYWdTdGF0ZS5hbmltYXRpb25JZCA9IDA7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gSW50ZXJwb2xhdGUgdG93YXJkcyB0YXJnZXRcbiAgICAgICAgICAgIHRoaXMuZHJhZ1N0YXRlLmN1cnJlbnRZICs9IGRpZmYgKiB0aGlzLklOVEVSUE9MQVRJT05fRkFDVE9SO1xuICAgICAgICAgICAgLy8gVXBkYXRlIGVsZW1lbnQgcG9zaXRpb25cbiAgICAgICAgICAgIHRoaXMuZHJhZ1N0YXRlLmVsZW1lbnQuc3R5bGUudG9wID0gYCR7dGhpcy5kcmFnU3RhdGUuY3VycmVudFl9cHhgO1xuICAgICAgICAgICAgLy8gRW1pdCBkcmFnOm1vdmUgKG9ubHkgaWYgd2UgaGF2ZSBhIGNvbHVtbilcbiAgICAgICAgICAgIGlmICh0aGlzLmRyYWdTdGF0ZS5jb2x1bW5FbGVtZW50KSB7XG4gICAgICAgICAgICAgICAgY29uc3QgcGF5bG9hZCA9IHtcbiAgICAgICAgICAgICAgICAgICAgZXZlbnRJZDogdGhpcy5kcmFnU3RhdGUuZXZlbnRJZCxcbiAgICAgICAgICAgICAgICAgICAgZWxlbWVudDogdGhpcy5kcmFnU3RhdGUuZWxlbWVudCxcbiAgICAgICAgICAgICAgICAgICAgY3VycmVudFk6IHRoaXMuZHJhZ1N0YXRlLmN1cnJlbnRZLFxuICAgICAgICAgICAgICAgICAgICBjb2x1bW5FbGVtZW50OiB0aGlzLmRyYWdTdGF0ZS5jb2x1bW5FbGVtZW50XG4gICAgICAgICAgICAgICAgfTtcbiAgICAgICAgICAgICAgICB0aGlzLmV2ZW50QnVzLmVtaXQoQ29yZUV2ZW50cy5FVkVOVF9EUkFHX01PVkUsIHBheWxvYWQpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gQ29udGludWUgYW5pbWF0aW9uXG4gICAgICAgICAgICB0aGlzLmRyYWdTdGF0ZS5hbmltYXRpb25JZCA9IHJlcXVlc3RBbmltYXRpb25GcmFtZSh0aGlzLmFuaW1hdGVEcmFnKTtcbiAgICAgICAgfTtcbiAgICAgICAgdGhpcy5zZXR1cFNjcm9sbExpc3RlbmVyKCk7XG4gICAgfVxuICAgIHNldHVwU2Nyb2xsTGlzdGVuZXIoKSB7XG4gICAgICAgIHRoaXMuZXZlbnRCdXMub24oQ29yZUV2ZW50cy5FREdFX1NDUk9MTF9USUNLLCAoZSkgPT4ge1xuICAgICAgICAgICAgaWYgKCF0aGlzLmRyYWdTdGF0ZSlcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICBjb25zdCB7IHNjcm9sbERlbHRhIH0gPSBlLmRldGFpbDtcbiAgICAgICAgICAgIC8vIEVsZW1lbnQgc2thbCBmbHl0dGUgbWVkIHNjcm9sbCBmb3IgYXQgZm9yYmxpdmUgdW5kZXIgbXVzZW5cbiAgICAgICAgICAgIC8vIChlbGVtZW50ZXRzIHRvcCBlciByZWxhdGl2IHRpbCBrb2xvbm5lbiwgc29tIHNjcm9sbGVyIG1lZCB2aWV3cG9ydClcbiAgICAgICAgICAgIHRoaXMuZHJhZ1N0YXRlLnRhcmdldFkgKz0gc2Nyb2xsRGVsdGE7XG4gICAgICAgICAgICB0aGlzLmRyYWdTdGF0ZS5jdXJyZW50WSArPSBzY3JvbGxEZWx0YTtcbiAgICAgICAgICAgIHRoaXMuZHJhZ1N0YXRlLmVsZW1lbnQuc3R5bGUudG9wID0gYCR7dGhpcy5kcmFnU3RhdGUuY3VycmVudFl9cHhgO1xuICAgICAgICB9KTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogSW5pdGlhbGl6ZSBkcmFnLWRyb3Agb24gYSBjb250YWluZXIgZWxlbWVudFxuICAgICAqL1xuICAgIGluaXQoY29udGFpbmVyKSB7XG4gICAgICAgIHRoaXMuY29udGFpbmVyID0gY29udGFpbmVyO1xuICAgICAgICBjb250YWluZXIuYWRkRXZlbnRMaXN0ZW5lcigncG9pbnRlcmRvd24nLCB0aGlzLmhhbmRsZVBvaW50ZXJEb3duKTtcbiAgICAgICAgZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcigncG9pbnRlcm1vdmUnLCB0aGlzLmhhbmRsZVBvaW50ZXJNb3ZlKTtcbiAgICAgICAgZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcigncG9pbnRlcnVwJywgdGhpcy5oYW5kbGVQb2ludGVyVXApO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBIYW5kbGUgZHJhZyBlbmQgZm9yIGhlYWRlciBpdGVtc1xuICAgICAqL1xuICAgIGhhbmRsZUhlYWRlckl0ZW1EcmFnRW5kKCkge1xuICAgICAgICBpZiAoIXRoaXMuZHJhZ1N0YXRlKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAvLyBJZiBkcm9wcGVkIGluIGdyaWQgKG5vdCBpbiBoZWFkZXIpLCB0aGUgc3dwLWV2ZW50IHdhcyBhbHJlYWR5IGNyZWF0ZWRcbiAgICAgICAgLy8gYnkgRXZlbnRSZW5kZXJlciBsaXN0ZW5pbmcgdG8gRVZFTlRfRFJBR19MRUFWRV9IRUFERVJcbiAgICAgICAgLy8gSnVzdCBlbWl0IGRyYWc6ZW5kIGZvciBwZXJzaXN0ZW5jZVxuICAgICAgICBpZiAoIXRoaXMuaW5IZWFkZXIgJiYgdGhpcy5kcmFnU3RhdGUuY3VycmVudENvbHVtbikge1xuICAgICAgICAgICAgLy8gRHJvcHBlZCBpbiBncmlkIC0gZW1pdCBkcmFnOmVuZCB3aXRoIHRoZSBuZXcgc3dwLWV2ZW50IGVsZW1lbnRcbiAgICAgICAgICAgIGNvbnN0IGdyaWRFdmVudCA9IHRoaXMuZHJhZ1N0YXRlLmN1cnJlbnRDb2x1bW4ucXVlcnlTZWxlY3Rvcihgc3dwLWV2ZW50W2RhdGEtZXZlbnQtaWQ9XCIke3RoaXMuZHJhZ1N0YXRlLmV2ZW50SWR9XCJdYCk7XG4gICAgICAgICAgICBpZiAoZ3JpZEV2ZW50KSB7XG4gICAgICAgICAgICAgICAgY29uc3QgY29sdW1uS2V5ID0gdGhpcy5kcmFnU3RhdGUuY3VycmVudENvbHVtbi5kYXRhc2V0LmNvbHVtbktleSB8fCAnJztcbiAgICAgICAgICAgICAgICBjb25zdCBkYXRlID0gdGhpcy5kcmFnU3RhdGUuY3VycmVudENvbHVtbi5kYXRhc2V0LmRhdGUgfHwgJyc7XG4gICAgICAgICAgICAgICAgY29uc3Qgc3dwRXZlbnQgPSBTd3BFdmVudC5mcm9tRWxlbWVudChncmlkRXZlbnQsIGNvbHVtbktleSwgZGF0ZSwgdGhpcy5ncmlkQ29uZmlnKTtcbiAgICAgICAgICAgICAgICBjb25zdCBwYXlsb2FkID0ge1xuICAgICAgICAgICAgICAgICAgICBzd3BFdmVudCxcbiAgICAgICAgICAgICAgICAgICAgc291cmNlQ29sdW1uS2V5OiB0aGlzLmRyYWdTdGF0ZS5zb3VyY2VDb2x1bW5LZXksXG4gICAgICAgICAgICAgICAgICAgIHRhcmdldDogJ2dyaWQnXG4gICAgICAgICAgICAgICAgfTtcbiAgICAgICAgICAgICAgICB0aGlzLmV2ZW50QnVzLmVtaXQoQ29yZUV2ZW50cy5FVkVOVF9EUkFHX0VORCwgcGF5bG9hZCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgLy8gSWYgc3RpbGwgaW4gaGVhZGVyLCBubyBwZXJzaXN0ZW5jZSBuZWVkZWQgKHN0YXllZCBpbiBoZWFkZXIpXG4gICAgfVxuICAgIC8qKlxuICAgICAqIEhhbmRsZSBkcmFnIGVuZCBmb3IgZ3JpZCBldmVudHNcbiAgICAgKi9cbiAgICBoYW5kbGVHcmlkRXZlbnREcmFnRW5kKCkge1xuICAgICAgICBpZiAoIXRoaXMuZHJhZ1N0YXRlIHx8ICF0aGlzLmRyYWdTdGF0ZS5jb2x1bW5FbGVtZW50KVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAvLyBTbmFwIHRvIGdyaWRcbiAgICAgICAgY29uc3Qgc25hcHBlZFkgPSBzbmFwVG9HcmlkKHRoaXMuZHJhZ1N0YXRlLmN1cnJlbnRZLCB0aGlzLmdyaWRDb25maWcpO1xuICAgICAgICB0aGlzLmRyYWdTdGF0ZS5lbGVtZW50LnN0eWxlLnRvcCA9IGAke3NuYXBwZWRZfXB4YDtcbiAgICAgICAgLy8gUmVtb3ZlIGdob3N0XG4gICAgICAgIHRoaXMuZHJhZ1N0YXRlLmdob3N0RWxlbWVudD8ucmVtb3ZlKCk7XG4gICAgICAgIC8vIEdldCBjb2x1bW5LZXkgYW5kIGRhdGUgZnJvbSB0YXJnZXQgY29sdW1uXG4gICAgICAgIGNvbnN0IGNvbHVtbktleSA9IHRoaXMuZHJhZ1N0YXRlLmNvbHVtbkVsZW1lbnQuZGF0YXNldC5jb2x1bW5LZXkgfHwgJyc7XG4gICAgICAgIGNvbnN0IGRhdGUgPSB0aGlzLmRyYWdTdGF0ZS5jb2x1bW5FbGVtZW50LmRhdGFzZXQuZGF0ZSB8fCAnJztcbiAgICAgICAgLy8gQ3JlYXRlIFN3cEV2ZW50IGZyb20gZWxlbWVudCAocmVhZHMgdG9wL2hlaWdodC9ldmVudElkIGZyb20gZWxlbWVudClcbiAgICAgICAgY29uc3Qgc3dwRXZlbnQgPSBTd3BFdmVudC5mcm9tRWxlbWVudCh0aGlzLmRyYWdTdGF0ZS5lbGVtZW50LCBjb2x1bW5LZXksIGRhdGUsIHRoaXMuZ3JpZENvbmZpZyk7XG4gICAgICAgIC8vIEVtaXQgZHJhZzplbmRcbiAgICAgICAgY29uc3QgcGF5bG9hZCA9IHtcbiAgICAgICAgICAgIHN3cEV2ZW50LFxuICAgICAgICAgICAgc291cmNlQ29sdW1uS2V5OiB0aGlzLmRyYWdTdGF0ZS5zb3VyY2VDb2x1bW5LZXksXG4gICAgICAgICAgICB0YXJnZXQ6IHRoaXMuaW5IZWFkZXIgPyAnaGVhZGVyJyA6ICdncmlkJ1xuICAgICAgICB9O1xuICAgICAgICB0aGlzLmV2ZW50QnVzLmVtaXQoQ29yZUV2ZW50cy5FVkVOVF9EUkFHX0VORCwgcGF5bG9hZCk7XG4gICAgfVxuICAgIGluaXRpYWxpemVEcmFnKGVsZW1lbnQsIG1vdXNlT2Zmc2V0LCBlKSB7XG4gICAgICAgIGNvbnN0IGV2ZW50SWQgPSBlbGVtZW50LmRhdGFzZXQuZXZlbnRJZCB8fCAnJztcbiAgICAgICAgY29uc3QgaXNIZWFkZXJJdGVtID0gZWxlbWVudC50YWdOYW1lLnRvTG93ZXJDYXNlKCkgPT09ICdzd3AtaGVhZGVyLWl0ZW0nO1xuICAgICAgICBjb25zdCBjb2x1bW5FbGVtZW50ID0gZWxlbWVudC5jbG9zZXN0KCdzd3AtZGF5LWNvbHVtbicpO1xuICAgICAgICAvLyBGb3IgZ3JpZCBldmVudHMsIHdlIG5lZWQgYSBjb2x1bW5cbiAgICAgICAgaWYgKCFpc0hlYWRlckl0ZW0gJiYgIWNvbHVtbkVsZW1lbnQpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIGlmIChpc0hlYWRlckl0ZW0pIHtcbiAgICAgICAgICAgIC8vIEhlYWRlciBpdGVtIGRyYWcgaW5pdGlhbGl6YXRpb25cbiAgICAgICAgICAgIHRoaXMuaW5pdGlhbGl6ZUhlYWRlckl0ZW1EcmFnKGVsZW1lbnQsIG1vdXNlT2Zmc2V0LCBldmVudElkKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIC8vIEdyaWQgZXZlbnQgZHJhZyBpbml0aWFsaXphdGlvblxuICAgICAgICAgICAgdGhpcy5pbml0aWFsaXplR3JpZEV2ZW50RHJhZyhlbGVtZW50LCBtb3VzZU9mZnNldCwgZSwgY29sdW1uRWxlbWVudCwgZXZlbnRJZCk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgLyoqXG4gICAgICogSW5pdGlhbGl6ZSBkcmFnIGZvciBhIGhlYWRlciBpdGVtIChhbGxEYXkgZXZlbnQpXG4gICAgICovXG4gICAgaW5pdGlhbGl6ZUhlYWRlckl0ZW1EcmFnKGVsZW1lbnQsIG1vdXNlT2Zmc2V0LCBldmVudElkKSB7XG4gICAgICAgIC8vIE1hcmsgYXMgZHJhZ2dpbmdcbiAgICAgICAgZWxlbWVudC5jbGFzc0xpc3QuYWRkKCdkcmFnZ2luZycpO1xuICAgICAgICAvLyBJbml0aWFsaXplIGRyYWcgc3RhdGUgZm9yIGhlYWRlciBpdGVtXG4gICAgICAgIHRoaXMuZHJhZ1N0YXRlID0ge1xuICAgICAgICAgICAgZXZlbnRJZCxcbiAgICAgICAgICAgIGVsZW1lbnQsXG4gICAgICAgICAgICBnaG9zdEVsZW1lbnQ6IG51bGwsIC8vIE5vIGdob3N0IGZvciBoZWFkZXIgaXRlbXNcbiAgICAgICAgICAgIHN0YXJ0WTogMCxcbiAgICAgICAgICAgIG1vdXNlT2Zmc2V0LFxuICAgICAgICAgICAgY29sdW1uRWxlbWVudDogbnVsbCxcbiAgICAgICAgICAgIGN1cnJlbnRDb2x1bW46IG51bGwsXG4gICAgICAgICAgICB0YXJnZXRZOiAwLFxuICAgICAgICAgICAgY3VycmVudFk6IDAsXG4gICAgICAgICAgICBhbmltYXRpb25JZDogMCxcbiAgICAgICAgICAgIHNvdXJjZUNvbHVtbktleTogJycsIC8vIFdpbGwgYmUgc2V0IGZyb20gaGVhZGVyIGl0ZW0gZGF0YVxuICAgICAgICAgICAgZHJhZ1NvdXJjZTogJ2hlYWRlcidcbiAgICAgICAgfTtcbiAgICAgICAgLy8gU3RhcnQgaW4gaGVhZGVyIG1vZGVcbiAgICAgICAgdGhpcy5pbkhlYWRlciA9IHRydWU7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEluaXRpYWxpemUgZHJhZyBmb3IgYSBncmlkIGV2ZW50XG4gICAgICovXG4gICAgaW5pdGlhbGl6ZUdyaWRFdmVudERyYWcoZWxlbWVudCwgbW91c2VPZmZzZXQsIGUsIGNvbHVtbkVsZW1lbnQsIGV2ZW50SWQpIHtcbiAgICAgICAgLy8gQ2FsY3VsYXRlIGFic29sdXRlIFkgcG9zaXRpb24gdXNpbmcgZ2V0Qm91bmRpbmdDbGllbnRSZWN0XG4gICAgICAgIGNvbnN0IGVsZW1lbnRSZWN0ID0gZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcbiAgICAgICAgY29uc3QgY29sdW1uUmVjdCA9IGNvbHVtbkVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG4gICAgICAgIGNvbnN0IHN0YXJ0WSA9IGVsZW1lbnRSZWN0LnRvcCAtIGNvbHVtblJlY3QudG9wO1xuICAgICAgICAvLyBJZiBldmVudCBpcyBpbnNpZGUgYSBncm91cCwgbW92ZSBpdCB0byBldmVudHMtbGF5ZXIgZm9yIGNvcnJlY3QgcG9zaXRpb25pbmcgZHVyaW5nIGRyYWdcbiAgICAgICAgY29uc3QgZ3JvdXAgPSBlbGVtZW50LmNsb3Nlc3QoJ3N3cC1ldmVudC1ncm91cCcpO1xuICAgICAgICBpZiAoZ3JvdXApIHtcbiAgICAgICAgICAgIGNvbnN0IGV2ZW50c0xheWVyID0gY29sdW1uRWxlbWVudC5xdWVyeVNlbGVjdG9yKCdzd3AtZXZlbnRzLWxheWVyJyk7XG4gICAgICAgICAgICBpZiAoZXZlbnRzTGF5ZXIpIHtcbiAgICAgICAgICAgICAgICBldmVudHNMYXllci5hcHBlbmRDaGlsZChlbGVtZW50KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICAvLyBTZXQgY29uc2lzdGVudCBwb3NpdGlvbmluZyBmb3IgZHJhZyAod29ya3MgZm9yIGJvdGggZ3JvdXBlZCBhbmQgc3RhY2tlZCBldmVudHMpXG4gICAgICAgIGVsZW1lbnQuc3R5bGUucG9zaXRpb24gPSAnYWJzb2x1dGUnO1xuICAgICAgICBlbGVtZW50LnN0eWxlLnRvcCA9IGAke3N0YXJ0WX1weGA7XG4gICAgICAgIGVsZW1lbnQuc3R5bGUubGVmdCA9ICcycHgnO1xuICAgICAgICBlbGVtZW50LnN0eWxlLnJpZ2h0ID0gJzJweCc7XG4gICAgICAgIGVsZW1lbnQuc3R5bGUubWFyZ2luTGVmdCA9ICcwJzsgLy8gUmVzZXQgc3RhY2tpbmcgbWFyZ2luXG4gICAgICAgIC8vIENyZWF0ZSBnaG9zdCBjbG9uZVxuICAgICAgICBjb25zdCBnaG9zdEVsZW1lbnQgPSBlbGVtZW50LmNsb25lTm9kZSh0cnVlKTtcbiAgICAgICAgZ2hvc3RFbGVtZW50LmNsYXNzTGlzdC5hZGQoJ2RyYWctZ2hvc3QnKTtcbiAgICAgICAgZ2hvc3RFbGVtZW50LnN0eWxlLm9wYWNpdHkgPSAnMC4zJztcbiAgICAgICAgZ2hvc3RFbGVtZW50LnN0eWxlLnBvaW50ZXJFdmVudHMgPSAnbm9uZSc7XG4gICAgICAgIC8vIEluc2VydCBnaG9zdCBiZWZvcmUgb3JpZ2luYWxcbiAgICAgICAgZWxlbWVudC5wYXJlbnROb2RlPy5pbnNlcnRCZWZvcmUoZ2hvc3RFbGVtZW50LCBlbGVtZW50KTtcbiAgICAgICAgLy8gU2V0dXAgZWxlbWVudCBmb3IgZHJhZ2dpbmdcbiAgICAgICAgZWxlbWVudC5jbGFzc0xpc3QuYWRkKCdkcmFnZ2luZycpO1xuICAgICAgICAvLyBDYWxjdWxhdGUgaW5pdGlhbCB0YXJnZXQgZnJvbSBtb3VzZSBwb3NpdGlvblxuICAgICAgICBjb25zdCB0YXJnZXRZID0gZS5jbGllbnRZIC0gY29sdW1uUmVjdC50b3AgLSBtb3VzZU9mZnNldC55O1xuICAgICAgICAvLyBJbml0aWFsaXplIGRyYWcgc3RhdGVcbiAgICAgICAgdGhpcy5kcmFnU3RhdGUgPSB7XG4gICAgICAgICAgICBldmVudElkLFxuICAgICAgICAgICAgZWxlbWVudCxcbiAgICAgICAgICAgIGdob3N0RWxlbWVudCxcbiAgICAgICAgICAgIHN0YXJ0WSxcbiAgICAgICAgICAgIG1vdXNlT2Zmc2V0LFxuICAgICAgICAgICAgY29sdW1uRWxlbWVudCxcbiAgICAgICAgICAgIGN1cnJlbnRDb2x1bW46IGNvbHVtbkVsZW1lbnQsXG4gICAgICAgICAgICB0YXJnZXRZOiBNYXRoLm1heCgwLCB0YXJnZXRZKSxcbiAgICAgICAgICAgIGN1cnJlbnRZOiBzdGFydFksXG4gICAgICAgICAgICBhbmltYXRpb25JZDogMCxcbiAgICAgICAgICAgIHNvdXJjZUNvbHVtbktleTogY29sdW1uRWxlbWVudC5kYXRhc2V0LmNvbHVtbktleSB8fCAnJyxcbiAgICAgICAgICAgIGRyYWdTb3VyY2U6ICdncmlkJ1xuICAgICAgICB9O1xuICAgICAgICAvLyBFbWl0IGRyYWc6c3RhcnRcbiAgICAgICAgY29uc3QgcGF5bG9hZCA9IHtcbiAgICAgICAgICAgIGV2ZW50SWQsXG4gICAgICAgICAgICBlbGVtZW50LFxuICAgICAgICAgICAgZ2hvc3RFbGVtZW50LFxuICAgICAgICAgICAgc3RhcnRZLFxuICAgICAgICAgICAgbW91c2VPZmZzZXQsXG4gICAgICAgICAgICBjb2x1bW5FbGVtZW50XG4gICAgICAgIH07XG4gICAgICAgIHRoaXMuZXZlbnRCdXMuZW1pdChDb3JlRXZlbnRzLkVWRU5UX0RSQUdfU1RBUlQsIHBheWxvYWQpO1xuICAgICAgICAvLyBTdGFydCBhbmltYXRpb24gbG9vcFxuICAgICAgICB0aGlzLmFuaW1hdGVEcmFnKCk7XG4gICAgfVxuICAgIHVwZGF0ZURyYWdUYXJnZXQoZSkge1xuICAgICAgICBpZiAoIXRoaXMuZHJhZ1N0YXRlKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAvLyBDaGVjayBoZWFkZXIgem9uZSBmaXJzdFxuICAgICAgICB0aGlzLmNoZWNrSGVhZGVyWm9uZShlKTtcbiAgICAgICAgLy8gU2tpcCBub3JtYWwgZ3JpZCBoYW5kbGluZyBpZiBpbiBoZWFkZXJcbiAgICAgICAgaWYgKHRoaXMuaW5IZWFkZXIpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIC8vIENoZWNrIGZvciBjb2x1bW4gY2hhbmdlXG4gICAgICAgIGNvbnN0IGNvbHVtbkF0UG9pbnQgPSB0aGlzLmdldENvbHVtbkF0UG9pbnQoZS5jbGllbnRYKTtcbiAgICAgICAgLy8gRm9yIGhlYWRlciBpdGVtcyBlbnRlcmluZyBncmlkLCBzZXQgaW5pdGlhbCBjb2x1bW5cbiAgICAgICAgaWYgKHRoaXMuZHJhZ1N0YXRlLmRyYWdTb3VyY2UgPT09ICdoZWFkZXInICYmIGNvbHVtbkF0UG9pbnQgJiYgIXRoaXMuZHJhZ1N0YXRlLmN1cnJlbnRDb2x1bW4pIHtcbiAgICAgICAgICAgIHRoaXMuZHJhZ1N0YXRlLmN1cnJlbnRDb2x1bW4gPSBjb2x1bW5BdFBvaW50O1xuICAgICAgICAgICAgdGhpcy5kcmFnU3RhdGUuY29sdW1uRWxlbWVudCA9IGNvbHVtbkF0UG9pbnQ7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGNvbHVtbkF0UG9pbnQgJiYgY29sdW1uQXRQb2ludCAhPT0gdGhpcy5kcmFnU3RhdGUuY3VycmVudENvbHVtbiAmJiB0aGlzLmRyYWdTdGF0ZS5jdXJyZW50Q29sdW1uKSB7XG4gICAgICAgICAgICBjb25zdCBwYXlsb2FkID0ge1xuICAgICAgICAgICAgICAgIGV2ZW50SWQ6IHRoaXMuZHJhZ1N0YXRlLmV2ZW50SWQsXG4gICAgICAgICAgICAgICAgZWxlbWVudDogdGhpcy5kcmFnU3RhdGUuZWxlbWVudCxcbiAgICAgICAgICAgICAgICBwcmV2aW91c0NvbHVtbjogdGhpcy5kcmFnU3RhdGUuY3VycmVudENvbHVtbixcbiAgICAgICAgICAgICAgICBuZXdDb2x1bW46IGNvbHVtbkF0UG9pbnQsXG4gICAgICAgICAgICAgICAgY3VycmVudFk6IHRoaXMuZHJhZ1N0YXRlLmN1cnJlbnRZXG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgdGhpcy5ldmVudEJ1cy5lbWl0KENvcmVFdmVudHMuRVZFTlRfRFJBR19DT0xVTU5fQ0hBTkdFLCBwYXlsb2FkKTtcbiAgICAgICAgICAgIHRoaXMuZHJhZ1N0YXRlLmN1cnJlbnRDb2x1bW4gPSBjb2x1bW5BdFBvaW50O1xuICAgICAgICAgICAgdGhpcy5kcmFnU3RhdGUuY29sdW1uRWxlbWVudCA9IGNvbHVtbkF0UG9pbnQ7XG4gICAgICAgIH1cbiAgICAgICAgLy8gU2tpcCBncmlkIHBvc2l0aW9uIHVwZGF0ZXMgaWYgbm8gY29sdW1uIHlldFxuICAgICAgICBpZiAoIXRoaXMuZHJhZ1N0YXRlLmNvbHVtbkVsZW1lbnQpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIGNvbnN0IGNvbHVtblJlY3QgPSB0aGlzLmRyYWdTdGF0ZS5jb2x1bW5FbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgICAgICBjb25zdCB0YXJnZXRZID0gZS5jbGllbnRZIC0gY29sdW1uUmVjdC50b3AgLSB0aGlzLmRyYWdTdGF0ZS5tb3VzZU9mZnNldC55O1xuICAgICAgICB0aGlzLmRyYWdTdGF0ZS50YXJnZXRZID0gTWF0aC5tYXgoMCwgdGFyZ2V0WSk7XG4gICAgICAgIC8vIFN0YXJ0IGFuaW1hdGlvbiBpZiBub3QgcnVubmluZ1xuICAgICAgICBpZiAoIXRoaXMuZHJhZ1N0YXRlLmFuaW1hdGlvbklkKSB7XG4gICAgICAgICAgICB0aGlzLmFuaW1hdGVEcmFnKCk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgLyoqXG4gICAgICogQ2hlY2sgaWYgcG9pbnRlciBpcyBpbiBoZWFkZXIgem9uZSBhbmQgZW1pdCBhcHByb3ByaWF0ZSBldmVudHNcbiAgICAgKi9cbiAgICBjaGVja0hlYWRlclpvbmUoZSkge1xuICAgICAgICBpZiAoIXRoaXMuZHJhZ1N0YXRlKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICBjb25zdCBoZWFkZXJWaWV3cG9ydCA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJ3N3cC1oZWFkZXItdmlld3BvcnQnKTtcbiAgICAgICAgaWYgKCFoZWFkZXJWaWV3cG9ydClcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgY29uc3QgcmVjdCA9IGhlYWRlclZpZXdwb3J0LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgICAgICBjb25zdCBpc0luSGVhZGVyID0gZS5jbGllbnRZIDwgcmVjdC5ib3R0b207XG4gICAgICAgIGlmIChpc0luSGVhZGVyICYmICF0aGlzLmluSGVhZGVyKSB7XG4gICAgICAgICAgICAvLyBFbnRlcmVkIGhlYWRlciAoZnJvbSBncmlkKVxuICAgICAgICAgICAgdGhpcy5pbkhlYWRlciA9IHRydWU7XG4gICAgICAgICAgICBpZiAodGhpcy5kcmFnU3RhdGUuZHJhZ1NvdXJjZSA9PT0gJ2dyaWQnICYmIHRoaXMuZHJhZ1N0YXRlLmNvbHVtbkVsZW1lbnQpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBwYXlsb2FkID0ge1xuICAgICAgICAgICAgICAgICAgICBldmVudElkOiB0aGlzLmRyYWdTdGF0ZS5ldmVudElkLFxuICAgICAgICAgICAgICAgICAgICBlbGVtZW50OiB0aGlzLmRyYWdTdGF0ZS5lbGVtZW50LFxuICAgICAgICAgICAgICAgICAgICBzb3VyY2VDb2x1bW5JbmRleDogdGhpcy5nZXRDb2x1bW5JbmRleCh0aGlzLmRyYWdTdGF0ZS5jb2x1bW5FbGVtZW50KSxcbiAgICAgICAgICAgICAgICAgICAgc291cmNlQ29sdW1uS2V5OiB0aGlzLmRyYWdTdGF0ZS5jb2x1bW5FbGVtZW50LmRhdGFzZXQuY29sdW1uS2V5IHx8ICcnLFxuICAgICAgICAgICAgICAgICAgICB0aXRsZTogdGhpcy5kcmFnU3RhdGUuZWxlbWVudC5xdWVyeVNlbGVjdG9yKCdzd3AtZXZlbnQtdGl0bGUnKT8udGV4dENvbnRlbnQgfHwgJycsXG4gICAgICAgICAgICAgICAgICAgIGNvbG9yQ2xhc3M6IFsuLi50aGlzLmRyYWdTdGF0ZS5lbGVtZW50LmNsYXNzTGlzdF0uZmluZChjID0+IGMuc3RhcnRzV2l0aCgnaXMtJykpLFxuICAgICAgICAgICAgICAgICAgICBpdGVtVHlwZTogJ2V2ZW50JyxcbiAgICAgICAgICAgICAgICAgICAgZHVyYXRpb246IDFcbiAgICAgICAgICAgICAgICB9O1xuICAgICAgICAgICAgICAgIHRoaXMuZXZlbnRCdXMuZW1pdChDb3JlRXZlbnRzLkVWRU5UX0RSQUdfRU5URVJfSEVBREVSLCBwYXlsb2FkKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIEZvciBoZWFkZXIgc291cmNlIHJlLWVudGVyaW5nIGhlYWRlciwganVzdCB1cGRhdGUgaW5IZWFkZXIgZmxhZ1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKCFpc0luSGVhZGVyICYmIHRoaXMuaW5IZWFkZXIpIHtcbiAgICAgICAgICAgIC8vIExlZnQgaGVhZGVyIChlbnRlcmluZyBncmlkKVxuICAgICAgICAgICAgdGhpcy5pbkhlYWRlciA9IGZhbHNlO1xuICAgICAgICAgICAgY29uc3QgdGFyZ2V0Q29sdW1uID0gdGhpcy5nZXRDb2x1bW5BdFBvaW50KGUuY2xpZW50WCk7XG4gICAgICAgICAgICBpZiAodGhpcy5kcmFnU3RhdGUuZHJhZ1NvdXJjZSA9PT0gJ2hlYWRlcicpIHtcbiAgICAgICAgICAgICAgICAvLyBIZWFkZXIgaXRlbSBsZWF2aW5nIGhlYWRlciBcdTIxOTIgY3JlYXRlIHN3cC1ldmVudCBpbiBncmlkXG4gICAgICAgICAgICAgICAgY29uc3QgcGF5bG9hZCA9IHtcbiAgICAgICAgICAgICAgICAgICAgZXZlbnRJZDogdGhpcy5kcmFnU3RhdGUuZXZlbnRJZCxcbiAgICAgICAgICAgICAgICAgICAgc291cmNlOiAnaGVhZGVyJyxcbiAgICAgICAgICAgICAgICAgICAgZWxlbWVudDogdGhpcy5kcmFnU3RhdGUuZWxlbWVudCxcbiAgICAgICAgICAgICAgICAgICAgdGFyZ2V0Q29sdW1uOiB0YXJnZXRDb2x1bW4gfHwgdW5kZWZpbmVkLFxuICAgICAgICAgICAgICAgICAgICBzdGFydDogdGhpcy5kcmFnU3RhdGUuZWxlbWVudC5kYXRhc2V0LnN0YXJ0ID8gbmV3IERhdGUodGhpcy5kcmFnU3RhdGUuZWxlbWVudC5kYXRhc2V0LnN0YXJ0KSA6IHVuZGVmaW5lZCxcbiAgICAgICAgICAgICAgICAgICAgZW5kOiB0aGlzLmRyYWdTdGF0ZS5lbGVtZW50LmRhdGFzZXQuZW5kID8gbmV3IERhdGUodGhpcy5kcmFnU3RhdGUuZWxlbWVudC5kYXRhc2V0LmVuZCkgOiB1bmRlZmluZWQsXG4gICAgICAgICAgICAgICAgICAgIHRpdGxlOiB0aGlzLmRyYWdTdGF0ZS5lbGVtZW50LnRleHRDb250ZW50IHx8ICcnLFxuICAgICAgICAgICAgICAgICAgICBjb2xvckNsYXNzOiBbLi4udGhpcy5kcmFnU3RhdGUuZWxlbWVudC5jbGFzc0xpc3RdLmZpbmQoYyA9PiBjLnN0YXJ0c1dpdGgoJ2lzLScpKVxuICAgICAgICAgICAgICAgIH07XG4gICAgICAgICAgICAgICAgdGhpcy5ldmVudEJ1cy5lbWl0KENvcmVFdmVudHMuRVZFTlRfRFJBR19MRUFWRV9IRUFERVIsIHBheWxvYWQpO1xuICAgICAgICAgICAgICAgIC8vIFJlLWF0dGFjaCB0byB0aGUgbmV3IHN3cC1ldmVudCBjcmVhdGVkIGJ5IEV2ZW50UmVuZGVyZXJcbiAgICAgICAgICAgICAgICBpZiAodGFyZ2V0Q29sdW1uKSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IG5ld0VsZW1lbnQgPSB0YXJnZXRDb2x1bW4ucXVlcnlTZWxlY3Rvcihgc3dwLWV2ZW50W2RhdGEtZXZlbnQtaWQ9XCIke3RoaXMuZHJhZ1N0YXRlLmV2ZW50SWR9XCJdYCk7XG4gICAgICAgICAgICAgICAgICAgIGlmIChuZXdFbGVtZW50KSB7XG4gICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmRyYWdTdGF0ZS5lbGVtZW50ID0gbmV3RWxlbWVudDtcbiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuZHJhZ1N0YXRlLmNvbHVtbkVsZW1lbnQgPSB0YXJnZXRDb2x1bW47XG4gICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmRyYWdTdGF0ZS5jdXJyZW50Q29sdW1uID0gdGFyZ2V0Q29sdW1uO1xuICAgICAgICAgICAgICAgICAgICAgICAgLy8gU3RhcnQgYW5pbWF0aW9uIGZvciB0aGUgbmV3IGVsZW1lbnRcbiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuYW5pbWF0ZURyYWcoKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIC8vIEdyaWQgZXZlbnQgbGVhdmluZyBoZWFkZXIgXHUyMTkyIHJlc3RvcmUgdG8gZ3JpZFxuICAgICAgICAgICAgICAgIGNvbnN0IHBheWxvYWQgPSB7XG4gICAgICAgICAgICAgICAgICAgIGV2ZW50SWQ6IHRoaXMuZHJhZ1N0YXRlLmV2ZW50SWQsXG4gICAgICAgICAgICAgICAgICAgIHNvdXJjZTogJ2dyaWQnXG4gICAgICAgICAgICAgICAgfTtcbiAgICAgICAgICAgICAgICB0aGlzLmV2ZW50QnVzLmVtaXQoQ29yZUV2ZW50cy5FVkVOVF9EUkFHX0xFQVZFX0hFQURFUiwgcGF5bG9hZCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAoaXNJbkhlYWRlcikge1xuICAgICAgICAgICAgLy8gTW92aW5nIHdpdGhpbiBoZWFkZXJcbiAgICAgICAgICAgIGNvbnN0IGNvbHVtbiA9IHRoaXMuZ2V0Q29sdW1uQXRYKGUuY2xpZW50WCk7XG4gICAgICAgICAgICBpZiAoY29sdW1uKSB7XG4gICAgICAgICAgICAgICAgY29uc3QgcGF5bG9hZCA9IHtcbiAgICAgICAgICAgICAgICAgICAgZXZlbnRJZDogdGhpcy5kcmFnU3RhdGUuZXZlbnRJZCxcbiAgICAgICAgICAgICAgICAgICAgY29sdW1uSW5kZXg6IHRoaXMuZ2V0Q29sdW1uSW5kZXgoY29sdW1uKSxcbiAgICAgICAgICAgICAgICAgICAgY29sdW1uS2V5OiBjb2x1bW4uZGF0YXNldC5jb2x1bW5LZXkgfHwgJydcbiAgICAgICAgICAgICAgICB9O1xuICAgICAgICAgICAgICAgIHRoaXMuZXZlbnRCdXMuZW1pdChDb3JlRXZlbnRzLkVWRU5UX0RSQUdfTU9WRV9IRUFERVIsIHBheWxvYWQpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCBjb2x1bW4gaW5kZXggKDAtYmFzZWQpIGZvciBhIGNvbHVtbiBlbGVtZW50XG4gICAgICovXG4gICAgZ2V0Q29sdW1uSW5kZXgoY29sdW1uKSB7XG4gICAgICAgIGlmICghdGhpcy5jb250YWluZXIgfHwgIWNvbHVtbilcbiAgICAgICAgICAgIHJldHVybiAwO1xuICAgICAgICBjb25zdCBjb2x1bW5zID0gQXJyYXkuZnJvbSh0aGlzLmNvbnRhaW5lci5xdWVyeVNlbGVjdG9yQWxsKCdzd3AtZGF5LWNvbHVtbicpKTtcbiAgICAgICAgcmV0dXJuIGNvbHVtbnMuaW5kZXhPZihjb2x1bW4pO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgY29sdW1uIGF0IFggY29vcmRpbmF0ZSAoYWxpYXMgZm9yIGdldENvbHVtbkF0UG9pbnQpXG4gICAgICovXG4gICAgZ2V0Q29sdW1uQXRYKGNsaWVudFgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuZ2V0Q29sdW1uQXRQb2ludChjbGllbnRYKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogRmluZCBjb2x1bW4gZWxlbWVudCBhdCBnaXZlbiBYIGNvb3JkaW5hdGVcbiAgICAgKi9cbiAgICBnZXRDb2x1bW5BdFBvaW50KGNsaWVudFgpIHtcbiAgICAgICAgaWYgKCF0aGlzLmNvbnRhaW5lcilcbiAgICAgICAgICAgIHJldHVybiBudWxsO1xuICAgICAgICBjb25zdCBjb2x1bW5zID0gdGhpcy5jb250YWluZXIucXVlcnlTZWxlY3RvckFsbCgnc3dwLWRheS1jb2x1bW4nKTtcbiAgICAgICAgZm9yIChjb25zdCBjb2wgb2YgY29sdW1ucykge1xuICAgICAgICAgICAgY29uc3QgcmVjdCA9IGNvbC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcbiAgICAgICAgICAgIGlmIChjbGllbnRYID49IHJlY3QubGVmdCAmJiBjbGllbnRYIDw9IHJlY3QucmlnaHQpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gY29sO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiBudWxsO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBDYW5jZWwgZHJhZyBhbmQgYW5pbWF0ZSBiYWNrIHRvIHN0YXJ0IHBvc2l0aW9uXG4gICAgICovXG4gICAgY2FuY2VsRHJhZygpIHtcbiAgICAgICAgaWYgKCF0aGlzLmRyYWdTdGF0ZSlcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgLy8gU3RvcCBhbmltYXRpb25cbiAgICAgICAgY2FuY2VsQW5pbWF0aW9uRnJhbWUodGhpcy5kcmFnU3RhdGUuYW5pbWF0aW9uSWQpO1xuICAgICAgICBjb25zdCB7IGVsZW1lbnQsIGdob3N0RWxlbWVudCwgc3RhcnRZLCBldmVudElkIH0gPSB0aGlzLmRyYWdTdGF0ZTtcbiAgICAgICAgLy8gQW5pbWF0ZSBiYWNrIHRvIHN0YXJ0XG4gICAgICAgIGVsZW1lbnQuc3R5bGUudHJhbnNpdGlvbiA9ICd0b3AgMjAwbXMgZWFzZS1vdXQnO1xuICAgICAgICBlbGVtZW50LnN0eWxlLnRvcCA9IGAke3N0YXJ0WX1weGA7XG4gICAgICAgIC8vIFJlbW92ZSBnaG9zdCBhZnRlciBhbmltYXRpb24gKGlmIGV4aXN0cylcbiAgICAgICAgc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICAgICAgICBnaG9zdEVsZW1lbnQ/LnJlbW92ZSgpO1xuICAgICAgICAgICAgZWxlbWVudC5zdHlsZS50cmFuc2l0aW9uID0gJyc7XG4gICAgICAgICAgICBlbGVtZW50LmNsYXNzTGlzdC5yZW1vdmUoJ2RyYWdnaW5nJyk7XG4gICAgICAgIH0sIDIwMCk7XG4gICAgICAgIC8vIEVtaXQgZHJhZzpjYW5jZWxcbiAgICAgICAgY29uc3QgcGF5bG9hZCA9IHtcbiAgICAgICAgICAgIGV2ZW50SWQsXG4gICAgICAgICAgICBlbGVtZW50LFxuICAgICAgICAgICAgc3RhcnRZXG4gICAgICAgIH07XG4gICAgICAgIHRoaXMuZXZlbnRCdXMuZW1pdChDb3JlRXZlbnRzLkVWRU5UX0RSQUdfQ0FOQ0VMLCBwYXlsb2FkKTtcbiAgICAgICAgdGhpcy5kcmFnU3RhdGUgPSBudWxsO1xuICAgICAgICB0aGlzLmluSGVhZGVyID0gZmFsc2U7XG4gICAgfVxufVxuIiwgImltcG9ydCB7IENvcmVFdmVudHMgfSBmcm9tICcuLi9jb25zdGFudHMvQ29yZUV2ZW50cyc7XG5leHBvcnQgY2xhc3MgRWRnZVNjcm9sbE1hbmFnZXIge1xuICAgIGNvbnN0cnVjdG9yKGV2ZW50QnVzKSB7XG4gICAgICAgIHRoaXMuZXZlbnRCdXMgPSBldmVudEJ1cztcbiAgICAgICAgdGhpcy5zY3JvbGxhYmxlQ29udGVudCA9IG51bGw7XG4gICAgICAgIHRoaXMudGltZUdyaWQgPSBudWxsO1xuICAgICAgICB0aGlzLmRyYWdnZWRFbGVtZW50ID0gbnVsbDtcbiAgICAgICAgdGhpcy5zY3JvbGxSQUYgPSBudWxsO1xuICAgICAgICB0aGlzLm1vdXNlWSA9IDA7XG4gICAgICAgIHRoaXMuaXNEcmFnZ2luZyA9IGZhbHNlO1xuICAgICAgICB0aGlzLmlzU2Nyb2xsaW5nID0gZmFsc2U7XG4gICAgICAgIHRoaXMubGFzdFRzID0gMDtcbiAgICAgICAgdGhpcy5yZWN0ID0gbnVsbDtcbiAgICAgICAgdGhpcy5pbml0aWFsU2Nyb2xsVG9wID0gMDtcbiAgICAgICAgdGhpcy5PVVRFUl9aT05FID0gMTAwO1xuICAgICAgICB0aGlzLklOTkVSX1pPTkUgPSA1MDtcbiAgICAgICAgdGhpcy5TTE9XX1NQRUVEID0gMTQwO1xuICAgICAgICB0aGlzLkZBU1RfU1BFRUQgPSA2NDA7XG4gICAgICAgIHRoaXMudHJhY2tNb3VzZSA9IChlKSA9PiB7XG4gICAgICAgICAgICBpZiAodGhpcy5pc0RyYWdnaW5nKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5tb3VzZVkgPSBlLmNsaWVudFk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH07XG4gICAgICAgIHRoaXMuc2Nyb2xsVGljayA9ICh0cykgPT4ge1xuICAgICAgICAgICAgaWYgKCF0aGlzLmlzRHJhZ2dpbmcgfHwgIXRoaXMuc2Nyb2xsYWJsZUNvbnRlbnQpXG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgY29uc3QgZHQgPSB0aGlzLmxhc3RUcyA/ICh0cyAtIHRoaXMubGFzdFRzKSAvIDEwMDAgOiAwO1xuICAgICAgICAgICAgdGhpcy5sYXN0VHMgPSB0cztcbiAgICAgICAgICAgIHRoaXMucmVjdCA/PyAodGhpcy5yZWN0ID0gdGhpcy5zY3JvbGxhYmxlQ29udGVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKSk7XG4gICAgICAgICAgICBjb25zdCB2ZWxvY2l0eSA9IHRoaXMuY2FsY3VsYXRlVmVsb2NpdHkoKTtcbiAgICAgICAgICAgIGlmICh2ZWxvY2l0eSAhPT0gMCAmJiAhdGhpcy5pc0F0Qm91bmRhcnkodmVsb2NpdHkpKSB7XG4gICAgICAgICAgICAgICAgY29uc3Qgc2Nyb2xsRGVsdGEgPSB2ZWxvY2l0eSAqIGR0O1xuICAgICAgICAgICAgICAgIHRoaXMuc2Nyb2xsYWJsZUNvbnRlbnQuc2Nyb2xsVG9wICs9IHNjcm9sbERlbHRhO1xuICAgICAgICAgICAgICAgIHRoaXMucmVjdCA9IG51bGw7XG4gICAgICAgICAgICAgICAgdGhpcy5ldmVudEJ1cy5lbWl0KENvcmVFdmVudHMuRURHRV9TQ1JPTExfVElDSywgeyBzY3JvbGxEZWx0YSB9KTtcbiAgICAgICAgICAgICAgICB0aGlzLnNldFNjcm9sbGluZ1N0YXRlKHRydWUpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgdGhpcy5zZXRTY3JvbGxpbmdTdGF0ZShmYWxzZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICB0aGlzLnNjcm9sbFJBRiA9IHJlcXVlc3RBbmltYXRpb25GcmFtZSh0aGlzLnNjcm9sbFRpY2spO1xuICAgICAgICB9O1xuICAgICAgICB0aGlzLnN1YnNjcmliZVRvRXZlbnRzKCk7XG4gICAgICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ3BvaW50ZXJtb3ZlJywgdGhpcy50cmFja01vdXNlKTtcbiAgICB9XG4gICAgaW5pdChzY3JvbGxhYmxlQ29udGVudCkge1xuICAgICAgICB0aGlzLnNjcm9sbGFibGVDb250ZW50ID0gc2Nyb2xsYWJsZUNvbnRlbnQ7XG4gICAgICAgIHRoaXMudGltZUdyaWQgPSBzY3JvbGxhYmxlQ29udGVudC5xdWVyeVNlbGVjdG9yKCdzd3AtdGltZS1ncmlkJyk7XG4gICAgICAgIHRoaXMuc2Nyb2xsYWJsZUNvbnRlbnQuc3R5bGUuc2Nyb2xsQmVoYXZpb3IgPSAnYXV0byc7XG4gICAgfVxuICAgIHN1YnNjcmliZVRvRXZlbnRzKCkge1xuICAgICAgICB0aGlzLmV2ZW50QnVzLm9uKENvcmVFdmVudHMuRVZFTlRfRFJBR19TVEFSVCwgKGV2ZW50KSA9PiB7XG4gICAgICAgICAgICBjb25zdCBwYXlsb2FkID0gZXZlbnQuZGV0YWlsO1xuICAgICAgICAgICAgdGhpcy5kcmFnZ2VkRWxlbWVudCA9IHBheWxvYWQuZWxlbWVudDtcbiAgICAgICAgICAgIHRoaXMuc3RhcnREcmFnKCk7XG4gICAgICAgIH0pO1xuICAgICAgICB0aGlzLmV2ZW50QnVzLm9uKENvcmVFdmVudHMuRVZFTlRfRFJBR19FTkQsICgpID0+IHRoaXMuc3RvcERyYWcoKSk7XG4gICAgICAgIHRoaXMuZXZlbnRCdXMub24oQ29yZUV2ZW50cy5FVkVOVF9EUkFHX0NBTkNFTCwgKCkgPT4gdGhpcy5zdG9wRHJhZygpKTtcbiAgICB9XG4gICAgc3RhcnREcmFnKCkge1xuICAgICAgICB0aGlzLmlzRHJhZ2dpbmcgPSB0cnVlO1xuICAgICAgICB0aGlzLmlzU2Nyb2xsaW5nID0gZmFsc2U7XG4gICAgICAgIHRoaXMubGFzdFRzID0gMDtcbiAgICAgICAgdGhpcy5pbml0aWFsU2Nyb2xsVG9wID0gdGhpcy5zY3JvbGxhYmxlQ29udGVudD8uc2Nyb2xsVG9wID8/IDA7XG4gICAgICAgIGlmICh0aGlzLnNjcm9sbFJBRiA9PT0gbnVsbCkge1xuICAgICAgICAgICAgdGhpcy5zY3JvbGxSQUYgPSByZXF1ZXN0QW5pbWF0aW9uRnJhbWUodGhpcy5zY3JvbGxUaWNrKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBzdG9wRHJhZygpIHtcbiAgICAgICAgdGhpcy5pc0RyYWdnaW5nID0gZmFsc2U7XG4gICAgICAgIHRoaXMuc2V0U2Nyb2xsaW5nU3RhdGUoZmFsc2UpO1xuICAgICAgICBpZiAodGhpcy5zY3JvbGxSQUYgIT09IG51bGwpIHtcbiAgICAgICAgICAgIGNhbmNlbEFuaW1hdGlvbkZyYW1lKHRoaXMuc2Nyb2xsUkFGKTtcbiAgICAgICAgICAgIHRoaXMuc2Nyb2xsUkFGID0gbnVsbDtcbiAgICAgICAgfVxuICAgICAgICB0aGlzLnJlY3QgPSBudWxsO1xuICAgICAgICB0aGlzLmxhc3RUcyA9IDA7XG4gICAgICAgIHRoaXMuaW5pdGlhbFNjcm9sbFRvcCA9IDA7XG4gICAgfVxuICAgIGNhbGN1bGF0ZVZlbG9jaXR5KCkge1xuICAgICAgICBpZiAoIXRoaXMucmVjdClcbiAgICAgICAgICAgIHJldHVybiAwO1xuICAgICAgICBjb25zdCBkaXN0VG9wID0gdGhpcy5tb3VzZVkgLSB0aGlzLnJlY3QudG9wO1xuICAgICAgICBjb25zdCBkaXN0Qm90ID0gdGhpcy5yZWN0LmJvdHRvbSAtIHRoaXMubW91c2VZO1xuICAgICAgICBpZiAoZGlzdFRvcCA8IHRoaXMuSU5ORVJfWk9ORSlcbiAgICAgICAgICAgIHJldHVybiAtdGhpcy5GQVNUX1NQRUVEO1xuICAgICAgICBpZiAoZGlzdFRvcCA8IHRoaXMuT1VURVJfWk9ORSlcbiAgICAgICAgICAgIHJldHVybiAtdGhpcy5TTE9XX1NQRUVEO1xuICAgICAgICBpZiAoZGlzdEJvdCA8IHRoaXMuSU5ORVJfWk9ORSlcbiAgICAgICAgICAgIHJldHVybiB0aGlzLkZBU1RfU1BFRUQ7XG4gICAgICAgIGlmIChkaXN0Qm90IDwgdGhpcy5PVVRFUl9aT05FKVxuICAgICAgICAgICAgcmV0dXJuIHRoaXMuU0xPV19TUEVFRDtcbiAgICAgICAgcmV0dXJuIDA7XG4gICAgfVxuICAgIGlzQXRCb3VuZGFyeSh2ZWxvY2l0eSkge1xuICAgICAgICBpZiAoIXRoaXMuc2Nyb2xsYWJsZUNvbnRlbnQgfHwgIXRoaXMudGltZUdyaWQgfHwgIXRoaXMuZHJhZ2dlZEVsZW1lbnQpXG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIGNvbnN0IGF0VG9wID0gdGhpcy5zY3JvbGxhYmxlQ29udGVudC5zY3JvbGxUb3AgPD0gMCAmJiB2ZWxvY2l0eSA8IDA7XG4gICAgICAgIGNvbnN0IGF0Qm90dG9tID0gdmVsb2NpdHkgPiAwICYmXG4gICAgICAgICAgICB0aGlzLmRyYWdnZWRFbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLmJvdHRvbSA+PVxuICAgICAgICAgICAgICAgIHRoaXMudGltZUdyaWQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkuYm90dG9tO1xuICAgICAgICByZXR1cm4gYXRUb3AgfHwgYXRCb3R0b207XG4gICAgfVxuICAgIHNldFNjcm9sbGluZ1N0YXRlKHNjcm9sbGluZykge1xuICAgICAgICBpZiAodGhpcy5pc1Njcm9sbGluZyA9PT0gc2Nyb2xsaW5nKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB0aGlzLmlzU2Nyb2xsaW5nID0gc2Nyb2xsaW5nO1xuICAgICAgICBpZiAoc2Nyb2xsaW5nKSB7XG4gICAgICAgICAgICB0aGlzLmV2ZW50QnVzLmVtaXQoQ29yZUV2ZW50cy5FREdFX1NDUk9MTF9TVEFSVEVELCB7fSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICB0aGlzLmluaXRpYWxTY3JvbGxUb3AgPSB0aGlzLnNjcm9sbGFibGVDb250ZW50Py5zY3JvbGxUb3AgPz8gMDtcbiAgICAgICAgICAgIHRoaXMuZXZlbnRCdXMuZW1pdChDb3JlRXZlbnRzLkVER0VfU0NST0xMX1NUT1BQRUQsIHt9KTtcbiAgICAgICAgfVxuICAgIH1cbn1cbiIsICJpbXBvcnQgeyBwaXhlbHNUb01pbnV0ZXMsIG1pbnV0ZXNUb1BpeGVscywgc25hcFRvR3JpZCB9IGZyb20gJy4uL3V0aWxzL1Bvc2l0aW9uVXRpbHMnO1xuaW1wb3J0IHsgQ29yZUV2ZW50cyB9IGZyb20gJy4uL2NvbnN0YW50cy9Db3JlRXZlbnRzJztcbmltcG9ydCB7IFN3cEV2ZW50IH0gZnJvbSAnLi4vdHlwZXMvU3dwRXZlbnQnO1xuZXhwb3J0IGNsYXNzIFJlc2l6ZU1hbmFnZXIge1xuICAgIGNvbnN0cnVjdG9yKGV2ZW50QnVzLCBncmlkQ29uZmlnLCBkYXRlU2VydmljZSkge1xuICAgICAgICB0aGlzLmV2ZW50QnVzID0gZXZlbnRCdXM7XG4gICAgICAgIHRoaXMuZ3JpZENvbmZpZyA9IGdyaWRDb25maWc7XG4gICAgICAgIHRoaXMuZGF0ZVNlcnZpY2UgPSBkYXRlU2VydmljZTtcbiAgICAgICAgdGhpcy5jb250YWluZXIgPSBudWxsO1xuICAgICAgICB0aGlzLnJlc2l6ZVN0YXRlID0gbnVsbDtcbiAgICAgICAgdGhpcy5aX0lOREVYX1JFU0laSU5HID0gJzEwMDAnO1xuICAgICAgICB0aGlzLkFOSU1BVElPTl9TUEVFRCA9IDAuMzU7XG4gICAgICAgIHRoaXMuTUlOX0hFSUdIVF9NSU5VVEVTID0gMTU7XG4gICAgICAgIC8qKlxuICAgICAgICAgKiBIYW5kbGUgbW91c2VvdmVyIC0gY3JlYXRlIHJlc2l6ZSBoYW5kbGUgaWYgbm90IGV4aXN0c1xuICAgICAgICAgKi9cbiAgICAgICAgdGhpcy5oYW5kbGVNb3VzZU92ZXIgPSAoZSkgPT4ge1xuICAgICAgICAgICAgY29uc3QgdGFyZ2V0ID0gZS50YXJnZXQ7XG4gICAgICAgICAgICBjb25zdCBldmVudEVsZW1lbnQgPSB0YXJnZXQuY2xvc2VzdCgnc3dwLWV2ZW50Jyk7XG4gICAgICAgICAgICBpZiAoIWV2ZW50RWxlbWVudCB8fCB0aGlzLnJlc2l6ZVN0YXRlKVxuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIC8vIENoZWNrIGlmIGhhbmRsZSBhbHJlYWR5IGV4aXN0c1xuICAgICAgICAgICAgaWYgKCFldmVudEVsZW1lbnQucXVlcnlTZWxlY3RvcignOnNjb3BlID4gc3dwLXJlc2l6ZS1oYW5kbGUnKSkge1xuICAgICAgICAgICAgICAgIGNvbnN0IGhhbmRsZSA9IHRoaXMuY3JlYXRlUmVzaXplSGFuZGxlKCk7XG4gICAgICAgICAgICAgICAgZXZlbnRFbGVtZW50LmFwcGVuZENoaWxkKGhhbmRsZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH07XG4gICAgICAgIC8qKlxuICAgICAgICAgKiBIYW5kbGUgcG9pbnRlcmRvd24gLSBzdGFydCByZXNpemUgaWYgb24gaGFuZGxlXG4gICAgICAgICAqL1xuICAgICAgICB0aGlzLmhhbmRsZVBvaW50ZXJEb3duID0gKGUpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IGhhbmRsZSA9IGUudGFyZ2V0LmNsb3Nlc3QoJ3N3cC1yZXNpemUtaGFuZGxlJyk7XG4gICAgICAgICAgICBpZiAoIWhhbmRsZSlcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICBjb25zdCBlbGVtZW50ID0gaGFuZGxlLnBhcmVudEVsZW1lbnQ7XG4gICAgICAgICAgICBpZiAoIWVsZW1lbnQpXG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgY29uc3QgZXZlbnRJZCA9IGVsZW1lbnQuZGF0YXNldC5ldmVudElkIHx8ICcnO1xuICAgICAgICAgICAgY29uc3Qgc3RhcnRIZWlnaHQgPSBlbGVtZW50Lm9mZnNldEhlaWdodDtcbiAgICAgICAgICAgIGNvbnN0IHN0YXJ0RHVyYXRpb25NaW51dGVzID0gcGl4ZWxzVG9NaW51dGVzKHN0YXJ0SGVpZ2h0LCB0aGlzLmdyaWRDb25maWcpO1xuICAgICAgICAgICAgLy8gU3RvcmUgcHJldmlvdXMgei1pbmRleFxuICAgICAgICAgICAgY29uc3QgY29udGFpbmVyID0gZWxlbWVudC5jbG9zZXN0KCdzd3AtZXZlbnQtZ3JvdXAnKSA/PyBlbGVtZW50O1xuICAgICAgICAgICAgY29uc3QgcHJldlpJbmRleCA9IGNvbnRhaW5lci5zdHlsZS56SW5kZXg7XG4gICAgICAgICAgICAvLyBTZXQgcmVzaXplIHN0YXRlXG4gICAgICAgICAgICB0aGlzLnJlc2l6ZVN0YXRlID0ge1xuICAgICAgICAgICAgICAgIGV2ZW50SWQsXG4gICAgICAgICAgICAgICAgZWxlbWVudCxcbiAgICAgICAgICAgICAgICBoYW5kbGVFbGVtZW50OiBoYW5kbGUsXG4gICAgICAgICAgICAgICAgc3RhcnRZOiBlLmNsaWVudFksXG4gICAgICAgICAgICAgICAgc3RhcnRIZWlnaHQsXG4gICAgICAgICAgICAgICAgc3RhcnREdXJhdGlvbk1pbnV0ZXMsXG4gICAgICAgICAgICAgICAgcG9pbnRlcklkOiBlLnBvaW50ZXJJZCxcbiAgICAgICAgICAgICAgICBwcmV2WkluZGV4LFxuICAgICAgICAgICAgICAgIC8vIEFuaW1hdGlvbiBzdGF0ZVxuICAgICAgICAgICAgICAgIGN1cnJlbnRIZWlnaHQ6IHN0YXJ0SGVpZ2h0LFxuICAgICAgICAgICAgICAgIHRhcmdldEhlaWdodDogc3RhcnRIZWlnaHQsXG4gICAgICAgICAgICAgICAgYW5pbWF0aW9uSWQ6IG51bGxcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICAvLyBFbGV2YXRlIHotaW5kZXhcbiAgICAgICAgICAgIGNvbnRhaW5lci5zdHlsZS56SW5kZXggPSB0aGlzLlpfSU5ERVhfUkVTSVpJTkc7XG4gICAgICAgICAgICAvLyBDYXB0dXJlIHBvaW50ZXIgZm9yIHNtb290aCB0cmFja2luZ1xuICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICBoYW5kbGUuc2V0UG9pbnRlckNhcHR1cmUoZS5wb2ludGVySWQpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY2F0Y2ggKGVycikge1xuICAgICAgICAgICAgICAgIGNvbnNvbGUud2FybignUG9pbnRlciBjYXB0dXJlIGZhaWxlZDonLCBlcnIpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gQWRkIGdsb2JhbCByZXNpemluZyBjbGFzc1xuICAgICAgICAgICAgZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LmNsYXNzTGlzdC5hZGQoJ3N3cC0tcmVzaXppbmcnKTtcbiAgICAgICAgICAgIC8vIEVtaXQgcmVzaXplIHN0YXJ0IGV2ZW50XG4gICAgICAgICAgICB0aGlzLmV2ZW50QnVzLmVtaXQoQ29yZUV2ZW50cy5FVkVOVF9SRVNJWkVfU1RBUlQsIHtcbiAgICAgICAgICAgICAgICBldmVudElkLFxuICAgICAgICAgICAgICAgIGVsZW1lbnQsXG4gICAgICAgICAgICAgICAgc3RhcnRIZWlnaHRcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgZS5wcmV2ZW50RGVmYXVsdCgpO1xuICAgICAgICB9O1xuICAgICAgICAvKipcbiAgICAgICAgICogSGFuZGxlIHBvaW50ZXJtb3ZlIC0gdXBkYXRlIHRhcmdldCBoZWlnaHQgZHVyaW5nIHJlc2l6ZVxuICAgICAgICAgKi9cbiAgICAgICAgdGhpcy5oYW5kbGVQb2ludGVyTW92ZSA9IChlKSA9PiB7XG4gICAgICAgICAgICBpZiAoIXRoaXMucmVzaXplU3RhdGUpXG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgY29uc3QgZGVsdGFZID0gZS5jbGllbnRZIC0gdGhpcy5yZXNpemVTdGF0ZS5zdGFydFk7XG4gICAgICAgICAgICBjb25zdCBtaW5IZWlnaHQgPSAodGhpcy5NSU5fSEVJR0hUX01JTlVURVMgLyA2MCkgKiB0aGlzLmdyaWRDb25maWcuaG91ckhlaWdodDtcbiAgICAgICAgICAgIGNvbnN0IG5ld0hlaWdodCA9IE1hdGgubWF4KG1pbkhlaWdodCwgdGhpcy5yZXNpemVTdGF0ZS5zdGFydEhlaWdodCArIGRlbHRhWSk7XG4gICAgICAgICAgICAvLyBTZXQgdGFyZ2V0IGhlaWdodCBmb3IgYW5pbWF0aW9uXG4gICAgICAgICAgICB0aGlzLnJlc2l6ZVN0YXRlLnRhcmdldEhlaWdodCA9IG5ld0hlaWdodDtcbiAgICAgICAgICAgIC8vIFN0YXJ0IGFuaW1hdGlvbiBpZiBub3QgcnVubmluZ1xuICAgICAgICAgICAgaWYgKHRoaXMucmVzaXplU3RhdGUuYW5pbWF0aW9uSWQgPT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICB0aGlzLmFuaW1hdGVIZWlnaHQoKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfTtcbiAgICAgICAgLyoqXG4gICAgICAgICAqIFJBRiBhbmltYXRpb24gbG9vcCBmb3Igc21vb3RoIGhlaWdodCBpbnRlcnBvbGF0aW9uXG4gICAgICAgICAqL1xuICAgICAgICB0aGlzLmFuaW1hdGVIZWlnaHQgPSAoKSA9PiB7XG4gICAgICAgICAgICBpZiAoIXRoaXMucmVzaXplU3RhdGUpXG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgY29uc3QgZGlmZiA9IHRoaXMucmVzaXplU3RhdGUudGFyZ2V0SGVpZ2h0IC0gdGhpcy5yZXNpemVTdGF0ZS5jdXJyZW50SGVpZ2h0O1xuICAgICAgICAgICAgLy8gU3RvcCBhbmltYXRpb24gd2hlbiBjbG9zZSBlbm91Z2hcbiAgICAgICAgICAgIGlmIChNYXRoLmFicyhkaWZmKSA8IDAuNSkge1xuICAgICAgICAgICAgICAgIHRoaXMucmVzaXplU3RhdGUuYW5pbWF0aW9uSWQgPSBudWxsO1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIEludGVycG9sYXRlIHRvd2FyZHMgdGFyZ2V0ICgzNSUgcGVyIGZyYW1lIGxpa2UgVjEpXG4gICAgICAgICAgICB0aGlzLnJlc2l6ZVN0YXRlLmN1cnJlbnRIZWlnaHQgKz0gZGlmZiAqIHRoaXMuQU5JTUFUSU9OX1NQRUVEO1xuICAgICAgICAgICAgdGhpcy5yZXNpemVTdGF0ZS5lbGVtZW50LnN0eWxlLmhlaWdodCA9IGAke3RoaXMucmVzaXplU3RhdGUuY3VycmVudEhlaWdodH1weGA7XG4gICAgICAgICAgICAvLyBVcGRhdGUgdGltZXN0YW1wIGRpc3BsYXkgKHNuYXBwZWQpXG4gICAgICAgICAgICB0aGlzLnVwZGF0ZVRpbWVzdGFtcERpc3BsYXkoKTtcbiAgICAgICAgICAgIC8vIENvbnRpbnVlIGFuaW1hdGlvblxuICAgICAgICAgICAgdGhpcy5yZXNpemVTdGF0ZS5hbmltYXRpb25JZCA9IHJlcXVlc3RBbmltYXRpb25GcmFtZSh0aGlzLmFuaW1hdGVIZWlnaHQpO1xuICAgICAgICB9O1xuICAgICAgICAvKipcbiAgICAgICAgICogSGFuZGxlIHBvaW50ZXJ1cCAtIGZpbmlzaCByZXNpemVcbiAgICAgICAgICovXG4gICAgICAgIHRoaXMuaGFuZGxlUG9pbnRlclVwID0gKGUpID0+IHtcbiAgICAgICAgICAgIGlmICghdGhpcy5yZXNpemVTdGF0ZSlcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAvLyBDYW5jZWwgYW55IHBlbmRpbmcgYW5pbWF0aW9uXG4gICAgICAgICAgICBpZiAodGhpcy5yZXNpemVTdGF0ZS5hbmltYXRpb25JZCAhPT0gbnVsbCkge1xuICAgICAgICAgICAgICAgIGNhbmNlbEFuaW1hdGlvbkZyYW1lKHRoaXMucmVzaXplU3RhdGUuYW5pbWF0aW9uSWQpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gUmVsZWFzZSBwb2ludGVyIGNhcHR1cmVcbiAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgdGhpcy5yZXNpemVTdGF0ZS5oYW5kbGVFbGVtZW50LnJlbGVhc2VQb2ludGVyQ2FwdHVyZShlLnBvaW50ZXJJZCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgICAgICAgY29uc29sZS53YXJuKCdQb2ludGVyIHJlbGVhc2UgZmFpbGVkOicsIGVycik7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBTbmFwIGZpbmFsIGhlaWdodCB0byBncmlkXG4gICAgICAgICAgICB0aGlzLnNuYXBUb0dyaWRGaW5hbCgpO1xuICAgICAgICAgICAgLy8gVXBkYXRlIHRpbWVzdGFtcCBvbmUgZmluYWwgdGltZVxuICAgICAgICAgICAgdGhpcy51cGRhdGVUaW1lc3RhbXBEaXNwbGF5KCk7XG4gICAgICAgICAgICAvLyBSZXN0b3JlIHotaW5kZXhcbiAgICAgICAgICAgIGNvbnN0IGNvbnRhaW5lciA9IHRoaXMucmVzaXplU3RhdGUuZWxlbWVudC5jbG9zZXN0KCdzd3AtZXZlbnQtZ3JvdXAnKSA/PyB0aGlzLnJlc2l6ZVN0YXRlLmVsZW1lbnQ7XG4gICAgICAgICAgICBjb250YWluZXIuc3R5bGUuekluZGV4ID0gdGhpcy5yZXNpemVTdGF0ZS5wcmV2WkluZGV4O1xuICAgICAgICAgICAgLy8gUmVtb3ZlIGdsb2JhbCByZXNpemluZyBjbGFzc1xuICAgICAgICAgICAgZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LmNsYXNzTGlzdC5yZW1vdmUoJ3N3cC0tcmVzaXppbmcnKTtcbiAgICAgICAgICAgIC8vIEdldCBjb2x1bW5LZXkgYW5kIGRhdGUgZnJvbSBwYXJlbnQgY29sdW1uXG4gICAgICAgICAgICBjb25zdCBjb2x1bW4gPSB0aGlzLnJlc2l6ZVN0YXRlLmVsZW1lbnQuY2xvc2VzdCgnc3dwLWRheS1jb2x1bW4nKTtcbiAgICAgICAgICAgIGNvbnN0IGNvbHVtbktleSA9IGNvbHVtbj8uZGF0YXNldC5jb2x1bW5LZXkgfHwgJyc7XG4gICAgICAgICAgICBjb25zdCBkYXRlID0gY29sdW1uPy5kYXRhc2V0LmRhdGUgfHwgJyc7XG4gICAgICAgICAgICAvLyBDcmVhdGUgU3dwRXZlbnQgZnJvbSBlbGVtZW50IChyZWFkcyB0b3AvaGVpZ2h0L2V2ZW50SWQgZnJvbSBlbGVtZW50KVxuICAgICAgICAgICAgY29uc3Qgc3dwRXZlbnQgPSBTd3BFdmVudC5mcm9tRWxlbWVudCh0aGlzLnJlc2l6ZVN0YXRlLmVsZW1lbnQsIGNvbHVtbktleSwgZGF0ZSwgdGhpcy5ncmlkQ29uZmlnKTtcbiAgICAgICAgICAgIC8vIEVtaXQgcmVzaXplIGVuZCBldmVudFxuICAgICAgICAgICAgdGhpcy5ldmVudEJ1cy5lbWl0KENvcmVFdmVudHMuRVZFTlRfUkVTSVpFX0VORCwge1xuICAgICAgICAgICAgICAgIHN3cEV2ZW50XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIC8vIFJlc2V0IHN0YXRlXG4gICAgICAgICAgICB0aGlzLnJlc2l6ZVN0YXRlID0gbnVsbDtcbiAgICAgICAgfTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogSW5pdGlhbGl6ZSByZXNpemUgZnVuY3Rpb25hbGl0eSBvbiBjb250YWluZXJcbiAgICAgKi9cbiAgICBpbml0KGNvbnRhaW5lcikge1xuICAgICAgICB0aGlzLmNvbnRhaW5lciA9IGNvbnRhaW5lcjtcbiAgICAgICAgLy8gTW91c2VvdmVyIGxpc3RlbmVyIGZvciBoYW5kbGUgY3JlYXRpb24gKGNhcHR1cmUgcGhhc2UgbGlrZSBWMSlcbiAgICAgICAgY29udGFpbmVyLmFkZEV2ZW50TGlzdGVuZXIoJ21vdXNlb3ZlcicsIHRoaXMuaGFuZGxlTW91c2VPdmVyLCB0cnVlKTtcbiAgICAgICAgLy8gUG9pbnRlciBsaXN0ZW5lcnMgZm9yIHJlc2l6ZSAoY2FwdHVyZSBwaGFzZSBsaWtlIFYxKVxuICAgICAgICBkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdwb2ludGVyZG93bicsIHRoaXMuaGFuZGxlUG9pbnRlckRvd24sIHRydWUpO1xuICAgICAgICBkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdwb2ludGVybW92ZScsIHRoaXMuaGFuZGxlUG9pbnRlck1vdmUsIHRydWUpO1xuICAgICAgICBkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdwb2ludGVydXAnLCB0aGlzLmhhbmRsZVBvaW50ZXJVcCwgdHJ1ZSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIENyZWF0ZSByZXNpemUgaGFuZGxlIGVsZW1lbnRcbiAgICAgKi9cbiAgICBjcmVhdGVSZXNpemVIYW5kbGUoKSB7XG4gICAgICAgIGNvbnN0IGhhbmRsZSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3N3cC1yZXNpemUtaGFuZGxlJyk7XG4gICAgICAgIGhhbmRsZS5zZXRBdHRyaWJ1dGUoJ2FyaWEtbGFiZWwnLCAnUmVzaXplIGV2ZW50Jyk7XG4gICAgICAgIGhhbmRsZS5zZXRBdHRyaWJ1dGUoJ3JvbGUnLCAnc2VwYXJhdG9yJyk7XG4gICAgICAgIHJldHVybiBoYW5kbGU7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFVwZGF0ZSB0aW1lc3RhbXAgZGlzcGxheSB3aXRoIHNuYXBwZWQgZW5kIHRpbWVcbiAgICAgKi9cbiAgICB1cGRhdGVUaW1lc3RhbXBEaXNwbGF5KCkge1xuICAgICAgICBpZiAoIXRoaXMucmVzaXplU3RhdGUpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIGNvbnN0IHRpbWVFbCA9IHRoaXMucmVzaXplU3RhdGUuZWxlbWVudC5xdWVyeVNlbGVjdG9yKCdzd3AtZXZlbnQtdGltZScpO1xuICAgICAgICBpZiAoIXRpbWVFbClcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgLy8gR2V0IHN0YXJ0IHRpbWUgZnJvbSBlbGVtZW50IHBvc2l0aW9uXG4gICAgICAgIGNvbnN0IHRvcCA9IHBhcnNlRmxvYXQodGhpcy5yZXNpemVTdGF0ZS5lbGVtZW50LnN0eWxlLnRvcCkgfHwgMDtcbiAgICAgICAgY29uc3Qgc3RhcnRNaW51dGVzRnJvbUdyaWQgPSBwaXhlbHNUb01pbnV0ZXModG9wLCB0aGlzLmdyaWRDb25maWcpO1xuICAgICAgICBjb25zdCBzdGFydE1pbnV0ZXMgPSAodGhpcy5ncmlkQ29uZmlnLmRheVN0YXJ0SG91ciAqIDYwKSArIHN0YXJ0TWludXRlc0Zyb21HcmlkO1xuICAgICAgICAvLyBDYWxjdWxhdGUgc25hcHBlZCBlbmQgdGltZSBmcm9tIGN1cnJlbnQgaGVpZ2h0XG4gICAgICAgIGNvbnN0IHNuYXBwZWRIZWlnaHQgPSBzbmFwVG9HcmlkKHRoaXMucmVzaXplU3RhdGUuY3VycmVudEhlaWdodCwgdGhpcy5ncmlkQ29uZmlnKTtcbiAgICAgICAgY29uc3QgZHVyYXRpb25NaW51dGVzID0gcGl4ZWxzVG9NaW51dGVzKHNuYXBwZWRIZWlnaHQsIHRoaXMuZ3JpZENvbmZpZyk7XG4gICAgICAgIGNvbnN0IGVuZE1pbnV0ZXMgPSBzdGFydE1pbnV0ZXMgKyBkdXJhdGlvbk1pbnV0ZXM7XG4gICAgICAgIC8vIEZvcm1hdCBhbmQgdXBkYXRlXG4gICAgICAgIGNvbnN0IHN0YXJ0ID0gdGhpcy5taW51dGVzVG9EYXRlKHN0YXJ0TWludXRlcyk7XG4gICAgICAgIGNvbnN0IGVuZCA9IHRoaXMubWludXRlc1RvRGF0ZShlbmRNaW51dGVzKTtcbiAgICAgICAgdGltZUVsLnRleHRDb250ZW50ID0gdGhpcy5kYXRlU2VydmljZS5mb3JtYXRUaW1lUmFuZ2Uoc3RhcnQsIGVuZCk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIENvbnZlcnQgbWludXRlcyBzaW5jZSBtaWRuaWdodCB0byBEYXRlXG4gICAgICovXG4gICAgbWludXRlc1RvRGF0ZShtaW51dGVzKSB7XG4gICAgICAgIGNvbnN0IGRhdGUgPSBuZXcgRGF0ZSgpO1xuICAgICAgICBkYXRlLnNldEhvdXJzKE1hdGguZmxvb3IobWludXRlcyAvIDYwKSAlIDI0LCBtaW51dGVzICUgNjAsIDAsIDApO1xuICAgICAgICByZXR1cm4gZGF0ZTtcbiAgICB9XG4gICAgO1xuICAgIC8qKlxuICAgICAqIFNuYXAgZmluYWwgaGVpZ2h0IHRvIGdyaWQgaW50ZXJ2YWxcbiAgICAgKi9cbiAgICBzbmFwVG9HcmlkRmluYWwoKSB7XG4gICAgICAgIGlmICghdGhpcy5yZXNpemVTdGF0ZSlcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgY29uc3QgY3VycmVudEhlaWdodCA9IHRoaXMucmVzaXplU3RhdGUuZWxlbWVudC5vZmZzZXRIZWlnaHQ7XG4gICAgICAgIGNvbnN0IHNuYXBwZWRIZWlnaHQgPSBzbmFwVG9HcmlkKGN1cnJlbnRIZWlnaHQsIHRoaXMuZ3JpZENvbmZpZyk7XG4gICAgICAgIGNvbnN0IG1pbkhlaWdodCA9IG1pbnV0ZXNUb1BpeGVscyh0aGlzLk1JTl9IRUlHSFRfTUlOVVRFUywgdGhpcy5ncmlkQ29uZmlnKTtcbiAgICAgICAgY29uc3QgZmluYWxIZWlnaHQgPSBNYXRoLm1heChtaW5IZWlnaHQsIHNuYXBwZWRIZWlnaHQpO1xuICAgICAgICB0aGlzLnJlc2l6ZVN0YXRlLmVsZW1lbnQuc3R5bGUuaGVpZ2h0ID0gYCR7ZmluYWxIZWlnaHR9cHhgO1xuICAgICAgICB0aGlzLnJlc2l6ZVN0YXRlLmN1cnJlbnRIZWlnaHQgPSBmaW5hbEhlaWdodDtcbiAgICB9XG59XG4iLCAiaW1wb3J0IHsgQ29yZUV2ZW50cyB9IGZyb20gJy4uL2NvbnN0YW50cy9Db3JlRXZlbnRzJztcbmV4cG9ydCBjbGFzcyBFdmVudFBlcnNpc3RlbmNlTWFuYWdlciB7XG4gICAgY29uc3RydWN0b3IoZXZlbnRTZXJ2aWNlLCBldmVudEJ1cywgZGF0ZVNlcnZpY2UpIHtcbiAgICAgICAgdGhpcy5ldmVudFNlcnZpY2UgPSBldmVudFNlcnZpY2U7XG4gICAgICAgIHRoaXMuZXZlbnRCdXMgPSBldmVudEJ1cztcbiAgICAgICAgdGhpcy5kYXRlU2VydmljZSA9IGRhdGVTZXJ2aWNlO1xuICAgICAgICAvKipcbiAgICAgICAgICogSGFuZGxlIGRyYWcgZW5kIC0gdXBkYXRlIGV2ZW50IHBvc2l0aW9uIGluIEluZGV4ZWREQlxuICAgICAgICAgKi9cbiAgICAgICAgdGhpcy5oYW5kbGVEcmFnRW5kID0gYXN5bmMgKGUpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHBheWxvYWQgPSBlLmRldGFpbDtcbiAgICAgICAgICAgIGNvbnN0IHsgc3dwRXZlbnQgfSA9IHBheWxvYWQ7XG4gICAgICAgICAgICAvLyBHZXQgZXhpc3RpbmcgZXZlbnQgdG8gbWVyZ2Ugd2l0aFxuICAgICAgICAgICAgY29uc3QgZXZlbnQgPSBhd2FpdCB0aGlzLmV2ZW50U2VydmljZS5nZXQoc3dwRXZlbnQuZXZlbnRJZCk7XG4gICAgICAgICAgICBpZiAoIWV2ZW50KSB7XG4gICAgICAgICAgICAgICAgY29uc29sZS53YXJuKGBFdmVudFBlcnNpc3RlbmNlTWFuYWdlcjogRXZlbnQgJHtzd3BFdmVudC5ldmVudElkfSBub3QgZm91bmRgKTtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBQYXJzZSByZXNvdXJjZUlkIGZyb20gY29sdW1uS2V5IGlmIHByZXNlbnRcbiAgICAgICAgICAgIGNvbnN0IHsgcmVzb3VyY2UgfSA9IHRoaXMuZGF0ZVNlcnZpY2UucGFyc2VDb2x1bW5LZXkoc3dwRXZlbnQuY29sdW1uS2V5KTtcbiAgICAgICAgICAgIC8vIFVwZGF0ZSBhbmQgc2F2ZSAtIHN0YXJ0L2VuZCBhbHJlYWR5IGNhbGN1bGF0ZWQgaW4gU3dwRXZlbnRcbiAgICAgICAgICAgIC8vIFNldCBhbGxEYXkgYmFzZWQgb24gZHJvcCB0YXJnZXQ6XG4gICAgICAgICAgICAvLyAtIGhlYWRlcjogYWxsRGF5ID0gdHJ1ZVxuICAgICAgICAgICAgLy8gLSBncmlkOiBhbGxEYXkgPSBmYWxzZSAoY29udmVydHMgYWxsRGF5IGV2ZW50IHRvIHRpbWVkKVxuICAgICAgICAgICAgY29uc3QgdXBkYXRlZEV2ZW50ID0ge1xuICAgICAgICAgICAgICAgIC4uLmV2ZW50LFxuICAgICAgICAgICAgICAgIHN0YXJ0OiBzd3BFdmVudC5zdGFydCxcbiAgICAgICAgICAgICAgICBlbmQ6IHN3cEV2ZW50LmVuZCxcbiAgICAgICAgICAgICAgICByZXNvdXJjZUlkOiByZXNvdXJjZSA/PyBldmVudC5yZXNvdXJjZUlkLFxuICAgICAgICAgICAgICAgIGFsbERheTogcGF5bG9hZC50YXJnZXQgPT09ICdoZWFkZXInLFxuICAgICAgICAgICAgICAgIHN5bmNTdGF0dXM6ICdwZW5kaW5nJ1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIGF3YWl0IHRoaXMuZXZlbnRTZXJ2aWNlLnNhdmUodXBkYXRlZEV2ZW50KTtcbiAgICAgICAgICAgIC8vIEVtaXQgRVZFTlRfVVBEQVRFRCBmb3IgRXZlbnRSZW5kZXJlciB0byByZS1yZW5kZXIgYWZmZWN0ZWQgY29sdW1uc1xuICAgICAgICAgICAgY29uc3QgdXBkYXRlUGF5bG9hZCA9IHtcbiAgICAgICAgICAgICAgICBldmVudElkOiB1cGRhdGVkRXZlbnQuaWQsXG4gICAgICAgICAgICAgICAgc291cmNlQ29sdW1uS2V5OiBwYXlsb2FkLnNvdXJjZUNvbHVtbktleSxcbiAgICAgICAgICAgICAgICB0YXJnZXRDb2x1bW5LZXk6IHN3cEV2ZW50LmNvbHVtbktleVxuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHRoaXMuZXZlbnRCdXMuZW1pdChDb3JlRXZlbnRzLkVWRU5UX1VQREFURUQsIHVwZGF0ZVBheWxvYWQpO1xuICAgICAgICB9O1xuICAgICAgICAvKipcbiAgICAgICAgICogSGFuZGxlIHJlc2l6ZSBlbmQgLSB1cGRhdGUgZXZlbnQgZHVyYXRpb24gaW4gSW5kZXhlZERCXG4gICAgICAgICAqL1xuICAgICAgICB0aGlzLmhhbmRsZVJlc2l6ZUVuZCA9IGFzeW5jIChlKSA9PiB7XG4gICAgICAgICAgICBjb25zdCBwYXlsb2FkID0gZS5kZXRhaWw7XG4gICAgICAgICAgICBjb25zdCB7IHN3cEV2ZW50IH0gPSBwYXlsb2FkO1xuICAgICAgICAgICAgLy8gR2V0IGV4aXN0aW5nIGV2ZW50IHRvIG1lcmdlIHdpdGhcbiAgICAgICAgICAgIGNvbnN0IGV2ZW50ID0gYXdhaXQgdGhpcy5ldmVudFNlcnZpY2UuZ2V0KHN3cEV2ZW50LmV2ZW50SWQpO1xuICAgICAgICAgICAgaWYgKCFldmVudCkge1xuICAgICAgICAgICAgICAgIGNvbnNvbGUud2FybihgRXZlbnRQZXJzaXN0ZW5jZU1hbmFnZXI6IEV2ZW50ICR7c3dwRXZlbnQuZXZlbnRJZH0gbm90IGZvdW5kYCk7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gVXBkYXRlIGFuZCBzYXZlIC0gZW5kIGFscmVhZHkgY2FsY3VsYXRlZCBpbiBTd3BFdmVudFxuICAgICAgICAgICAgY29uc3QgdXBkYXRlZEV2ZW50ID0ge1xuICAgICAgICAgICAgICAgIC4uLmV2ZW50LFxuICAgICAgICAgICAgICAgIGVuZDogc3dwRXZlbnQuZW5kLFxuICAgICAgICAgICAgICAgIHN5bmNTdGF0dXM6ICdwZW5kaW5nJ1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIGF3YWl0IHRoaXMuZXZlbnRTZXJ2aWNlLnNhdmUodXBkYXRlZEV2ZW50KTtcbiAgICAgICAgICAgIC8vIEVtaXQgRVZFTlRfVVBEQVRFRCBmb3IgRXZlbnRSZW5kZXJlciB0byByZS1yZW5kZXIgdGhlIGNvbHVtblxuICAgICAgICAgICAgLy8gUmVzaXplIHN0YXlzIGluIHNhbWUgY29sdW1uLCBzbyBzb3VyY2UgYW5kIHRhcmdldCBhcmUgdGhlIHNhbWVcbiAgICAgICAgICAgIGNvbnN0IHVwZGF0ZVBheWxvYWQgPSB7XG4gICAgICAgICAgICAgICAgZXZlbnRJZDogdXBkYXRlZEV2ZW50LmlkLFxuICAgICAgICAgICAgICAgIHNvdXJjZUNvbHVtbktleTogc3dwRXZlbnQuY29sdW1uS2V5LFxuICAgICAgICAgICAgICAgIHRhcmdldENvbHVtbktleTogc3dwRXZlbnQuY29sdW1uS2V5XG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgdGhpcy5ldmVudEJ1cy5lbWl0KENvcmVFdmVudHMuRVZFTlRfVVBEQVRFRCwgdXBkYXRlUGF5bG9hZCk7XG4gICAgICAgIH07XG4gICAgICAgIHRoaXMuc2V0dXBMaXN0ZW5lcnMoKTtcbiAgICB9XG4gICAgc2V0dXBMaXN0ZW5lcnMoKSB7XG4gICAgICAgIHRoaXMuZXZlbnRCdXMub24oQ29yZUV2ZW50cy5FVkVOVF9EUkFHX0VORCwgdGhpcy5oYW5kbGVEcmFnRW5kKTtcbiAgICAgICAgdGhpcy5ldmVudEJ1cy5vbihDb3JlRXZlbnRzLkVWRU5UX1JFU0laRV9FTkQsIHRoaXMuaGFuZGxlUmVzaXplRW5kKTtcbiAgICB9XG59XG4iLCAiaW1wb3J0IHsgQ29udGFpbmVyIH0gZnJvbSAnQG5vdmFkaS9jb3JlJztcbmltcG9ydCB7IERhdGVSZW5kZXJlciB9IGZyb20gJy4vZmVhdHVyZXMvZGF0ZS9EYXRlUmVuZGVyZXInO1xuaW1wb3J0IHsgRGF0ZVNlcnZpY2UgfSBmcm9tICcuL2NvcmUvRGF0ZVNlcnZpY2UnO1xuaW1wb3J0IHsgUmVzb3VyY2VSZW5kZXJlciB9IGZyb20gJy4vZmVhdHVyZXMvcmVzb3VyY2UvUmVzb3VyY2VSZW5kZXJlcic7XG5pbXBvcnQgeyBUZWFtUmVuZGVyZXIgfSBmcm9tICcuL2ZlYXR1cmVzL3RlYW0vVGVhbVJlbmRlcmVyJztcbmltcG9ydCB7IERlcGFydG1lbnRSZW5kZXJlciB9IGZyb20gJy4vZmVhdHVyZXMvZGVwYXJ0bWVudC9EZXBhcnRtZW50UmVuZGVyZXInO1xuaW1wb3J0IHsgQ2FsZW5kYXJPcmNoZXN0cmF0b3IgfSBmcm9tICcuL2NvcmUvQ2FsZW5kYXJPcmNoZXN0cmF0b3InO1xuaW1wb3J0IHsgQ2FsZW5kYXJBcHAgfSBmcm9tICcuL2NvcmUvQ2FsZW5kYXJBcHAnO1xuaW1wb3J0IHsgVGltZUF4aXNSZW5kZXJlciB9IGZyb20gJy4vZmVhdHVyZXMvdGltZWF4aXMvVGltZUF4aXNSZW5kZXJlcic7XG5pbXBvcnQgeyBTY3JvbGxNYW5hZ2VyIH0gZnJvbSAnLi9jb3JlL1Njcm9sbE1hbmFnZXInO1xuaW1wb3J0IHsgSGVhZGVyRHJhd2VyTWFuYWdlciB9IGZyb20gJy4vY29yZS9IZWFkZXJEcmF3ZXJNYW5hZ2VyJztcbmltcG9ydCB7IE1vY2tUZWFtU3RvcmUsIE1vY2tSZXNvdXJjZVN0b3JlIH0gZnJvbSAnLi9kZW1vL01vY2tTdG9yZXMnO1xuaW1wb3J0IHsgRGVtb0FwcCB9IGZyb20gJy4vZGVtby9EZW1vQXBwJztcbi8vIEV2ZW50IHN5c3RlbVxuaW1wb3J0IHsgRXZlbnRCdXMgfSBmcm9tICcuL2NvcmUvRXZlbnRCdXMnO1xuLy8gU3RvcmFnZVxuaW1wb3J0IHsgSW5kZXhlZERCQ29udGV4dCB9IGZyb20gJy4vc3RvcmFnZS9JbmRleGVkREJDb250ZXh0JztcbmltcG9ydCB7IEV2ZW50U3RvcmUgfSBmcm9tICcuL3N0b3JhZ2UvZXZlbnRzL0V2ZW50U3RvcmUnO1xuaW1wb3J0IHsgRXZlbnRTZXJ2aWNlIH0gZnJvbSAnLi9zdG9yYWdlL2V2ZW50cy9FdmVudFNlcnZpY2UnO1xuaW1wb3J0IHsgUmVzb3VyY2VTdG9yZSB9IGZyb20gJy4vc3RvcmFnZS9yZXNvdXJjZXMvUmVzb3VyY2VTdG9yZSc7XG5pbXBvcnQgeyBSZXNvdXJjZVNlcnZpY2UgfSBmcm9tICcuL3N0b3JhZ2UvcmVzb3VyY2VzL1Jlc291cmNlU2VydmljZSc7XG5pbXBvcnQgeyBCb29raW5nU3RvcmUgfSBmcm9tICcuL3N0b3JhZ2UvYm9va2luZ3MvQm9va2luZ1N0b3JlJztcbmltcG9ydCB7IEJvb2tpbmdTZXJ2aWNlIH0gZnJvbSAnLi9zdG9yYWdlL2Jvb2tpbmdzL0Jvb2tpbmdTZXJ2aWNlJztcbmltcG9ydCB7IEN1c3RvbWVyU3RvcmUgfSBmcm9tICcuL3N0b3JhZ2UvY3VzdG9tZXJzL0N1c3RvbWVyU3RvcmUnO1xuaW1wb3J0IHsgQ3VzdG9tZXJTZXJ2aWNlIH0gZnJvbSAnLi9zdG9yYWdlL2N1c3RvbWVycy9DdXN0b21lclNlcnZpY2UnO1xuaW1wb3J0IHsgVGVhbVN0b3JlIH0gZnJvbSAnLi9zdG9yYWdlL3RlYW1zL1RlYW1TdG9yZSc7XG5pbXBvcnQgeyBUZWFtU2VydmljZSB9IGZyb20gJy4vc3RvcmFnZS90ZWFtcy9UZWFtU2VydmljZSc7XG5pbXBvcnQgeyBEZXBhcnRtZW50U3RvcmUgfSBmcm9tICcuL3N0b3JhZ2UvZGVwYXJ0bWVudHMvRGVwYXJ0bWVudFN0b3JlJztcbmltcG9ydCB7IERlcGFydG1lbnRTZXJ2aWNlIH0gZnJvbSAnLi9zdG9yYWdlL2RlcGFydG1lbnRzL0RlcGFydG1lbnRTZXJ2aWNlJztcbmltcG9ydCB7IFNldHRpbmdzU3RvcmUgfSBmcm9tICcuL3N0b3JhZ2Uvc2V0dGluZ3MvU2V0dGluZ3NTdG9yZSc7XG5pbXBvcnQgeyBTZXR0aW5nc1NlcnZpY2UgfSBmcm9tICcuL3N0b3JhZ2Uvc2V0dGluZ3MvU2V0dGluZ3NTZXJ2aWNlJztcbmltcG9ydCB7IFZpZXdDb25maWdTdG9yZSB9IGZyb20gJy4vc3RvcmFnZS92aWV3Y29uZmlncy9WaWV3Q29uZmlnU3RvcmUnO1xuaW1wb3J0IHsgVmlld0NvbmZpZ1NlcnZpY2UgfSBmcm9tICcuL3N0b3JhZ2Uvdmlld2NvbmZpZ3MvVmlld0NvbmZpZ1NlcnZpY2UnO1xuLy8gQXVkaXRcbmltcG9ydCB7IEF1ZGl0U3RvcmUgfSBmcm9tICcuL3N0b3JhZ2UvYXVkaXQvQXVkaXRTdG9yZSc7XG5pbXBvcnQgeyBBdWRpdFNlcnZpY2UgfSBmcm9tICcuL3N0b3JhZ2UvYXVkaXQvQXVkaXRTZXJ2aWNlJztcbmltcG9ydCB7IE1vY2tFdmVudFJlcG9zaXRvcnkgfSBmcm9tICcuL3JlcG9zaXRvcmllcy9Nb2NrRXZlbnRSZXBvc2l0b3J5JztcbmltcG9ydCB7IE1vY2tSZXNvdXJjZVJlcG9zaXRvcnkgfSBmcm9tICcuL3JlcG9zaXRvcmllcy9Nb2NrUmVzb3VyY2VSZXBvc2l0b3J5JztcbmltcG9ydCB7IE1vY2tCb29raW5nUmVwb3NpdG9yeSB9IGZyb20gJy4vcmVwb3NpdG9yaWVzL01vY2tCb29raW5nUmVwb3NpdG9yeSc7XG5pbXBvcnQgeyBNb2NrQ3VzdG9tZXJSZXBvc2l0b3J5IH0gZnJvbSAnLi9yZXBvc2l0b3JpZXMvTW9ja0N1c3RvbWVyUmVwb3NpdG9yeSc7XG5pbXBvcnQgeyBNb2NrQXVkaXRSZXBvc2l0b3J5IH0gZnJvbSAnLi9yZXBvc2l0b3JpZXMvTW9ja0F1ZGl0UmVwb3NpdG9yeSc7XG5pbXBvcnQgeyBNb2NrVGVhbVJlcG9zaXRvcnkgfSBmcm9tICcuL3JlcG9zaXRvcmllcy9Nb2NrVGVhbVJlcG9zaXRvcnknO1xuaW1wb3J0IHsgTW9ja0RlcGFydG1lbnRSZXBvc2l0b3J5IH0gZnJvbSAnLi9yZXBvc2l0b3JpZXMvTW9ja0RlcGFydG1lbnRSZXBvc2l0b3J5JztcbmltcG9ydCB7IE1vY2tTZXR0aW5nc1JlcG9zaXRvcnkgfSBmcm9tICcuL3JlcG9zaXRvcmllcy9Nb2NrU2V0dGluZ3NSZXBvc2l0b3J5JztcbmltcG9ydCB7IE1vY2tWaWV3Q29uZmlnUmVwb3NpdG9yeSB9IGZyb20gJy4vcmVwb3NpdG9yaWVzL01vY2tWaWV3Q29uZmlnUmVwb3NpdG9yeSc7XG4vLyBXb3JrZXJzXG5pbXBvcnQgeyBEYXRhU2VlZGVyIH0gZnJvbSAnLi93b3JrZXJzL0RhdGFTZWVkZXInO1xuLy8gRmVhdHVyZXNcbmltcG9ydCB7IEV2ZW50UmVuZGVyZXIgfSBmcm9tICcuL2ZlYXR1cmVzL2V2ZW50L0V2ZW50UmVuZGVyZXInO1xuaW1wb3J0IHsgU2NoZWR1bGVSZW5kZXJlciB9IGZyb20gJy4vZmVhdHVyZXMvc2NoZWR1bGUvU2NoZWR1bGVSZW5kZXJlcic7XG5pbXBvcnQgeyBIZWFkZXJEcmF3ZXJSZW5kZXJlciB9IGZyb20gJy4vZmVhdHVyZXMvaGVhZGVyZHJhd2VyL0hlYWRlckRyYXdlclJlbmRlcmVyJztcbi8vIFNjaGVkdWxlXG5pbXBvcnQgeyBTY2hlZHVsZU92ZXJyaWRlU3RvcmUgfSBmcm9tICcuL3N0b3JhZ2Uvc2NoZWR1bGVzL1NjaGVkdWxlT3ZlcnJpZGVTdG9yZSc7XG5pbXBvcnQgeyBTY2hlZHVsZU92ZXJyaWRlU2VydmljZSB9IGZyb20gJy4vc3RvcmFnZS9zY2hlZHVsZXMvU2NoZWR1bGVPdmVycmlkZVNlcnZpY2UnO1xuaW1wb3J0IHsgUmVzb3VyY2VTY2hlZHVsZVNlcnZpY2UgfSBmcm9tICcuL3N0b3JhZ2Uvc2NoZWR1bGVzL1Jlc291cmNlU2NoZWR1bGVTZXJ2aWNlJztcbi8vIE1hbmFnZXJzXG5pbXBvcnQgeyBEcmFnRHJvcE1hbmFnZXIgfSBmcm9tICcuL21hbmFnZXJzL0RyYWdEcm9wTWFuYWdlcic7XG5pbXBvcnQgeyBFZGdlU2Nyb2xsTWFuYWdlciB9IGZyb20gJy4vbWFuYWdlcnMvRWRnZVNjcm9sbE1hbmFnZXInO1xuaW1wb3J0IHsgUmVzaXplTWFuYWdlciB9IGZyb20gJy4vbWFuYWdlcnMvUmVzaXplTWFuYWdlcic7XG5pbXBvcnQgeyBFdmVudFBlcnNpc3RlbmNlTWFuYWdlciB9IGZyb20gJy4vbWFuYWdlcnMvRXZlbnRQZXJzaXN0ZW5jZU1hbmFnZXInO1xuY29uc3QgZGVmYXVsdFRpbWVGb3JtYXRDb25maWcgPSB7XG4gICAgdGltZXpvbmU6IEludGwuRGF0ZVRpbWVGb3JtYXQoKS5yZXNvbHZlZE9wdGlvbnMoKS50aW1lWm9uZSxcbiAgICB1c2UyNEhvdXJGb3JtYXQ6IHRydWUsXG4gICAgbG9jYWxlOiAnZGEtREsnLFxuICAgIGRhdGVGb3JtYXQ6ICdsb2NhbGUnLFxuICAgIHNob3dTZWNvbmRzOiBmYWxzZVxufTtcbmNvbnN0IGRlZmF1bHRHcmlkQ29uZmlnID0ge1xuICAgIGhvdXJIZWlnaHQ6IDY0LFxuICAgIGRheVN0YXJ0SG91cjogNixcbiAgICBkYXlFbmRIb3VyOiAxOCxcbiAgICBzbmFwSW50ZXJ2YWw6IDE1LFxuICAgIGdyaWRTdGFydFRocmVzaG9sZE1pbnV0ZXM6IDMwXG59O1xuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUNvbnRhaW5lcigpIHtcbiAgICBjb25zdCBjb250YWluZXIgPSBuZXcgQ29udGFpbmVyKCk7XG4gICAgY29uc3QgYnVpbGRlciA9IGNvbnRhaW5lci5idWlsZGVyKCk7XG4gICAgLy8gQ29uZmlnXG4gICAgYnVpbGRlci5yZWdpc3Rlckluc3RhbmNlKGRlZmF1bHRUaW1lRm9ybWF0Q29uZmlnKS5hcyhcIklUaW1lRm9ybWF0Q29uZmlnXCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJJbnN0YW5jZShkZWZhdWx0R3JpZENvbmZpZykuYXMoXCJJR3JpZENvbmZpZ1wiKTtcbiAgICAvLyBDb3JlIC0gRXZlbnRCdXNcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShFdmVudEJ1cykuYXMoXCJFdmVudEJ1c1wiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShFdmVudEJ1cykuYXMoXCJJRXZlbnRCdXNcIik7XG4gICAgLy8gU2VydmljZXNcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShEYXRlU2VydmljZSkuYXMoXCJEYXRlU2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSVRpbWVGb3JtYXRDb25maWdcIiksXG4gICAgICAgICAgICB1bmRlZmluZWRcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIC8vIFN0b3JhZ2UgaW5mcmFzdHJ1Y3R1cmVcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShJbmRleGVkREJDb250ZXh0KS5hcyhcIkluZGV4ZWREQkNvbnRleHRcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZUFsbChcIklTdG9yZVwiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgLy8gU3RvcmVzIChmb3IgSW5kZXhlZERCIHNjaGVtYSBjcmVhdGlvbilcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShFdmVudFN0b3JlKS5hcyhcIklTdG9yZVwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShSZXNvdXJjZVN0b3JlKS5hcyhcIklTdG9yZVwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShCb29raW5nU3RvcmUpLmFzKFwiSVN0b3JlXCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKEN1c3RvbWVyU3RvcmUpLmFzKFwiSVN0b3JlXCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFRlYW1TdG9yZSkuYXMoXCJJU3RvcmVcIik7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoRGVwYXJ0bWVudFN0b3JlKS5hcyhcIklTdG9yZVwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShTY2hlZHVsZU92ZXJyaWRlU3RvcmUpLmFzKFwiSVN0b3JlXCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKEF1ZGl0U3RvcmUpLmFzKFwiSVN0b3JlXCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFNldHRpbmdzU3RvcmUpLmFzKFwiSVN0b3JlXCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFZpZXdDb25maWdTdG9yZSkuYXMoXCJJU3RvcmVcIik7XG4gICAgLy8gRW50aXR5IHNlcnZpY2VzIChmb3IgRGF0YVNlZWRlciBwb2x5bW9ycGhpYyBhcnJheSlcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShFdmVudFNlcnZpY2UpLmFzKFwiSUVudGl0eVNlcnZpY2VcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkluZGV4ZWREQkNvbnRleHRcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKEV2ZW50U2VydmljZSkuYXMoXCJJRW50aXR5U2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSW5kZXhlZERCQ29udGV4dFwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklFdmVudEJ1c1wiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoRXZlbnRTZXJ2aWNlKS5hcyhcIkV2ZW50U2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSW5kZXhlZERCQ29udGV4dFwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklFdmVudEJ1c1wiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoUmVzb3VyY2VTZXJ2aWNlKS5hcyhcIklFbnRpdHlTZXJ2aWNlXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJbmRleGVkREJDb250ZXh0XCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShSZXNvdXJjZVNlcnZpY2UpLmFzKFwiSUVudGl0eVNlcnZpY2VcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkluZGV4ZWREQkNvbnRleHRcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFJlc291cmNlU2VydmljZSkuYXMoXCJSZXNvdXJjZVNlcnZpY2VcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkluZGV4ZWREQkNvbnRleHRcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKEJvb2tpbmdTZXJ2aWNlKS5hcyhcIklFbnRpdHlTZXJ2aWNlXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJbmRleGVkREJDb250ZXh0XCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShCb29raW5nU2VydmljZSkuYXMoXCJJRW50aXR5U2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSW5kZXhlZERCQ29udGV4dFwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklFdmVudEJ1c1wiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoQm9va2luZ1NlcnZpY2UpLmFzKFwiQm9va2luZ1NlcnZpY2VcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkluZGV4ZWREQkNvbnRleHRcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKEN1c3RvbWVyU2VydmljZSkuYXMoXCJJRW50aXR5U2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSW5kZXhlZERCQ29udGV4dFwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklFdmVudEJ1c1wiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoQ3VzdG9tZXJTZXJ2aWNlKS5hcyhcIklFbnRpdHlTZXJ2aWNlXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJbmRleGVkREJDb250ZXh0XCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShDdXN0b21lclNlcnZpY2UpLmFzKFwiQ3VzdG9tZXJTZXJ2aWNlXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJbmRleGVkREJDb250ZXh0XCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShUZWFtU2VydmljZSkuYXMoXCJJRW50aXR5U2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSW5kZXhlZERCQ29udGV4dFwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklFdmVudEJ1c1wiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoVGVhbVNlcnZpY2UpLmFzKFwiSUVudGl0eVNlcnZpY2VcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkluZGV4ZWREQkNvbnRleHRcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFRlYW1TZXJ2aWNlKS5hcyhcIlRlYW1TZXJ2aWNlXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJbmRleGVkREJDb250ZXh0XCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShEZXBhcnRtZW50U2VydmljZSkuYXMoXCJJRW50aXR5U2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSW5kZXhlZERCQ29udGV4dFwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklFdmVudEJ1c1wiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoRGVwYXJ0bWVudFNlcnZpY2UpLmFzKFwiSUVudGl0eVNlcnZpY2VcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkluZGV4ZWREQkNvbnRleHRcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKERlcGFydG1lbnRTZXJ2aWNlKS5hcyhcIkRlcGFydG1lbnRTZXJ2aWNlXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJbmRleGVkREJDb250ZXh0XCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShTZXR0aW5nc1NlcnZpY2UpLmFzKFwiSUVudGl0eVNlcnZpY2VcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkluZGV4ZWREQkNvbnRleHRcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFNldHRpbmdzU2VydmljZSkuYXMoXCJJRW50aXR5U2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSW5kZXhlZERCQ29udGV4dFwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklFdmVudEJ1c1wiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoU2V0dGluZ3NTZXJ2aWNlKS5hcyhcIlNldHRpbmdzU2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSW5kZXhlZERCQ29udGV4dFwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklFdmVudEJ1c1wiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoVmlld0NvbmZpZ1NlcnZpY2UpLmFzKFwiSUVudGl0eVNlcnZpY2VcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkluZGV4ZWREQkNvbnRleHRcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFZpZXdDb25maWdTZXJ2aWNlKS5hcyhcIklFbnRpdHlTZXJ2aWNlXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJbmRleGVkREJDb250ZXh0XCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShWaWV3Q29uZmlnU2VydmljZSkuYXMoXCJWaWV3Q29uZmlnU2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSW5kZXhlZERCQ29udGV4dFwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklFdmVudEJ1c1wiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgLy8gUmVwb3NpdG9yaWVzIChmb3IgRGF0YVNlZWRlciBwb2x5bW9ycGhpYyBhcnJheSlcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShNb2NrRXZlbnRSZXBvc2l0b3J5KS5hcyhcIklBcGlSZXBvc2l0b3J5XCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKE1vY2tFdmVudFJlcG9zaXRvcnkpLmFzKFwiSUFwaVJlcG9zaXRvcnlcIik7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoTW9ja1Jlc291cmNlUmVwb3NpdG9yeSkuYXMoXCJJQXBpUmVwb3NpdG9yeVwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShNb2NrUmVzb3VyY2VSZXBvc2l0b3J5KS5hcyhcIklBcGlSZXBvc2l0b3J5XCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKE1vY2tCb29raW5nUmVwb3NpdG9yeSkuYXMoXCJJQXBpUmVwb3NpdG9yeVwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShNb2NrQm9va2luZ1JlcG9zaXRvcnkpLmFzKFwiSUFwaVJlcG9zaXRvcnlcIik7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoTW9ja0N1c3RvbWVyUmVwb3NpdG9yeSkuYXMoXCJJQXBpUmVwb3NpdG9yeVwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShNb2NrQ3VzdG9tZXJSZXBvc2l0b3J5KS5hcyhcIklBcGlSZXBvc2l0b3J5XCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKE1vY2tBdWRpdFJlcG9zaXRvcnkpLmFzKFwiSUFwaVJlcG9zaXRvcnlcIik7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoTW9ja0F1ZGl0UmVwb3NpdG9yeSkuYXMoXCJJQXBpUmVwb3NpdG9yeVwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShNb2NrVGVhbVJlcG9zaXRvcnkpLmFzKFwiSUFwaVJlcG9zaXRvcnlcIik7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoTW9ja1RlYW1SZXBvc2l0b3J5KS5hcyhcIklBcGlSZXBvc2l0b3J5XCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKE1vY2tEZXBhcnRtZW50UmVwb3NpdG9yeSkuYXMoXCJJQXBpUmVwb3NpdG9yeVwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShNb2NrRGVwYXJ0bWVudFJlcG9zaXRvcnkpLmFzKFwiSUFwaVJlcG9zaXRvcnlcIik7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoTW9ja1NldHRpbmdzUmVwb3NpdG9yeSkuYXMoXCJJQXBpUmVwb3NpdG9yeVwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShNb2NrU2V0dGluZ3NSZXBvc2l0b3J5KS5hcyhcIklBcGlSZXBvc2l0b3J5XCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKE1vY2tWaWV3Q29uZmlnUmVwb3NpdG9yeSkuYXMoXCJJQXBpUmVwb3NpdG9yeVwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShNb2NrVmlld0NvbmZpZ1JlcG9zaXRvcnkpLmFzKFwiSUFwaVJlcG9zaXRvcnlcIik7XG4gICAgLy8gQXVkaXQgc2VydmljZSAobGlzdGVucyB0byBFTlRJVFlfU0FWRUQvREVMRVRFRCBldmVudHMgYXV0b21hdGljYWxseSlcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShBdWRpdFNlcnZpY2UpLmFzKFwiQXVkaXRTZXJ2aWNlXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJbmRleGVkREJDb250ZXh0XCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICAvLyBXb3JrZXJzXG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoRGF0YVNlZWRlcikuYXMoXCJEYXRhU2VlZGVyXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGVBbGwoXCJJRW50aXR5U2VydmljZVwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZUFsbChcIklBcGlSZXBvc2l0b3J5XCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICAvLyBTY2hlZHVsZSBzZXJ2aWNlc1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFNjaGVkdWxlT3ZlcnJpZGVTZXJ2aWNlKS5hcyhcIlNjaGVkdWxlT3ZlcnJpZGVTZXJ2aWNlXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJbmRleGVkREJDb250ZXh0XCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShSZXNvdXJjZVNjaGVkdWxlU2VydmljZSkuYXMoXCJSZXNvdXJjZVNjaGVkdWxlU2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiUmVzb3VyY2VTZXJ2aWNlXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiU2NoZWR1bGVPdmVycmlkZVNlcnZpY2VcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJEYXRlU2VydmljZVwiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgLy8gRmVhdHVyZXNcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShFdmVudFJlbmRlcmVyKS5hcyhcIkV2ZW50UmVuZGVyZXJcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkV2ZW50U2VydmljZVwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkRhdGVTZXJ2aWNlXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUdyaWRDb25maWdcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFNjaGVkdWxlUmVuZGVyZXIpLmFzKFwiU2NoZWR1bGVSZW5kZXJlclwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiUmVzb3VyY2VTY2hlZHVsZVNlcnZpY2VcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJEYXRlU2VydmljZVwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklHcmlkQ29uZmlnXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShIZWFkZXJEcmF3ZXJSZW5kZXJlcikuYXMoXCJIZWFkZXJEcmF3ZXJSZW5kZXJlclwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUdyaWRDb25maWdcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJIZWFkZXJEcmF3ZXJNYW5hZ2VyXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiRXZlbnRTZXJ2aWNlXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiRGF0ZVNlcnZpY2VcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIC8vIFJlbmRlcmVycyAtIHJlZ2lzdHJlcmVzIHNvbSBSZW5kZXJlciAoYXJyYXkgaW5qZWN0aW9uIHRpbCBDYWxlbmRhck9yY2hlc3RyYXRvcilcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShEYXRlUmVuZGVyZXIpLmFzKFwiSVJlbmRlcmVyXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJEYXRlU2VydmljZVwiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoUmVzb3VyY2VSZW5kZXJlcikuYXMoXCJJUmVuZGVyZXJcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIlJlc291cmNlU2VydmljZVwiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoVGVhbVJlbmRlcmVyKS5hcyhcIklSZW5kZXJlclwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiVGVhbVNlcnZpY2VcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKERlcGFydG1lbnRSZW5kZXJlcikuYXMoXCJJUmVuZGVyZXJcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkRlcGFydG1lbnRTZXJ2aWNlXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICAvLyBTdG9yZXMgLSByZWdpc3RyZXJlcyBzb20gSUdyb3VwaW5nU3RvcmVcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShNb2NrVGVhbVN0b3JlKS5hcyhcIklHcm91cGluZ1N0b3JlXCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKE1vY2tSZXNvdXJjZVN0b3JlKS5hcyhcIklHcm91cGluZ1N0b3JlXCIpO1xuICAgIC8vIENhbGVuZGFyT3JjaGVzdHJhdG9yIG1vZHRhZ2VyIElHcm91cGluZ1N0b3JlW10gYXV0b21hdGlzayAoYXJyYXkgaW5qZWN0aW9uKVxuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKENhbGVuZGFyT3JjaGVzdHJhdG9yKS5hcyhcIkNhbGVuZGFyT3JjaGVzdHJhdG9yXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGVBbGwoXCJJUmVuZGVyZXJcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJFdmVudFJlbmRlcmVyXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiU2NoZWR1bGVSZW5kZXJlclwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkhlYWRlckRyYXdlclJlbmRlcmVyXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiRGF0ZVNlcnZpY2VcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGVBbGwoXCJJRW50aXR5U2VydmljZVwiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoVGltZUF4aXNSZW5kZXJlcikuYXMoXCJUaW1lQXhpc1JlbmRlcmVyXCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFNjcm9sbE1hbmFnZXIpLmFzKFwiU2Nyb2xsTWFuYWdlclwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShIZWFkZXJEcmF3ZXJNYW5hZ2VyKS5hcyhcIkhlYWRlckRyYXdlck1hbmFnZXJcIik7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoRHJhZ0Ryb3BNYW5hZ2VyKS5hcyhcIkRyYWdEcm9wTWFuYWdlclwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUdyaWRDb25maWdcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKEVkZ2VTY3JvbGxNYW5hZ2VyKS5hcyhcIkVkZ2VTY3JvbGxNYW5hZ2VyXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFJlc2l6ZU1hbmFnZXIpLmFzKFwiUmVzaXplTWFuYWdlclwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUdyaWRDb25maWdcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJEYXRlU2VydmljZVwiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoRXZlbnRQZXJzaXN0ZW5jZU1hbmFnZXIpLmFzKFwiRXZlbnRQZXJzaXN0ZW5jZU1hbmFnZXJcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkV2ZW50U2VydmljZVwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklFdmVudEJ1c1wiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkRhdGVTZXJ2aWNlXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICAvLyBDYWxlbmRhckFwcCAtIGdlbmJydWdlbGlnIGthbGVuZGVya29tcG9uZW50XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoQ2FsZW5kYXJBcHApLmFzKFwiQ2FsZW5kYXJBcHBcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkNhbGVuZGFyT3JjaGVzdHJhdG9yXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiVGltZUF4aXNSZW5kZXJlclwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkRhdGVTZXJ2aWNlXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiU2Nyb2xsTWFuYWdlclwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkhlYWRlckRyYXdlck1hbmFnZXJcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJEcmFnRHJvcE1hbmFnZXJcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJFZGdlU2Nyb2xsTWFuYWdlclwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIlJlc2l6ZU1hbmFnZXJcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJIZWFkZXJEcmF3ZXJSZW5kZXJlclwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkV2ZW50UGVyc2lzdGVuY2VNYW5hZ2VyXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiU2V0dGluZ3NTZXJ2aWNlXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiVmlld0NvbmZpZ1NlcnZpY2VcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIC8vIERlbW8gYXBwXG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoRGVtb0FwcCkuYXMoXCJEZW1vQXBwXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJbmRleGVkREJDb250ZXh0XCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiRGF0YVNlZWRlclwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkF1ZGl0U2VydmljZVwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkNhbGVuZGFyQXBwXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiRGF0ZVNlcnZpY2VcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJSZXNvdXJjZVNlcnZpY2VcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIHJldHVybiBidWlsZGVyLmJ1aWxkKCk7XG59XG4iLCAiaW1wb3J0IHsgY3JlYXRlQ29udGFpbmVyIH0gZnJvbSAnLi4vQ29tcG9zaXRpb25Sb290JztcbmNvbnN0IGNvbnRhaW5lciA9IGNyZWF0ZUNvbnRhaW5lcigpO1xuY29udGFpbmVyLnJlc29sdmVUeXBlKFwiRGVtb0FwcFwiKS5pbml0KCkuY2F0Y2goY29uc29sZS5lcnJvcik7XG4iXSwKICAibWFwcGluZ3MiOiAiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUE7QUFBQTtBQUFBLEtBQUMsU0FBUyxHQUFFLEdBQUU7QUFBQyxrQkFBVSxPQUFPLFdBQVMsZUFBYSxPQUFPLFNBQU8sT0FBTyxVQUFRLEVBQUUsSUFBRSxjQUFZLE9BQU8sVUFBUSxPQUFPLE1BQUksT0FBTyxDQUFDLEtBQUcsSUFBRSxlQUFhLE9BQU8sYUFBVyxhQUFXLEtBQUcsTUFBTSxRQUFNLEVBQUU7QUFBQSxJQUFDLEVBQUUsU0FBTSxXQUFVO0FBQUM7QUFBYSxVQUFJLElBQUUsS0FBSSxJQUFFLEtBQUksSUFBRSxNQUFLLElBQUUsZUFBYyxJQUFFLFVBQVMsSUFBRSxVQUFTLElBQUUsUUFBTyxJQUFFLE9BQU0sSUFBRSxRQUFPLElBQUUsU0FBUSxJQUFFLFdBQVUsSUFBRSxRQUFPLElBQUUsUUFBTyxJQUFFLGdCQUFlLElBQUUsOEZBQTZGLElBQUUsdUZBQXNGLElBQUUsRUFBQyxNQUFLLE1BQUssVUFBUywyREFBMkQsTUFBTSxHQUFHLEdBQUUsUUFBTyx3RkFBd0YsTUFBTSxHQUFHLEdBQUUsU0FBUSxTQUFTQSxJQUFFO0FBQUMsWUFBSUMsS0FBRSxDQUFDLE1BQUssTUFBSyxNQUFLLElBQUksR0FBRUMsS0FBRUYsS0FBRTtBQUFJLGVBQU0sTUFBSUEsTUFBR0MsSUFBR0MsS0FBRSxNQUFJLEVBQUUsS0FBR0QsR0FBRUMsRUFBQyxLQUFHRCxHQUFF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LGFBQWE7QUFBQSxNQUNoQyxPQUFLLEVBQUUsWUFBWSxlQUFlO0FBQUEsTUFDbEMsT0FBSyxFQUFFLFlBQVkscUJBQXFCO0FBQUEsTUFDeEMsT0FBSyxFQUFFLFlBQVksaUJBQWlCO0FBQUEsTUFDcEMsT0FBSyxFQUFFLFlBQVksbUJBQW1CO0FBQUEsTUFDdEMsT0FBSyxFQUFFLFlBQVksZUFBZTtBQUFBLE1BQ2xDLE9BQUssRUFBRSxZQUFZLHNCQUFzQjtBQUFBLE1BQ3pDLE9BQUssRUFBRSxZQUFZLHlCQUF5QjtBQUFBLE1BQzVDLE9BQUssRUFBRSxZQUFZLGlCQUFpQjtBQUFBLE1BQ3BDLE9BQUssRUFBRSxZQUFZLG1CQUFtQjtBQUFBLE1BQ3RDLE9BQUssRUFBRSxZQUFZLFdBQVc7QUFBQSxJQUNsQztBQUFBLEVBQ0osQ0FBQztBQUVELFVBQVEsYUFBYSxPQUFPLEVBQUUsR0FBRyxTQUFTLEVBQUUsU0FBUztBQUFBLElBQ2pELGNBQWM7QUFBQSxNQUNWLE9BQUssRUFBRSxZQUFZLGtCQUFrQjtBQUFBLE1BQ3JDLE9BQUssRUFBRSxZQUFZLFlBQVk7QUFBQSxNQUMvQixPQUFLLEVBQUUsWUFBWSxjQUFjO0FBQUEsTUFDakMsT0FBSyxFQUFFLFlBQVksYUFBYTtBQUFBLE1BQ2hDLE9BQUssRUFBRSxZQUFZLGFBQWE7QUFBQSxNQUNoQyxPQUFLLEVBQUUsWUFBWSxpQkFBaUI7QUFBQSxNQUNwQyxPQUFLLEVBQUUsWUFBWSxXQUFXO0FBQUEsSUFDbEM7QUFBQSxFQUNKLENBQUM7QUFDRCxTQUFPLFFBQVEsTUFBTTtBQUN6QjtBQXZWZ0I7OztBQ3pFaEIsSUFBTSxZQUFZLGdCQUFnQjtBQUNsQyxVQUFVLFlBQVksU0FBUyxFQUFFLEtBQUssRUFBRSxNQUFNLFFBQVEsS0FBSzsiLAogICJuYW1lcyI6IFsidCIsICJlIiwgIm4iLCAiciIsICJpIiwgInMiLCAidSIsICJhIiwgIk0iLCAibSIsICJmIiwgImwiLCAiJCIsICJ5IiwgInYiLCAiZyIsICJEIiwgIm8iLCAiZCIsICJjIiwgImgiLCAidCIsICJpIiwgImUiLCAicyIsICJmIiwgIm4iLCAidSIsICJyIiwgIm8iLCAidCIsICJuIiwgImkiLCAibyIsICJyIiwgImUiLCAidSIsICJmIiwgInMiLCAiYSIsICJ0IiwgImkiLCAiZCIsICJuIiwgImUiLCAicyIsICJ0b2tlbiIsICJjb250YWluZXIiLCAiY29udGFpbmVyIiwgInRva2VuIiwgImNvbnRhaW5lciIsICJkYXlqcyIsICJ1dGMiLCAidGltZXpvbmUiLCAiaXNvV2VlayIsICJjb250YWluZXIiLCAiY29udGFpbmVyIiwgImNvbnRhaW5lciIsICJjb250YWluZXIiLCAiY29udGFpbmVyIiwgImNvbnRhaW5lciIsICJnZXRLZXkiLCAic2tpcFBhdGgiLCAia2V5IiwgImNvbnRhaW5lciIsICJjb250YWluZXIiLCAiY29udGFpbmVyIiwgImRpZmYiLCAiY29udGFpbmVyIiwgImNvbnRhaW5lciIsICJkaWZmIiwgImNvbnRhaW5lciJdCn0K diff --git a/wwwroot/js/edge-scroll.js b/wwwroot/js/edge-scroll.js new file mode 100644 index 0000000..e8b0198 --- /dev/null +++ b/wwwroot/js/edge-scroll.js @@ -0,0 +1,104 @@ +// edge-scroll.js - med timeout + tidsbaseret scroll +(function() { + 'use strict'; + + const OUTER_ZONE = 100; // px fra kant (langsom zone) + const INNER_ZONE = 50; // px fra kant (hurtig zone) + const SLOW_SPEED_PXS = 800; // px/sek i outer zone + const FAST_SPEED_PXS = 2400; // px/sek i inner zone + + let scrollableContent = null; + let scrollRAF = null; + let mouseY = 0; + let haveMouse = false; + let lastTs = 0; + let rect = null; + + function init() { + console.log('edge-scroll.js: waiting 1000ms before setup...'); + setTimeout(setup, 1000); + } + + function setup() { + console.log('edge-scroll.js: setup() called'); + + scrollableContent = document.querySelector('swp-scrollable-content'); + if (!scrollableContent) { + console.error('edge-scroll.js: swp-scrollable-content NOT FOUND'); + return; + } + + console.log('edge-scroll.js: found scrollableContent:', scrollableContent); + + // slå smooth scroll fra, så autoscroll er øjeblikkelig + scrollableContent.style.scrollBehavior = 'auto'; + + scrollableContent.addEventListener('mousemove', handleMouseMove, { passive: true }); + scrollableContent.addEventListener('mouseleave', handleMouseLeave, { passive: true }); + + console.log('edge-scroll.js: ✅ listeners attached'); + } + + function handleMouseMove(e) { + haveMouse = true; + mouseY = e.clientY; + if (scrollRAF == null) { + lastTs = performance.now(); + scrollRAF = requestAnimationFrame(scrollTick); + } + } + + function handleMouseLeave() { + haveMouse = false; + stopScrolling(); + } + + function stopScrolling() { + if (scrollRAF != null) { + cancelAnimationFrame(scrollRAF); + scrollRAF = null; + } + lastTs = 0; + } + + function scrollTick(ts) { + const dt = lastTs ? (ts - lastTs) / 1000 : 0; + lastTs = ts; + + if (!rect) rect = scrollableContent.getBoundingClientRect(); + + let vy = 0; + if (haveMouse) { + const distTop = mouseY - rect.top; + const distBot = rect.bottom - mouseY; + + // Check top edge + if (distTop < INNER_ZONE) { + // Inner zone (0-50px) - fast speed + vy = -FAST_SPEED_PXS; + } else if (distTop < OUTER_ZONE) { + // Outer zone (50-100px) - slow speed + vy = -SLOW_SPEED_PXS; + } + // Check bottom edge + else if (distBot < INNER_ZONE) { + // Inner zone (0-50px) - fast speed + vy = FAST_SPEED_PXS; + } else if (distBot < OUTER_ZONE) { + // Outer zone (50-100px) - slow speed + vy = SLOW_SPEED_PXS; + } + } + + if (vy !== 0) { + scrollableContent.scrollTop += vy * dt; + rect = null; // mål kun én gang pr. frame + scrollRAF = requestAnimationFrame(scrollTick); + } else { + stopScrolling(); + } + } + + // start init + init(); +})(); diff --git a/wwwroot/js/elements/SwpEventElement.d.ts b/wwwroot/js/elements/SwpEventElement.d.ts new file mode 100644 index 0000000..fc38ac2 --- /dev/null +++ b/wwwroot/js/elements/SwpEventElement.d.ts @@ -0,0 +1,98 @@ +import { ICalendarEvent } from '../types/CalendarTypes'; +import { Configuration } from '../configurations/CalendarConfig'; +import { DateService } from '../utils/DateService'; +/** + * Base class for event elements + */ +export declare abstract class BaseSwpEventElement extends HTMLElement { + protected dateService: DateService; + protected config: Configuration; + constructor(); + /** + * Create a clone for drag operations + * Must be implemented by subclasses + */ + abstract createClone(): HTMLElement; + get eventId(): string; + set eventId(value: string); + get start(): Date; + set start(value: Date); + get end(): Date; + set end(value: Date); + get title(): string; + set title(value: string); + get description(): string; + set description(value: string); + get type(): string; + set type(value: string); +} +/** + * Web Component for timed calendar events (Light DOM) + */ +export declare class SwpEventElement extends BaseSwpEventElement { + /** + * Observed attributes - changes trigger attributeChangedCallback + */ + static get observedAttributes(): string[]; + /** + * Called when element is added to DOM + */ + connectedCallback(): void; + /** + * Called when observed attribute changes + */ + attributeChangedCallback(name: string, oldValue: string, newValue: string): void; + /** + * Update event position during drag + * @param columnDate - The date of the column + * @param snappedY - The Y position in pixels + */ + updatePosition(columnDate: Date, snappedY: number): void; + /** + * Update event height during resize + * @param newHeight - The new height in pixels + */ + updateHeight(newHeight: number): void; + /** + * Create a clone for drag operations + */ + createClone(): SwpEventElement; + /** + * Render inner HTML structure + */ + private render; + /** + * Update time display when attributes change + */ + private updateDisplay; + /** + * Calculate start/end minutes from Y position + */ + private calculateTimesFromPosition; + /** + * Create SwpEventElement from ICalendarEvent + */ + static fromCalendarEvent(event: ICalendarEvent): SwpEventElement; + /** + * Extract ICalendarEvent from DOM element + */ + static extractCalendarEventFromElement(element: HTMLElement): ICalendarEvent; +} +/** + * Web Component for all-day calendar events + */ +export declare class SwpAllDayEventElement extends BaseSwpEventElement { + connectedCallback(): void; + /** + * Create a clone for drag operations + */ + createClone(): SwpAllDayEventElement; + /** + * Apply CSS grid positioning + */ + applyGridPositioning(row: number, startColumn: number, endColumn: number): void; + /** + * Create from ICalendarEvent + */ + static fromCalendarEvent(event: ICalendarEvent): SwpAllDayEventElement; +} diff --git a/wwwroot/js/elements/SwpEventElement.js b/wwwroot/js/elements/SwpEventElement.js new file mode 100644 index 0000000..96c188f --- /dev/null +++ b/wwwroot/js/elements/SwpEventElement.js @@ -0,0 +1,303 @@ +import { Configuration } from '../configurations/CalendarConfig'; +import { TimeFormatter } from '../utils/TimeFormatter'; +import { DateService } from '../utils/DateService'; +/** + * Base class for event elements + */ +export class BaseSwpEventElement extends HTMLElement { + constructor() { + super(); + // Get singleton instance for web components (can't use DI) + this.config = Configuration.getInstance(); + this.dateService = new DateService(this.config); + } + // ============================================ + // Common Getters/Setters + // ============================================ + get eventId() { + return this.dataset.eventId || ''; + } + set eventId(value) { + this.dataset.eventId = value; + } + get start() { + return new Date(this.dataset.start || ''); + } + set start(value) { + this.dataset.start = this.dateService.toUTC(value); + } + get end() { + return new Date(this.dataset.end || ''); + } + set end(value) { + this.dataset.end = this.dateService.toUTC(value); + } + get title() { + return this.dataset.title || ''; + } + set title(value) { + this.dataset.title = value; + } + get description() { + return this.dataset.description || ''; + } + set description(value) { + this.dataset.description = value; + } + get type() { + return this.dataset.type || 'work'; + } + set type(value) { + this.dataset.type = value; + } +} +/** + * Web Component for timed calendar events (Light DOM) + */ +export class SwpEventElement extends BaseSwpEventElement { + /** + * Observed attributes - changes trigger attributeChangedCallback + */ + static get observedAttributes() { + return ['data-start', 'data-end', 'data-title', 'data-description', 'data-type']; + } + /** + * Called when element is added to DOM + */ + connectedCallback() { + if (!this.hasChildNodes()) { + this.render(); + } + } + /** + * Called when observed attribute changes + */ + attributeChangedCallback(name, oldValue, newValue) { + if (oldValue !== newValue && this.isConnected) { + this.updateDisplay(); + } + } + // ============================================ + // Public Methods + // ============================================ + /** + * Update event position during drag + * @param columnDate - The date of the column + * @param snappedY - The Y position in pixels + */ + updatePosition(columnDate, snappedY) { + // 1. Update visual position + this.style.top = `${snappedY + 1}px`; + // 2. Calculate new timestamps + const { startMinutes, endMinutes } = this.calculateTimesFromPosition(snappedY); + // 3. Update data attributes (triggers attributeChangedCallback) + const startDate = this.dateService.createDateAtTime(columnDate, startMinutes); + let endDate = this.dateService.createDateAtTime(columnDate, endMinutes); + // Handle cross-midnight events + if (endMinutes >= 1440) { + const extraDays = Math.floor(endMinutes / 1440); + endDate = this.dateService.addDays(endDate, extraDays); + } + this.start = startDate; + this.end = endDate; + } + /** + * Update event height during resize + * @param newHeight - The new height in pixels + */ + updateHeight(newHeight) { + // 1. Update visual height + this.style.height = `${newHeight}px`; + // 2. Calculate new end time based on height + const gridSettings = this.config.gridSettings; + const { hourHeight, snapInterval } = gridSettings; + // Get current start time + const start = this.start; + // Calculate duration from height + const rawDurationMinutes = (newHeight / hourHeight) * 60; + // Snap duration to grid interval (like drag & drop) + const snappedDurationMinutes = Math.round(rawDurationMinutes / snapInterval) * snapInterval; + // Calculate new end time by adding snapped duration to start (using DateService for timezone safety) + const endDate = this.dateService.addMinutes(start, snappedDurationMinutes); + // 3. Update end attribute (triggers attributeChangedCallback → updateDisplay) + this.end = endDate; + } + /** + * Create a clone for drag operations + */ + createClone() { + const clone = this.cloneNode(true); + // Apply "clone-" prefix to ID + clone.dataset.eventId = `clone-${this.eventId}`; + // Disable pointer events on clone so it doesn't interfere with hover detection + clone.style.pointerEvents = 'none'; + // Cache original duration + const timeEl = this.querySelector('swp-event-time'); + if (timeEl) { + const duration = timeEl.getAttribute('data-duration'); + if (duration) { + clone.dataset.originalDuration = duration; + } + } + // Set height from original + clone.style.height = this.style.height || `${this.getBoundingClientRect().height}px`; + return clone; + } + // ============================================ + // Private Methods + // ============================================ + /** + * Render inner HTML structure + */ + render() { + const start = this.start; + const end = this.end; + const timeRange = TimeFormatter.formatTimeRange(start, end); + const durationMinutes = (end.getTime() - start.getTime()) / (1000 * 60); + this.innerHTML = ` + ${timeRange} + ${this.title} + ${this.description ? `${this.description}` : ''} + `; + } + /** + * Update time display when attributes change + */ + updateDisplay() { + const timeEl = this.querySelector('swp-event-time'); + const titleEl = this.querySelector('swp-event-title'); + const descEl = this.querySelector('swp-event-description'); + if (timeEl && this.dataset.start && this.dataset.end) { + const start = new Date(this.dataset.start); + const end = new Date(this.dataset.end); + const timeRange = TimeFormatter.formatTimeRange(start, end); + timeEl.textContent = timeRange; + // Update duration attribute + const durationMinutes = (end.getTime() - start.getTime()) / (1000 * 60); + timeEl.setAttribute('data-duration', durationMinutes.toString()); + } + if (titleEl && this.dataset.title) { + titleEl.textContent = this.dataset.title; + } + if (this.dataset.description) { + if (descEl) { + descEl.textContent = this.dataset.description; + } + else if (this.description) { + // Add description element if it doesn't exist + const newDescEl = document.createElement('swp-event-description'); + newDescEl.textContent = this.description; + this.appendChild(newDescEl); + } + } + else if (descEl) { + // Remove description element if description is empty + descEl.remove(); + } + } + /** + * Calculate start/end minutes from Y position + */ + calculateTimesFromPosition(snappedY) { + const gridSettings = this.config.gridSettings; + const { hourHeight, dayStartHour, snapInterval } = gridSettings; + // Get original duration + const originalDuration = parseInt(this.dataset.originalDuration || + this.dataset.duration || + '60'); + // Calculate snapped start minutes + const minutesFromGridStart = (snappedY / hourHeight) * 60; + const actualStartMinutes = (dayStartHour * 60) + minutesFromGridStart; + const snappedStartMinutes = Math.round(actualStartMinutes / snapInterval) * snapInterval; + // Calculate end minutes + const endMinutes = snappedStartMinutes + originalDuration; + return { startMinutes: snappedStartMinutes, endMinutes }; + } + // ============================================ + // Static Factory Methods + // ============================================ + /** + * Create SwpEventElement from ICalendarEvent + */ + static fromCalendarEvent(event) { + const element = document.createElement('swp-event'); + const config = Configuration.getInstance(); + const dateService = new DateService(config); + element.dataset.eventId = event.id; + element.dataset.title = event.title; + element.dataset.description = event.description || ''; + element.dataset.start = dateService.toUTC(event.start); + element.dataset.end = dateService.toUTC(event.end); + element.dataset.type = event.type; + element.dataset.duration = event.metadata?.duration?.toString() || '60'; + return element; + } + /** + * Extract ICalendarEvent from DOM element + */ + static extractCalendarEventFromElement(element) { + return { + id: element.dataset.eventId || '', + title: element.dataset.title || '', + description: element.dataset.description || undefined, + start: new Date(element.dataset.start || ''), + end: new Date(element.dataset.end || ''), + type: element.dataset.type || 'work', + allDay: false, + syncStatus: 'synced', + metadata: { + duration: element.dataset.duration + } + }; + } +} +/** + * Web Component for all-day calendar events + */ +export class SwpAllDayEventElement extends BaseSwpEventElement { + connectedCallback() { + if (!this.textContent) { + this.textContent = this.dataset.title || 'Untitled'; + } + } + /** + * Create a clone for drag operations + */ + createClone() { + const clone = this.cloneNode(true); + // Apply "clone-" prefix to ID + clone.dataset.eventId = `clone-${this.eventId}`; + // Disable pointer events on clone so it doesn't interfere with hover detection + clone.style.pointerEvents = 'none'; + // Preserve full opacity during drag + clone.style.opacity = '1'; + return clone; + } + /** + * Apply CSS grid positioning + */ + applyGridPositioning(row, startColumn, endColumn) { + const gridArea = `${row} / ${startColumn} / ${row + 1} / ${endColumn + 1}`; + this.style.gridArea = gridArea; + } + /** + * Create from ICalendarEvent + */ + static fromCalendarEvent(event) { + const element = document.createElement('swp-allday-event'); + const config = Configuration.getInstance(); + const dateService = new DateService(config); + element.dataset.eventId = event.id; + element.dataset.title = event.title; + element.dataset.start = dateService.toUTC(event.start); + element.dataset.end = dateService.toUTC(event.end); + element.dataset.type = event.type; + element.dataset.allday = 'true'; + element.textContent = event.title; + return element; + } +} +// Register custom elements +customElements.define('swp-event', SwpEventElement); +customElements.define('swp-allday-event', SwpAllDayEventElement); +//# sourceMappingURL=SwpEventElement.js.map \ No newline at end of file diff --git a/wwwroot/js/elements/SwpEventElement.js.map b/wwwroot/js/elements/SwpEventElement.js.map new file mode 100644 index 0000000..e05d269 --- /dev/null +++ b/wwwroot/js/elements/SwpEventElement.js.map @@ -0,0 +1 @@ +{"version":3,"file":"SwpEventElement.js","sourceRoot":"","sources":["../../../src/elements/SwpEventElement.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/factories/CalendarTypeFactory.d.ts b/wwwroot/js/factories/CalendarTypeFactory.d.ts new file mode 100644 index 0000000..2c71862 --- /dev/null +++ b/wwwroot/js/factories/CalendarTypeFactory.d.ts @@ -0,0 +1,55 @@ +import { CalendarMode } from '../types/CalendarTypes'; +import { HeaderRenderer } from '../renderers/HeaderRenderer'; +import { ColumnRenderer } from '../renderers/ColumnRenderer'; +import { EventRendererStrategy } from '../renderers/EventRenderer'; +/** + * Renderer configuration for a calendar type + */ +export interface RendererConfig { + headerRenderer: HeaderRenderer; + columnRenderer: ColumnRenderer; + eventRenderer: EventRendererStrategy; +} +/** + * Factory for creating calendar type-specific renderers + */ +export declare class CalendarTypeFactory { + private static renderers; + private static isInitialized; + /** + * Initialize the factory with default renderers (only runs once) + */ + static initialize(): void; + /** + * Register renderers for a calendar type + */ + static registerRenderers(type: CalendarMode, config: RendererConfig): void; + /** + * Get renderers for a calendar type + */ + static getRenderers(type: CalendarMode): RendererConfig; + /** + * Get header renderer for a calendar type + */ + static getHeaderRenderer(type: CalendarMode): HeaderRenderer; + /** + * Get column renderer for a calendar type + */ + static getColumnRenderer(type: CalendarMode): ColumnRenderer; + /** + * Get event renderer for a calendar type + */ + static getEventRenderer(type: CalendarMode): EventRendererStrategy; + /** + * Check if a calendar type is supported + */ + static isSupported(type: CalendarMode): boolean; + /** + * Get all supported calendar types + */ + static getSupportedTypes(): CalendarMode[]; + /** + * Clear all registered renderers (useful for testing) + */ + static clear(): void; +} diff --git a/wwwroot/js/factories/CalendarTypeFactory.js b/wwwroot/js/factories/CalendarTypeFactory.js new file mode 100644 index 0000000..98bb1ca --- /dev/null +++ b/wwwroot/js/factories/CalendarTypeFactory.js @@ -0,0 +1,84 @@ +// Factory for creating calendar type-specific renderers +import { DateHeaderRenderer, ResourceHeaderRenderer } from '../renderers/HeaderRenderer'; +import { DateColumnRenderer, ResourceColumnRenderer } from '../renderers/ColumnRenderer'; +import { DateEventRenderer, ResourceEventRenderer } from '../renderers/EventRenderer'; +/** + * Factory for creating calendar type-specific renderers + */ +export class CalendarTypeFactory { + /** + * Initialize the factory with default renderers (only runs once) + */ + static initialize() { + if (this.isInitialized) { + return; + } + // Register default renderers + this.registerRenderers('date', { + headerRenderer: new DateHeaderRenderer(), + columnRenderer: new DateColumnRenderer(), + eventRenderer: new DateEventRenderer() + }); + this.registerRenderers('resource', { + headerRenderer: new ResourceHeaderRenderer(), + columnRenderer: new ResourceColumnRenderer(), + eventRenderer: new ResourceEventRenderer() + }); + this.isInitialized = true; + } + /** + * Register renderers for a calendar type + */ + static registerRenderers(type, config) { + this.renderers.set(type, config); + } + /** + * Get renderers for a calendar type + */ + static getRenderers(type) { + const renderers = this.renderers.get(type); + if (!renderers) { + return this.renderers.get('date'); + } + return renderers; + } + /** + * Get header renderer for a calendar type + */ + static getHeaderRenderer(type) { + return this.getRenderers(type).headerRenderer; + } + /** + * Get column renderer for a calendar type + */ + static getColumnRenderer(type) { + return this.getRenderers(type).columnRenderer; + } + /** + * Get event renderer for a calendar type + */ + static getEventRenderer(type) { + return this.getRenderers(type).eventRenderer; + } + /** + * Check if a calendar type is supported + */ + static isSupported(type) { + return this.renderers.has(type); + } + /** + * Get all supported calendar types + */ + static getSupportedTypes() { + return Array.from(this.renderers.keys()); + } + /** + * Clear all registered renderers (useful for testing) + */ + static clear() { + this.renderers.clear(); + } +} +CalendarTypeFactory.renderers = new Map(); +CalendarTypeFactory.isInitialized = false; +//# sourceMappingURL=CalendarTypeFactory.js.map \ No newline at end of file diff --git a/wwwroot/js/factories/CalendarTypeFactory.js.map b/wwwroot/js/factories/CalendarTypeFactory.js.map new file mode 100644 index 0000000..a1d85d8 --- /dev/null +++ b/wwwroot/js/factories/CalendarTypeFactory.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CalendarTypeFactory.js","sourceRoot":"","sources":["../../../src/factories/CalendarTypeFactory.ts"],"names":[],"mappings":"AAAA,wDAAwD;AAGxD,OAAO,EAAkB,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACzG,OAAO,EAAkB,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACzG,OAAO,EAAyB,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAY7G;;GAEG;AACH,MAAM,OAAO,mBAAmB;IAI9B;;OAEG;IACH,MAAM,CAAC,UAAU;QACf,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,OAAO;QACT,CAAC;QAED,6BAA6B;QAC7B,IAAI,CAAC,iBAAiB,CAAC,MAAM,EAAE;YAC7B,cAAc,EAAE,IAAI,kBAAkB,EAAE;YACxC,cAAc,EAAE,IAAI,kBAAkB,EAAE;YACxC,aAAa,EAAE,IAAI,iBAAiB,EAAE;SACvC,CAAC,CAAC;QAEH,IAAI,CAAC,iBAAiB,CAAC,UAAU,EAAE;YACjC,cAAc,EAAE,IAAI,sBAAsB,EAAE;YAC5C,cAAc,EAAE,IAAI,sBAAsB,EAAE;YAC5C,aAAa,EAAE,IAAI,qBAAqB,EAAE;SAC3C,CAAC,CAAC;QAEH,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;IAC5B,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,iBAAiB,CAAC,IAAkB,EAAE,MAAsB;QACjE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,IAAkB;QACpC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAE3C,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC;QACrC,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,iBAAiB,CAAC,IAAkB;QACzC,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,cAAc,CAAC;IAChD,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,iBAAiB,CAAC,IAAkB;QACzC,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,cAAc,CAAC;IAChD,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,IAAkB;QACxC,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,aAAa,CAAC;IAC/C,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,WAAW,CAAC,IAAkB;QACnC,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,iBAAiB;QACtB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,KAAK;QACV,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC;;AAvFc,6BAAS,GAAsC,IAAI,GAAG,EAAE,CAAC;AACzD,iCAAa,GAAY,KAAK,CAAC"} \ No newline at end of file diff --git a/wwwroot/js/factories/ManagerFactory.d.ts b/wwwroot/js/factories/ManagerFactory.d.ts new file mode 100644 index 0000000..2f9edb6 --- /dev/null +++ b/wwwroot/js/factories/ManagerFactory.d.ts @@ -0,0 +1,18 @@ +import { IEventBus } from '../types/CalendarTypes'; +import { CalendarManagers } from '../types/ManagerTypes'; +/** + * Factory for creating and managing calendar managers with proper dependency injection + */ +export declare class ManagerFactory { + private static instance; + private constructor(); + static getInstance(): ManagerFactory; + /** + * Create all managers with proper dependency injection + */ + createManagers(eventBus: IEventBus): CalendarManagers; + /** + * Initialize all managers in the correct order + */ + initializeManagers(managers: CalendarManagers): Promise; +} diff --git a/wwwroot/js/factories/ManagerFactory.js b/wwwroot/js/factories/ManagerFactory.js new file mode 100644 index 0000000..14636be --- /dev/null +++ b/wwwroot/js/factories/ManagerFactory.js @@ -0,0 +1,60 @@ +import { EventManager } from '../managers/EventManager'; +import { EventRenderingService } from '../renderers/EventRendererManager'; +import { GridManager } from '../managers/GridManager'; +import { ScrollManager } from '../managers/ScrollManager'; +import { NavigationManager } from '../managers/NavigationManager'; +import { ViewManager } from '../managers/ViewManager'; +import { CalendarManager } from '../managers/CalendarManager'; +import { DragDropManager } from '../managers/DragDropManager'; +import { AllDayManager } from '../managers/AllDayManager'; +/** + * Factory for creating and managing calendar managers with proper dependency injection + */ +export class ManagerFactory { + constructor() { } + static getInstance() { + if (!ManagerFactory.instance) { + ManagerFactory.instance = new ManagerFactory(); + } + return ManagerFactory.instance; + } + /** + * Create all managers with proper dependency injection + */ + createManagers(eventBus) { + // Create managers in dependency order + const eventManager = new EventManager(eventBus); + const eventRenderer = new EventRenderingService(eventBus, eventManager); + const gridManager = new GridManager(); + const scrollManager = new ScrollManager(); + const navigationManager = new NavigationManager(eventBus, eventRenderer); + const viewManager = new ViewManager(eventBus); + const dragDropManager = new DragDropManager(eventBus); + const allDayManager = new AllDayManager(); + // CalendarManager depends on all other managers + const calendarManager = new CalendarManager(eventBus, eventManager, gridManager, eventRenderer, scrollManager); + return { + eventManager, + eventRenderer, + gridManager, + scrollManager, + navigationManager, + viewManager, + calendarManager, + dragDropManager, + allDayManager + }; + } + /** + * Initialize all managers in the correct order + */ + async initializeManagers(managers) { + try { + await managers.calendarManager.initialize?.(); + } + catch (error) { + throw error; + } + } +} +//# sourceMappingURL=ManagerFactory.js.map \ No newline at end of file diff --git a/wwwroot/js/factories/ManagerFactory.js.map b/wwwroot/js/factories/ManagerFactory.js.map new file mode 100644 index 0000000..e05ff59 --- /dev/null +++ b/wwwroot/js/factories/ManagerFactory.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ManagerFactory.js","sourceRoot":"","sources":["../../../src/factories/ManagerFactory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAG1D;;GAEG;AACH,MAAM,OAAO,cAAc;IAGzB,gBAAuB,CAAC;IAEjB,MAAM,CAAC,WAAW;QACvB,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,CAAC;YAC7B,cAAc,CAAC,QAAQ,GAAG,IAAI,cAAc,EAAE,CAAC;QACjD,CAAC;QACD,OAAO,cAAc,CAAC,QAAQ,CAAC;IACjC,CAAC;IAED;;OAEG;IACI,cAAc,CAAC,QAAmB;QAEvC,sCAAsC;QACtC,MAAM,YAAY,GAAG,IAAI,YAAY,CAAC,QAAQ,CAAC,CAAC;QAChD,MAAM,aAAa,GAAG,IAAI,qBAAqB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QACxE,MAAM,WAAW,GAAG,IAAI,WAAW,EAAE,CAAC;QACtC,MAAM,aAAa,GAAG,IAAI,aAAa,EAAE,CAAC;QAC1C,MAAM,iBAAiB,GAAG,IAAI,iBAAiB,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;QACzE,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,QAAQ,CAAC,CAAC;QAC9C,MAAM,eAAe,GAAG,IAAI,eAAe,CAAC,QAAQ,CAAC,CAAC;QACtD,MAAM,aAAa,GAAG,IAAI,aAAa,EAAE,CAAC;QAE1C,gDAAgD;QAChD,MAAM,eAAe,GAAG,IAAI,eAAe,CACzC,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,aAAa,EACb,aAAa,CACd,CAAC;QAGF,OAAO;YACL,YAAY;YACZ,aAAa;YACb,WAAW;YACX,aAAa;YACb,iBAAiB;YACjB,WAAW;YACX,eAAe;YACf,eAAe;YACf,aAAa;SACd,CAAC;IACJ,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,kBAAkB,CAAC,QAA0B;QAExD,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,eAAe,CAAC,UAAU,EAAE,EAAE,CAAC;QAChD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;CACF"} \ No newline at end of file diff --git a/wwwroot/js/features/all-day/AllDayCollapseService.d.ts b/wwwroot/js/features/all-day/AllDayCollapseService.d.ts new file mode 100644 index 0000000..10a0dc3 --- /dev/null +++ b/wwwroot/js/features/all-day/AllDayCollapseService.d.ts @@ -0,0 +1,45 @@ +/** + * AllDayCollapseService - Manages collapse/expand UI for all-day events + * + * STATELESS SERVICE - Reads expanded state from DOM via AllDayDomReader + * - No persistent state + * - Reads expanded state from DOM CSS class + * - Updates chevron button and overflow indicators + * - Controls event visibility based on row number + */ +import { AllDayHeightService } from './AllDayHeightService'; +export declare class AllDayCollapseService { + private heightService; + constructor(heightService: AllDayHeightService); + /** + * Toggle between expanded and collapsed state + * Reads current state from DOM, toggles it, and updates UI + */ + toggleExpanded(): void; + /** + * Update all UI elements based on current DOM state + */ + private updateUI; + /** + * Update event visibility based on expanded state + */ + private updateEventVisibility; + /** + * Update chevron button visibility and state + */ + private updateChevronButton; + /** + * Update overflow indicators for collapsed state + * Shows "+X more" indicators in columns with overflow + */ + private updateOverflowIndicators; + /** + * Clear all overflow indicators + */ + private clearOverflowIndicators; + /** + * Initialize collapse/expand UI based on current DOM state + * Called after events are rendered + */ + initializeUI(): void; +} diff --git a/wwwroot/js/features/all-day/AllDayCollapseService.js b/wwwroot/js/features/all-day/AllDayCollapseService.js new file mode 100644 index 0000000..fa9036f --- /dev/null +++ b/wwwroot/js/features/all-day/AllDayCollapseService.js @@ -0,0 +1,168 @@ +/** + * AllDayCollapseService - Manages collapse/expand UI for all-day events + * + * STATELESS SERVICE - Reads expanded state from DOM via AllDayDomReader + * - No persistent state + * - Reads expanded state from DOM CSS class + * - Updates chevron button and overflow indicators + * - Controls event visibility based on row number + */ +import { ALL_DAY_CONSTANTS } from '../../configurations/CalendarConfig'; +import { ColumnDetectionUtils } from '../../utils/ColumnDetectionUtils'; +import { AllDayDomReader } from './AllDayDomReader'; +export class AllDayCollapseService { + constructor(heightService) { + this.heightService = heightService; + } + /** + * Toggle between expanded and collapsed state + * Reads current state from DOM, toggles it, and updates UI + */ + toggleExpanded() { + const container = AllDayDomReader.getAllDayContainer(); + if (!container) + return; + // Read current state from DOM + const isCurrentlyExpanded = container.classList.contains('expanded'); + // Toggle state in DOM + if (isCurrentlyExpanded) { + container.classList.remove('expanded'); + } + else { + container.classList.add('expanded'); + } + // Update UI based on new state + this.updateUI(); + } + /** + * Update all UI elements based on current DOM state + */ + updateUI() { + const isExpanded = AllDayDomReader.isExpanded(); + const maxRows = AllDayDomReader.getMaxRowFromEvents(); + // Update chevron button + if (maxRows > ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS) { + this.updateChevronButton(true, isExpanded); + if (isExpanded) { + this.clearOverflowIndicators(); + } + else { + this.updateOverflowIndicators(); + } + } + else { + this.updateChevronButton(false, isExpanded); + this.clearOverflowIndicators(); + } + // Update event visibility + this.updateEventVisibility(isExpanded); + // Calculate height based on expanded state + // When collapsed, show max MAX_COLLAPSED_ROWS, when expanded show all rows + const targetRows = isExpanded ? maxRows : Math.min(maxRows, ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS); + this.heightService.animateToRows(targetRows); + } + /** + * Update event visibility based on expanded state + */ + updateEventVisibility(isExpanded) { + const events = AllDayDomReader.getEventElements(); + events.forEach(event => { + const row = AllDayDomReader.getGridRow(event); + if (row > ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS) { + if (isExpanded) { + event.classList.remove('max-event-overflow-hide'); + event.classList.add('max-event-overflow-show'); + } + else { + event.classList.remove('max-event-overflow-show'); + event.classList.add('max-event-overflow-hide'); + } + } + }); + } + /** + * Update chevron button visibility and state + */ + updateChevronButton(show, isExpanded) { + const headerSpacer = AllDayDomReader.getHeaderSpacer(); + if (!headerSpacer) + return; + let chevron = headerSpacer.querySelector('.allday-chevron'); + if (show && !chevron) { + // Create chevron button + chevron = document.createElement('button'); + chevron.className = 'allday-chevron collapsed'; + chevron.innerHTML = ` + + + + `; + chevron.onclick = () => this.toggleExpanded(); + headerSpacer.appendChild(chevron); + } + else if (!show && chevron) { + // Remove chevron button + chevron.remove(); + } + else if (chevron) { + // Update chevron state + chevron.classList.toggle('collapsed', !isExpanded); + chevron.classList.toggle('expanded', isExpanded); + } + } + /** + * Update overflow indicators for collapsed state + * Shows "+X more" indicators in columns with overflow + */ + updateOverflowIndicators() { + const container = AllDayDomReader.getAllDayContainer(); + if (!container) + return; + const columns = ColumnDetectionUtils.getColumns(); + columns.forEach((columnBounds) => { + const totalEventsInColumn = AllDayDomReader.countEventsInColumn(columnBounds.index); + const overflowCount = totalEventsInColumn - ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS; + if (overflowCount > 0) { + // Check if indicator already exists + let existingIndicator = container.querySelector(`.max-event-indicator[data-column="${columnBounds.index}"]`); + if (existingIndicator) { + // Update existing indicator + existingIndicator.innerHTML = `+${overflowCount + 1} more`; + } + else { + // Create new overflow indicator + const overflowElement = document.createElement('swp-allday-event'); + overflowElement.className = 'max-event-indicator'; + overflowElement.setAttribute('data-column', columnBounds.index.toString()); + overflowElement.style.gridRow = ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS.toString(); + overflowElement.style.gridColumn = columnBounds.index.toString(); + overflowElement.innerHTML = `+${overflowCount + 1} more`; + overflowElement.onclick = (e) => { + e.stopPropagation(); + this.toggleExpanded(); + }; + container.appendChild(overflowElement); + } + } + }); + } + /** + * Clear all overflow indicators + */ + clearOverflowIndicators() { + const container = AllDayDomReader.getAllDayContainer(); + if (!container) + return; + container.querySelectorAll('.max-event-indicator').forEach((element) => { + element.remove(); + }); + } + /** + * Initialize collapse/expand UI based on current DOM state + * Called after events are rendered + */ + initializeUI() { + this.updateUI(); + } +} +//# sourceMappingURL=AllDayCollapseService.js.map \ No newline at end of file diff --git a/wwwroot/js/features/all-day/AllDayCollapseService.js.map b/wwwroot/js/features/all-day/AllDayCollapseService.js.map new file mode 100644 index 0000000..188222f --- /dev/null +++ b/wwwroot/js/features/all-day/AllDayCollapseService.js.map @@ -0,0 +1 @@ +{"version":3,"file":"AllDayCollapseService.js","sourceRoot":"","sources":["../../../../src/features/all-day/AllDayCollapseService.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/features/all-day/AllDayCoordinator.d.ts b/wwwroot/js/features/all-day/AllDayCoordinator.d.ts new file mode 100644 index 0000000..992a8a3 --- /dev/null +++ b/wwwroot/js/features/all-day/AllDayCoordinator.d.ts @@ -0,0 +1,45 @@ +/** + * AllDayCoordinator - Orchestrates all-day event functionality + * + * NO STATE - Only coordinates between services + * - Listens to EventBus events + * - Delegates to specialized services + * - Manages service lifecycle + */ +import { AllDayEventRenderer } from '../../renderers/AllDayEventRenderer'; +import { EventManager } from '../../managers/EventManager'; +import { DateService } from '../../utils/DateService'; +import { AllDayHeightService } from './AllDayHeightService'; +import { AllDayCollapseService } from './AllDayCollapseService'; +import { AllDayDragService } from './AllDayDragService'; +/** + * AllDayCoordinator - Orchestrates all-day event functionality + * Replaces the monolithic AllDayManager with a coordinated service architecture + */ +export declare class AllDayCoordinator { + private allDayEventRenderer; + private eventManager; + private dateService; + private heightService; + private collapseService; + private dragService; + constructor(eventManager: EventManager, allDayEventRenderer: AllDayEventRenderer, dateService: DateService, heightService: AllDayHeightService, collapseService: AllDayCollapseService, dragService: AllDayDragService); + /** + * Setup event listeners and delegate to services + */ + private setupEventListeners; + /** + * Calculate layout for ALL all-day events using AllDayLayoutEngine + */ + private calculateAllDayEventsLayout; + /** + * Recalculate layouts and update height + * Called after events are added/removed/moved in all-day area + * Uses AllDayLayoutEngine to optimally reorganize all events + */ + private recalculateLayoutsAndHeight; + /** + * Public API for collapsing all-day row + */ + collapseAllDayRow(): void; +} diff --git a/wwwroot/js/features/all-day/AllDayCoordinator.js b/wwwroot/js/features/all-day/AllDayCoordinator.js new file mode 100644 index 0000000..65dc583 --- /dev/null +++ b/wwwroot/js/features/all-day/AllDayCoordinator.js @@ -0,0 +1,168 @@ +/** + * AllDayCoordinator - Orchestrates all-day event functionality + * + * NO STATE - Only coordinates between services + * - Listens to EventBus events + * - Delegates to specialized services + * - Manages service lifecycle + */ +import { eventBus } from '../../core/EventBus'; +import { ALL_DAY_CONSTANTS } from '../../configurations/CalendarConfig'; +import { AllDayLayoutEngine } from '../../utils/AllDayLayoutEngine'; +import { CoreEvents } from '../../constants/CoreEvents'; +import { AllDayDomReader } from './AllDayDomReader'; +import { ColumnDetectionUtils } from '../../utils/ColumnDetectionUtils'; +/** + * AllDayCoordinator - Orchestrates all-day event functionality + * Replaces the monolithic AllDayManager with a coordinated service architecture + */ +export class AllDayCoordinator { + constructor(eventManager, allDayEventRenderer, dateService, heightService, collapseService, dragService) { + this.eventManager = eventManager; + this.allDayEventRenderer = allDayEventRenderer; + this.dateService = dateService; + this.heightService = heightService; + this.collapseService = collapseService; + this.dragService = dragService; + // Sync CSS variable with TypeScript constant + document.documentElement.style.setProperty('--single-row-height', `${ALL_DAY_CONSTANTS.EVENT_HEIGHT}px`); + this.setupEventListeners(); + } + /** + * Setup event listeners and delegate to services + */ + setupEventListeners() { + // Timed → All-day conversion + eventBus.on('drag:mouseenter-header', (event) => { + const payload = event.detail; + if (payload.draggedClone.hasAttribute('data-allday')) + return; + console.log('🔄 AllDayCoordinator: Received drag:mouseenter-header', { + targetDate: payload.targetColumn, + originalElementId: payload.originalElement?.dataset?.eventId, + originalElementTag: payload.originalElement?.tagName + }); + this.dragService.handleConvertToAllDay(payload); + // Recalculate layouts and height after timed → all-day conversion + this.recalculateLayoutsAndHeight(); + }); + eventBus.on('drag:mouseleave-header', (event) => { + const { originalElement } = event.detail; + console.log('🚪 AllDayCoordinator: Received drag:mouseleave-header', { + originalElementId: originalElement?.dataset?.eventId + }); + }); + // All-day drag start + eventBus.on('drag:start', (event) => { + const payload = event.detail; + if (!payload.draggedClone?.hasAttribute('data-allday')) + return; + this.allDayEventRenderer.handleDragStart(payload); + }); + // All-day column change + eventBus.on('drag:column-change', (event) => { + const payload = event.detail; + if (!payload.draggedClone?.hasAttribute('data-allday')) + return; + this.dragService.handleColumnChange(payload); + }); + // Drag end + eventBus.on('drag:end', (event) => { + const dragEndPayload = event.detail; + console.log('🎯 AllDayCoordinator: drag:end received', { + target: dragEndPayload.target, + originalElementTag: dragEndPayload.originalElement?.tagName, + hasAllDayAttribute: dragEndPayload.originalElement?.hasAttribute('data-allday'), + eventId: dragEndPayload.originalElement?.dataset.eventId + }); + // Handle all-day → all-day drops (within header) + if (dragEndPayload.target === 'swp-day-header') { + console.log('✅ AllDayCoordinator: Handling all-day → all-day drop'); + this.dragService.handleDragEnd(dragEndPayload); + // Recalculate layouts and height after all-day → all-day repositioning + this.recalculateLayoutsAndHeight(); + return; + } + // Handle all-day → timed conversion (dropped in column) + if (dragEndPayload.target === 'swp-day-column' && + dragEndPayload.originalElement?.hasAttribute('data-allday')) { + const eventId = dragEndPayload.originalElement.dataset.eventId; + console.log('🔄 AllDayCoordinator: All-day → timed conversion', { + eventId + }); + // Remove event element from DOM + const container = AllDayDomReader.getAllDayContainer(); + const eventElement = container?.querySelector(`[data-event-id="${eventId}"]`); + if (eventElement) { + eventElement.remove(); + } + // Recalculate layouts and height after event removal + this.recalculateLayoutsAndHeight(); + } + }); + // Drag cancelled + eventBus.on('drag:cancelled', (event) => { + const { draggedElement, reason } = event.detail; + console.log('🚫 AllDayCoordinator: Drag cancelled', { + eventId: draggedElement?.dataset?.eventId, + reason + }); + }); + // Header ready - render all-day events + eventBus.on('header:ready', async (event) => { + const headerReadyEventPayload = event.detail; + const startDate = new Date(headerReadyEventPayload.headerElements.at(0).date); + const endDate = new Date(headerReadyEventPayload.headerElements.at(-1).date); + const events = await this.eventManager.getEventsForPeriod(startDate, endDate); + // Filter for all-day events + const allDayEvents = events.filter(event => event.allDay); + // Calculate layouts + const layouts = this.calculateAllDayEventsLayout(allDayEvents, headerReadyEventPayload.headerElements); + // Render events + this.allDayEventRenderer.renderAllDayEventsForPeriod(layouts); + // Initialize collapse/expand UI and calculate height + this.collapseService.initializeUI(); + }); + // View changed + eventBus.on(CoreEvents.VIEW_CHANGED, (event) => { + this.allDayEventRenderer.handleViewChanged(event); + }); + } + /** + * Calculate layout for ALL all-day events using AllDayLayoutEngine + */ + calculateAllDayEventsLayout(events, dayHeaders) { + // Initialize layout engine with provided week dates + const layoutEngine = new AllDayLayoutEngine(dayHeaders.map(column => column.date)); + // Calculate layout for all events together + return layoutEngine.calculateLayout(events); + } + /** + * Recalculate layouts and update height + * Called after events are added/removed/moved in all-day area + * Uses AllDayLayoutEngine to optimally reorganize all events + */ + recalculateLayoutsAndHeight() { + // 1. Read current events from DOM + const events = AllDayDomReader.getEventsAsData(); + const weekDates = ColumnDetectionUtils.getColumns(); + // 2. Calculate optimal layouts using greedy algorithm + const layouts = this.calculateAllDayEventsLayout(events, weekDates); + // 3. Apply layouts to DOM + this.dragService.applyLayoutUpdates(layouts); + // 4. Calculate max row from NEW layouts + const maxRow = layouts.length > 0 ? Math.max(...layouts.map(l => l.row)) : 0; + // 5. Check if collapsed state should be maintained + const isExpanded = AllDayDomReader.isExpanded(); + const targetRows = isExpanded ? maxRow : Math.min(maxRow, ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS); + // 6. Animate height to target + this.heightService.animateToRows(targetRows); + } + /** + * Public API for collapsing all-day row + */ + collapseAllDayRow() { + this.heightService.collapseAllDayRow(); + } +} +//# sourceMappingURL=AllDayCoordinator.js.map \ No newline at end of file diff --git a/wwwroot/js/features/all-day/AllDayCoordinator.js.map b/wwwroot/js/features/all-day/AllDayCoordinator.js.map new file mode 100644 index 0000000..7522289 --- /dev/null +++ b/wwwroot/js/features/all-day/AllDayCoordinator.js.map @@ -0,0 +1 @@ +{"version":3,"file":"AllDayCoordinator.js","sourceRoot":"","sources":["../../../../src/features/all-day/AllDayCoordinator.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/features/all-day/AllDayDomReader.d.ts b/wwwroot/js/features/all-day/AllDayDomReader.d.ts new file mode 100644 index 0000000..5142286 --- /dev/null +++ b/wwwroot/js/features/all-day/AllDayDomReader.d.ts @@ -0,0 +1,74 @@ +import { ICalendarEvent } from '../../types/CalendarTypes'; +/** + * AllDayDomReader - Centralized DOM reading utilities for all-day services + * + * STATELESS UTILITY - Pure functions for reading DOM state + * - Consistent selectors across all services + * - Unified computed style approach (not inline styles) + * - Type-safe return values + * - Single source of truth for DOM queries + */ +export declare class AllDayDomReader { + /** + * Get the all-day events container element + */ + static getAllDayContainer(): HTMLElement | null; + /** + * Get the calendar header element + */ + static getCalendarHeader(): HTMLElement | null; + /** + * Get the header spacer element + */ + static getHeaderSpacer(): HTMLElement | null; + /** + * Get all all-day event elements (excluding overflow indicators) + * Returns raw HTMLElements for DOM manipulation + */ + static getEventElements(): HTMLElement[]; + /** + * Get all-day events as ICalendarEvent objects + * Returns parsed data for business logic + */ + static getEventsAsData(): ICalendarEvent[]; + /** + * Get grid row from element using computed style + * Always uses computed style for consistency + */ + static getGridRow(element: HTMLElement): number; + /** + * Get grid column range from element using computed style + */ + static getGridColumnRange(element: HTMLElement): { + start: number; + end: number; + }; + /** + * Get grid area from element using computed style + */ + static getGridArea(element: HTMLElement): string; + /** + * Calculate max row number from all events + * Uses computed styles for accurate reading + */ + static getMaxRowFromEvents(): number; + /** + * Check if all-day container is expanded + */ + static isExpanded(): boolean; + /** + * Get current all-day height from CSS variable + */ + static getCurrentHeight(): number; + /** + * Count events in specific column + */ + static countEventsInColumn(columnIndex: number): number; + /** + * Get current layouts from DOM elements + * Returns map of eventId → layout info for comparison + */ + static getCurrentLayouts(): Map; +} diff --git a/wwwroot/js/features/all-day/AllDayDomReader.js b/wwwroot/js/features/all-day/AllDayDomReader.js new file mode 100644 index 0000000..93405ca --- /dev/null +++ b/wwwroot/js/features/all-day/AllDayDomReader.js @@ -0,0 +1,175 @@ +/** + * AllDayDomReader - Centralized DOM reading utilities for all-day services + * + * STATELESS UTILITY - Pure functions for reading DOM state + * - Consistent selectors across all services + * - Unified computed style approach (not inline styles) + * - Type-safe return values + * - Single source of truth for DOM queries + */ +export class AllDayDomReader { + // ============================================ + // CONTAINER GETTERS + // ============================================ + /** + * Get the all-day events container element + */ + static getAllDayContainer() { + return document.querySelector('swp-calendar-header swp-allday-container'); + } + /** + * Get the calendar header element + */ + static getCalendarHeader() { + return document.querySelector('swp-calendar-header'); + } + /** + * Get the header spacer element + */ + static getHeaderSpacer() { + return document.querySelector('swp-header-spacer'); + } + // ============================================ + // EVENT ELEMENT GETTERS + // ============================================ + /** + * Get all all-day event elements (excluding overflow indicators) + * Returns raw HTMLElements for DOM manipulation + */ + static getEventElements() { + const container = this.getAllDayContainer(); + if (!container) + return []; + return Array.from(container.querySelectorAll('swp-allday-event:not(.max-event-indicator)')); + } + /** + * Get all-day events as ICalendarEvent objects + * Returns parsed data for business logic + */ + static getEventsAsData() { + const elements = this.getEventElements(); + return elements + .map(element => { + const eventId = element.dataset.eventId; + const startStr = element.dataset.start; + const endStr = element.dataset.end; + // Validate required fields + if (!eventId || !startStr || !endStr) { + console.warn('AllDayDomReader: Invalid event data in DOM:', element); + return null; + } + const start = new Date(startStr); + const end = new Date(endStr); + if (isNaN(start.getTime()) || isNaN(end.getTime())) { + console.warn('AllDayDomReader: Invalid event dates:', { startStr, endStr }); + return null; + } + return { + id: eventId, + title: element.dataset.title || '', + start, + end, + type: element.dataset.type || 'task', + allDay: true, + syncStatus: (element.dataset.syncStatus || 'synced') + }; + }) + .filter((event) => event !== null); + } + // ============================================ + // GRID POSITION READERS + // ============================================ + /** + * Get grid row from element using computed style + * Always uses computed style for consistency + */ + static getGridRow(element) { + const computedStyle = window.getComputedStyle(element); + return parseInt(computedStyle.gridRowStart) || 0; + } + /** + * Get grid column range from element using computed style + */ + static getGridColumnRange(element) { + const computedStyle = window.getComputedStyle(element); + return { + start: parseInt(computedStyle.gridColumnStart) || 0, + end: parseInt(computedStyle.gridColumnEnd) || 0 + }; + } + /** + * Get grid area from element using computed style + */ + static getGridArea(element) { + const computedStyle = window.getComputedStyle(element); + return computedStyle.gridArea; + } + /** + * Calculate max row number from all events + * Uses computed styles for accurate reading + */ + static getMaxRowFromEvents() { + const events = this.getEventElements(); + if (events.length === 0) + return 0; + let maxRow = 0; + events.forEach(event => { + const row = this.getGridRow(event); + maxRow = Math.max(maxRow, row); + }); + return maxRow; + } + // ============================================ + // STATE READERS + // ============================================ + /** + * Check if all-day container is expanded + */ + static isExpanded() { + const container = this.getAllDayContainer(); + return container?.classList.contains('expanded') || false; + } + /** + * Get current all-day height from CSS variable + */ + static getCurrentHeight() { + const root = document.documentElement; + const heightStr = root.style.getPropertyValue('--all-day-row-height') || '0px'; + return parseInt(heightStr) || 0; + } + /** + * Count events in specific column + */ + static countEventsInColumn(columnIndex) { + const events = this.getEventElements(); + let count = 0; + events.forEach((event) => { + const { start, end } = this.getGridColumnRange(event); + if (start <= columnIndex && end > columnIndex) { + count++; + } + }); + return count; + } + // ============================================ + // LAYOUT READERS + // ============================================ + /** + * Get current layouts from DOM elements + * Returns map of eventId → layout info for comparison + */ + static getCurrentLayouts() { + const layoutsMap = new Map(); + const events = this.getEventElements(); + events.forEach(event => { + const eventId = event.dataset.eventId; + if (eventId) { + layoutsMap.set(eventId, { + gridArea: this.getGridArea(event) + }); + } + }); + return layoutsMap; + } +} +//# sourceMappingURL=AllDayDomReader.js.map \ No newline at end of file diff --git a/wwwroot/js/features/all-day/AllDayDomReader.js.map b/wwwroot/js/features/all-day/AllDayDomReader.js.map new file mode 100644 index 0000000..9a27f22 --- /dev/null +++ b/wwwroot/js/features/all-day/AllDayDomReader.js.map @@ -0,0 +1 @@ +{"version":3,"file":"AllDayDomReader.js","sourceRoot":"","sources":["../../../../src/features/all-day/AllDayDomReader.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/features/all-day/AllDayDragService.d.ts b/wwwroot/js/features/all-day/AllDayDragService.d.ts new file mode 100644 index 0000000..602d2e2 --- /dev/null +++ b/wwwroot/js/features/all-day/AllDayDragService.d.ts @@ -0,0 +1,50 @@ +/** + * AllDayDragService - Manages drag and drop operations for all-day events + * + * STATELESS SERVICE - Reads all data from DOM via AllDayDomReader + * - No persistent state + * - Handles timed → all-day conversion + * - Handles all-day → all-day repositioning + * - Handles column changes during drag + * - Calculates layouts on-demand from DOM + */ +import { IEventLayout } from '../../utils/AllDayLayoutEngine'; +import { IDragMouseEnterHeaderEventPayload, IDragColumnChangeEventPayload, IDragEndEventPayload } from '../../types/EventTypes'; +import { EventManager } from '../../managers/EventManager'; +import { AllDayEventRenderer } from '../../renderers/AllDayEventRenderer'; +import { DateService } from '../../utils/DateService'; +export declare class AllDayDragService { + private eventManager; + private allDayEventRenderer; + private dateService; + constructor(eventManager: EventManager, allDayEventRenderer: AllDayEventRenderer, dateService: DateService); + /** + * Handle conversion from timed event to all-day event + * Called when dragging a timed event into the header + */ + handleConvertToAllDay(payload: IDragMouseEnterHeaderEventPayload): void; + /** + * Handle column change during drag of all-day event + * Updates grid position while maintaining event span + */ + handleColumnChange(payload: IDragColumnChangeEventPayload): void; + /** + * Handle drag end for all-day → all-day drops + * Recalculates layouts and updates event positions + */ + handleDragEnd(dragEndEvent: IDragEndEventPayload): Promise; + /** + * Calculate layouts for events using AllDayLayoutEngine + */ + private calculateLayouts; + /** + * Apply layout updates to DOM elements + * Only updates elements that have changed position + * Public so AllDayCoordinator can use it for full recalculation + */ + applyLayoutUpdates(newLayouts: IEventLayout[]): void; + /** + * Fade out and remove element + */ + private fadeOutAndRemove; +} diff --git a/wwwroot/js/features/all-day/AllDayDragService.js b/wwwroot/js/features/all-day/AllDayDragService.js new file mode 100644 index 0000000..000994b --- /dev/null +++ b/wwwroot/js/features/all-day/AllDayDragService.js @@ -0,0 +1,183 @@ +/** + * AllDayDragService - Manages drag and drop operations for all-day events + * + * STATELESS SERVICE - Reads all data from DOM via AllDayDomReader + * - No persistent state + * - Handles timed → all-day conversion + * - Handles all-day → all-day repositioning + * - Handles column changes during drag + * - Calculates layouts on-demand from DOM + */ +import { SwpAllDayEventElement } from '../../elements/SwpEventElement'; +import { AllDayLayoutEngine } from '../../utils/AllDayLayoutEngine'; +import { ColumnDetectionUtils } from '../../utils/ColumnDetectionUtils'; +import { ALL_DAY_CONSTANTS } from '../../configurations/CalendarConfig'; +import { AllDayDomReader } from './AllDayDomReader'; +export class AllDayDragService { + constructor(eventManager, allDayEventRenderer, dateService) { + this.eventManager = eventManager; + this.allDayEventRenderer = allDayEventRenderer; + this.dateService = dateService; + } + /** + * Handle conversion from timed event to all-day event + * Called when dragging a timed event into the header + */ + handleConvertToAllDay(payload) { + const allDayContainer = AllDayDomReader.getAllDayContainer(); + if (!allDayContainer) + return; + // Create SwpAllDayEventElement from ICalendarEvent + const allDayElement = SwpAllDayEventElement.fromCalendarEvent(payload.calendarEvent); + // Apply grid positioning + allDayElement.style.gridRow = '1'; + allDayElement.style.gridColumn = payload.targetColumn.index.toString(); + // Remove old swp-event clone + payload.draggedClone.remove(); + // Call delegate to update DragDropManager's draggedClone reference + payload.replaceClone(allDayElement); + // Append to container + allDayContainer.appendChild(allDayElement); + ColumnDetectionUtils.updateColumnBoundsCache(); + } + /** + * Handle column change during drag of all-day event + * Updates grid position while maintaining event span + */ + handleColumnChange(payload) { + const allDayContainer = AllDayDomReader.getAllDayContainer(); + if (!allDayContainer) + return; + const targetColumn = ColumnDetectionUtils.getColumnBounds(payload.mousePosition); + if (!targetColumn || !payload.draggedClone) + return; + // Calculate event span from original grid positioning + const { start: gridColumnStart, end: gridColumnEnd } = AllDayDomReader.getGridColumnRange(payload.draggedClone); + const span = gridColumnEnd - gridColumnStart; + // Update clone position maintaining the span + const newStartColumn = targetColumn.index; + const newEndColumn = newStartColumn + span; + payload.draggedClone.style.gridColumn = `${newStartColumn} / ${newEndColumn}`; + } + /** + * Handle drag end for all-day → all-day drops + * Recalculates layouts and updates event positions + */ + async handleDragEnd(dragEndEvent) { + if (!dragEndEvent.draggedClone) + return; + // Normalize clone ID + dragEndEvent.draggedClone.dataset.eventId = dragEndEvent.draggedClone.dataset.eventId?.replace('clone-', ''); + dragEndEvent.draggedClone.style.pointerEvents = ''; // Re-enable pointer events + dragEndEvent.originalElement.dataset.eventId += '_'; + const eventId = dragEndEvent.draggedClone.dataset.eventId; + const eventDate = dragEndEvent.finalPosition.column?.date; + const eventType = dragEndEvent.draggedClone.dataset.type; + if (!eventDate || !eventId || !eventType) + return; + // Get original dates to preserve time + const originalStartDate = new Date(dragEndEvent.draggedClone.dataset.start); + const originalEndDate = new Date(dragEndEvent.draggedClone.dataset.end); + // Calculate actual duration in milliseconds (preserves hours/minutes/seconds) + const durationMs = originalEndDate.getTime() - originalStartDate.getTime(); + // Create new start date with the new day but preserve original time + const newStartDate = new Date(eventDate); + newStartDate.setHours(originalStartDate.getHours(), originalStartDate.getMinutes(), originalStartDate.getSeconds(), originalStartDate.getMilliseconds()); + // Create new end date by adding duration in milliseconds + const newEndDate = new Date(newStartDate.getTime() + durationMs); + // Update data attributes with new dates (convert to UTC) + dragEndEvent.draggedClone.dataset.start = this.dateService.toUTC(newStartDate); + dragEndEvent.draggedClone.dataset.end = this.dateService.toUTC(newEndDate); + const droppedEvent = { + id: eventId, + title: dragEndEvent.draggedClone.dataset.title || '', + start: newStartDate, + end: newEndDate, + type: eventType, + allDay: true, + syncStatus: 'synced' + }; + // Get all events from DOM and recalculate layouts + const allEventsFromDOM = AllDayDomReader.getEventsAsData(); + const weekDates = ColumnDetectionUtils.getColumns(); + // Replace old event with dropped event + const updatedEvents = [ + ...allEventsFromDOM.filter(event => event.id !== eventId), + droppedEvent + ]; + // Calculate new layouts for ALL events + const newLayouts = this.calculateLayouts(updatedEvents, weekDates); + // Apply layout updates to DOM + this.applyLayoutUpdates(newLayouts); + // Clean up drag styles from the dropped clone + dragEndEvent.draggedClone.classList.remove('dragging'); + dragEndEvent.draggedClone.style.zIndex = ''; + dragEndEvent.draggedClone.style.cursor = ''; + dragEndEvent.draggedClone.style.opacity = ''; + // Apply highlight class to show the dropped event with highlight color + dragEndEvent.draggedClone.classList.add('highlight'); + // Update event in repository to mark as allDay=true + await this.eventManager.updateEvent(eventId, { + start: newStartDate, + end: newEndDate, + allDay: true + }); + this.fadeOutAndRemove(dragEndEvent.originalElement); + } + /** + * Calculate layouts for events using AllDayLayoutEngine + */ + calculateLayouts(events, weekDates) { + const layoutEngine = new AllDayLayoutEngine(weekDates.map(column => column.date)); + return layoutEngine.calculateLayout(events); + } + /** + * Apply layout updates to DOM elements + * Only updates elements that have changed position + * Public so AllDayCoordinator can use it for full recalculation + */ + applyLayoutUpdates(newLayouts) { + const container = AllDayDomReader.getAllDayContainer(); + if (!container) + return; + // Read current layouts from DOM + const currentLayoutsMap = AllDayDomReader.getCurrentLayouts(); + newLayouts.forEach((layout) => { + const currentLayout = currentLayoutsMap.get(layout.calenderEvent.id); + // Only update if layout changed + if (currentLayout?.gridArea !== layout.gridArea) { + const element = container.querySelector(`[data-event-id="${layout.calenderEvent.id}"]`); + if (element) { + element.classList.add('transitioning'); + element.style.gridArea = layout.gridArea; + element.style.gridRow = layout.row.toString(); + element.style.gridColumn = `${layout.startColumn} / ${layout.endColumn + 1}`; + // Update overflow classes based on row + element.classList.remove('max-event-overflow-hide', 'max-event-overflow-show'); + if (layout.row > ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS) { + const isExpanded = AllDayDomReader.isExpanded(); + if (isExpanded) { + element.classList.add('max-event-overflow-show'); + } + else { + element.classList.add('max-event-overflow-hide'); + } + } + // Remove transition class after animation + setTimeout(() => element.classList.remove('transitioning'), 200); + } + } + }); + } + /** + * Fade out and remove element + */ + fadeOutAndRemove(element) { + element.style.transition = 'opacity 0.3s ease-out'; + element.style.opacity = '0'; + setTimeout(() => { + element.remove(); + }, 300); + } +} +//# sourceMappingURL=AllDayDragService.js.map \ No newline at end of file diff --git a/wwwroot/js/features/all-day/AllDayDragService.js.map b/wwwroot/js/features/all-day/AllDayDragService.js.map new file mode 100644 index 0000000..cd7900e --- /dev/null +++ b/wwwroot/js/features/all-day/AllDayDragService.js.map @@ -0,0 +1 @@ +{"version":3,"file":"AllDayDragService.js","sourceRoot":"","sources":["../../../../src/features/all-day/AllDayDragService.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/features/all-day/AllDayHeightService.d.ts b/wwwroot/js/features/all-day/AllDayHeightService.d.ts new file mode 100644 index 0000000..bf02632 --- /dev/null +++ b/wwwroot/js/features/all-day/AllDayHeightService.d.ts @@ -0,0 +1,26 @@ +/** + * AllDayHeightService - Manages all-day row height calculations and animations + * + * STATELESS SERVICE - Reads all data from DOM via AllDayDomReader + * - No persistent state + * - Calculates required rows by reading DOM elements + * - Animates header height based on DOM state + */ +export declare class AllDayHeightService { + /** + * Main entry point - recalculate and animate header height based on DOM + */ + recalculateAndAnimate(): void; + /** + * Animate all-day container to specific number of rows + */ + animateToRows(targetRows: number): void; + /** + * Calculate all-day height based on number of rows + */ + private calculateAllDayHeight; + /** + * Collapse all-day row (animate to 0 rows) + */ + collapseAllDayRow(): void; +} diff --git a/wwwroot/js/features/all-day/AllDayHeightService.js b/wwwroot/js/features/all-day/AllDayHeightService.js new file mode 100644 index 0000000..17d344d --- /dev/null +++ b/wwwroot/js/features/all-day/AllDayHeightService.js @@ -0,0 +1,85 @@ +/** + * AllDayHeightService - Manages all-day row height calculations and animations + * + * STATELESS SERVICE - Reads all data from DOM via AllDayDomReader + * - No persistent state + * - Calculates required rows by reading DOM elements + * - Animates header height based on DOM state + */ +import { ALL_DAY_CONSTANTS } from '../../configurations/CalendarConfig'; +import { eventBus } from '../../core/EventBus'; +import { AllDayDomReader } from './AllDayDomReader'; +export class AllDayHeightService { + /** + * Main entry point - recalculate and animate header height based on DOM + */ + recalculateAndAnimate() { + const requiredRows = AllDayDomReader.getMaxRowFromEvents(); + this.animateToRows(requiredRows); + } + /** + * Animate all-day container to specific number of rows + */ + animateToRows(targetRows) { + const { targetHeight, currentHeight, heightDifference } = this.calculateAllDayHeight(targetRows); + if (targetHeight === currentHeight) + return; // No animation needed + console.log(`🎬 All-day height animation: ${currentHeight}px → ${targetHeight}px (${Math.ceil(currentHeight / ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT)} → ${targetRows} rows)`); + // Get elements + const calendarHeader = AllDayDomReader.getCalendarHeader(); + const headerSpacer = AllDayDomReader.getHeaderSpacer(); + const allDayContainer = AllDayDomReader.getAllDayContainer(); + if (!calendarHeader || !allDayContainer) + return; + // Get current parent height for animation + const currentParentHeight = parseFloat(getComputedStyle(calendarHeader).height); + const targetParentHeight = currentParentHeight + heightDifference; + const animations = [ + calendarHeader.animate([ + { height: `${currentParentHeight}px` }, + { height: `${targetParentHeight}px` } + ], { + duration: 150, + easing: 'ease-out', + fill: 'forwards' + }) + ]; + // Add spacer animation if spacer exists + if (headerSpacer) { + const root = document.documentElement; + const headerHeightStr = root.style.getPropertyValue('--header-height'); + const headerHeight = parseInt(headerHeightStr); + const currentSpacerHeight = headerHeight + currentHeight; + const targetSpacerHeight = headerHeight + targetHeight; + animations.push(headerSpacer.animate([ + { height: `${currentSpacerHeight}px` }, + { height: `${targetSpacerHeight}px` } + ], { + duration: 150, + easing: 'ease-out' + })); + } + // Update CSS variable after animation + Promise.all(animations.map(anim => anim.finished)).then(() => { + const root = document.documentElement; + root.style.setProperty('--all-day-row-height', `${targetHeight}px`); + eventBus.emit('header:height-changed'); + }); + } + /** + * Calculate all-day height based on number of rows + */ + calculateAllDayHeight(targetRows) { + const targetHeight = targetRows * ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT; + const currentHeight = AllDayDomReader.getCurrentHeight(); + const heightDifference = targetHeight - currentHeight; + return { targetHeight, currentHeight, heightDifference }; + } + /** + * Collapse all-day row (animate to 0 rows) + */ + collapseAllDayRow() { + this.animateToRows(0); + } +} +//# sourceMappingURL=AllDayHeightService.js.map \ No newline at end of file diff --git a/wwwroot/js/features/all-day/AllDayHeightService.js.map b/wwwroot/js/features/all-day/AllDayHeightService.js.map new file mode 100644 index 0000000..7652b58 --- /dev/null +++ b/wwwroot/js/features/all-day/AllDayHeightService.js.map @@ -0,0 +1 @@ +{"version":3,"file":"AllDayHeightService.js","sourceRoot":"","sources":["../../../../src/features/all-day/AllDayHeightService.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/features/all-day/index.d.ts b/wwwroot/js/features/all-day/index.d.ts new file mode 100644 index 0000000..2cd4836 --- /dev/null +++ b/wwwroot/js/features/all-day/index.d.ts @@ -0,0 +1,9 @@ +/** + * All-day feature barrel export + * + * Exports all public APIs from the all-day feature + */ +export { AllDayCoordinator } from './AllDayCoordinator'; +export { AllDayHeightService } from './AllDayHeightService'; +export { AllDayCollapseService } from './AllDayCollapseService'; +export { AllDayDragService } from './AllDayDragService'; diff --git a/wwwroot/js/features/all-day/index.js b/wwwroot/js/features/all-day/index.js new file mode 100644 index 0000000..ad0078d --- /dev/null +++ b/wwwroot/js/features/all-day/index.js @@ -0,0 +1,10 @@ +/** + * All-day feature barrel export + * + * Exports all public APIs from the all-day feature + */ +export { AllDayCoordinator } from './AllDayCoordinator'; +export { AllDayHeightService } from './AllDayHeightService'; +export { AllDayCollapseService } from './AllDayCollapseService'; +export { AllDayDragService } from './AllDayDragService'; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/wwwroot/js/features/all-day/index.js.map b/wwwroot/js/features/all-day/index.js.map new file mode 100644 index 0000000..166080e --- /dev/null +++ b/wwwroot/js/features/all-day/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/features/all-day/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC"} \ No newline at end of file diff --git a/wwwroot/js/features/all-day/utils/AllDayDomReader.d.ts b/wwwroot/js/features/all-day/utils/AllDayDomReader.d.ts new file mode 100644 index 0000000..7026c04 --- /dev/null +++ b/wwwroot/js/features/all-day/utils/AllDayDomReader.d.ts @@ -0,0 +1,74 @@ +import { ICalendarEvent } from '../../../types/CalendarTypes'; +/** + * AllDayDomReader - Centralized DOM reading utilities for all-day services + * + * STATELESS UTILITY - Pure functions for reading DOM state + * - Consistent selectors across all services + * - Unified computed style approach (not inline styles) + * - Type-safe return values + * - Single source of truth for DOM queries + */ +export declare class AllDayDomReader { + /** + * Get the all-day events container element + */ + static getAllDayContainer(): HTMLElement | null; + /** + * Get the calendar header element + */ + static getCalendarHeader(): HTMLElement | null; + /** + * Get the header spacer element + */ + static getHeaderSpacer(): HTMLElement | null; + /** + * Get all all-day event elements (excluding overflow indicators) + * Returns raw HTMLElements for DOM manipulation + */ + static getEventElements(): HTMLElement[]; + /** + * Get all-day events as ICalendarEvent objects + * Returns parsed data for business logic + */ + static getEventsAsData(): ICalendarEvent[]; + /** + * Get grid row from element using computed style + * Always uses computed style for consistency + */ + static getGridRow(element: HTMLElement): number; + /** + * Get grid column range from element using computed style + */ + static getGridColumnRange(element: HTMLElement): { + start: number; + end: number; + }; + /** + * Get grid area from element using computed style + */ + static getGridArea(element: HTMLElement): string; + /** + * Calculate max row number from all events + * Uses computed styles for accurate reading + */ + static getMaxRowFromEvents(): number; + /** + * Check if all-day container is expanded + */ + static isExpanded(): boolean; + /** + * Get current all-day height from CSS variable + */ + static getCurrentHeight(): number; + /** + * Count events in specific column + */ + static countEventsInColumn(columnIndex: number): number; + /** + * Get current layouts from DOM elements + * Returns map of eventId → layout info for comparison + */ + static getCurrentLayouts(): Map; +} diff --git a/wwwroot/js/features/all-day/utils/AllDayDomReader.js b/wwwroot/js/features/all-day/utils/AllDayDomReader.js new file mode 100644 index 0000000..93405ca --- /dev/null +++ b/wwwroot/js/features/all-day/utils/AllDayDomReader.js @@ -0,0 +1,175 @@ +/** + * AllDayDomReader - Centralized DOM reading utilities for all-day services + * + * STATELESS UTILITY - Pure functions for reading DOM state + * - Consistent selectors across all services + * - Unified computed style approach (not inline styles) + * - Type-safe return values + * - Single source of truth for DOM queries + */ +export class AllDayDomReader { + // ============================================ + // CONTAINER GETTERS + // ============================================ + /** + * Get the all-day events container element + */ + static getAllDayContainer() { + return document.querySelector('swp-calendar-header swp-allday-container'); + } + /** + * Get the calendar header element + */ + static getCalendarHeader() { + return document.querySelector('swp-calendar-header'); + } + /** + * Get the header spacer element + */ + static getHeaderSpacer() { + return document.querySelector('swp-header-spacer'); + } + // ============================================ + // EVENT ELEMENT GETTERS + // ============================================ + /** + * Get all all-day event elements (excluding overflow indicators) + * Returns raw HTMLElements for DOM manipulation + */ + static getEventElements() { + const container = this.getAllDayContainer(); + if (!container) + return []; + return Array.from(container.querySelectorAll('swp-allday-event:not(.max-event-indicator)')); + } + /** + * Get all-day events as ICalendarEvent objects + * Returns parsed data for business logic + */ + static getEventsAsData() { + const elements = this.getEventElements(); + return elements + .map(element => { + const eventId = element.dataset.eventId; + const startStr = element.dataset.start; + const endStr = element.dataset.end; + // Validate required fields + if (!eventId || !startStr || !endStr) { + console.warn('AllDayDomReader: Invalid event data in DOM:', element); + return null; + } + const start = new Date(startStr); + const end = new Date(endStr); + if (isNaN(start.getTime()) || isNaN(end.getTime())) { + console.warn('AllDayDomReader: Invalid event dates:', { startStr, endStr }); + return null; + } + return { + id: eventId, + title: element.dataset.title || '', + start, + end, + type: element.dataset.type || 'task', + allDay: true, + syncStatus: (element.dataset.syncStatus || 'synced') + }; + }) + .filter((event) => event !== null); + } + // ============================================ + // GRID POSITION READERS + // ============================================ + /** + * Get grid row from element using computed style + * Always uses computed style for consistency + */ + static getGridRow(element) { + const computedStyle = window.getComputedStyle(element); + return parseInt(computedStyle.gridRowStart) || 0; + } + /** + * Get grid column range from element using computed style + */ + static getGridColumnRange(element) { + const computedStyle = window.getComputedStyle(element); + return { + start: parseInt(computedStyle.gridColumnStart) || 0, + end: parseInt(computedStyle.gridColumnEnd) || 0 + }; + } + /** + * Get grid area from element using computed style + */ + static getGridArea(element) { + const computedStyle = window.getComputedStyle(element); + return computedStyle.gridArea; + } + /** + * Calculate max row number from all events + * Uses computed styles for accurate reading + */ + static getMaxRowFromEvents() { + const events = this.getEventElements(); + if (events.length === 0) + return 0; + let maxRow = 0; + events.forEach(event => { + const row = this.getGridRow(event); + maxRow = Math.max(maxRow, row); + }); + return maxRow; + } + // ============================================ + // STATE READERS + // ============================================ + /** + * Check if all-day container is expanded + */ + static isExpanded() { + const container = this.getAllDayContainer(); + return container?.classList.contains('expanded') || false; + } + /** + * Get current all-day height from CSS variable + */ + static getCurrentHeight() { + const root = document.documentElement; + const heightStr = root.style.getPropertyValue('--all-day-row-height') || '0px'; + return parseInt(heightStr) || 0; + } + /** + * Count events in specific column + */ + static countEventsInColumn(columnIndex) { + const events = this.getEventElements(); + let count = 0; + events.forEach((event) => { + const { start, end } = this.getGridColumnRange(event); + if (start <= columnIndex && end > columnIndex) { + count++; + } + }); + return count; + } + // ============================================ + // LAYOUT READERS + // ============================================ + /** + * Get current layouts from DOM elements + * Returns map of eventId → layout info for comparison + */ + static getCurrentLayouts() { + const layoutsMap = new Map(); + const events = this.getEventElements(); + events.forEach(event => { + const eventId = event.dataset.eventId; + if (eventId) { + layoutsMap.set(eventId, { + gridArea: this.getGridArea(event) + }); + } + }); + return layoutsMap; + } +} +//# sourceMappingURL=AllDayDomReader.js.map \ No newline at end of file diff --git a/wwwroot/js/features/all-day/utils/AllDayDomReader.js.map b/wwwroot/js/features/all-day/utils/AllDayDomReader.js.map new file mode 100644 index 0000000..5c193a7 --- /dev/null +++ b/wwwroot/js/features/all-day/utils/AllDayDomReader.js.map @@ -0,0 +1 @@ +{"version":3,"file":"AllDayDomReader.js","sourceRoot":"","sources":["../../../../../src/features/all-day/utils/AllDayDomReader.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/index.d.ts b/wwwroot/js/index.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/wwwroot/js/index.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/wwwroot/js/index.js b/wwwroot/js/index.js new file mode 100644 index 0000000..c6d0063 --- /dev/null +++ b/wwwroot/js/index.js @@ -0,0 +1,171 @@ +// Main entry point for Calendar Plantempus +import { Container } from '@novadi/core'; +import { eventBus } from './core/EventBus'; +import { ConfigManager } from './configurations/ConfigManager'; +import { URLManager } from './utils/URLManager'; +// Import all managers +import { EventManager } from './managers/EventManager'; +import { EventRenderingService } from './renderers/EventRendererManager'; +import { GridManager } from './managers/GridManager'; +import { ScrollManager } from './managers/ScrollManager'; +import { NavigationManager } from './managers/NavigationManager'; +import { NavigationButtons } from './components/NavigationButtons'; +import { ViewSelector } from './components/ViewSelector'; +import { CalendarManager } from './managers/CalendarManager'; +import { DragDropManager } from './managers/DragDropManager'; +import { AllDayManager } from './managers/AllDayManager'; +import { ResizeHandleManager } from './managers/ResizeHandleManager'; +import { EdgeScrollManager } from './managers/EdgeScrollManager'; +import { HeaderManager } from './managers/HeaderManager'; +import { WorkweekPresets } from './components/WorkweekPresets'; +import { IndexedDBEventRepository } from './repositories/IndexedDBEventRepository'; +import { ApiEventRepository } from './repositories/ApiEventRepository'; +import { IndexedDBService } from './storage/IndexedDBService'; +import { OperationQueue } from './storage/OperationQueue'; +// Import workers +import { SyncManager } from './workers/SyncManager'; +// Import renderers +import { DateHeaderRenderer } from './renderers/DateHeaderRenderer'; +import { DateColumnRenderer } from './renderers/ColumnRenderer'; +import { DateEventRenderer } from './renderers/EventRenderer'; +import { AllDayEventRenderer } from './renderers/AllDayEventRenderer'; +import { GridRenderer } from './renderers/GridRenderer'; +import { WeekInfoRenderer } from './renderers/WeekInfoRenderer'; +// Import utilities and services +import { DateService } from './utils/DateService'; +import { TimeFormatter } from './utils/TimeFormatter'; +import { PositionUtils } from './utils/PositionUtils'; +import { WorkHoursManager } from './managers/WorkHoursManager'; +import { EventStackManager } from './managers/EventStackManager'; +import { EventLayoutCoordinator } from './managers/EventLayoutCoordinator'; +/** + * Handle deep linking functionality after managers are initialized + */ +async function handleDeepLinking(eventManager, urlManager) { + try { + const eventId = urlManager.parseEventIdFromURL(); + if (eventId) { + console.log(`Deep linking to event ID: ${eventId}`); + // Wait a bit for managers to be fully ready + setTimeout(async () => { + const success = await eventManager.navigateToEvent(eventId); + if (!success) { + console.warn(`Deep linking failed: Event with ID ${eventId} not found`); + } + }, 500); + } + } + catch (error) { + console.warn('Deep linking failed:', error); + } +} +/** + * Initialize the calendar application using NovaDI + */ +async function initializeCalendar() { + try { + // Load configuration from JSON + const config = await ConfigManager.load(); + // Create NovaDI container + const container = new Container(); + const builder = container.builder(); + // Enable debug mode for development + eventBus.setDebug(true); + // Bind core services as instances + builder.registerInstance(eventBus).as(); + // Register configuration instance + builder.registerInstance(config).as(); + // Register storage and repository services + builder.registerType(IndexedDBService).as(); + builder.registerType(OperationQueue).as(); + builder.registerType(ApiEventRepository).as(); + builder.registerType(IndexedDBEventRepository).as(); + // Register workers + builder.registerType(SyncManager).as(); + // Register renderers + builder.registerType(DateHeaderRenderer).as(); + builder.registerType(DateColumnRenderer).as(); + builder.registerType(DateEventRenderer).as(); + // Register core services and utilities + builder.registerType(DateService).as(); + builder.registerType(EventStackManager).as(); + builder.registerType(EventLayoutCoordinator).as(); + builder.registerType(WorkHoursManager).as(); + builder.registerType(URLManager).as(); + builder.registerType(TimeFormatter).as(); + builder.registerType(PositionUtils).as(); + // Note: AllDayLayoutEngine is instantiated per-operation with specific dates, not a singleton + builder.registerType(WeekInfoRenderer).as(); + builder.registerType(AllDayEventRenderer).as(); + builder.registerType(EventRenderingService).as(); + builder.registerType(GridRenderer).as(); + builder.registerType(GridManager).as(); + builder.registerType(ScrollManager).as(); + builder.registerType(NavigationManager).as(); + builder.registerType(NavigationButtons).as(); + builder.registerType(ViewSelector).as(); + builder.registerType(DragDropManager).as(); + builder.registerType(AllDayManager).as(); + builder.registerType(ResizeHandleManager).as(); + builder.registerType(EdgeScrollManager).as(); + builder.registerType(HeaderManager).as(); + builder.registerType(CalendarManager).as(); + builder.registerType(WorkweekPresets).as(); + builder.registerType(ConfigManager).as(); + builder.registerType(EventManager).as(); + // Build the container + const app = builder.build(); + // Get managers from container + const eb = app.resolveType(); + const calendarManager = app.resolveType(); + const eventManager = app.resolveType(); + const resizeHandleManager = app.resolveType(); + const headerManager = app.resolveType(); + const dragDropManager = app.resolveType(); + const viewSelectorManager = app.resolveType(); + const navigationManager = app.resolveType(); + const navigationButtonsManager = app.resolveType(); + const edgeScrollManager = app.resolveType(); + const allDayManager = app.resolveType(); + const urlManager = app.resolveType(); + const workweekPresetsManager = app.resolveType(); + const configManager = app.resolveType(); + // Initialize managers + await calendarManager.initialize?.(); + await resizeHandleManager.initialize?.(); + // Resolve SyncManager (starts automatically in constructor) + // Resolve SyncManager (starts automatically in constructor) + // Resolve SyncManager (starts automatically in constructor) + // Resolve SyncManager (starts automatically in constructor) + // Resolve SyncManager (starts automatically in constructor) + //const syncManager = app.resolveType(); + // Handle deep linking after managers are initialized + await handleDeepLinking(eventManager, urlManager); + // Expose to window for debugging (with proper typing) + window.calendarDebug = { + eventBus, + app, + calendarManager, + eventManager, + workweekPresetsManager, + //syncManager, + }; + } + catch (error) { + throw error; + } +} +// Initialize when DOM is ready - now handles async properly +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + initializeCalendar().catch(error => { + console.error('Calendar initialization failed:', error); + }); + }); +} +else { + initializeCalendar().catch(error => { + console.error('Calendar initialization failed:', error); + }); +} +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/wwwroot/js/index.js.map b/wwwroot/js/index.js.map new file mode 100644 index 0000000..089ad70 --- /dev/null +++ b/wwwroot/js/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/interfaces/IManager.d.ts b/wwwroot/js/interfaces/IManager.d.ts new file mode 100644 index 0000000..581e5fd --- /dev/null +++ b/wwwroot/js/interfaces/IManager.d.ts @@ -0,0 +1,48 @@ +import { CalendarEvent } from '../types/CalendarTypes'; +/** + * Base interface for all managers + */ +export interface IManager { + /** + * Initialize the manager + */ + initialize?(): Promise | void; + /** + * Refresh the manager's state + */ + refresh?(): void; + /** + * Destroy the manager and clean up resources + */ + destroy?(): void; +} +/** + * Interface for managers that handle events + */ +export interface IEventManager extends IManager { + loadData(): Promise; + getEvents(): CalendarEvent[]; + getEventsForPeriod(startDate: Date, endDate: Date): CalendarEvent[]; +} +/** + * Interface for managers that handle rendering + */ +export interface IRenderingManager extends IManager { + render(): Promise | void; +} +/** + * Interface for managers that handle navigation + */ +export interface INavigationManager extends IManager { + getCurrentWeek(): Date; + navigateToToday(): void; + navigateToNextWeek(): void; + navigateToPreviousWeek(): void; +} +/** + * Interface for managers that handle scrolling + */ +export interface IScrollManager extends IManager { + scrollTo(scrollTop: number): void; + scrollToHour(hour: number): void; +} diff --git a/wwwroot/js/interfaces/IManager.js b/wwwroot/js/interfaces/IManager.js new file mode 100644 index 0000000..6768e2b --- /dev/null +++ b/wwwroot/js/interfaces/IManager.js @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=IManager.js.map \ No newline at end of file diff --git a/wwwroot/js/interfaces/IManager.js.map b/wwwroot/js/interfaces/IManager.js.map new file mode 100644 index 0000000..6495ffb --- /dev/null +++ b/wwwroot/js/interfaces/IManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"IManager.js","sourceRoot":"","sources":["../../../src/interfaces/IManager.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/wwwroot/js/managers/AllDayManager.d.ts b/wwwroot/js/managers/AllDayManager.d.ts new file mode 100644 index 0000000..0fc9919 --- /dev/null +++ b/wwwroot/js/managers/AllDayManager.d.ts @@ -0,0 +1,91 @@ +import { AllDayEventRenderer } from '../renderers/AllDayEventRenderer'; +import { EventManager } from './EventManager'; +import { DateService } from '../utils/DateService'; +/** + * AllDayManager - Handles all-day row height animations and management + * Uses AllDayLayoutEngine for all overlap detection and layout calculation + */ +export declare class AllDayManager { + private allDayEventRenderer; + private eventManager; + private dateService; + private layoutEngine; + private currentAllDayEvents; + private currentWeekDates; + private isExpanded; + private actualRowCount; + constructor(eventManager: EventManager, allDayEventRenderer: AllDayEventRenderer, dateService: DateService); + /** + * Setup event listeners for drag conversions + */ + private setupEventListeners; + private getAllDayContainer; + private getCalendarHeader; + private getHeaderSpacer; + /** + * Read current max row from DOM elements + * Excludes events marked as removing (data-removing attribute) + */ + private getMaxRowFromDOM; + /** + * Get current gridArea for an event from DOM + */ + private getGridAreaFromDOM; + /** + * Count events in a specific column by reading DOM + */ + private countEventsInColumnFromDOM; + /** + * Calculate all-day height based on number of rows + */ + private calculateAllDayHeight; + /** + * Check current all-day events and animate to correct height + * Reads max row directly from DOM elements + */ + checkAndAnimateAllDayHeight(): void; + /** + * Animate all-day container to specific number of rows + */ + animateToRows(targetRows: number): void; + /** + * Calculate layout for ALL all-day events using AllDayLayoutEngine + * This is the correct method that processes all events together for proper overlap detection + */ + private calculateAllDayEventsLayout; + private handleConvertToAllDay; + /** + * Handle drag move for all-day events - SPECIALIZED FOR ALL-DAY CONTAINER + */ + private handleColumnChange; + private fadeOutAndRemove; + /** + * Handle timed → all-day conversion on drop + */ + private handleTimedToAllDayDrop; + /** + * Handle all-day → all-day drop (moving within header) + */ + private handleDragEnd; + /** + * Update chevron button visibility and state + */ + private updateChevronButton; + /** + * Toggle between expanded and collapsed state + */ + private toggleExpanded; + /** + * Count number of events in a specific column using IColumnBounds + * Reads directly from DOM elements + */ + private countEventsInColumn; + /** + * Update overflow indicators for collapsed state + */ + private updateOverflowIndicators; + /** + * Clear overflow indicators and restore normal state + */ + private clearOverflowIndicators; +} diff --git a/wwwroot/js/managers/AllDayManager.js b/wwwroot/js/managers/AllDayManager.js new file mode 100644 index 0000000..4fb2956 --- /dev/null +++ b/wwwroot/js/managers/AllDayManager.js @@ -0,0 +1,528 @@ +// All-day row height management and animations +import { eventBus } from '../core/EventBus'; +import { ALL_DAY_CONSTANTS } from '../configurations/CalendarConfig'; +import { AllDayLayoutEngine } from '../utils/AllDayLayoutEngine'; +import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils'; +import { SwpAllDayEventElement } from '../elements/SwpEventElement'; +import { CoreEvents } from '../constants/CoreEvents'; +/** + * AllDayManager - Handles all-day row height animations and management + * Uses AllDayLayoutEngine for all overlap detection and layout calculation + */ +export class AllDayManager { + constructor(eventManager, allDayEventRenderer, dateService) { + this.layoutEngine = null; + // State tracking for layout calculation + this.currentAllDayEvents = []; + this.currentWeekDates = []; + // Expand/collapse state + this.isExpanded = false; + this.actualRowCount = 0; + this.eventManager = eventManager; + this.allDayEventRenderer = allDayEventRenderer; + this.dateService = dateService; + // Sync CSS variable with TypeScript constant to ensure consistency + document.documentElement.style.setProperty('--single-row-height', `${ALL_DAY_CONSTANTS.EVENT_HEIGHT}px`); + this.setupEventListeners(); + } + /** + * Setup event listeners for drag conversions + */ + setupEventListeners() { + eventBus.on('drag:mouseenter-header', (event) => { + const payload = event.detail; + if (payload.draggedClone.hasAttribute('data-allday')) + return; + console.log('🔄 AllDayManager: Received drag:mouseenter-header', { + targetDate: payload.targetColumn, + originalElementId: payload.originalElement?.dataset?.eventId, + originalElementTag: payload.originalElement?.tagName + }); + this.handleConvertToAllDay(payload); + }); + eventBus.on('drag:mouseleave-header', (event) => { + const { originalElement, cloneElement } = event.detail; + console.log('🚪 AllDayManager: Received drag:mouseleave-header', { + originalElementId: originalElement?.dataset?.eventId + }); + }); + // Listen for drag operations on all-day events + eventBus.on('drag:start', (event) => { + let payload = event.detail; + if (!payload.draggedClone?.hasAttribute('data-allday')) { + return; + } + this.allDayEventRenderer.handleDragStart(payload); + }); + eventBus.on('drag:column-change', (event) => { + let payload = event.detail; + if (!payload.draggedClone?.hasAttribute('data-allday')) { + return; + } + this.handleColumnChange(payload); + }); + eventBus.on('drag:end', (event) => { + let dragEndPayload = event.detail; + console.log('🎯 AllDayManager: drag:end received', { + target: dragEndPayload.target, + originalElementTag: dragEndPayload.originalElement?.tagName, + hasAllDayAttribute: dragEndPayload.originalElement?.hasAttribute('data-allday'), + eventId: dragEndPayload.originalElement?.dataset.eventId + }); + // Handle all-day → all-day drops (within header) + if (dragEndPayload.target === 'swp-day-header' && dragEndPayload.originalElement?.hasAttribute('data-allday')) { + console.log('✅ AllDayManager: Handling all-day → all-day drop'); + this.handleDragEnd(dragEndPayload); + return; + } + // Handle timed → all-day conversion (dropped in header) + if (dragEndPayload.target === 'swp-day-header' && !dragEndPayload.originalElement?.hasAttribute('data-allday')) { + console.log('🔄 AllDayManager: Timed → all-day conversion on drop'); + this.handleTimedToAllDayDrop(dragEndPayload); + return; + } + // Handle all-day → timed conversion (dropped in column) + if (dragEndPayload.target === 'swp-day-column' && dragEndPayload.originalElement?.hasAttribute('data-allday')) { + const eventId = dragEndPayload.originalElement.dataset.eventId; + console.log('🔄 AllDayManager: All-day → timed conversion', { eventId }); + // Mark for removal (sets data-removing attribute) + this.fadeOutAndRemove(dragEndPayload.originalElement); + // Recalculate layout WITHOUT the removed event to compress gaps + const remainingEvents = this.currentAllDayEvents.filter(e => e.id !== eventId); + const newLayouts = this.calculateAllDayEventsLayout(remainingEvents, this.currentWeekDates); + // Re-render all-day events with compressed layout + this.allDayEventRenderer.renderAllDayEventsForPeriod(newLayouts); + // NOW animate height with compressed layout + this.checkAndAnimateAllDayHeight(); + } + }); + // Listen for drag cancellation to recalculate height + eventBus.on('drag:cancelled', (event) => { + const { draggedElement, reason } = event.detail; + console.log('🚫 AllDayManager: Drag cancelled', { + eventId: draggedElement?.dataset?.eventId, + reason + }); + }); + // Listen for header ready - when dates are populated with period data + eventBus.on('header:ready', async (event) => { + let headerReadyEventPayload = event.detail; + let startDate = new Date(headerReadyEventPayload.headerElements.at(0).date); + let endDate = new Date(headerReadyEventPayload.headerElements.at(-1).date); + let events = await this.eventManager.getEventsForPeriod(startDate, endDate); + // Filter for all-day events + const allDayEvents = events.filter(event => event.allDay); + const layouts = this.calculateAllDayEventsLayout(allDayEvents, headerReadyEventPayload.headerElements); + this.allDayEventRenderer.renderAllDayEventsForPeriod(layouts); + this.checkAndAnimateAllDayHeight(); + }); + eventBus.on(CoreEvents.VIEW_CHANGED, (event) => { + this.allDayEventRenderer.handleViewChanged(event); + }); + } + getAllDayContainer() { + return document.querySelector('swp-calendar-header swp-allday-container'); + } + getCalendarHeader() { + return document.querySelector('swp-calendar-header'); + } + getHeaderSpacer() { + return document.querySelector('swp-header-spacer'); + } + /** + * Read current max row from DOM elements + * Excludes events marked as removing (data-removing attribute) + */ + getMaxRowFromDOM() { + const container = this.getAllDayContainer(); + if (!container) + return 0; + let maxRow = 0; + const allDayEvents = container.querySelectorAll('swp-allday-event:not(.max-event-indicator):not([data-removing])'); + allDayEvents.forEach((element) => { + const htmlElement = element; + const row = parseInt(htmlElement.style.gridRow) || 1; + maxRow = Math.max(maxRow, row); + }); + return maxRow; + } + /** + * Get current gridArea for an event from DOM + */ + getGridAreaFromDOM(eventId) { + const container = this.getAllDayContainer(); + if (!container) + return null; + const element = container.querySelector(`[data-event-id="${eventId}"]`); + return element?.style.gridArea || null; + } + /** + * Count events in a specific column by reading DOM + */ + countEventsInColumnFromDOM(columnIndex) { + const container = this.getAllDayContainer(); + if (!container) + return 0; + let count = 0; + const allDayEvents = container.querySelectorAll('swp-allday-event:not(.max-event-indicator)'); + allDayEvents.forEach((element) => { + const htmlElement = element; + const gridColumn = htmlElement.style.gridColumn; + // Parse "1 / 3" format + const match = gridColumn.match(/(\d+)\s*\/\s*(\d+)/); + if (match) { + const startCol = parseInt(match[1]); + const endCol = parseInt(match[2]) - 1; // End is exclusive in CSS + if (startCol <= columnIndex && endCol >= columnIndex) { + count++; + } + } + }); + return count; + } + /** + * Calculate all-day height based on number of rows + */ + calculateAllDayHeight(targetRows) { + const root = document.documentElement; + const targetHeight = targetRows * ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT; + // Read CSS variable directly from style property or default to 0 + const currentHeightStr = root.style.getPropertyValue('--all-day-row-height') || '0px'; + const currentHeight = parseInt(currentHeightStr) || 0; + const heightDifference = targetHeight - currentHeight; + return { targetHeight, currentHeight, heightDifference }; + } + /** + * Check current all-day events and animate to correct height + * Reads max row directly from DOM elements + */ + checkAndAnimateAllDayHeight() { + // Read max row directly from DOM + const maxRows = this.getMaxRowFromDOM(); + console.log('📊 AllDayManager: Height calculation', { + maxRows, + isExpanded: this.isExpanded + }); + // Store actual row count + this.actualRowCount = maxRows; + // Determine what to display + let displayRows = maxRows; + if (maxRows > ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS) { + // Show chevron button + this.updateChevronButton(true); + // Show 4 rows when collapsed (3 events + indicators) + if (!this.isExpanded) { + displayRows = ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS; + this.updateOverflowIndicators(); + } + else { + this.clearOverflowIndicators(); + } + } + else { + // Hide chevron - not needed + this.updateChevronButton(false); + this.clearOverflowIndicators(); + } + console.log('🎬 AllDayManager: Will animate to', { + displayRows, + maxRows, + willAnimate: displayRows !== this.actualRowCount + }); + console.log(`🎯 AllDayManager: Animating to ${displayRows} rows`); + // Animate to required rows (0 = collapse, >0 = expand) + this.animateToRows(displayRows); + } + /** + * Animate all-day container to specific number of rows + */ + animateToRows(targetRows) { + const { targetHeight, currentHeight, heightDifference } = this.calculateAllDayHeight(targetRows); + if (targetHeight === currentHeight) + return; // No animation needed + console.log(`🎬 All-day height animation: ${currentHeight}px → ${targetHeight}px (${Math.ceil(currentHeight / ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT)} → ${targetRows} rows)`); + // Get cached elements + const calendarHeader = this.getCalendarHeader(); + const headerSpacer = this.getHeaderSpacer(); + const allDayContainer = this.getAllDayContainer(); + if (!calendarHeader || !allDayContainer) + return; + // Get current parent height for animation + const currentParentHeight = parseFloat(getComputedStyle(calendarHeader).height); + const targetParentHeight = currentParentHeight + heightDifference; + const animations = [ + calendarHeader.animate([ + { height: `${currentParentHeight}px` }, + { height: `${targetParentHeight}px` } + ], { + duration: 150, + easing: 'ease-out', + fill: 'forwards' + }) + ]; + // Add spacer animation if spacer exists, but don't use fill: 'forwards' + if (headerSpacer) { + const root = document.documentElement; + const headerHeightStr = root.style.getPropertyValue('--header-height'); + const headerHeight = parseInt(headerHeightStr); + const currentSpacerHeight = headerHeight + currentHeight; + const targetSpacerHeight = headerHeight + targetHeight; + animations.push(headerSpacer.animate([ + { height: `${currentSpacerHeight}px` }, + { height: `${targetSpacerHeight}px` } + ], { + duration: 150, + easing: 'ease-out' + // No fill: 'forwards' - let CSS calc() take over after animation + })); + } + // Update CSS variable after animation + Promise.all(animations.map(anim => anim.finished)).then(() => { + const root = document.documentElement; + root.style.setProperty('--all-day-row-height', `${targetHeight}px`); + eventBus.emit('header:height-changed'); + }); + } + /** + * Calculate layout for ALL all-day events using AllDayLayoutEngine + * This is the correct method that processes all events together for proper overlap detection + */ + calculateAllDayEventsLayout(events, dayHeaders) { + // Store current state + this.currentAllDayEvents = events; + this.currentWeekDates = dayHeaders; + // Initialize layout engine with provided week dates + let layoutEngine = new AllDayLayoutEngine(dayHeaders.map(column => column.date)); + // Calculate layout for all events together - AllDayLayoutEngine handles CalendarEvents directly + return layoutEngine.calculateLayout(events); + } + handleConvertToAllDay(payload) { + let allDayContainer = this.getAllDayContainer(); + if (!allDayContainer) + return; + // Create SwpAllDayEventElement from ICalendarEvent + const allDayElement = SwpAllDayEventElement.fromCalendarEvent(payload.calendarEvent); + // Apply grid positioning + allDayElement.style.gridRow = '1'; + allDayElement.style.gridColumn = payload.targetColumn.index.toString(); + // Remove old swp-event clone + payload.draggedClone.remove(); + // Call delegate to update DragDropManager's draggedClone reference + payload.replaceClone(allDayElement); + // Append to container + allDayContainer.appendChild(allDayElement); + ColumnDetectionUtils.updateColumnBoundsCache(); + // Recalculate height after adding all-day event + this.checkAndAnimateAllDayHeight(); + } + /** + * Handle drag move for all-day events - SPECIALIZED FOR ALL-DAY CONTAINER + */ + handleColumnChange(dragColumnChangeEventPayload) { + let allDayContainer = this.getAllDayContainer(); + if (!allDayContainer) + return; + let targetColumn = ColumnDetectionUtils.getColumnBounds(dragColumnChangeEventPayload.mousePosition); + if (targetColumn == null) + return; + if (!dragColumnChangeEventPayload.draggedClone) + return; + // Calculate event span from original grid positioning + const computedStyle = window.getComputedStyle(dragColumnChangeEventPayload.draggedClone); + const gridColumnStart = parseInt(computedStyle.gridColumnStart) || targetColumn.index; + const gridColumnEnd = parseInt(computedStyle.gridColumnEnd) || targetColumn.index + 1; + const span = gridColumnEnd - gridColumnStart; + // Update clone position maintaining the span + const newStartColumn = targetColumn.index; + const newEndColumn = newStartColumn + span; + dragColumnChangeEventPayload.draggedClone.style.gridColumn = `${newStartColumn} / ${newEndColumn}`; + } + fadeOutAndRemove(element) { + console.log('🗑️ AllDayManager: About to remove all-day event', { + eventId: element.dataset.eventId, + element: element.tagName + }); + // Mark element as removing so it's excluded from height calculations + element.setAttribute('data-removing', 'true'); + element.style.transition = 'opacity 0.3s ease-out'; + element.style.opacity = '0'; + setTimeout(() => { + element.remove(); + console.log('✅ AllDayManager: All-day event removed from DOM'); + }, 300); + } + /** + * Handle timed → all-day conversion on drop + */ + async handleTimedToAllDayDrop(dragEndEvent) { + if (!dragEndEvent.draggedClone || !dragEndEvent.finalPosition.column) + return; + const clone = dragEndEvent.draggedClone; + const eventId = clone.eventId.replace('clone-', ''); + const targetDate = dragEndEvent.finalPosition.column.date; + console.log('🔄 AllDayManager: Converting timed event to all-day', { eventId, targetDate }); + // Create new dates preserving time + const newStart = new Date(targetDate); + newStart.setHours(clone.start.getHours(), clone.start.getMinutes(), 0, 0); + const newEnd = new Date(targetDate); + newEnd.setHours(clone.end.getHours(), clone.end.getMinutes(), 0, 0); + // Update event in repository + await this.eventManager.updateEvent(eventId, { + start: newStart, + end: newEnd, + allDay: true + }); + // Remove original timed event + this.fadeOutAndRemove(dragEndEvent.originalElement); + // Add to current all-day events and recalculate layout + const newEvent = { + id: eventId, + title: clone.title, + start: newStart, + end: newEnd, + type: clone.type, + allDay: true, + syncStatus: 'synced' + }; + const updatedEvents = [...this.currentAllDayEvents, newEvent]; + const newLayouts = this.calculateAllDayEventsLayout(updatedEvents, this.currentWeekDates); + this.allDayEventRenderer.renderAllDayEventsForPeriod(newLayouts); + // Animate height + this.checkAndAnimateAllDayHeight(); + } + /** + * Handle all-day → all-day drop (moving within header) + */ + async handleDragEnd(dragEndEvent) { + if (!dragEndEvent.draggedClone || !dragEndEvent.finalPosition.column) + return; + const clone = dragEndEvent.draggedClone; + const eventId = clone.eventId.replace('clone-', ''); + const targetDate = dragEndEvent.finalPosition.column.date; + // Calculate duration in days + const durationDays = this.dateService.differenceInCalendarDays(clone.end, clone.start); + // Create new dates preserving time + const newStart = new Date(targetDate); + newStart.setHours(clone.start.getHours(), clone.start.getMinutes(), 0, 0); + const newEnd = new Date(targetDate); + newEnd.setDate(newEnd.getDate() + durationDays); + newEnd.setHours(clone.end.getHours(), clone.end.getMinutes(), 0, 0); + // Update event in repository + await this.eventManager.updateEvent(eventId, { + start: newStart, + end: newEnd, + allDay: true + }); + // Remove original and fade out + this.fadeOutAndRemove(dragEndEvent.originalElement); + // Recalculate and re-render ALL events + const updatedEvents = this.currentAllDayEvents.map(e => e.id === eventId ? { ...e, start: newStart, end: newEnd } : e); + const newLayouts = this.calculateAllDayEventsLayout(updatedEvents, this.currentWeekDates); + this.allDayEventRenderer.renderAllDayEventsForPeriod(newLayouts); + // Animate height - this also handles overflow classes! + this.checkAndAnimateAllDayHeight(); + } + /** + * Update chevron button visibility and state + */ + updateChevronButton(show) { + const headerSpacer = this.getHeaderSpacer(); + if (!headerSpacer) + return; + let chevron = headerSpacer.querySelector('.allday-chevron'); + if (show && !chevron) { + chevron = document.createElement('button'); + chevron.className = 'allday-chevron collapsed'; + chevron.innerHTML = ` + + + + `; + chevron.onclick = () => this.toggleExpanded(); + headerSpacer.appendChild(chevron); + } + else if (!show && chevron) { + chevron.remove(); + } + else if (chevron) { + chevron.classList.toggle('collapsed', !this.isExpanded); + chevron.classList.toggle('expanded', this.isExpanded); + } + } + /** + * Toggle between expanded and collapsed state + */ + toggleExpanded() { + this.isExpanded = !this.isExpanded; + this.checkAndAnimateAllDayHeight(); + const elements = document.querySelectorAll('swp-allday-container swp-allday-event.max-event-overflow-hide, swp-allday-container swp-allday-event.max-event-overflow-show'); + elements.forEach((element) => { + if (this.isExpanded) { + // ALTID vis når expanded=true + element.classList.remove('max-event-overflow-hide'); + element.classList.add('max-event-overflow-show'); + } + else { + // ALTID skjul når expanded=false + element.classList.remove('max-event-overflow-show'); + element.classList.add('max-event-overflow-hide'); + } + }); + } + /** + * Count number of events in a specific column using IColumnBounds + * Reads directly from DOM elements + */ + countEventsInColumn(columnBounds) { + return this.countEventsInColumnFromDOM(columnBounds.index); + } + /** + * Update overflow indicators for collapsed state + */ + updateOverflowIndicators() { + const container = this.getAllDayContainer(); + if (!container) + return; + // Create overflow indicators for each column that needs them + let columns = ColumnDetectionUtils.getColumns(); + columns.forEach((columnBounds) => { + let totalEventsInColumn = this.countEventsInColumn(columnBounds); + let overflowCount = totalEventsInColumn - ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS; + if (overflowCount > 0) { + // Check if indicator already exists in this column + let existingIndicator = container.querySelector(`.max-event-indicator[data-column="${columnBounds.index}"]`); + if (existingIndicator) { + // Update existing indicator + existingIndicator.innerHTML = `+${overflowCount + 1} more`; + } + else { + // Create new overflow indicator element + let overflowElement = document.createElement('swp-allday-event'); + overflowElement.className = 'max-event-indicator'; + overflowElement.setAttribute('data-column', columnBounds.index.toString()); + overflowElement.style.gridRow = ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS.toString(); + overflowElement.style.gridColumn = columnBounds.index.toString(); + overflowElement.innerHTML = `+${overflowCount + 1} more`; + overflowElement.onclick = (e) => { + e.stopPropagation(); + this.toggleExpanded(); + }; + container.appendChild(overflowElement); + } + } + }); + } + /** + * Clear overflow indicators and restore normal state + */ + clearOverflowIndicators() { + const container = this.getAllDayContainer(); + if (!container) + return; + // Remove all overflow indicator elements + container.querySelectorAll('.max-event-indicator').forEach((element) => { + element.remove(); + }); + } +} +//# sourceMappingURL=AllDayManager.js.map \ No newline at end of file diff --git a/wwwroot/js/managers/AllDayManager.js.map b/wwwroot/js/managers/AllDayManager.js.map new file mode 100644 index 0000000..64ba752 --- /dev/null +++ b/wwwroot/js/managers/AllDayManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"AllDayManager.js","sourceRoot":"","sources":["../../../src/managers/AllDayManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/managers/CalendarManager.d.ts b/wwwroot/js/managers/CalendarManager.d.ts new file mode 100644 index 0000000..b0cf0d2 --- /dev/null +++ b/wwwroot/js/managers/CalendarManager.d.ts @@ -0,0 +1,45 @@ +import { Configuration } from '../configurations/CalendarConfig'; +import { CalendarView, IEventBus } from '../types/CalendarTypes'; +import { EventManager } from './EventManager'; +import { GridManager } from './GridManager'; +import { EventRenderingService } from '../renderers/EventRendererManager'; +import { ScrollManager } from './ScrollManager'; +/** + * CalendarManager - Main coordinator for all calendar managers + */ +export declare class CalendarManager { + private eventBus; + private eventManager; + private gridManager; + private eventRenderer; + private scrollManager; + private config; + private currentView; + private currentDate; + private isInitialized; + constructor(eventBus: IEventBus, eventManager: EventManager, gridManager: GridManager, eventRenderingService: EventRenderingService, scrollManager: ScrollManager, config: Configuration); + /** + * Initialize calendar system using simple direct calls + */ + initialize(): Promise; + /** + * Skift calendar view (dag/uge/måned) + */ + setView(view: CalendarView): void; + /** + * Sæt aktuel dato + */ + setCurrentDate(date: Date): void; + /** + * Setup event listeners for at håndtere events fra andre managers + */ + private setupEventListeners; + /** + * Calculate the current period based on view and date + */ + private calculateCurrentPeriod; + /** + * Handle workweek configuration changes + */ + private handleWorkweekChange; +} diff --git a/wwwroot/js/managers/CalendarManager.js b/wwwroot/js/managers/CalendarManager.js new file mode 100644 index 0000000..fc0caa3 --- /dev/null +++ b/wwwroot/js/managers/CalendarManager.js @@ -0,0 +1,145 @@ +import { CoreEvents } from '../constants/CoreEvents'; +/** + * CalendarManager - Main coordinator for all calendar managers + */ +export class CalendarManager { + constructor(eventBus, eventManager, gridManager, eventRenderingService, scrollManager, config) { + this.currentView = 'week'; + this.currentDate = new Date(); + this.isInitialized = false; + this.eventBus = eventBus; + this.eventManager = eventManager; + this.gridManager = gridManager; + this.eventRenderer = eventRenderingService; + this.scrollManager = scrollManager; + this.config = config; + this.setupEventListeners(); + } + /** + * Initialize calendar system using simple direct calls + */ + async initialize() { + if (this.isInitialized) { + return; + } + try { + // Step 1: Load data + await this.eventManager.loadData(); + // Step 2: Render grid structure + await this.gridManager.render(); + this.scrollManager.initialize(); + this.setView(this.currentView); + this.setCurrentDate(this.currentDate); + this.isInitialized = true; + // Emit initialization complete event + this.eventBus.emit(CoreEvents.INITIALIZED, { + currentDate: this.currentDate, + currentView: this.currentView + }); + } + catch (error) { + throw error; + } + } + /** + * Skift calendar view (dag/uge/måned) + */ + setView(view) { + if (this.currentView === view) { + return; + } + const previousView = this.currentView; + this.currentView = view; + // Emit view change event + this.eventBus.emit(CoreEvents.VIEW_CHANGED, { + previousView, + currentView: view, + date: this.currentDate + }); + } + /** + * Sæt aktuel dato + */ + setCurrentDate(date) { + const previousDate = this.currentDate; + this.currentDate = new Date(date); + // Emit date change event + this.eventBus.emit(CoreEvents.DATE_CHANGED, { + previousDate, + currentDate: this.currentDate, + view: this.currentView + }); + } + /** + * Setup event listeners for at håndtere events fra andre managers + */ + setupEventListeners() { + // Listen for workweek changes only + this.eventBus.on(CoreEvents.WORKWEEK_CHANGED, (event) => { + const customEvent = event; + this.handleWorkweekChange(); + }); + } + /** + * Calculate the current period based on view and date + */ + calculateCurrentPeriod() { + const current = new Date(this.currentDate); + switch (this.currentView) { + case 'day': + const dayStart = new Date(current); + dayStart.setHours(0, 0, 0, 0); + const dayEnd = new Date(current); + dayEnd.setHours(23, 59, 59, 999); + return { + start: dayStart.toISOString(), + end: dayEnd.toISOString() + }; + case 'week': + // Find start of week (Monday) + const weekStart = new Date(current); + const dayOfWeek = weekStart.getDay(); + const daysToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // Sunday = 0, so 6 days back to Monday + weekStart.setDate(weekStart.getDate() - daysToMonday); + weekStart.setHours(0, 0, 0, 0); + // Find end of week (Sunday) + const weekEnd = new Date(weekStart); + weekEnd.setDate(weekEnd.getDate() + 6); + weekEnd.setHours(23, 59, 59, 999); + return { + start: weekStart.toISOString(), + end: weekEnd.toISOString() + }; + case 'month': + const monthStart = new Date(current.getFullYear(), current.getMonth(), 1); + const monthEnd = new Date(current.getFullYear(), current.getMonth() + 1, 0, 23, 59, 59, 999); + return { + start: monthStart.toISOString(), + end: monthEnd.toISOString() + }; + default: + // Fallback to week view + const fallbackStart = new Date(current); + fallbackStart.setDate(fallbackStart.getDate() - 3); + fallbackStart.setHours(0, 0, 0, 0); + const fallbackEnd = new Date(current); + fallbackEnd.setDate(fallbackEnd.getDate() + 3); + fallbackEnd.setHours(23, 59, 59, 999); + return { + start: fallbackStart.toISOString(), + end: fallbackEnd.toISOString() + }; + } + } + /** + * Handle workweek configuration changes + */ + handleWorkweekChange() { + // Simply relay the event - workweek info is in the WORKWEEK_CHANGED event + this.eventBus.emit('workweek:header-update', { + currentDate: this.currentDate, + currentView: this.currentView + }); + } +} +//# sourceMappingURL=CalendarManager.js.map \ No newline at end of file diff --git a/wwwroot/js/managers/CalendarManager.js.map b/wwwroot/js/managers/CalendarManager.js.map new file mode 100644 index 0000000..38d05f7 --- /dev/null +++ b/wwwroot/js/managers/CalendarManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CalendarManager.js","sourceRoot":"","sources":["../../../src/managers/CalendarManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/managers/DragDropManager.d.ts b/wwwroot/js/managers/DragDropManager.d.ts new file mode 100644 index 0000000..f9d266d --- /dev/null +++ b/wwwroot/js/managers/DragDropManager.d.ts @@ -0,0 +1,222 @@ +/** + * DragDropManager - Advanced drag-and-drop system with smooth animations and event type conversion + * + * ARCHITECTURE OVERVIEW: + * ===================== + * DragDropManager provides a sophisticated drag-and-drop system for calendar events that supports: + * - Smooth animated dragging with requestAnimationFrame + * - Automatic event type conversion (timed events ↔ all-day events) + * - Scroll compensation during edge scrolling + * - Grid snapping for precise event placement + * - Column detection and change tracking + * + * KEY FEATURES: + * ============= + * 1. DRAG DETECTION + * - Movement threshold (5px) to distinguish clicks from drags + * - Immediate visual feedback with cloned element + * - Mouse offset tracking for natural drag feel + * + * 2. SMOOTH ANIMATION + * - Uses requestAnimationFrame for 60fps animations + * - Interpolated movement (30% per frame) for smooth transitions + * - Continuous drag:move events for real-time updates + * + * 3. EVENT TYPE CONVERSION + * - Timed → All-day: When dragging into calendar header + * - All-day → Timed: When dragging into day columns + * - Automatic clone replacement with appropriate element type + * + * 4. SCROLL COMPENSATION + * - Tracks scroll delta during edge-scrolling + * - Compensates dragged element position during scroll + * - Prevents visual "jumping" when scrolling while dragging + * + * 5. GRID SNAPPING + * - Snaps to time grid on mouse up + * - Uses PositionUtils for consistent positioning + * - Accounts for mouse offset within event + * + * STATE MANAGEMENT: + * ================= + * Mouse Tracking: + * - mouseDownPosition: Initial click position + * - currentMousePosition: Latest mouse position + * - mouseOffset: Click offset within event (for natural dragging) + * + * Drag State: + * - originalElement: Source event being dragged + * - draggedClone: Animated clone following mouse + * - currentColumn: Column mouse is currently over + * - previousColumn: Last column (for detecting changes) + * - isDragStarted: Whether drag threshold exceeded + * + * Scroll State: + * - scrollDeltaY: Accumulated scroll offset during drag + * - lastScrollTop: Previous scroll position + * - isScrollCompensating: Whether edge-scroll is active + * + * Animation State: + * - dragAnimationId: requestAnimationFrame ID + * - targetY: Desired position for smooth interpolation + * - currentY: Current interpolated position + * + * EVENT FLOW: + * =========== + * 1. Mouse Down (handleMouseDown) + * ├─ Store originalElement and mouse offset + * └─ Wait for movement + * + * 2. Mouse Move (handleMouseMove) + * ├─ Check movement threshold + * ├─ Initialize drag if threshold exceeded (initializeDrag) + * │ ├─ Create clone + * │ ├─ Emit drag:start + * │ └─ Start animation loop + * ├─ Continue drag (continueDrag) + * │ ├─ Calculate target position with scroll compensation + * │ └─ Update animation target + * └─ Detect column changes (detectColumnChange) + * └─ Emit drag:column-change + * + * 3. Animation Loop (animateDrag) + * ├─ Interpolate currentY toward targetY + * ├─ Emit drag:move on each frame + * └─ Schedule next frame until target reached + * + * 4. Event Type Conversion + * ├─ Entering header (handleHeaderMouseEnter) + * │ ├─ Emit drag:mouseenter-header + * │ └─ AllDayManager creates all-day clone + * └─ Entering column (handleColumnMouseEnter) + * ├─ Emit drag:mouseenter-column + * └─ EventRenderingService creates timed clone + * + * 5. Mouse Up (handleMouseUp) + * ├─ Stop animation + * ├─ Snap to grid + * ├─ Detect drop target (header or column) + * ├─ Emit drag:end with final position + * └─ Cleanup drag state + * + * SCROLL COMPENSATION SYSTEM: + * =========================== + * Problem: When EdgeScrollManager scrolls the grid during drag, the dragged element + * can appear to "jump" because the mouse position stays the same but the + * coordinate system (scrollable content) has moved. + * + * Solution: Track cumulative scroll delta and add it to mouse position calculations + * + * Flow: + * 1. EdgeScrollManager starts scrolling → emit edgescroll:started + * 2. DragDropManager sets isScrollCompensating = true + * 3. On each scroll event: + * ├─ Calculate scrollDelta = currentScrollTop - lastScrollTop + * ├─ Accumulate into scrollDeltaY + * └─ Call continueDrag with adjusted position + * 4. continueDrag adds scrollDeltaY to mouse Y coordinate + * 5. On event conversion, reset scrollDeltaY (new clone, new coordinate system) + * + * PERFORMANCE OPTIMIZATIONS: + * ========================== + * - Uses ColumnDetectionUtils cache for fast column lookups + * - Single requestAnimationFrame loop (not per-mousemove) + * - Interpolated animation reduces update frequency + * - Passive scroll listeners + * - Event delegation for header/column detection + * + * USAGE: + * ====== + * const dragDropManager = new DragDropManager(eventBus, positionUtils); + * // Automatically attaches event listeners and manages drag lifecycle + * // Other managers listen to drag:start, drag:move, drag:end, etc. + */ +import { IEventBus } from '../types/CalendarTypes'; +import { PositionUtils } from '../utils/PositionUtils'; +export declare class DragDropManager { + private eventBus; + private mouseDownPosition; + private currentMousePosition; + private mouseOffset; + private originalElement; + private draggedClone; + private currentColumn; + private previousColumn; + private originalSourceColumn; + private isDragStarted; + private readonly dragThreshold; + private scrollableContent; + private scrollDeltaY; + private lastScrollTop; + private isScrollCompensating; + private dragAnimationId; + private targetY; + private currentY; + private targetColumn; + private positionUtils; + constructor(eventBus: IEventBus, positionUtils: PositionUtils); + /** + * Initialize with optimized event listener setup + */ + private init; + private handleGridRendered; + private handleMouseDown; + private handleMouseMove; + /** + * Try to initialize drag based on movement threshold + * Returns true if drag was initialized, false if not enough movement + */ + private initializeDrag; + private continueDrag; + /** + * Detect column change and emit event + */ + private detectColumnChange; + /** + * Optimized mouse up handler with consolidated cleanup + */ + private handleMouseUp; + private cleanupAllClones; + /** + * Cancel drag operation when mouse leaves grid container + * Animates clone back to original position before cleanup + */ + private cancelDrag; + /** + * Optimized snap position calculation using PositionUtils + */ + private calculateSnapPosition; + /** + * Smooth drag animation using requestAnimationFrame + * Emits drag:move events with current draggedClone reference on each frame + */ + private animateDrag; + /** + * Handle scroll during drag - update scrollDeltaY and call continueDrag + */ + private handleScroll; + /** + * Stop drag animation + */ + private stopDragAnimation; + /** + * Clean up drag state + */ + private cleanupDragState; + /** + * Detect drop target - whether dropped in swp-day-column or swp-day-header + */ + private detectDropTarget; + /** + * Handle mouse enter on calendar header - simplified using native events + */ + private handleHeaderMouseEnter; + /** + * Handle mouse enter on day column - for converting all-day to timed events + */ + private handleColumnMouseEnter; + /** + * Handle mouse leave from calendar header - simplified using native events + */ + private handleHeaderMouseLeave; +} diff --git a/wwwroot/js/managers/DragDropManager.js b/wwwroot/js/managers/DragDropManager.js new file mode 100644 index 0000000..5801e24 --- /dev/null +++ b/wwwroot/js/managers/DragDropManager.js @@ -0,0 +1,626 @@ +/** + * DragDropManager - Advanced drag-and-drop system with smooth animations and event type conversion + * + * ARCHITECTURE OVERVIEW: + * ===================== + * DragDropManager provides a sophisticated drag-and-drop system for calendar events that supports: + * - Smooth animated dragging with requestAnimationFrame + * - Automatic event type conversion (timed events ↔ all-day events) + * - Scroll compensation during edge scrolling + * - Grid snapping for precise event placement + * - Column detection and change tracking + * + * KEY FEATURES: + * ============= + * 1. DRAG DETECTION + * - Movement threshold (5px) to distinguish clicks from drags + * - Immediate visual feedback with cloned element + * - Mouse offset tracking for natural drag feel + * + * 2. SMOOTH ANIMATION + * - Uses requestAnimationFrame for 60fps animations + * - Interpolated movement (30% per frame) for smooth transitions + * - Continuous drag:move events for real-time updates + * + * 3. EVENT TYPE CONVERSION + * - Timed → All-day: When dragging into calendar header + * - All-day → Timed: When dragging into day columns + * - Automatic clone replacement with appropriate element type + * + * 4. SCROLL COMPENSATION + * - Tracks scroll delta during edge-scrolling + * - Compensates dragged element position during scroll + * - Prevents visual "jumping" when scrolling while dragging + * + * 5. GRID SNAPPING + * - Snaps to time grid on mouse up + * - Uses PositionUtils for consistent positioning + * - Accounts for mouse offset within event + * + * STATE MANAGEMENT: + * ================= + * Mouse Tracking: + * - mouseDownPosition: Initial click position + * - currentMousePosition: Latest mouse position + * - mouseOffset: Click offset within event (for natural dragging) + * + * Drag State: + * - originalElement: Source event being dragged + * - draggedClone: Animated clone following mouse + * - currentColumn: Column mouse is currently over + * - previousColumn: Last column (for detecting changes) + * - isDragStarted: Whether drag threshold exceeded + * + * Scroll State: + * - scrollDeltaY: Accumulated scroll offset during drag + * - lastScrollTop: Previous scroll position + * - isScrollCompensating: Whether edge-scroll is active + * + * Animation State: + * - dragAnimationId: requestAnimationFrame ID + * - targetY: Desired position for smooth interpolation + * - currentY: Current interpolated position + * + * EVENT FLOW: + * =========== + * 1. Mouse Down (handleMouseDown) + * ├─ Store originalElement and mouse offset + * └─ Wait for movement + * + * 2. Mouse Move (handleMouseMove) + * ├─ Check movement threshold + * ├─ Initialize drag if threshold exceeded (initializeDrag) + * │ ├─ Create clone + * │ ├─ Emit drag:start + * │ └─ Start animation loop + * ├─ Continue drag (continueDrag) + * │ ├─ Calculate target position with scroll compensation + * │ └─ Update animation target + * └─ Detect column changes (detectColumnChange) + * └─ Emit drag:column-change + * + * 3. Animation Loop (animateDrag) + * ├─ Interpolate currentY toward targetY + * ├─ Emit drag:move on each frame + * └─ Schedule next frame until target reached + * + * 4. Event Type Conversion + * ├─ Entering header (handleHeaderMouseEnter) + * │ ├─ Emit drag:mouseenter-header + * │ └─ AllDayManager creates all-day clone + * └─ Entering column (handleColumnMouseEnter) + * ├─ Emit drag:mouseenter-column + * └─ EventRenderingService creates timed clone + * + * 5. Mouse Up (handleMouseUp) + * ├─ Stop animation + * ├─ Snap to grid + * ├─ Detect drop target (header or column) + * ├─ Emit drag:end with final position + * └─ Cleanup drag state + * + * SCROLL COMPENSATION SYSTEM: + * =========================== + * Problem: When EdgeScrollManager scrolls the grid during drag, the dragged element + * can appear to "jump" because the mouse position stays the same but the + * coordinate system (scrollable content) has moved. + * + * Solution: Track cumulative scroll delta and add it to mouse position calculations + * + * Flow: + * 1. EdgeScrollManager starts scrolling → emit edgescroll:started + * 2. DragDropManager sets isScrollCompensating = true + * 3. On each scroll event: + * ├─ Calculate scrollDelta = currentScrollTop - lastScrollTop + * ├─ Accumulate into scrollDeltaY + * └─ Call continueDrag with adjusted position + * 4. continueDrag adds scrollDeltaY to mouse Y coordinate + * 5. On event conversion, reset scrollDeltaY (new clone, new coordinate system) + * + * PERFORMANCE OPTIMIZATIONS: + * ========================== + * - Uses ColumnDetectionUtils cache for fast column lookups + * - Single requestAnimationFrame loop (not per-mousemove) + * - Interpolated animation reduces update frequency + * - Passive scroll listeners + * - Event delegation for header/column detection + * + * USAGE: + * ====== + * const dragDropManager = new DragDropManager(eventBus, positionUtils); + * // Automatically attaches event listeners and manages drag lifecycle + * // Other managers listen to drag:start, drag:move, drag:end, etc. + */ +import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils'; +import { SwpEventElement } from '../elements/SwpEventElement'; +import { CoreEvents } from '../constants/CoreEvents'; +export class DragDropManager { + constructor(eventBus, positionUtils) { + // Mouse tracking with optimized state + this.mouseDownPosition = { x: 0, y: 0 }; + this.currentMousePosition = { x: 0, y: 0 }; + this.mouseOffset = { x: 0, y: 0 }; + this.currentColumn = null; + this.previousColumn = null; + this.originalSourceColumn = null; // Track original start column + this.isDragStarted = false; + // Movement threshold to distinguish click from drag + this.dragThreshold = 5; // pixels + // Scroll compensation + this.scrollableContent = null; + this.scrollDeltaY = 0; // Current scroll delta to apply in continueDrag + this.lastScrollTop = 0; // Last scroll position for delta calculation + this.isScrollCompensating = false; // Track if scroll compensation is active + // Smooth drag animation + this.dragAnimationId = null; + this.targetY = 0; + this.currentY = 0; + this.targetColumn = null; + this.eventBus = eventBus; + this.positionUtils = positionUtils; + this.init(); + } + /** + * Initialize with optimized event listener setup + */ + init() { + // Add event listeners + document.body.addEventListener('mousemove', this.handleMouseMove.bind(this)); + document.body.addEventListener('mousedown', this.handleMouseDown.bind(this)); + document.body.addEventListener('mouseup', this.handleMouseUp.bind(this)); + const calendarContainer = document.querySelector('swp-calendar-container'); + if (calendarContainer) { + calendarContainer.addEventListener('mouseleave', () => { + if (this.originalElement && this.isDragStarted) { + this.cancelDrag(); + } + }); + // Event delegation for header enter/leave + calendarContainer.addEventListener('mouseenter', (e) => { + const target = e.target; + if (target.closest('swp-calendar-header')) { + this.handleHeaderMouseEnter(e); + } + else if (target.closest('swp-day-column')) { + this.handleColumnMouseEnter(e); + } + }, true); // Use capture phase + calendarContainer.addEventListener('mouseleave', (e) => { + const target = e.target; + if (target.closest('swp-calendar-header')) { + this.handleHeaderMouseLeave(e); + } + // Don't handle swp-event mouseleave here - let mousemove handle it + }, true); // Use capture phase + } + // Initialize column bounds cache + ColumnDetectionUtils.updateColumnBoundsCache(); + // Listen to resize events to update cache + window.addEventListener('resize', () => { + ColumnDetectionUtils.updateColumnBoundsCache(); + }); + // Listen to navigation events to update cache + this.eventBus.on('navigation:completed', () => { + ColumnDetectionUtils.updateColumnBoundsCache(); + }); + this.eventBus.on(CoreEvents.GRID_RENDERED, (event) => { + this.handleGridRendered(event); + }); + // Listen to edge-scroll events to control scroll compensation + this.eventBus.on('edgescroll:started', () => { + this.isScrollCompensating = true; + // Gem nuværende scroll position for delta beregning + if (this.scrollableContent) { + this.lastScrollTop = this.scrollableContent.scrollTop; + } + }); + this.eventBus.on('edgescroll:stopped', () => { + this.isScrollCompensating = false; + }); + // Reset scrollDeltaY when event converts (new clone created) + this.eventBus.on('drag:mouseenter-header', () => { + this.scrollDeltaY = 0; + this.lastScrollTop = 0; + }); + this.eventBus.on('drag:mouseenter-column', () => { + this.scrollDeltaY = 0; + this.lastScrollTop = 0; + }); + } + handleGridRendered(event) { + this.scrollableContent = document.querySelector('swp-scrollable-content'); + this.scrollableContent.addEventListener('scroll', this.handleScroll.bind(this), { passive: true }); + } + handleMouseDown(event) { + // Clean up drag state first + this.cleanupDragState(); + ColumnDetectionUtils.updateColumnBoundsCache(); + //this.lastMousePosition = { x: event.clientX, y: event.clientY }; + //this.initialMousePosition = { x: event.clientX, y: event.clientY }; + // Check if mousedown is on an event + const target = event.target; + if (target.closest('swp-resize-handle')) + return; + let eventElement = target; + while (eventElement && eventElement.tagName !== 'SWP-GRID-CONTAINER') { + if (eventElement.tagName === 'SWP-EVENT' || eventElement.tagName === 'SWP-ALLDAY-EVENT') { + break; + } + eventElement = eventElement.parentElement; + if (!eventElement) + return; + } + if (eventElement) { + // Normal drag - prepare for potential dragging + this.originalElement = eventElement; + // Calculate mouse offset within event + const eventRect = eventElement.getBoundingClientRect(); + this.mouseOffset = { + x: event.clientX - eventRect.left, + y: event.clientY - eventRect.top + }; + this.mouseDownPosition = { x: event.clientX, y: event.clientY }; + } + } + handleMouseMove(event) { + if (event.buttons === 1) { + // Always update mouse position from event + this.currentMousePosition = { x: event.clientX, y: event.clientY }; + // Try to initialize drag if not started + if (!this.isDragStarted && this.originalElement) { + if (!this.initializeDrag(this.currentMousePosition)) { + return; // Not enough movement yet + } + } + // Continue drag if started (også under scroll - accumulatedScrollDelta kompenserer) + if (this.isDragStarted && this.originalElement && this.draggedClone) { + this.continueDrag(this.currentMousePosition); + this.detectColumnChange(this.currentMousePosition); + } + } + } + /** + * Try to initialize drag based on movement threshold + * Returns true if drag was initialized, false if not enough movement + */ + initializeDrag(currentPosition) { + const deltaX = Math.abs(currentPosition.x - this.mouseDownPosition.x); + const deltaY = Math.abs(currentPosition.y - this.mouseDownPosition.y); + const totalMovement = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + if (totalMovement < this.dragThreshold) { + return false; // Not enough movement + } + // Start drag + this.isDragStarted = true; + // Set high z-index on event-group if exists, otherwise on event itself + const eventGroup = this.originalElement.closest('swp-event-group'); + if (eventGroup) { + eventGroup.style.zIndex = '9999'; + } + else { + this.originalElement.style.zIndex = '9999'; + } + const originalElement = this.originalElement; + this.currentColumn = ColumnDetectionUtils.getColumnBounds(currentPosition); + this.originalSourceColumn = this.currentColumn; // Store original source column at drag start + this.draggedClone = originalElement.createClone(); + const dragStartPayload = { + originalElement: this.originalElement, + draggedClone: this.draggedClone, + mousePosition: this.mouseDownPosition, + mouseOffset: this.mouseOffset, + columnBounds: this.currentColumn + }; + this.eventBus.emit('drag:start', dragStartPayload); + return true; + } + continueDrag(currentPosition) { + if (!this.draggedClone.hasAttribute("data-allday")) { + // Calculate raw position from mouse (no snapping) + const column = ColumnDetectionUtils.getColumnBounds(currentPosition); + if (column) { + // Calculate raw Y position relative to column (accounting for mouse offset) + const columnRect = column.boundingClientRect; + // Beregn position fra mus + scroll delta kompensation + const adjustedMouseY = currentPosition.y + this.scrollDeltaY; + const eventTopY = adjustedMouseY - columnRect.top - this.mouseOffset.y; + this.targetY = Math.max(0, eventTopY); + this.targetColumn = column; + // Start animation loop if not already running + if (this.dragAnimationId === null) { + this.currentY = parseFloat(this.draggedClone.style.top) || 0; + this.animateDrag(); + } + } + } + } + /** + * Detect column change and emit event + */ + detectColumnChange(currentPosition) { + const newColumn = ColumnDetectionUtils.getColumnBounds(currentPosition); + if (newColumn == null) + return; + if (newColumn.index !== this.currentColumn?.index) { + this.previousColumn = this.currentColumn; + this.currentColumn = newColumn; + const dragColumnChangePayload = { + originalElement: this.originalElement, + draggedClone: this.draggedClone, + previousColumn: this.previousColumn, + newColumn, + mousePosition: currentPosition + }; + this.eventBus.emit('drag:column-change', dragColumnChangePayload); + } + } + /** + * Optimized mouse up handler with consolidated cleanup + */ + handleMouseUp(event) { + this.stopDragAnimation(); + if (this.originalElement) { + // Only emit drag:end if drag was actually started + if (this.isDragStarted) { + const mousePosition = { x: event.clientX, y: event.clientY }; + // Snap to grid on mouse up (like ResizeHandleManager) + const column = ColumnDetectionUtils.getColumnBounds(mousePosition); + if (!column) + return; + // Get current position and snap it to grid + const snappedY = this.calculateSnapPosition(mousePosition.y, column); + // Update clone to snapped position immediately + if (this.draggedClone) { + this.draggedClone.style.top = `${snappedY}px`; + } + // Detect drop target (swp-day-column or swp-day-header) + const dropTarget = this.detectDropTarget(mousePosition); + if (!dropTarget) + throw "dropTarget is null"; + const dragEndPayload = { + originalElement: this.originalElement, + draggedClone: this.draggedClone, + mousePosition, + originalSourceColumn: this.originalSourceColumn, + finalPosition: { column, snappedY }, // Where drag ended + target: dropTarget + }; + this.eventBus.emit('drag:end', dragEndPayload); + this.cleanupDragState(); + } + else { + // This was just a click - emit click event instead + this.eventBus.emit('event:click', { + clickedElement: this.originalElement, + mousePosition: { x: event.clientX, y: event.clientY } + }); + } + } + } + // Add a cleanup method that finds and removes ALL clones + cleanupAllClones() { + // Remove clones from all possible locations + const allClones = document.querySelectorAll('[data-event-id^="clone"]'); + if (allClones.length > 0) { + allClones.forEach(clone => clone.remove()); + } + } + /** + * Cancel drag operation when mouse leaves grid container + * Animates clone back to original position before cleanup + */ + cancelDrag() { + if (!this.originalElement || !this.draggedClone) + return; + // Get current clone position + const cloneRect = this.draggedClone.getBoundingClientRect(); + // Get original element position + const originalRect = this.originalElement.getBoundingClientRect(); + // Calculate distance to animate + const deltaX = originalRect.left - cloneRect.left; + const deltaY = originalRect.top - cloneRect.top; + // Add transition for smooth animation + this.draggedClone.style.transition = 'transform 300ms ease-out'; + this.draggedClone.style.transform = `translate(${deltaX}px, ${deltaY}px)`; + // Wait for animation to complete, then cleanup + setTimeout(() => { + this.cleanupAllClones(); + if (this.originalElement) { + this.originalElement.style.opacity = ''; + this.originalElement.style.cursor = ''; + } + this.eventBus.emit('drag:cancelled', { + originalElement: this.originalElement, + reason: 'mouse-left-grid' + }); + this.cleanupDragState(); + this.stopDragAnimation(); + }, 300); + } + /** + * Optimized snap position calculation using PositionUtils + */ + calculateSnapPosition(mouseY, column) { + // Calculate where the event top would be (accounting for mouse offset) + const eventTopY = mouseY - this.mouseOffset.y; + // Snap the event top position, not the mouse position + const snappedY = this.positionUtils.getPositionFromCoordinate(eventTopY, column); + return Math.max(0, snappedY); + } + /** + * Smooth drag animation using requestAnimationFrame + * Emits drag:move events with current draggedClone reference on each frame + */ + animateDrag() { + if (!this.isDragStarted || !this.draggedClone || !this.targetColumn) { + this.dragAnimationId = null; + return; + } + // Smooth interpolation towards target + const diff = this.targetY - this.currentY; + const step = diff * 0.3; // 30% of distance per frame + // Update if difference is significant + if (Math.abs(diff) > 0.5) { + this.currentY += step; + // Emit drag:move event with current draggedClone reference + const dragMovePayload = { + originalElement: this.originalElement, + draggedClone: this.draggedClone, // Always uses current reference + mousePosition: this.currentMousePosition, // Use current mouse position! + snappedY: this.currentY, + columnBounds: this.targetColumn, + mouseOffset: this.mouseOffset + }; + this.eventBus.emit('drag:move', dragMovePayload); + this.dragAnimationId = requestAnimationFrame(() => this.animateDrag()); + } + else { + // Close enough - snap to target + this.currentY = this.targetY; + // Emit final position + const dragMovePayload = { + originalElement: this.originalElement, + draggedClone: this.draggedClone, + mousePosition: this.currentMousePosition, // Use current mouse position! + snappedY: this.currentY, + columnBounds: this.targetColumn, + mouseOffset: this.mouseOffset + }; + this.eventBus.emit('drag:move', dragMovePayload); + this.dragAnimationId = null; + } + } + /** + * Handle scroll during drag - update scrollDeltaY and call continueDrag + */ + handleScroll() { + if (!this.isDragStarted || !this.draggedClone || !this.scrollableContent || !this.isScrollCompensating) + return; + const currentScrollTop = this.scrollableContent.scrollTop; + const scrollDelta = currentScrollTop - this.lastScrollTop; + // Gem scroll delta for continueDrag + this.scrollDeltaY += scrollDelta; + this.lastScrollTop = currentScrollTop; + // Kald continueDrag med nuværende mus position + this.continueDrag(this.currentMousePosition); + } + /** + * Stop drag animation + */ + stopDragAnimation() { + if (this.dragAnimationId !== null) { + cancelAnimationFrame(this.dragAnimationId); + this.dragAnimationId = null; + } + } + /** + * Clean up drag state + */ + cleanupDragState() { + this.previousColumn = null; + this.originalElement = null; + this.draggedClone = null; + this.currentColumn = null; + this.originalSourceColumn = null; + this.isDragStarted = false; + this.scrollDeltaY = 0; + this.lastScrollTop = 0; + } + /** + * Detect drop target - whether dropped in swp-day-column or swp-day-header + */ + detectDropTarget(position) { + // Traverse up the DOM tree to find the target container + let currentElement = this.draggedClone; + while (currentElement && currentElement !== document.body) { + if (currentElement.tagName === 'SWP-ALLDAY-CONTAINER') { + return 'swp-day-header'; + } + if (currentElement.tagName === 'SWP-DAY-COLUMN') { + return 'swp-day-column'; + } + currentElement = currentElement.parentElement; + } + return null; + } + /** + * Handle mouse enter on calendar header - simplified using native events + */ + handleHeaderMouseEnter(event) { + // Only handle if we're dragging a timed event (not all-day) + if (!this.isDragStarted || !this.draggedClone) { + return; + } + const position = { x: event.clientX, y: event.clientY }; + const targetColumn = ColumnDetectionUtils.getColumnBounds(position); + if (targetColumn) { + const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone); + const dragMouseEnterPayload = { + targetColumn: targetColumn, + mousePosition: position, + originalElement: this.originalElement, + draggedClone: this.draggedClone, + calendarEvent: calendarEvent, + replaceClone: (newClone) => { + this.draggedClone = newClone; + this.dragAnimationId === null; + } + }; + this.eventBus.emit('drag:mouseenter-header', dragMouseEnterPayload); + } + } + /** + * Handle mouse enter on day column - for converting all-day to timed events + */ + handleColumnMouseEnter(event) { + // Only handle if we're dragging an all-day event + if (!this.isDragStarted || !this.draggedClone || !this.draggedClone.hasAttribute('data-allday')) { + return; + } + const position = { x: event.clientX, y: event.clientY }; + const targetColumn = ColumnDetectionUtils.getColumnBounds(position); + if (!targetColumn) { + return; + } + // Calculate snapped Y position + const snappedY = this.calculateSnapPosition(position.y, targetColumn); + // Extract ICalendarEvent from the dragged clone + const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone); + const dragMouseEnterPayload = { + targetColumn: targetColumn, + mousePosition: position, + snappedY: snappedY, + originalElement: this.originalElement, + draggedClone: this.draggedClone, + calendarEvent: calendarEvent, + replaceClone: (newClone) => { + this.draggedClone = newClone; + this.dragAnimationId === null; + this.stopDragAnimation(); + } + }; + this.eventBus.emit('drag:mouseenter-column', dragMouseEnterPayload); + } + /** + * Handle mouse leave from calendar header - simplified using native events + */ + handleHeaderMouseLeave(event) { + // Only handle if we're dragging an all-day event + if (!this.isDragStarted || !this.draggedClone || !this.draggedClone.hasAttribute("data-allday")) { + return; + } + const position = { x: event.clientX, y: event.clientY }; + const targetColumn = ColumnDetectionUtils.getColumnBounds(position); + if (!targetColumn) { + return; + } + const dragMouseLeavePayload = { + targetDate: targetColumn.date, + mousePosition: position, + originalElement: this.originalElement, + draggedClone: this.draggedClone + }; + this.eventBus.emit('drag:mouseleave-header', dragMouseLeavePayload); + } +} +//# sourceMappingURL=DragDropManager.js.map \ No newline at end of file diff --git a/wwwroot/js/managers/DragDropManager.js.map b/wwwroot/js/managers/DragDropManager.js.map new file mode 100644 index 0000000..fc410e8 --- /dev/null +++ b/wwwroot/js/managers/DragDropManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"DragDropManager.js","sourceRoot":"","sources":["../../../src/managers/DragDropManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/managers/DragHoverManager.d.ts b/wwwroot/js/managers/DragHoverManager.d.ts new file mode 100644 index 0000000..000bddb --- /dev/null +++ b/wwwroot/js/managers/DragHoverManager.d.ts @@ -0,0 +1,31 @@ +/** + * DragHoverManager - Handles event hover tracking + * Fully autonomous - listens to mouse events and manages hover state independently + */ +import { IEventBus } from '../types/CalendarTypes'; +export declare class DragHoverManager { + private eventBus; + private isHoverTrackingActive; + private currentHoveredEvent; + private calendarContainer; + constructor(eventBus: IEventBus); + private init; + private setupEventListeners; + /** + * Handle mouse enter on swp-event - activate hover tracking + */ + private handleEventMouseEnter; + /** + * Check if mouse is still over the currently hovered event + */ + private checkEventHover; + /** + * Clear hover state + */ + private clearEventHover; + /** + * Deactivate hover tracking and clear any current hover + * Called via event bus when drag starts + */ + private deactivateTracking; +} diff --git a/wwwroot/js/managers/DragHoverManager.js b/wwwroot/js/managers/DragHoverManager.js new file mode 100644 index 0000000..c92b9f3 --- /dev/null +++ b/wwwroot/js/managers/DragHoverManager.js @@ -0,0 +1,101 @@ +/** + * DragHoverManager - Handles event hover tracking + * Fully autonomous - listens to mouse events and manages hover state independently + */ +export class DragHoverManager { + constructor(eventBus) { + this.eventBus = eventBus; + this.isHoverTrackingActive = false; + this.currentHoveredEvent = null; + this.calendarContainer = null; + this.init(); + } + init() { + // Wait for DOM to be ready + setTimeout(() => { + this.calendarContainer = document.querySelector('swp-calendar-container'); + if (this.calendarContainer) { + this.setupEventListeners(); + } + }, 100); + // Listen to drag start to deactivate hover tracking + this.eventBus.on('drag:start', () => { + this.deactivateTracking(); + }); + } + setupEventListeners() { + if (!this.calendarContainer) + return; + // Listen to mouseenter on events (using event delegation) + this.calendarContainer.addEventListener('mouseenter', (e) => { + const target = e.target; + const eventElement = target.closest('swp-event'); + if (eventElement) { + this.handleEventMouseEnter(e, eventElement); + } + }, true); // Use capture phase + // Listen to mousemove globally to track when mouse leaves event bounds + document.body.addEventListener('mousemove', (e) => { + if (this.isHoverTrackingActive && e.buttons === 0) { + this.checkEventHover(e); + } + }); + } + /** + * Handle mouse enter on swp-event - activate hover tracking + */ + handleEventMouseEnter(event, eventElement) { + // Only handle hover if mouse button is up + if (event.buttons === 0) { + // Clear any previous hover first + if (this.currentHoveredEvent && this.currentHoveredEvent !== eventElement) { + this.currentHoveredEvent.classList.remove('hover'); + } + this.isHoverTrackingActive = true; + this.currentHoveredEvent = eventElement; + eventElement.classList.add('hover'); + this.eventBus.emit('event:hover:start', { element: eventElement }); + } + } + /** + * Check if mouse is still over the currently hovered event + */ + checkEventHover(event) { + // Only track hover when active and mouse button is up + if (!this.isHoverTrackingActive || !this.currentHoveredEvent) + return; + const rect = this.currentHoveredEvent.getBoundingClientRect(); + const mouseX = event.clientX; + const mouseY = event.clientY; + // Check if mouse is still within the current hovered event + const isStillInside = mouseX >= rect.left && mouseX <= rect.right && + mouseY >= rect.top && mouseY <= rect.bottom; + // If mouse left the event + if (!isStillInside) { + // Only disable tracking and clear if mouse is NOT pressed (allow resize to work) + if (event.buttons === 0) { + this.isHoverTrackingActive = false; + this.clearEventHover(); + } + } + } + /** + * Clear hover state + */ + clearEventHover() { + if (this.currentHoveredEvent) { + this.currentHoveredEvent.classList.remove('hover'); + this.eventBus.emit('event:hover:end', { element: this.currentHoveredEvent }); + this.currentHoveredEvent = null; + } + } + /** + * Deactivate hover tracking and clear any current hover + * Called via event bus when drag starts + */ + deactivateTracking() { + this.isHoverTrackingActive = false; + this.clearEventHover(); + } +} +//# sourceMappingURL=DragHoverManager.js.map \ No newline at end of file diff --git a/wwwroot/js/managers/DragHoverManager.js.map b/wwwroot/js/managers/DragHoverManager.js.map new file mode 100644 index 0000000..fdda8d6 --- /dev/null +++ b/wwwroot/js/managers/DragHoverManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"DragHoverManager.js","sourceRoot":"","sources":["../../../src/managers/DragHoverManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/managers/EdgeScrollManager.d.ts b/wwwroot/js/managers/EdgeScrollManager.d.ts new file mode 100644 index 0000000..da8cdda --- /dev/null +++ b/wwwroot/js/managers/EdgeScrollManager.d.ts @@ -0,0 +1,30 @@ +/** + * EdgeScrollManager - Auto-scroll when dragging near edges + * Uses time-based scrolling with 2-zone system for variable speed + */ +import { IEventBus } from '../types/CalendarTypes'; +export declare class EdgeScrollManager { + private eventBus; + private scrollableContent; + private timeGrid; + private draggedClone; + private scrollRAF; + private mouseY; + private isDragging; + private isScrolling; + private lastTs; + private rect; + private initialScrollTop; + private scrollListener; + private readonly OUTER_ZONE; + private readonly INNER_ZONE; + private readonly SLOW_SPEED_PXS; + private readonly FAST_SPEED_PXS; + constructor(eventBus: IEventBus); + private init; + private subscribeToEvents; + private startDrag; + private stopDrag; + private handleScroll; + private scrollTick; +} diff --git a/wwwroot/js/managers/EdgeScrollManager.js b/wwwroot/js/managers/EdgeScrollManager.js new file mode 100644 index 0000000..7855e51 --- /dev/null +++ b/wwwroot/js/managers/EdgeScrollManager.js @@ -0,0 +1,191 @@ +/** + * EdgeScrollManager - Auto-scroll when dragging near edges + * Uses time-based scrolling with 2-zone system for variable speed + */ +export class EdgeScrollManager { + constructor(eventBus) { + this.eventBus = eventBus; + this.scrollableContent = null; + this.timeGrid = null; + this.draggedClone = null; + this.scrollRAF = null; + this.mouseY = 0; + this.isDragging = false; + this.isScrolling = false; // Track if edge-scroll is active + this.lastTs = 0; + this.rect = null; + this.initialScrollTop = 0; + this.scrollListener = null; + // Constants - fixed values as per requirements + this.OUTER_ZONE = 100; // px from edge (slow zone) + this.INNER_ZONE = 50; // px from edge (fast zone) + this.SLOW_SPEED_PXS = 140; // px/sec in outer zone + this.FAST_SPEED_PXS = 640; // px/sec in inner zone + this.init(); + } + init() { + // Wait for DOM to be ready + setTimeout(() => { + this.scrollableContent = document.querySelector('swp-scrollable-content'); + this.timeGrid = document.querySelector('swp-time-grid'); + if (this.scrollableContent) { + // Disable smooth scroll for instant auto-scroll + this.scrollableContent.style.scrollBehavior = 'auto'; + // Add scroll listener to detect actual scrolling + this.scrollListener = this.handleScroll.bind(this); + this.scrollableContent.addEventListener('scroll', this.scrollListener, { passive: true }); + } + }, 100); + // Listen to mousemove directly from document to always get mouse coords + document.body.addEventListener('mousemove', (e) => { + if (this.isDragging) { + this.mouseY = e.clientY; + } + }); + this.subscribeToEvents(); + } + subscribeToEvents() { + // Listen to drag events from DragDropManager + this.eventBus.on('drag:start', (event) => { + const payload = event.detail; + this.draggedClone = payload.draggedClone; + this.startDrag(); + }); + this.eventBus.on('drag:end', () => this.stopDrag()); + this.eventBus.on('drag:cancelled', () => this.stopDrag()); + // Stop scrolling when event converts to/from all-day + this.eventBus.on('drag:mouseenter-header', () => { + console.log('🔄 EdgeScrollManager: Event converting to all-day - stopping scroll'); + this.stopDrag(); + }); + this.eventBus.on('drag:mouseenter-column', () => { + this.startDrag(); + }); + } + startDrag() { + console.log('🎬 EdgeScrollManager: Starting drag'); + this.isDragging = true; + this.isScrolling = false; // Reset scroll state + this.lastTs = performance.now(); + // Save initial scroll position + if (this.scrollableContent) { + this.initialScrollTop = this.scrollableContent.scrollTop; + } + if (this.scrollRAF === null) { + this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts)); + } + } + stopDrag() { + this.isDragging = false; + // Emit stopped event if we were scrolling + if (this.isScrolling) { + this.isScrolling = false; + console.log('🛑 EdgeScrollManager: Edge-scroll stopped (drag ended)'); + this.eventBus.emit('edgescroll:stopped', {}); + } + if (this.scrollRAF !== null) { + cancelAnimationFrame(this.scrollRAF); + this.scrollRAF = null; + } + this.rect = null; + this.lastTs = 0; + this.initialScrollTop = 0; + } + handleScroll() { + if (!this.isDragging || !this.scrollableContent) + return; + const currentScrollTop = this.scrollableContent.scrollTop; + const scrollDelta = Math.abs(currentScrollTop - this.initialScrollTop); + // Only emit started event if we've actually scrolled more than 1px + if (scrollDelta > 1 && !this.isScrolling) { + this.isScrolling = true; + console.log('💾 EdgeScrollManager: Edge-scroll started (actual scroll detected)', { + initialScrollTop: this.initialScrollTop, + currentScrollTop, + scrollDelta + }); + this.eventBus.emit('edgescroll:started', {}); + } + } + scrollTick(ts) { + const dt = this.lastTs ? (ts - this.lastTs) / 1000 : 0; + this.lastTs = ts; + if (!this.scrollableContent) { + this.stopDrag(); + return; + } + // Cache rect for performance (only measure once per frame) + if (!this.rect) { + this.rect = this.scrollableContent.getBoundingClientRect(); + } + let vy = 0; + if (this.isDragging) { + const distTop = this.mouseY - this.rect.top; + const distBot = this.rect.bottom - this.mouseY; + // Check top edge + if (distTop < this.INNER_ZONE) { + vy = -this.FAST_SPEED_PXS; + } + else if (distTop < this.OUTER_ZONE) { + vy = -this.SLOW_SPEED_PXS; + } + // Check bottom edge + else if (distBot < this.INNER_ZONE) { + vy = this.FAST_SPEED_PXS; + } + else if (distBot < this.OUTER_ZONE) { + vy = this.SLOW_SPEED_PXS; + } + } + if (vy !== 0 && this.isDragging && this.timeGrid && this.draggedClone) { + // Check if we can scroll in the requested direction + const currentScrollTop = this.scrollableContent.scrollTop; + const scrollableHeight = this.scrollableContent.clientHeight; + const timeGridHeight = this.timeGrid.clientHeight; + // Get dragged element position and height + const cloneRect = this.draggedClone.getBoundingClientRect(); + const cloneBottom = cloneRect.bottom; + const timeGridRect = this.timeGrid.getBoundingClientRect(); + const timeGridBottom = timeGridRect.bottom; + // Check boundaries + const atTop = currentScrollTop <= 0 && vy < 0; + const atBottom = (cloneBottom >= timeGridBottom) && vy > 0; + if (atTop || atBottom) { + // At boundary - stop scrolling + if (this.isScrolling) { + this.isScrolling = false; + this.initialScrollTop = this.scrollableContent.scrollTop; + console.log('🛑 EdgeScrollManager: Edge-scroll stopped (reached boundary)'); + this.eventBus.emit('edgescroll:stopped', {}); + } + // Continue RAF loop to detect when mouse moves away from boundary + if (this.isDragging) { + this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts)); + } + } + else { + // Not at boundary - apply scroll + this.scrollableContent.scrollTop += vy * dt; + this.rect = null; // Invalidate cache for next frame + this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts)); + } + } + else { + // Mouse moved away from edge - stop scrolling + if (this.isScrolling) { + this.isScrolling = false; + this.initialScrollTop = this.scrollableContent.scrollTop; // Reset for next scroll + console.log('🛑 EdgeScrollManager: Edge-scroll stopped (mouse left edge)'); + this.eventBus.emit('edgescroll:stopped', {}); + } + // Continue RAF loop even if not scrolling, to detect edge entry + if (this.isDragging) { + this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts)); + } + else { + this.stopDrag(); + } + } + } +} +//# sourceMappingURL=EdgeScrollManager.js.map \ No newline at end of file diff --git a/wwwroot/js/managers/EdgeScrollManager.js.map b/wwwroot/js/managers/EdgeScrollManager.js.map new file mode 100644 index 0000000..72c0b1f --- /dev/null +++ b/wwwroot/js/managers/EdgeScrollManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"EdgeScrollManager.js","sourceRoot":"","sources":["../../../src/managers/EdgeScrollManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/managers/EventFilterManager.d.ts b/wwwroot/js/managers/EventFilterManager.d.ts new file mode 100644 index 0000000..91092b2 --- /dev/null +++ b/wwwroot/js/managers/EventFilterManager.d.ts @@ -0,0 +1,32 @@ +/** + * EventFilterManager - Handles fuzzy search filtering of calendar events + * Uses Fuse.js for fuzzy matching (Apache 2.0 License) + */ +export declare class EventFilterManager { + private searchInput; + private allEvents; + private matchingEventIds; + private isFilterActive; + private frameRequest; + private fuse; + constructor(); + private init; + private setupSearchListeners; + private subscribeToEvents; + private updateEventsList; + private handleSearchInput; + private applyFilter; + private clearFilter; + private updateVisualState; + /** + * Check if an event matches the current filter + */ + eventMatchesFilter(eventId: string): boolean; + /** + * Get current filter state + */ + getFilterState(): { + active: boolean; + matchingIds: string[]; + }; +} diff --git a/wwwroot/js/managers/EventFilterManager.js b/wwwroot/js/managers/EventFilterManager.js new file mode 100644 index 0000000..dd2bd84 --- /dev/null +++ b/wwwroot/js/managers/EventFilterManager.js @@ -0,0 +1,192 @@ +/** + * EventFilterManager - Handles fuzzy search filtering of calendar events + * Uses Fuse.js for fuzzy matching (Apache 2.0 License) + */ +import { eventBus } from '../core/EventBus'; +import { CoreEvents } from '../constants/CoreEvents'; +// Import Fuse.js from npm +import Fuse from 'fuse.js'; +export class EventFilterManager { + constructor() { + this.searchInput = null; + this.allEvents = []; + this.matchingEventIds = new Set(); + this.isFilterActive = false; + this.frameRequest = null; + this.fuse = null; + // Wait for DOM to be ready before initializing + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + this.init(); + }); + } + else { + this.init(); + } + } + init() { + // Find search input + this.searchInput = document.querySelector('swp-search-container input[type="search"]'); + if (!this.searchInput) { + return; + } + // Set up event listeners + this.setupSearchListeners(); + this.subscribeToEvents(); + // Initialization complete + } + setupSearchListeners() { + if (!this.searchInput) + return; + // Listen for input changes + this.searchInput.addEventListener('input', (e) => { + const query = e.target.value; + this.handleSearchInput(query); + }); + // Listen for escape key + this.searchInput.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + this.clearFilter(); + } + }); + } + subscribeToEvents() { + // Listen for events data updates + eventBus.on(CoreEvents.EVENTS_RENDERED, (e) => { + const detail = e.detail; + if (detail?.events) { + this.updateEventsList(detail.events); + } + }); + } + updateEventsList(events) { + this.allEvents = events; + // Initialize Fuse with the new events list + this.fuse = new Fuse(this.allEvents, { + keys: ['title', 'description'], + threshold: 0.3, + includeScore: true, + minMatchCharLength: 2, // Minimum 2 characters for a match + shouldSort: true, + ignoreLocation: true // Search anywhere in the string + }); + // Re-apply filter if active + if (this.isFilterActive && this.searchInput) { + this.applyFilter(this.searchInput.value); + } + } + handleSearchInput(query) { + // Cancel any pending filter + if (this.frameRequest) { + cancelAnimationFrame(this.frameRequest); + } + // Debounce with requestAnimationFrame + this.frameRequest = requestAnimationFrame(() => { + if (query.length === 0) { + // Only clear when input is completely empty + this.clearFilter(); + } + else { + // Let Fuse.js handle minimum character length via minMatchCharLength + this.applyFilter(query); + } + }); + } + applyFilter(query) { + if (!this.fuse) { + return; + } + // Perform fuzzy search + const results = this.fuse.search(query); + // Extract matching event IDs + this.matchingEventIds.clear(); + results.forEach((result) => { + if (result.item && result.item.id) { + this.matchingEventIds.add(result.item.id); + } + }); + // Update filter state + this.isFilterActive = true; + // Update visual state + this.updateVisualState(); + // Emit filter changed event + eventBus.emit(CoreEvents.FILTER_CHANGED, { + active: true, + query: query, + matchingIds: Array.from(this.matchingEventIds) + }); + } + clearFilter() { + this.isFilterActive = false; + this.matchingEventIds.clear(); + // Clear search input + if (this.searchInput) { + this.searchInput.value = ''; + } + // Update visual state + this.updateVisualState(); + // Emit filter cleared event + eventBus.emit(CoreEvents.FILTER_CHANGED, { + active: false, + query: '', + matchingIds: [] + }); + } + updateVisualState() { + // Update search container styling + const searchContainer = document.querySelector('swp-search-container'); + if (searchContainer) { + if (this.isFilterActive) { + searchContainer.classList.add('filter-active'); + } + else { + searchContainer.classList.remove('filter-active'); + } + } + // Update all events layers + const eventsLayers = document.querySelectorAll('swp-events-layer'); + eventsLayers.forEach(layer => { + if (this.isFilterActive) { + layer.setAttribute('data-filter-active', 'true'); + // Mark matching events + const events = layer.querySelectorAll('swp-event'); + events.forEach(event => { + const eventId = event.getAttribute('data-event-id'); + if (eventId && this.matchingEventIds.has(eventId)) { + event.setAttribute('data-matches', 'true'); + } + else { + event.removeAttribute('data-matches'); + } + }); + } + else { + layer.removeAttribute('data-filter-active'); + // Remove all match attributes + const events = layer.querySelectorAll('swp-event'); + events.forEach(event => { + event.removeAttribute('data-matches'); + }); + } + }); + } + /** + * Check if an event matches the current filter + */ + eventMatchesFilter(eventId) { + if (!this.isFilterActive) { + return true; // No filter active, all events match + } + return this.matchingEventIds.has(eventId); + } + /** + * Get current filter state + */ + getFilterState() { + return { + active: this.isFilterActive, + matchingIds: Array.from(this.matchingEventIds) + }; + } +} +//# sourceMappingURL=EventFilterManager.js.map \ No newline at end of file diff --git a/wwwroot/js/managers/EventFilterManager.js.map b/wwwroot/js/managers/EventFilterManager.js.map new file mode 100644 index 0000000..295cbd1 --- /dev/null +++ b/wwwroot/js/managers/EventFilterManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"EventFilterManager.js","sourceRoot":"","sources":["../../../src/managers/EventFilterManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/managers/EventLayoutCoordinator.d.ts b/wwwroot/js/managers/EventLayoutCoordinator.d.ts new file mode 100644 index 0000000..5079618 --- /dev/null +++ b/wwwroot/js/managers/EventLayoutCoordinator.d.ts @@ -0,0 +1,78 @@ +/** + * EventLayoutCoordinator - Coordinates event layout calculations + * + * Separates layout logic from rendering concerns. + * Calculates stack levels, groups events, and determines rendering strategy. + */ +import { ICalendarEvent } from '../types/CalendarTypes'; +import { EventStackManager, IStackLink } from './EventStackManager'; +import { PositionUtils } from '../utils/PositionUtils'; +import { Configuration } from '../configurations/CalendarConfig'; +export interface IGridGroupLayout { + events: ICalendarEvent[]; + stackLevel: number; + position: { + top: number; + }; + columns: ICalendarEvent[][]; +} +export interface IStackedEventLayout { + event: ICalendarEvent; + stackLink: IStackLink; + position: { + top: number; + height: number; + }; +} +export interface IColumnLayout { + gridGroups: IGridGroupLayout[]; + stackedEvents: IStackedEventLayout[]; +} +export declare class EventLayoutCoordinator { + private stackManager; + private config; + private positionUtils; + constructor(stackManager: EventStackManager, config: Configuration, positionUtils: PositionUtils); + /** + * Calculate complete layout for a column of events (recursive approach) + */ + calculateColumnLayout(columnEvents: ICalendarEvent[]): IColumnLayout; + /** + * Calculate stack level for a grid group based on already rendered events + */ + private calculateGridGroupStackLevelFromRendered; + /** + * Calculate stack level for a single stacked event based on already rendered events + */ + private calculateStackLevelFromRendered; + /** + * Detect if two events have a conflict based on threshold + * + * @param event1 - First event + * @param event2 - Second event + * @param thresholdMinutes - Threshold in minutes + * @returns true if events conflict + */ + private detectConflict; + /** + * Expand grid candidates to find all events connected by conflict chains + * + * Uses expanding search to find chains (A→B→C where each conflicts with next) + * + * @param firstEvent - The first event to start with + * @param remaining - Remaining events to check + * @param thresholdMinutes - Threshold in minutes + * @returns Array of all events in the conflict chain + */ + private expandGridCandidates; + /** + * Allocate events to columns within a grid group + * + * Events that don't overlap can share the same column. + * Uses a greedy algorithm to minimize the number of columns. + * + * @param events - Events in the grid group (should already be sorted by start time) + * @returns Array of columns, where each column is an array of events + */ + private allocateColumns; +} diff --git a/wwwroot/js/managers/EventLayoutCoordinator.js b/wwwroot/js/managers/EventLayoutCoordinator.js new file mode 100644 index 0000000..381bc25 --- /dev/null +++ b/wwwroot/js/managers/EventLayoutCoordinator.js @@ -0,0 +1,201 @@ +/** + * EventLayoutCoordinator - Coordinates event layout calculations + * + * Separates layout logic from rendering concerns. + * Calculates stack levels, groups events, and determines rendering strategy. + */ +export class EventLayoutCoordinator { + constructor(stackManager, config, positionUtils) { + this.stackManager = stackManager; + this.config = config; + this.positionUtils = positionUtils; + } + /** + * Calculate complete layout for a column of events (recursive approach) + */ + calculateColumnLayout(columnEvents) { + if (columnEvents.length === 0) { + return { gridGroups: [], stackedEvents: [] }; + } + const gridGroupLayouts = []; + const stackedEventLayouts = []; + const renderedEventsWithLevels = []; + let remaining = [...columnEvents].sort((a, b) => a.start.getTime() - b.start.getTime()); + // Process events recursively + while (remaining.length > 0) { + // Take first event + const firstEvent = remaining[0]; + // Find events that could be in GRID with first event + // Use expanding search to find chains (A→B→C where each conflicts with next) + const gridSettings = this.config.gridSettings; + const thresholdMinutes = gridSettings.gridStartThresholdMinutes; + // Use refactored method for expanding grid candidates + const gridCandidates = this.expandGridCandidates(firstEvent, remaining, thresholdMinutes); + // Decide: should this group be GRID or STACK? + const group = { + events: gridCandidates, + containerType: 'NONE', + startTime: firstEvent.start + }; + const containerType = this.stackManager.decideContainerType(group); + if (containerType === 'GRID' && gridCandidates.length > 1) { + // Render as GRID + const gridStackLevel = this.calculateGridGroupStackLevelFromRendered(gridCandidates, renderedEventsWithLevels); + // Ensure we get the earliest event (explicit sort for robustness) + const earliestEvent = [...gridCandidates].sort((a, b) => a.start.getTime() - b.start.getTime())[0]; + const position = this.positionUtils.calculateEventPosition(earliestEvent.start, earliestEvent.end); + const columns = this.allocateColumns(gridCandidates); + gridGroupLayouts.push({ + events: gridCandidates, + stackLevel: gridStackLevel, + position: { top: position.top + 1 }, + columns + }); + // Mark all events in grid with their stack level + gridCandidates.forEach(e => renderedEventsWithLevels.push({ event: e, level: gridStackLevel })); + // Remove all events in this grid from remaining + remaining = remaining.filter(e => !gridCandidates.includes(e)); + } + else { + // Render first event as STACKED + const stackLevel = this.calculateStackLevelFromRendered(firstEvent, renderedEventsWithLevels); + const position = this.positionUtils.calculateEventPosition(firstEvent.start, firstEvent.end); + stackedEventLayouts.push({ + event: firstEvent, + stackLink: { stackLevel }, + position: { top: position.top + 1, height: position.height - 3 } + }); + // Mark this event with its stack level + renderedEventsWithLevels.push({ event: firstEvent, level: stackLevel }); + // Remove only first event from remaining + remaining = remaining.slice(1); + } + } + return { + gridGroups: gridGroupLayouts, + stackedEvents: stackedEventLayouts + }; + } + /** + * Calculate stack level for a grid group based on already rendered events + */ + calculateGridGroupStackLevelFromRendered(gridEvents, renderedEventsWithLevels) { + // Find highest stack level of any rendered event that overlaps with this grid + let maxOverlappingLevel = -1; + for (const gridEvent of gridEvents) { + for (const rendered of renderedEventsWithLevels) { + if (this.stackManager.doEventsOverlap(gridEvent, rendered.event)) { + maxOverlappingLevel = Math.max(maxOverlappingLevel, rendered.level); + } + } + } + return maxOverlappingLevel + 1; + } + /** + * Calculate stack level for a single stacked event based on already rendered events + */ + calculateStackLevelFromRendered(event, renderedEventsWithLevels) { + // Find highest stack level of any rendered event that overlaps with this event + let maxOverlappingLevel = -1; + for (const rendered of renderedEventsWithLevels) { + if (this.stackManager.doEventsOverlap(event, rendered.event)) { + maxOverlappingLevel = Math.max(maxOverlappingLevel, rendered.level); + } + } + return maxOverlappingLevel + 1; + } + /** + * Detect if two events have a conflict based on threshold + * + * @param event1 - First event + * @param event2 - Second event + * @param thresholdMinutes - Threshold in minutes + * @returns true if events conflict + */ + detectConflict(event1, event2, thresholdMinutes) { + // Check 1: Start-to-start conflict (starts within threshold) + const startToStartDiff = Math.abs(event1.start.getTime() - event2.start.getTime()) / (1000 * 60); + if (startToStartDiff <= thresholdMinutes && this.stackManager.doEventsOverlap(event1, event2)) { + return true; + } + // Check 2: End-to-start conflict (event1 starts within threshold before event2 ends) + const endToStartMinutes = (event2.end.getTime() - event1.start.getTime()) / (1000 * 60); + if (endToStartMinutes > 0 && endToStartMinutes <= thresholdMinutes) { + return true; + } + // Check 3: Reverse end-to-start (event2 starts within threshold before event1 ends) + const reverseEndToStart = (event1.end.getTime() - event2.start.getTime()) / (1000 * 60); + if (reverseEndToStart > 0 && reverseEndToStart <= thresholdMinutes) { + return true; + } + return false; + } + /** + * Expand grid candidates to find all events connected by conflict chains + * + * Uses expanding search to find chains (A→B→C where each conflicts with next) + * + * @param firstEvent - The first event to start with + * @param remaining - Remaining events to check + * @param thresholdMinutes - Threshold in minutes + * @returns Array of all events in the conflict chain + */ + expandGridCandidates(firstEvent, remaining, thresholdMinutes) { + const gridCandidates = [firstEvent]; + let candidatesChanged = true; + // Keep expanding until no new candidates can be added + while (candidatesChanged) { + candidatesChanged = false; + for (let i = 1; i < remaining.length; i++) { + const candidate = remaining[i]; + // Skip if already in candidates + if (gridCandidates.includes(candidate)) + continue; + // Check if candidate conflicts with ANY event in gridCandidates + for (const existingCandidate of gridCandidates) { + if (this.detectConflict(candidate, existingCandidate, thresholdMinutes)) { + gridCandidates.push(candidate); + candidatesChanged = true; + break; // Found conflict, move to next candidate + } + } + } + } + return gridCandidates; + } + /** + * Allocate events to columns within a grid group + * + * Events that don't overlap can share the same column. + * Uses a greedy algorithm to minimize the number of columns. + * + * @param events - Events in the grid group (should already be sorted by start time) + * @returns Array of columns, where each column is an array of events + */ + allocateColumns(events) { + if (events.length === 0) + return []; + if (events.length === 1) + return [[events[0]]]; + const columns = []; + // For each event, try to place it in an existing column where it doesn't overlap + for (const event of events) { + let placed = false; + // Try to find a column where this event doesn't overlap with any existing event + for (const column of columns) { + const hasOverlap = column.some(colEvent => this.stackManager.doEventsOverlap(event, colEvent)); + if (!hasOverlap) { + column.push(event); + placed = true; + break; + } + } + // If no suitable column found, create a new one + if (!placed) { + columns.push([event]); + } + } + return columns; + } +} +//# sourceMappingURL=EventLayoutCoordinator.js.map \ No newline at end of file diff --git a/wwwroot/js/managers/EventLayoutCoordinator.js.map b/wwwroot/js/managers/EventLayoutCoordinator.js.map new file mode 100644 index 0000000..18f9e09 --- /dev/null +++ b/wwwroot/js/managers/EventLayoutCoordinator.js.map @@ -0,0 +1 @@ +{"version":3,"file":"EventLayoutCoordinator.js","sourceRoot":"","sources":["../../../src/managers/EventLayoutCoordinator.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/managers/EventManager.d.ts b/wwwroot/js/managers/EventManager.d.ts new file mode 100644 index 0000000..dde95d1 --- /dev/null +++ b/wwwroot/js/managers/EventManager.d.ts @@ -0,0 +1,69 @@ +import { IEventBus, ICalendarEvent } from '../types/CalendarTypes'; +import { Configuration } from '../configurations/CalendarConfig'; +import { DateService } from '../utils/DateService'; +import { IEventRepository } from '../repositories/IEventRepository'; +/** + * EventManager - Event lifecycle and CRUD operations + * Delegates all data operations to IEventRepository + * No longer maintains in-memory cache - repository is single source of truth + */ +export declare class EventManager { + private eventBus; + private dateService; + private config; + private repository; + constructor(eventBus: IEventBus, dateService: DateService, config: Configuration, repository: IEventRepository); + /** + * Load event data from repository + * No longer caches - delegates to repository + */ + loadData(): Promise; + /** + * Get all events from repository + */ + getEvents(copy?: boolean): Promise; + /** + * Get event by ID from repository + */ + getEventById(id: string): Promise; + /** + * Get event by ID and return event info for navigation + * @param id Event ID to find + * @returns Event with navigation info or null if not found + */ + getEventForNavigation(id: string): Promise<{ + event: ICalendarEvent; + eventDate: Date; + } | null>; + /** + * Navigate to specific event by ID + * Emits navigation events for other managers to handle + * @param eventId Event ID to navigate to + * @returns true if event found and navigation initiated, false otherwise + */ + navigateToEvent(eventId: string): Promise; + /** + * Get events that overlap with a given time period + */ + getEventsForPeriod(startDate: Date, endDate: Date): Promise; + /** + * Create a new event and add it to the calendar + * Delegates to repository with source='local' + */ + addEvent(event: Omit): Promise; + /** + * Update an existing event + * Delegates to repository with source='local' + */ + updateEvent(id: string, updates: Partial): Promise; + /** + * Delete an event + * Delegates to repository with source='local' + */ + deleteEvent(id: string): Promise; + /** + * Handle remote update from SignalR + * Delegates to repository with source='remote' + */ + handleRemoteUpdate(event: ICalendarEvent): Promise; +} diff --git a/wwwroot/js/managers/EventManager.js b/wwwroot/js/managers/EventManager.js new file mode 100644 index 0000000..982105f --- /dev/null +++ b/wwwroot/js/managers/EventManager.js @@ -0,0 +1,164 @@ +import { CoreEvents } from '../constants/CoreEvents'; +/** + * EventManager - Event lifecycle and CRUD operations + * Delegates all data operations to IEventRepository + * No longer maintains in-memory cache - repository is single source of truth + */ +export class EventManager { + constructor(eventBus, dateService, config, repository) { + this.eventBus = eventBus; + this.dateService = dateService; + this.config = config; + this.repository = repository; + } + /** + * Load event data from repository + * No longer caches - delegates to repository + */ + async loadData() { + try { + // Just ensure repository is ready - no caching + await this.repository.loadEvents(); + } + catch (error) { + console.error('Failed to load event data:', error); + throw error; + } + } + /** + * Get all events from repository + */ + async getEvents(copy = false) { + const events = await this.repository.loadEvents(); + return copy ? [...events] : events; + } + /** + * Get event by ID from repository + */ + async getEventById(id) { + const events = await this.repository.loadEvents(); + return events.find(event => event.id === id); + } + /** + * Get event by ID and return event info for navigation + * @param id Event ID to find + * @returns Event with navigation info or null if not found + */ + async getEventForNavigation(id) { + const event = await this.getEventById(id); + if (!event) { + return null; + } + // Validate event dates + const validation = this.dateService.validateDate(event.start); + if (!validation.valid) { + console.warn(`EventManager: Invalid event start date for event ${id}:`, validation.error); + return null; + } + // Validate date range + if (!this.dateService.isValidRange(event.start, event.end)) { + console.warn(`EventManager: Invalid date range for event ${id}: start must be before end`); + return null; + } + return { + event, + eventDate: event.start + }; + } + /** + * Navigate to specific event by ID + * Emits navigation events for other managers to handle + * @param eventId Event ID to navigate to + * @returns true if event found and navigation initiated, false otherwise + */ + async navigateToEvent(eventId) { + const eventInfo = await this.getEventForNavigation(eventId); + if (!eventInfo) { + console.warn(`EventManager: Event with ID ${eventId} not found`); + return false; + } + const { event, eventDate } = eventInfo; + // Emit navigation request event + this.eventBus.emit(CoreEvents.NAVIGATE_TO_EVENT, { + eventId, + event, + eventDate, + eventStartTime: event.start + }); + return true; + } + /** + * Get events that overlap with a given time period + */ + async getEventsForPeriod(startDate, endDate) { + const events = await this.repository.loadEvents(); + // Event overlaps period if it starts before period ends AND ends after period starts + return events.filter(event => { + return event.start <= endDate && event.end >= startDate; + }); + } + /** + * Create a new event and add it to the calendar + * Delegates to repository with source='local' + */ + async addEvent(event) { + const newEvent = await this.repository.createEvent(event, 'local'); + this.eventBus.emit(CoreEvents.EVENT_CREATED, { + event: newEvent + }); + return newEvent; + } + /** + * Update an existing event + * Delegates to repository with source='local' + */ + async updateEvent(id, updates) { + try { + const updatedEvent = await this.repository.updateEvent(id, updates, 'local'); + this.eventBus.emit(CoreEvents.EVENT_UPDATED, { + event: updatedEvent + }); + return updatedEvent; + } + catch (error) { + console.error(`Failed to update event ${id}:`, error); + return null; + } + } + /** + * Delete an event + * Delegates to repository with source='local' + */ + async deleteEvent(id) { + try { + await this.repository.deleteEvent(id, 'local'); + this.eventBus.emit(CoreEvents.EVENT_DELETED, { + eventId: id + }); + return true; + } + catch (error) { + console.error(`Failed to delete event ${id}:`, error); + return false; + } + } + /** + * Handle remote update from SignalR + * Delegates to repository with source='remote' + */ + async handleRemoteUpdate(event) { + try { + await this.repository.updateEvent(event.id, event, 'remote'); + this.eventBus.emit(CoreEvents.REMOTE_UPDATE_RECEIVED, { + event + }); + this.eventBus.emit(CoreEvents.EVENT_UPDATED, { + event + }); + } + catch (error) { + console.error(`Failed to handle remote update for event ${event.id}:`, error); + } + } +} +//# sourceMappingURL=EventManager.js.map \ No newline at end of file diff --git a/wwwroot/js/managers/EventManager.js.map b/wwwroot/js/managers/EventManager.js.map new file mode 100644 index 0000000..5ff19fb --- /dev/null +++ b/wwwroot/js/managers/EventManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"EventManager.js","sourceRoot":"","sources":["../../../src/managers/EventManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/managers/EventStackManager.d.ts b/wwwroot/js/managers/EventStackManager.d.ts new file mode 100644 index 0000000..e2de953 --- /dev/null +++ b/wwwroot/js/managers/EventStackManager.d.ts @@ -0,0 +1,91 @@ +/** + * EventStackManager - Manages visual stacking of overlapping calendar events + * + * This class handles the creation and maintenance of "stack chains" - doubly-linked + * lists of overlapping events stored directly in DOM elements via data attributes. + * + * Implements 3-phase algorithm for grid + nested stacking: + * Phase 1: Group events by start time proximity (configurable threshold) + * Phase 2: Decide container type (GRID vs STACKING) + * Phase 3: Handle late arrivals (nested stacking - NOT IMPLEMENTED) + * + * @see STACKING_CONCEPT.md for detailed documentation + * @see stacking-visualization.html for visual examples + */ +import { ICalendarEvent } from '../types/CalendarTypes'; +import { Configuration } from '../configurations/CalendarConfig'; +export interface IStackLink { + prev?: string; + next?: string; + stackLevel: number; +} +export interface IEventGroup { + events: ICalendarEvent[]; + containerType: 'NONE' | 'GRID' | 'STACKING'; + startTime: Date; +} +export declare class EventStackManager { + private static readonly STACK_OFFSET_PX; + private config; + constructor(config: Configuration); + /** + * Group events by time conflicts (both start-to-start and end-to-start within threshold) + * + * Events are grouped if: + * 1. They start within ±threshold minutes of each other (start-to-start) + * 2. One event starts within threshold minutes before another ends (end-to-start conflict) + */ + groupEventsByStartTime(events: ICalendarEvent[]): IEventGroup[]; + /** + * Decide container type for a group of events + * + * Rule: Events starting simultaneously (within threshold) should ALWAYS use GRID, + * even if they overlap each other. This provides better visual indication that + * events start at the same time. + */ + decideContainerType(group: IEventGroup): 'NONE' | 'GRID' | 'STACKING'; + /** + * Check if two events overlap in time + */ + doEventsOverlap(event1: ICalendarEvent, event2: ICalendarEvent): boolean; + /** + * Create optimized stack links (events share levels when possible) + */ + createOptimizedStackLinks(events: ICalendarEvent[]): Map; + /** + * Calculate marginLeft based on stack level + */ + calculateMarginLeft(stackLevel: number): number; + /** + * Calculate zIndex based on stack level + */ + calculateZIndex(stackLevel: number): number; + /** + * Serialize stack link to JSON string + */ + serializeStackLink(stackLink: IStackLink): string; + /** + * Deserialize JSON string to stack link + */ + deserializeStackLink(json: string): IStackLink | null; + /** + * Apply stack link to DOM element + */ + applyStackLinkToElement(element: HTMLElement, stackLink: IStackLink): void; + /** + * Get stack link from DOM element + */ + getStackLinkFromElement(element: HTMLElement): IStackLink | null; + /** + * Apply visual styling to element based on stack level + */ + applyVisualStyling(element: HTMLElement, stackLevel: number): void; + /** + * Clear stack link from element + */ + clearStackLinkFromElement(element: HTMLElement): void; + /** + * Clear visual styling from element + */ + clearVisualStyling(element: HTMLElement): void; +} diff --git a/wwwroot/js/managers/EventStackManager.js b/wwwroot/js/managers/EventStackManager.js new file mode 100644 index 0000000..cb48109 --- /dev/null +++ b/wwwroot/js/managers/EventStackManager.js @@ -0,0 +1,217 @@ +/** + * EventStackManager - Manages visual stacking of overlapping calendar events + * + * This class handles the creation and maintenance of "stack chains" - doubly-linked + * lists of overlapping events stored directly in DOM elements via data attributes. + * + * Implements 3-phase algorithm for grid + nested stacking: + * Phase 1: Group events by start time proximity (configurable threshold) + * Phase 2: Decide container type (GRID vs STACKING) + * Phase 3: Handle late arrivals (nested stacking - NOT IMPLEMENTED) + * + * @see STACKING_CONCEPT.md for detailed documentation + * @see stacking-visualization.html for visual examples + */ +export class EventStackManager { + constructor(config) { + this.config = config; + } + // ============================================ + // PHASE 1: Start Time Grouping + // ============================================ + /** + * Group events by time conflicts (both start-to-start and end-to-start within threshold) + * + * Events are grouped if: + * 1. They start within ±threshold minutes of each other (start-to-start) + * 2. One event starts within threshold minutes before another ends (end-to-start conflict) + */ + groupEventsByStartTime(events) { + if (events.length === 0) + return []; + // Get threshold from config + const gridSettings = this.config.gridSettings; + const thresholdMinutes = gridSettings.gridStartThresholdMinutes; + // Sort events by start time + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + const groups = []; + for (const event of sorted) { + // Find existing group that this event conflicts with + const existingGroup = groups.find(group => { + // Check if event conflicts with ANY event in the group + return group.events.some(groupEvent => { + // Start-to-start conflict: events start within threshold + const startToStartMinutes = Math.abs(event.start.getTime() - groupEvent.start.getTime()) / (1000 * 60); + if (startToStartMinutes <= thresholdMinutes) { + return true; + } + // End-to-start conflict: event starts within threshold before groupEvent ends + const endToStartMinutes = (groupEvent.end.getTime() - event.start.getTime()) / (1000 * 60); + if (endToStartMinutes > 0 && endToStartMinutes <= thresholdMinutes) { + return true; + } + // Also check reverse: groupEvent starts within threshold before event ends + const reverseEndToStart = (event.end.getTime() - groupEvent.start.getTime()) / (1000 * 60); + if (reverseEndToStart > 0 && reverseEndToStart <= thresholdMinutes) { + return true; + } + return false; + }); + }); + if (existingGroup) { + existingGroup.events.push(event); + } + else { + groups.push({ + events: [event], + containerType: 'NONE', + startTime: event.start + }); + } + } + return groups; + } + // ============================================ + // PHASE 2: Container Type Decision + // ============================================ + /** + * Decide container type for a group of events + * + * Rule: Events starting simultaneously (within threshold) should ALWAYS use GRID, + * even if they overlap each other. This provides better visual indication that + * events start at the same time. + */ + decideContainerType(group) { + if (group.events.length === 1) { + return 'NONE'; + } + // If events are grouped together (start within threshold), they should share columns (GRID) + // This is true EVEN if they overlap, because the visual priority is to show + // that they start simultaneously. + return 'GRID'; + } + /** + * Check if two events overlap in time + */ + doEventsOverlap(event1, event2) { + return event1.start < event2.end && event1.end > event2.start; + } + // ============================================ + // Stack Level Calculation + // ============================================ + /** + * Create optimized stack links (events share levels when possible) + */ + createOptimizedStackLinks(events) { + const stackLinks = new Map(); + if (events.length === 0) + return stackLinks; + // Sort by start time + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + // Step 1: Assign stack levels + for (const event of sorted) { + // Find all events this event overlaps with + const overlapping = sorted.filter(other => other !== event && this.doEventsOverlap(event, other)); + // Find the MINIMUM required level (must be above all overlapping events) + let minRequiredLevel = 0; + for (const other of overlapping) { + const otherLink = stackLinks.get(other.id); + if (otherLink) { + // Must be at least one level above the overlapping event + minRequiredLevel = Math.max(minRequiredLevel, otherLink.stackLevel + 1); + } + } + stackLinks.set(event.id, { stackLevel: minRequiredLevel }); + } + // Step 2: Build prev/next chains for overlapping events at adjacent stack levels + for (const event of sorted) { + const currentLink = stackLinks.get(event.id); + // Find overlapping events that are directly below (stackLevel - 1) + const overlapping = sorted.filter(other => other !== event && this.doEventsOverlap(event, other)); + const directlyBelow = overlapping.filter(other => { + const otherLink = stackLinks.get(other.id); + return otherLink && otherLink.stackLevel === currentLink.stackLevel - 1; + }); + if (directlyBelow.length > 0) { + // Use the first one in sorted order as prev + currentLink.prev = directlyBelow[0].id; + } + // Find overlapping events that are directly above (stackLevel + 1) + const directlyAbove = overlapping.filter(other => { + const otherLink = stackLinks.get(other.id); + return otherLink && otherLink.stackLevel === currentLink.stackLevel + 1; + }); + if (directlyAbove.length > 0) { + // Use the first one in sorted order as next + currentLink.next = directlyAbove[0].id; + } + } + return stackLinks; + } + /** + * Calculate marginLeft based on stack level + */ + calculateMarginLeft(stackLevel) { + return stackLevel * EventStackManager.STACK_OFFSET_PX; + } + /** + * Calculate zIndex based on stack level + */ + calculateZIndex(stackLevel) { + return 100 + stackLevel; + } + /** + * Serialize stack link to JSON string + */ + serializeStackLink(stackLink) { + return JSON.stringify(stackLink); + } + /** + * Deserialize JSON string to stack link + */ + deserializeStackLink(json) { + try { + return JSON.parse(json); + } + catch (e) { + return null; + } + } + /** + * Apply stack link to DOM element + */ + applyStackLinkToElement(element, stackLink) { + element.dataset.stackLink = this.serializeStackLink(stackLink); + } + /** + * Get stack link from DOM element + */ + getStackLinkFromElement(element) { + const data = element.dataset.stackLink; + if (!data) + return null; + return this.deserializeStackLink(data); + } + /** + * Apply visual styling to element based on stack level + */ + applyVisualStyling(element, stackLevel) { + element.style.marginLeft = `${this.calculateMarginLeft(stackLevel)}px`; + element.style.zIndex = `${this.calculateZIndex(stackLevel)}`; + } + /** + * Clear stack link from element + */ + clearStackLinkFromElement(element) { + delete element.dataset.stackLink; + } + /** + * Clear visual styling from element + */ + clearVisualStyling(element) { + element.style.marginLeft = ''; + element.style.zIndex = ''; + } +} +EventStackManager.STACK_OFFSET_PX = 15; +//# sourceMappingURL=EventStackManager.js.map \ No newline at end of file diff --git a/wwwroot/js/managers/EventStackManager.js.map b/wwwroot/js/managers/EventStackManager.js.map new file mode 100644 index 0000000..cf98e2a --- /dev/null +++ b/wwwroot/js/managers/EventStackManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"EventStackManager.js","sourceRoot":"","sources":["../../../src/managers/EventStackManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/managers/GridManager.d.ts b/wwwroot/js/managers/GridManager.d.ts new file mode 100644 index 0000000..2f4d451 --- /dev/null +++ b/wwwroot/js/managers/GridManager.d.ts @@ -0,0 +1,30 @@ +/** + * GridManager - Simplified grid manager using centralized GridRenderer + * Delegates DOM rendering to GridRenderer, focuses on coordination + */ +import { GridRenderer } from '../renderers/GridRenderer'; +import { DateService } from '../utils/DateService'; +import { Configuration } from '../configurations/CalendarConfig'; +import { EventManager } from './EventManager'; +/** + * Simplified GridManager focused on coordination, delegates rendering to GridRenderer + */ +export declare class GridManager { + private container; + private currentDate; + private currentView; + private gridRenderer; + private dateService; + private config; + private dataSource; + private eventManager; + constructor(gridRenderer: GridRenderer, dateService: DateService, config: Configuration, eventManager: EventManager); + private init; + private findElements; + private subscribeToEvents; + /** + * Main render method - delegates to GridRenderer + * Note: CSS variables are automatically updated by ConfigManager when config changes + */ + render(): Promise; +} diff --git a/wwwroot/js/managers/GridManager.js b/wwwroot/js/managers/GridManager.js new file mode 100644 index 0000000..c3294e8 --- /dev/null +++ b/wwwroot/js/managers/GridManager.js @@ -0,0 +1,77 @@ +/** + * GridManager - Simplified grid manager using centralized GridRenderer + * Delegates DOM rendering to GridRenderer, focuses on coordination + */ +import { eventBus } from '../core/EventBus'; +import { CoreEvents } from '../constants/CoreEvents'; +import { DateColumnDataSource } from '../datasources/DateColumnDataSource'; +/** + * Simplified GridManager focused on coordination, delegates rendering to GridRenderer + */ +export class GridManager { + constructor(gridRenderer, dateService, config, eventManager) { + this.container = null; + this.currentDate = new Date(); + this.currentView = 'week'; + this.gridRenderer = gridRenderer; + this.dateService = dateService; + this.config = config; + this.eventManager = eventManager; + this.dataSource = new DateColumnDataSource(dateService, config, this.currentDate, this.currentView); + this.init(); + } + init() { + this.findElements(); + this.subscribeToEvents(); + } + findElements() { + this.container = document.querySelector('swp-calendar-container'); + } + subscribeToEvents() { + // Listen for view changes + eventBus.on(CoreEvents.VIEW_CHANGED, (e) => { + const detail = e.detail; + this.currentView = detail.currentView; + this.dataSource.setCurrentView(this.currentView); + this.render(); + }); + // Listen for navigation events from NavigationButtons + eventBus.on(CoreEvents.NAVIGATION_COMPLETED, (e) => { + const detail = e.detail; + this.currentDate = detail.newDate; + this.dataSource.setCurrentDate(this.currentDate); + this.render(); + }); + // Listen for config changes that affect rendering + eventBus.on(CoreEvents.REFRESH_REQUESTED, (e) => { + this.render(); + }); + eventBus.on(CoreEvents.WORKWEEK_CHANGED, () => { + this.render(); + }); + } + /** + * Main render method - delegates to GridRenderer + * Note: CSS variables are automatically updated by ConfigManager when config changes + */ + async render() { + if (!this.container) { + return; + } + // Get dates from datasource - single source of truth + const dates = this.dataSource.getColumns(); + // Get events for the period from EventManager + const startDate = dates[0]; + const endDate = dates[dates.length - 1]; + const events = await this.eventManager.getEventsForPeriod(startDate, endDate); + // Delegate to GridRenderer with dates and events + this.gridRenderer.renderGrid(this.container, this.currentDate, this.currentView, dates, events); + // Emit grid rendered event + eventBus.emit(CoreEvents.GRID_RENDERED, { + container: this.container, + currentDate: this.currentDate, + dates: dates + }); + } +} +//# sourceMappingURL=GridManager.js.map \ No newline at end of file diff --git a/wwwroot/js/managers/GridManager.js.map b/wwwroot/js/managers/GridManager.js.map new file mode 100644 index 0000000..d5a0f33 --- /dev/null +++ b/wwwroot/js/managers/GridManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"GridManager.js","sourceRoot":"","sources":["../../../src/managers/GridManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/managers/HeaderManager.d.ts b/wwwroot/js/managers/HeaderManager.d.ts new file mode 100644 index 0000000..6eabc82 --- /dev/null +++ b/wwwroot/js/managers/HeaderManager.d.ts @@ -0,0 +1,32 @@ +import { Configuration } from '../configurations/CalendarConfig'; +import { IHeaderRenderer } from '../renderers/DateHeaderRenderer'; +/** + * HeaderManager - Handles all header-related event logic + * Separates event handling from rendering concerns + * Uses dependency injection for renderer strategy + */ +export declare class HeaderManager { + private headerRenderer; + private config; + constructor(headerRenderer: IHeaderRenderer, config: Configuration); + /** + * Setup header drag event listeners - Listen to DragDropManager events + */ + setupHeaderDragListeners(): void; + /** + * Handle drag mouse enter header event + */ + private handleDragMouseEnterHeader; + /** + * Handle drag mouse leave header event + */ + private handleDragMouseLeaveHeader; + /** + * Setup navigation event listener + */ + private setupNavigationListener; + /** + * Update header content for navigation + */ + private updateHeader; +} diff --git a/wwwroot/js/managers/HeaderManager.js b/wwwroot/js/managers/HeaderManager.js new file mode 100644 index 0000000..f985c7a --- /dev/null +++ b/wwwroot/js/managers/HeaderManager.js @@ -0,0 +1,103 @@ +import { eventBus } from '../core/EventBus'; +import { CoreEvents } from '../constants/CoreEvents'; +import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils'; +/** + * HeaderManager - Handles all header-related event logic + * Separates event handling from rendering concerns + * Uses dependency injection for renderer strategy + */ +export class HeaderManager { + constructor(headerRenderer, config) { + this.headerRenderer = headerRenderer; + this.config = config; + // Bind handler methods for event listeners + this.handleDragMouseEnterHeader = this.handleDragMouseEnterHeader.bind(this); + this.handleDragMouseLeaveHeader = this.handleDragMouseLeaveHeader.bind(this); + // Listen for navigation events to update header + this.setupNavigationListener(); + } + /** + * Setup header drag event listeners - Listen to DragDropManager events + */ + setupHeaderDragListeners() { + console.log('🎯 HeaderManager: Setting up drag event listeners'); + // Subscribe to drag events from DragDropManager + eventBus.on('drag:mouseenter-header', this.handleDragMouseEnterHeader); + eventBus.on('drag:mouseleave-header', this.handleDragMouseLeaveHeader); + console.log('✅ HeaderManager: Drag event listeners attached'); + } + /** + * Handle drag mouse enter header event + */ + handleDragMouseEnterHeader(event) { + const { targetColumn: targetDate, mousePosition, originalElement, draggedClone: cloneElement } = event.detail; + console.log('🎯 HeaderManager: Received drag:mouseenter-header', { + targetDate, + originalElement: !!originalElement, + cloneElement: !!cloneElement + }); + } + /** + * Handle drag mouse leave header event + */ + handleDragMouseLeaveHeader(event) { + const { targetDate, mousePosition, originalElement, draggedClone: cloneElement } = event.detail; + console.log('🚪 HeaderManager: Received drag:mouseleave-header', { + targetDate, + originalElement: !!originalElement, + cloneElement: !!cloneElement + }); + } + /** + * Setup navigation event listener + */ + setupNavigationListener() { + eventBus.on(CoreEvents.NAVIGATION_COMPLETED, (event) => { + const { currentDate } = event.detail; + this.updateHeader(currentDate); + }); + // Also listen for date changes (including initial setup) + eventBus.on(CoreEvents.DATE_CHANGED, (event) => { + const { currentDate } = event.detail; + this.updateHeader(currentDate); + }); + // Listen for workweek header updates after grid rebuild + //currentDate: this.currentDate, + //currentView: this.currentView, + //workweek: this.config.currentWorkWeek + eventBus.on('workweek:header-update', (event) => { + const { currentDate } = event.detail; + this.updateHeader(currentDate); + }); + } + /** + * Update header content for navigation + */ + updateHeader(currentDate) { + console.log('🎯 HeaderManager.updateHeader called', { + currentDate, + rendererType: this.headerRenderer.constructor.name + }); + const calendarHeader = document.querySelector('swp-calendar-header'); + if (!calendarHeader) { + console.warn('❌ HeaderManager: No calendar header found!'); + return; + } + // Clear existing content + calendarHeader.innerHTML = ''; + // Render new header content using injected renderer + const context = { + currentWeek: currentDate, + config: this.config + }; + this.headerRenderer.render(calendarHeader, context); + // Setup event listeners on the new content + this.setupHeaderDragListeners(); + // Notify other managers that header is ready with period data + const payload = { + headerElements: ColumnDetectionUtils.getHeaderColumns(), + }; + eventBus.emit('header:ready', payload); + } +} +//# sourceMappingURL=HeaderManager.js.map \ No newline at end of file diff --git a/wwwroot/js/managers/HeaderManager.js.map b/wwwroot/js/managers/HeaderManager.js.map new file mode 100644 index 0000000..61da5cd --- /dev/null +++ b/wwwroot/js/managers/HeaderManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"HeaderManager.js","sourceRoot":"","sources":["../../../src/managers/HeaderManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/managers/NavigationButtonsManager.d.ts b/wwwroot/js/managers/NavigationButtonsManager.d.ts new file mode 100644 index 0000000..2fb76dc --- /dev/null +++ b/wwwroot/js/managers/NavigationButtonsManager.d.ts @@ -0,0 +1,40 @@ +import { IEventBus } from '../types/CalendarTypes'; +/** + * NavigationButtonsManager - Manages navigation button UI and state + * + * RESPONSIBILITY: + * =============== + * This manager owns all logic related to the UI element. + * It follows the principle that each functional UI element has its own manager. + * + * RESPONSIBILITIES: + * - Handles button clicks on swp-nav-button elements + * - Validates navigation actions (prev, next, today) + * - Emits NAV_BUTTON_CLICKED events + * - Manages button UI listeners + * + * EVENT FLOW: + * =========== + * User clicks button → validateAction() → emit event → NavigationManager handles navigation + * + * SUBSCRIBERS: + * ============ + * - NavigationManager: Performs actual navigation logic (animations, grid updates, week calculations) + */ +export declare class NavigationButtonsManager { + private eventBus; + private buttonListeners; + constructor(eventBus: IEventBus); + /** + * Setup click listeners on all navigation buttons + */ + private setupButtonListeners; + /** + * Handle navigation action + */ + private handleNavigation; + /** + * Validate if string is a valid navigation action + */ + private isValidAction; +} diff --git a/wwwroot/js/managers/NavigationButtonsManager.js b/wwwroot/js/managers/NavigationButtonsManager.js new file mode 100644 index 0000000..e1badd5 --- /dev/null +++ b/wwwroot/js/managers/NavigationButtonsManager.js @@ -0,0 +1,63 @@ +import { CoreEvents } from '../constants/CoreEvents'; +/** + * NavigationButtonsManager - Manages navigation button UI and state + * + * RESPONSIBILITY: + * =============== + * This manager owns all logic related to the UI element. + * It follows the principle that each functional UI element has its own manager. + * + * RESPONSIBILITIES: + * - Handles button clicks on swp-nav-button elements + * - Validates navigation actions (prev, next, today) + * - Emits NAV_BUTTON_CLICKED events + * - Manages button UI listeners + * + * EVENT FLOW: + * =========== + * User clicks button → validateAction() → emit event → NavigationManager handles navigation + * + * SUBSCRIBERS: + * ============ + * - NavigationManager: Performs actual navigation logic (animations, grid updates, week calculations) + */ +export class NavigationButtonsManager { + constructor(eventBus) { + this.buttonListeners = new Map(); + this.eventBus = eventBus; + this.setupButtonListeners(); + } + /** + * Setup click listeners on all navigation buttons + */ + setupButtonListeners() { + const buttons = document.querySelectorAll('swp-nav-button[data-action]'); + buttons.forEach(button => { + const clickHandler = (event) => { + event.preventDefault(); + const action = button.getAttribute('data-action'); + if (action && this.isValidAction(action)) { + this.handleNavigation(action); + } + }; + button.addEventListener('click', clickHandler); + this.buttonListeners.set(button, clickHandler); + }); + } + /** + * Handle navigation action + */ + handleNavigation(action) { + // Emit navigation button clicked event + this.eventBus.emit(CoreEvents.NAV_BUTTON_CLICKED, { + action: action + }); + } + /** + * Validate if string is a valid navigation action + */ + isValidAction(action) { + return ['prev', 'next', 'today'].includes(action); + } +} +//# sourceMappingURL=NavigationButtonsManager.js.map \ No newline at end of file diff --git a/wwwroot/js/managers/NavigationButtonsManager.js.map b/wwwroot/js/managers/NavigationButtonsManager.js.map new file mode 100644 index 0000000..ab7bd56 --- /dev/null +++ b/wwwroot/js/managers/NavigationButtonsManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"NavigationButtonsManager.js","sourceRoot":"","sources":["../../../src/managers/NavigationButtonsManager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,OAAO,wBAAwB;IAInC,YAAY,QAAmB;QAFvB,oBAAe,GAAgC,IAAI,GAAG,EAAE,CAAC;QAG/D,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,oBAAoB,EAAE,CAAC;IAC9B,CAAC;IAED;;OAEG;IACK,oBAAoB;QAC1B,MAAM,OAAO,GAAG,QAAQ,CAAC,gBAAgB,CAAC,6BAA6B,CAAC,CAAC;QAEzE,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;YACvB,MAAM,YAAY,GAAG,CAAC,KAAY,EAAE,EAAE;gBACpC,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC;gBAClD,IAAI,MAAM,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;oBACzC,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;gBAChC,CAAC;YACH,CAAC,CAAC;YAEF,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;YAC/C,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACK,gBAAgB,CAAC,MAAc;QACrC,uCAAuC;QACvC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,kBAAkB,EAAE;YAChD,MAAM,EAAE,MAAM;SACf,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACK,aAAa,CAAC,MAAc;QAClC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpD,CAAC;CACF"} \ No newline at end of file diff --git a/wwwroot/js/managers/NavigationManager.d.ts b/wwwroot/js/managers/NavigationManager.d.ts new file mode 100644 index 0000000..cc475be --- /dev/null +++ b/wwwroot/js/managers/NavigationManager.d.ts @@ -0,0 +1,32 @@ +import { IEventBus } from '../types/CalendarTypes'; +import { EventRenderingService } from '../renderers/EventRendererManager'; +import { DateService } from '../utils/DateService'; +import { WeekInfoRenderer } from '../renderers/WeekInfoRenderer'; +import { GridRenderer } from '../renderers/GridRenderer'; +export declare class NavigationManager { + private eventBus; + private weekInfoRenderer; + private gridRenderer; + private dateService; + private currentWeek; + private targetWeek; + private animationQueue; + constructor(eventBus: IEventBus, eventRenderer: EventRenderingService, gridRenderer: GridRenderer, dateService: DateService, weekInfoRenderer: WeekInfoRenderer); + private init; + /** + * Get the start of the ISO week (Monday) for a given date + * @param date - Any date in the week + * @returns The Monday of the ISO week + */ + private getISOWeekStart; + private setupEventListeners; + /** + * Navigate to specific event date and emit scroll event after navigation + */ + private navigateToEventDate; + private navigateToDate; + /** + * Animation transition using pre-rendered containers when available + */ + private animateTransition; +} diff --git a/wwwroot/js/managers/NavigationManager.js b/wwwroot/js/managers/NavigationManager.js new file mode 100644 index 0000000..a991117 --- /dev/null +++ b/wwwroot/js/managers/NavigationManager.js @@ -0,0 +1,188 @@ +import { CoreEvents } from '../constants/CoreEvents'; +export class NavigationManager { + constructor(eventBus, eventRenderer, gridRenderer, dateService, weekInfoRenderer) { + this.animationQueue = 0; + this.eventBus = eventBus; + this.dateService = dateService; + this.weekInfoRenderer = weekInfoRenderer; + this.gridRenderer = gridRenderer; + this.currentWeek = this.getISOWeekStart(new Date()); + this.targetWeek = new Date(this.currentWeek); + this.init(); + } + init() { + this.setupEventListeners(); + } + /** + * Get the start of the ISO week (Monday) for a given date + * @param date - Any date in the week + * @returns The Monday of the ISO week + */ + getISOWeekStart(date) { + const weekBounds = this.dateService.getWeekBounds(date); + return this.dateService.startOfDay(weekBounds.start); + } + setupEventListeners() { + // Listen for filter changes and apply to pre-rendered grids + this.eventBus.on(CoreEvents.FILTER_CHANGED, (e) => { + const detail = e.detail; + this.weekInfoRenderer.applyFilterToPreRenderedGrids(detail); + }); + // Listen for navigation button clicks from NavigationButtons + this.eventBus.on(CoreEvents.NAV_BUTTON_CLICKED, (event) => { + const { direction, newDate } = event.detail; + // Navigate to the new date with animation + this.navigateToDate(newDate, direction); + }); + // Listen for external navigation requests + this.eventBus.on(CoreEvents.DATE_CHANGED, (event) => { + const customEvent = event; + const dateFromEvent = customEvent.detail.currentDate; + // Validate date before processing + if (!dateFromEvent) { + console.warn('NavigationManager: No date provided in DATE_CHANGED event'); + return; + } + const targetDate = new Date(dateFromEvent); + // Use DateService validation + const validation = this.dateService.validateDate(targetDate); + if (!validation.valid) { + console.warn('NavigationManager: Invalid date received:', validation.error); + return; + } + this.navigateToDate(targetDate); + }); + // Listen for event navigation requests + this.eventBus.on(CoreEvents.NAVIGATE_TO_EVENT, (event) => { + const customEvent = event; + const { eventDate, eventStartTime } = customEvent.detail; + if (!eventDate || !eventStartTime) { + console.warn('NavigationManager: Invalid event navigation data'); + return; + } + this.navigateToEventDate(eventDate, eventStartTime); + }); + } + /** + * Navigate to specific event date and emit scroll event after navigation + */ + navigateToEventDate(eventDate, eventStartTime) { + const weekStart = this.getISOWeekStart(eventDate); + this.targetWeek = new Date(weekStart); + const currentTime = this.currentWeek.getTime(); + const targetTime = weekStart.getTime(); + // Store event start time for scrolling after navigation + const scrollAfterNavigation = () => { + // Emit scroll request after navigation is complete + this.eventBus.emit('scroll:to-event-time', { + eventStartTime + }); + }; + if (currentTime < targetTime) { + this.animationQueue++; + this.animateTransition('next', weekStart); + // Listen for navigation completion to trigger scroll + this.eventBus.once(CoreEvents.NAVIGATION_COMPLETED, scrollAfterNavigation); + } + else if (currentTime > targetTime) { + this.animationQueue++; + this.animateTransition('prev', weekStart); + // Listen for navigation completion to trigger scroll + this.eventBus.once(CoreEvents.NAVIGATION_COMPLETED, scrollAfterNavigation); + } + else { + // Already on correct week, just scroll + scrollAfterNavigation(); + } + } + navigateToDate(date, direction) { + const weekStart = this.getISOWeekStart(date); + this.targetWeek = new Date(weekStart); + const currentTime = this.currentWeek.getTime(); + const targetTime = weekStart.getTime(); + // Use provided direction or calculate based on time comparison + let animationDirection; + if (direction === 'next') { + animationDirection = 'next'; + } + else if (direction === 'previous') { + animationDirection = 'prev'; + } + else if (direction === 'today') { + // For "today", determine direction based on current position + animationDirection = currentTime < targetTime ? 'next' : 'prev'; + } + else { + // Fallback: calculate direction + animationDirection = currentTime < targetTime ? 'next' : 'prev'; + } + if (currentTime !== targetTime) { + this.animationQueue++; + this.animateTransition(animationDirection, weekStart); + } + } + /** + * Animation transition using pre-rendered containers when available + */ + animateTransition(direction, targetWeek) { + const container = document.querySelector('swp-calendar-container'); + const currentGrid = document.querySelector('swp-calendar-container swp-grid-container:not([data-prerendered])'); + if (!container || !currentGrid) { + return; + } + // Reset all-day height BEFORE creating new grid to ensure base height + const root = document.documentElement; + root.style.setProperty('--all-day-row-height', '0px'); + let newGrid; + console.group('🔧 NavigationManager.refactored'); + console.log('Calling GridRenderer instead of NavigationRenderer'); + console.log('Target week:', targetWeek); + // Always create a fresh container for consistent behavior + newGrid = this.gridRenderer.createNavigationGrid(container, targetWeek); + console.groupEnd(); + // Clear any existing transforms before animation + newGrid.style.transform = ''; + currentGrid.style.transform = ''; + // Animate transition using Web Animations API + const slideOutAnimation = currentGrid.animate([ + { transform: 'translateX(0)', opacity: '1' }, + { transform: direction === 'next' ? 'translateX(-100%)' : 'translateX(100%)', opacity: '0.5' } + ], { + duration: 400, + easing: 'ease-in-out', + fill: 'forwards' + }); + const slideInAnimation = newGrid.animate([ + { transform: direction === 'next' ? 'translateX(100%)' : 'translateX(-100%)' }, + { transform: 'translateX(0)' } + ], { + duration: 400, + easing: 'ease-in-out', + fill: 'forwards' + }); + // Handle animation completion + slideInAnimation.addEventListener('finish', () => { + // Cleanup: Remove all old grids except the new one + const allGrids = container.querySelectorAll('swp-grid-container'); + for (let i = 0; i < allGrids.length - 1; i++) { + allGrids[i].remove(); + } + // Reset positioning + newGrid.style.position = 'relative'; + newGrid.removeAttribute('data-prerendered'); + // Update state + this.currentWeek = new Date(targetWeek); + this.animationQueue--; + // If this was the last queued animation, ensure we're in sync + if (this.animationQueue === 0) { + this.currentWeek = new Date(this.targetWeek); + } + // Emit navigation completed event + this.eventBus.emit(CoreEvents.NAVIGATION_COMPLETED, { + direction, + newDate: this.currentWeek + }); + }); + } +} +//# sourceMappingURL=NavigationManager.js.map \ No newline at end of file diff --git a/wwwroot/js/managers/NavigationManager.js.map b/wwwroot/js/managers/NavigationManager.js.map new file mode 100644 index 0000000..8b411ff --- /dev/null +++ b/wwwroot/js/managers/NavigationManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"NavigationManager.js","sourceRoot":"","sources":["../../../src/managers/NavigationManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/managers/ResizeHandleManager.d.ts b/wwwroot/js/managers/ResizeHandleManager.d.ts new file mode 100644 index 0000000..90f9d9c --- /dev/null +++ b/wwwroot/js/managers/ResizeHandleManager.d.ts @@ -0,0 +1,42 @@ +import { Configuration } from '../configurations/CalendarConfig'; +import { PositionUtils } from '../utils/PositionUtils'; +export declare class ResizeHandleManager { + private config; + private positionUtils; + private isResizing; + private targetEl; + private startY; + private startDurationMin; + private snapMin; + private minDurationMin; + private animationId; + private currentHeight; + private targetHeight; + private pointerCaptured; + private prevZ?; + private readonly ANIMATION_SPEED; + private readonly Z_INDEX_RESIZING; + private readonly EVENT_REFRESH_THRESHOLD; + constructor(config: Configuration, positionUtils: PositionUtils); + initialize(): void; + destroy(): void; + private removeEventListeners; + private createResizeHandle; + private attachGlobalListeners; + private onMouseOver; + private onPointerDown; + private startResizing; + private setZIndexForResizing; + private capturePointer; + private onPointerMove; + private updateResizeHeight; + private animate; + private finalizeAnimation; + private onPointerUp; + private cleanupAnimation; + private snapToGrid; + private emitResizeEndEvent; + private cleanupResizing; + private restoreZIndex; + private releasePointer; +} diff --git a/wwwroot/js/managers/ResizeHandleManager.js b/wwwroot/js/managers/ResizeHandleManager.js new file mode 100644 index 0000000..c753f42 --- /dev/null +++ b/wwwroot/js/managers/ResizeHandleManager.js @@ -0,0 +1,194 @@ +import { eventBus } from '../core/EventBus'; +export class ResizeHandleManager { + constructor(config, positionUtils) { + this.config = config; + this.positionUtils = positionUtils; + this.isResizing = false; + this.targetEl = null; + this.startY = 0; + this.startDurationMin = 0; + this.animationId = null; + this.currentHeight = 0; + this.targetHeight = 0; + this.pointerCaptured = false; + // Constants for better maintainability + this.ANIMATION_SPEED = 0.35; + this.Z_INDEX_RESIZING = '1000'; + this.EVENT_REFRESH_THRESHOLD = 0.5; + this.onMouseOver = (e) => { + const target = e.target; + const eventElement = target.closest('swp-event'); + if (eventElement && !this.isResizing) { + // Check if handle already exists + if (!eventElement.querySelector(':scope > swp-resize-handle')) { + const handle = this.createResizeHandle(); + eventElement.appendChild(handle); + } + } + }; + this.onPointerDown = (e) => { + const handle = e.target.closest('swp-resize-handle'); + if (!handle) + return; + const element = handle.parentElement; + this.startResizing(element, e); + }; + this.onPointerMove = (e) => { + if (!this.isResizing || !this.targetEl) + return; + this.updateResizeHeight(e.clientY); + }; + this.animate = () => { + if (!this.isResizing || !this.targetEl) { + this.animationId = null; + return; + } + const diff = this.targetHeight - this.currentHeight; + if (Math.abs(diff) > this.EVENT_REFRESH_THRESHOLD) { + this.currentHeight += diff * this.ANIMATION_SPEED; + this.targetEl.updateHeight?.(this.currentHeight); + this.animationId = requestAnimationFrame(this.animate); + } + else { + this.finalizeAnimation(); + } + }; + this.onPointerUp = (e) => { + if (!this.isResizing || !this.targetEl) + return; + this.cleanupAnimation(); + this.snapToGrid(); + this.emitResizeEndEvent(); + this.cleanupResizing(e); + }; + const grid = this.config.gridSettings; + this.snapMin = grid.snapInterval; + this.minDurationMin = this.snapMin; + } + initialize() { + this.attachGlobalListeners(); + } + destroy() { + this.removeEventListeners(); + } + removeEventListeners() { + const calendarContainer = document.querySelector('swp-calendar-container'); + if (calendarContainer) { + calendarContainer.removeEventListener('mouseover', this.onMouseOver, true); + } + document.removeEventListener('pointerdown', this.onPointerDown, true); + document.removeEventListener('pointermove', this.onPointerMove, true); + document.removeEventListener('pointerup', this.onPointerUp, true); + } + createResizeHandle() { + const handle = document.createElement('swp-resize-handle'); + handle.setAttribute('aria-label', 'Resize event'); + handle.setAttribute('role', 'separator'); + return handle; + } + attachGlobalListeners() { + const calendarContainer = document.querySelector('swp-calendar-container'); + if (calendarContainer) { + calendarContainer.addEventListener('mouseover', this.onMouseOver, true); + } + document.addEventListener('pointerdown', this.onPointerDown, true); + document.addEventListener('pointermove', this.onPointerMove, true); + document.addEventListener('pointerup', this.onPointerUp, true); + } + startResizing(element, event) { + this.targetEl = element; + this.isResizing = true; + this.startY = event.clientY; + const startHeight = element.offsetHeight; + this.startDurationMin = Math.max(this.minDurationMin, Math.round(this.positionUtils.pixelsToMinutes(startHeight))); + this.setZIndexForResizing(element); + this.capturePointer(event); + document.documentElement.classList.add('swp--resizing'); + event.preventDefault(); + } + setZIndexForResizing(element) { + const container = element.closest('swp-event-group') ?? element; + this.prevZ = container.style.zIndex; + container.style.zIndex = this.Z_INDEX_RESIZING; + } + capturePointer(event) { + try { + event.target.setPointerCapture?.(event.pointerId); + this.pointerCaptured = true; + } + catch (error) { + console.warn('Pointer capture failed:', error); + } + } + updateResizeHeight(currentY) { + const deltaY = currentY - this.startY; + const startHeight = this.positionUtils.minutesToPixels(this.startDurationMin); + const rawHeight = startHeight + deltaY; + const minHeight = this.positionUtils.minutesToPixels(this.minDurationMin); + this.targetHeight = Math.max(minHeight, rawHeight); + if (this.animationId == null) { + this.currentHeight = this.targetEl?.offsetHeight; + this.animate(); + } + } + finalizeAnimation() { + if (!this.targetEl) + return; + this.currentHeight = this.targetHeight; + this.targetEl.updateHeight?.(this.currentHeight); + this.animationId = null; + } + cleanupAnimation() { + if (this.animationId != null) { + cancelAnimationFrame(this.animationId); + this.animationId = null; + } + } + snapToGrid() { + if (!this.targetEl) + return; + const currentHeight = this.targetEl.offsetHeight; + const snapDistancePx = this.positionUtils.minutesToPixels(this.snapMin); + const snappedHeight = Math.round(currentHeight / snapDistancePx) * snapDistancePx; + const minHeight = this.positionUtils.minutesToPixels(this.minDurationMin); + const finalHeight = Math.max(minHeight, snappedHeight) - 3; // Small gap to grid lines + this.targetEl.updateHeight?.(finalHeight); + } + emitResizeEndEvent() { + if (!this.targetEl) + return; + const eventId = this.targetEl.dataset.eventId || ''; + const resizeEndPayload = { + eventId, + element: this.targetEl, + finalHeight: this.targetEl.offsetHeight + }; + eventBus.emit('resize:end', resizeEndPayload); + } + cleanupResizing(event) { + this.restoreZIndex(); + this.releasePointer(event); + this.isResizing = false; + this.targetEl = null; + document.documentElement.classList.remove('swp--resizing'); + } + restoreZIndex() { + if (!this.targetEl || this.prevZ === undefined) + return; + const container = this.targetEl.closest('swp-event-group') ?? this.targetEl; + container.style.zIndex = this.prevZ; + this.prevZ = undefined; + } + releasePointer(event) { + if (!this.pointerCaptured) + return; + try { + event.target.releasePointerCapture?.(event.pointerId); + this.pointerCaptured = false; + } + catch (error) { + console.warn('Pointer release failed:', error); + } + } +} +//# sourceMappingURL=ResizeHandleManager.js.map \ No newline at end of file diff --git a/wwwroot/js/managers/ResizeHandleManager.js.map b/wwwroot/js/managers/ResizeHandleManager.js.map new file mode 100644 index 0000000..fa05fae --- /dev/null +++ b/wwwroot/js/managers/ResizeHandleManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ResizeHandleManager.js","sourceRoot":"","sources":["../../../src/managers/ResizeHandleManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/managers/ScrollManager.d.ts b/wwwroot/js/managers/ScrollManager.d.ts new file mode 100644 index 0000000..a3eda8a --- /dev/null +++ b/wwwroot/js/managers/ScrollManager.d.ts @@ -0,0 +1,64 @@ +import { PositionUtils } from '../utils/PositionUtils'; +/** + * Manages scrolling functionality for the calendar using native scrollbars + */ +export declare class ScrollManager { + private scrollableContent; + private calendarContainer; + private timeAxis; + private calendarHeader; + private resizeObserver; + private positionUtils; + constructor(positionUtils: PositionUtils); + private init; + /** + * Public method to initialize scroll after grid is rendered + */ + initialize(): void; + private subscribeToEvents; + /** + * Setup scrolling functionality after grid is rendered + */ + private setupScrolling; + /** + * Find DOM elements needed for scrolling + */ + private findElements; + /** + * Scroll to specific position + */ + scrollTo(scrollTop: number): void; + /** + * Scroll to specific hour using PositionUtils + */ + scrollToHour(hour: number): void; + /** + * Scroll to specific event time + * @param eventStartTime ISO string of event start time + */ + scrollToEventTime(eventStartTime: string): void; + /** + * Setup ResizeObserver to monitor container size changes + */ + private setupResizeObserver; + /** + * Calculate and update scrollable content height dynamically + */ + private updateScrollableHeight; + /** + * Setup scroll synchronization between scrollable content and time axis + */ + private setupScrollSynchronization; + /** + * Synchronize time axis position with scrollable content + */ + private syncTimeAxisPosition; + /** + * Setup horizontal scroll synchronization between scrollable content and calendar header + */ + private setupHorizontalScrollSynchronization; + /** + * Synchronize calendar header position with scrollable content horizontal scroll + */ + private syncCalendarHeaderPosition; +} diff --git a/wwwroot/js/managers/ScrollManager.js b/wwwroot/js/managers/ScrollManager.js new file mode 100644 index 0000000..c14533a --- /dev/null +++ b/wwwroot/js/managers/ScrollManager.js @@ -0,0 +1,217 @@ +// Custom scroll management for calendar week container +import { eventBus } from '../core/EventBus'; +import { CoreEvents } from '../constants/CoreEvents'; +/** + * Manages scrolling functionality for the calendar using native scrollbars + */ +export class ScrollManager { + constructor(positionUtils) { + this.scrollableContent = null; + this.calendarContainer = null; + this.timeAxis = null; + this.calendarHeader = null; + this.resizeObserver = null; + this.positionUtils = positionUtils; + this.init(); + } + init() { + this.subscribeToEvents(); + } + /** + * Public method to initialize scroll after grid is rendered + */ + initialize() { + this.setupScrolling(); + } + subscribeToEvents() { + // Handle navigation animation completion - sync time axis position + eventBus.on(CoreEvents.NAVIGATION_COMPLETED, () => { + this.syncTimeAxisPosition(); + this.setupScrolling(); + }); + // Handle all-day row height changes + eventBus.on('header:height-changed', () => { + this.updateScrollableHeight(); + }); + // Handle header ready - refresh header reference and re-sync + eventBus.on('header:ready', () => { + this.calendarHeader = document.querySelector('swp-calendar-header'); + if (this.scrollableContent && this.calendarHeader) { + this.setupHorizontalScrollSynchronization(); + this.syncCalendarHeaderPosition(); // Immediately sync position + } + this.updateScrollableHeight(); // Update height calculations + }); + // Handle window resize + window.addEventListener('resize', () => { + this.updateScrollableHeight(); + }); + // Listen for scroll to event time requests + eventBus.on('scroll:to-event-time', (event) => { + const customEvent = event; + const { eventStartTime } = customEvent.detail; + if (eventStartTime) { + this.scrollToEventTime(eventStartTime); + } + }); + } + /** + * Setup scrolling functionality after grid is rendered + */ + setupScrolling() { + this.findElements(); + if (this.scrollableContent && this.calendarContainer) { + this.setupResizeObserver(); + this.updateScrollableHeight(); + this.setupScrollSynchronization(); + } + // Setup horizontal scrolling synchronization + if (this.scrollableContent && this.calendarHeader) { + this.setupHorizontalScrollSynchronization(); + } + } + /** + * Find DOM elements needed for scrolling + */ + findElements() { + this.scrollableContent = document.querySelector('swp-scrollable-content'); + this.calendarContainer = document.querySelector('swp-calendar-container'); + this.timeAxis = document.querySelector('swp-time-axis'); + this.calendarHeader = document.querySelector('swp-calendar-header'); + } + /** + * Scroll to specific position + */ + scrollTo(scrollTop) { + if (!this.scrollableContent) + return; + this.scrollableContent.scrollTop = scrollTop; + } + /** + * Scroll to specific hour using PositionUtils + */ + scrollToHour(hour) { + // Create time string for the hour + const timeString = `${hour.toString().padStart(2, '0')}:00`; + const scrollTop = this.positionUtils.timeToPixels(timeString); + this.scrollTo(scrollTop); + } + /** + * Scroll to specific event time + * @param eventStartTime ISO string of event start time + */ + scrollToEventTime(eventStartTime) { + try { + const eventDate = new Date(eventStartTime); + const eventHour = eventDate.getHours(); + const eventMinutes = eventDate.getMinutes(); + // Convert to decimal hour (e.g., 14:30 becomes 14.5) + const decimalHour = eventHour + (eventMinutes / 60); + this.scrollToHour(decimalHour); + } + catch (error) { + console.warn('ScrollManager: Failed to scroll to event time:', error); + } + } + /** + * Setup ResizeObserver to monitor container size changes + */ + setupResizeObserver() { + if (!this.calendarContainer) + return; + // Clean up existing observer + if (this.resizeObserver) { + this.resizeObserver.disconnect(); + } + this.resizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + this.updateScrollableHeight(); + } + }); + this.resizeObserver.observe(this.calendarContainer); + } + /** + * Calculate and update scrollable content height dynamically + */ + updateScrollableHeight() { + if (!this.scrollableContent || !this.calendarContainer) + return; + // Get calendar container height + const containerRect = this.calendarContainer.getBoundingClientRect(); + // Find navigation height + const navigation = document.querySelector('swp-calendar-nav'); + const navHeight = navigation ? navigation.getBoundingClientRect().height : 0; + // Find calendar header height + const calendarHeaderElement = document.querySelector('swp-calendar-header'); + const headerHeight = calendarHeaderElement ? calendarHeaderElement.getBoundingClientRect().height : 80; + // Calculate available height for scrollable content + const availableHeight = containerRect.height - headerHeight; + // Calculate available width (container width minus time-axis) + const availableWidth = containerRect.width - 60; // 60px time-axis + // Set the height and width on scrollable content + if (availableHeight > 0) { + this.scrollableContent.style.height = `${availableHeight}px`; + } + if (availableWidth > 0) { + this.scrollableContent.style.width = `${availableWidth}px`; + } + } + /** + * Setup scroll synchronization between scrollable content and time axis + */ + setupScrollSynchronization() { + if (!this.scrollableContent || !this.timeAxis) + return; + // Throttle scroll events for better performance + let scrollTimeout = null; + this.scrollableContent.addEventListener('scroll', () => { + if (scrollTimeout) { + cancelAnimationFrame(scrollTimeout); + } + scrollTimeout = requestAnimationFrame(() => { + this.syncTimeAxisPosition(); + }); + }); + } + /** + * Synchronize time axis position with scrollable content + */ + syncTimeAxisPosition() { + if (!this.scrollableContent || !this.timeAxis) + return; + const scrollTop = this.scrollableContent.scrollTop; + const timeAxisContent = this.timeAxis.querySelector('swp-time-axis-content'); + if (timeAxisContent) { + // Use transform for smooth performance + timeAxisContent.style.transform = `translateY(-${scrollTop}px)`; + // Debug logging (can be removed later) + if (scrollTop % 100 === 0) { // Only log every 100px to avoid spam + } + } + } + /** + * Setup horizontal scroll synchronization between scrollable content and calendar header + */ + setupHorizontalScrollSynchronization() { + if (!this.scrollableContent || !this.calendarHeader) + return; + // Listen to horizontal scroll events + this.scrollableContent.addEventListener('scroll', () => { + this.syncCalendarHeaderPosition(); + }); + } + /** + * Synchronize calendar header position with scrollable content horizontal scroll + */ + syncCalendarHeaderPosition() { + if (!this.scrollableContent || !this.calendarHeader) + return; + const scrollLeft = this.scrollableContent.scrollLeft; + // Use transform for smooth performance + this.calendarHeader.style.transform = `translateX(-${scrollLeft}px)`; + // Debug logging (can be removed later) + if (scrollLeft % 100 === 0) { // Only log every 100px to avoid spam + } + } +} +//# sourceMappingURL=ScrollManager.js.map \ No newline at end of file diff --git a/wwwroot/js/managers/ScrollManager.js.map b/wwwroot/js/managers/ScrollManager.js.map new file mode 100644 index 0000000..63c28e1 --- /dev/null +++ b/wwwroot/js/managers/ScrollManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ScrollManager.js","sourceRoot":"","sources":["../../../src/managers/ScrollManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/managers/SimpleEventOverlapManager.d.ts b/wwwroot/js/managers/SimpleEventOverlapManager.d.ts new file mode 100644 index 0000000..a3dce25 --- /dev/null +++ b/wwwroot/js/managers/SimpleEventOverlapManager.d.ts @@ -0,0 +1,80 @@ +/** + * SimpleEventOverlapManager - Clean, focused overlap management + * Eliminates complex state tracking in favor of direct DOM manipulation + */ +import { CalendarEvent } from '../types/CalendarTypes'; +export declare enum OverlapType { + NONE = "none", + COLUMN_SHARING = "column_sharing", + STACKING = "stacking" +} +export interface OverlapGroup { + type: OverlapType; + events: CalendarEvent[]; + position: { + top: number; + height: number; + }; +} +export interface StackLink { + prev?: string; + next?: string; + stackLevel: number; +} +export declare class SimpleEventOverlapManager { + private static readonly STACKING_WIDTH_REDUCTION_PX; + /** + * Detect overlap type between two DOM elements - pixel-based logic + */ + resolveOverlapType(element1: HTMLElement, element2: HTMLElement): OverlapType; + /** + * Group overlapping elements - pixel-based algorithm + */ + groupOverlappingElements(elements: HTMLElement[]): HTMLElement[][]; + /** + * Create flexbox container for column sharing - clean and simple + */ + createEventGroup(events: CalendarEvent[], position: { + top: number; + height: number; + }): HTMLElement; + /** + * Add event to flexbox group - simple relative positioning + */ + addToEventGroup(container: HTMLElement, eventElement: HTMLElement): void; + /** + * Create stacked event with data-attribute tracking + */ + createStackedEvent(eventElement: HTMLElement, underlyingElement: HTMLElement, stackLevel: number): void; + /** + * Remove stacked styling with proper stack re-linking + */ + removeStackedStyling(eventElement: HTMLElement): void; + /** + * Update stack levels for all events following a given event ID + */ + private updateSubsequentStackLevels; + /** + * Check if element is stacked - check both style and data-stack-link + */ + isStackedEvent(element: HTMLElement): boolean; + /** + * Remove event from group with proper cleanup + */ + removeFromEventGroup(container: HTMLElement, eventId: string): boolean; + /** + * Restack events in container - respects separate stack chains + */ + restackEventsInContainer(container: HTMLElement): void; + /** + * Utility methods - simple DOM traversal + */ + getEventGroup(eventElement: HTMLElement): HTMLElement | null; + isInEventGroup(element: HTMLElement): boolean; + /** + * Helper methods for data-attribute based stack tracking + */ + getStackLink(element: HTMLElement): StackLink | null; + private setStackLink; + private findElementById; +} diff --git a/wwwroot/js/managers/SimpleEventOverlapManager.js b/wwwroot/js/managers/SimpleEventOverlapManager.js new file mode 100644 index 0000000..b782f02 --- /dev/null +++ b/wwwroot/js/managers/SimpleEventOverlapManager.js @@ -0,0 +1,399 @@ +/** + * SimpleEventOverlapManager - Clean, focused overlap management + * Eliminates complex state tracking in favor of direct DOM manipulation + */ +import { calendarConfig } from '../core/CalendarConfig'; +export var OverlapType; +(function (OverlapType) { + OverlapType["NONE"] = "none"; + OverlapType["COLUMN_SHARING"] = "column_sharing"; + OverlapType["STACKING"] = "stacking"; +})(OverlapType || (OverlapType = {})); +export class SimpleEventOverlapManager { + /** + * Detect overlap type between two DOM elements - pixel-based logic + */ + resolveOverlapType(element1, element2) { + const top1 = parseInt(element1.style.top) || 0; + const height1 = parseInt(element1.style.height) || 0; + const bottom1 = top1 + height1; + const top2 = parseInt(element2.style.top) || 0; + const height2 = parseInt(element2.style.height) || 0; + const bottom2 = top2 + height2; + // Check if events overlap in pixel space + const tolerance = 2; + if (bottom1 <= (top2 + tolerance) || bottom2 <= (top1 + tolerance)) { + return OverlapType.NONE; + } + // Events overlap - check start position difference for overlap type + const startDifference = Math.abs(top1 - top2); + // Over 40px start difference = stacking + if (startDifference > 40) { + return OverlapType.STACKING; + } + // Within 40px start difference = column sharing + return OverlapType.COLUMN_SHARING; + } + /** + * Group overlapping elements - pixel-based algorithm + */ + groupOverlappingElements(elements) { + const groups = []; + const processed = new Set(); + for (const element of elements) { + if (processed.has(element)) + continue; + // Find all elements that overlap with this one + const overlapping = elements.filter(other => { + if (processed.has(other)) + return false; + return other === element || this.resolveOverlapType(element, other) !== OverlapType.NONE; + }); + // Mark all as processed + overlapping.forEach(e => processed.add(e)); + groups.push(overlapping); + } + return groups; + } + /** + * Create flexbox container for column sharing - clean and simple + */ + createEventGroup(events, position) { + const container = document.createElement('swp-event-group'); + return container; + } + /** + * Add event to flexbox group - simple relative positioning + */ + addToEventGroup(container, eventElement) { + // Set duration-based height + const duration = eventElement.dataset.duration; + if (duration) { + const durationMinutes = parseInt(duration); + const gridSettings = calendarConfig.getGridSettings(); + const height = (durationMinutes / 60) * gridSettings.hourHeight; + eventElement.style.height = `${height - 3}px`; + } + // Flexbox styling + eventElement.style.position = 'relative'; + eventElement.style.flex = '1'; + eventElement.style.minWidth = '50px'; + container.appendChild(eventElement); + } + /** + * Create stacked event with data-attribute tracking + */ + createStackedEvent(eventElement, underlyingElement, stackLevel) { + const marginLeft = stackLevel * SimpleEventOverlapManager.STACKING_WIDTH_REDUCTION_PX; + // Apply visual styling + eventElement.style.marginLeft = `${marginLeft}px`; + eventElement.style.left = '2px'; + eventElement.style.right = '2px'; + eventElement.style.zIndex = `${100 + stackLevel}`; + // Set up stack linking via data attributes + const eventId = eventElement.dataset.eventId; + const underlyingId = underlyingElement.dataset.eventId; + if (!eventId || !underlyingId) { + console.warn('Missing event IDs for stack linking:', eventId, underlyingId); + return; + } + // Find the last event in the stack chain + let lastElement = underlyingElement; + let lastLink = this.getStackLink(lastElement); + // If underlying doesn't have stack link yet, create it + if (!lastLink) { + this.setStackLink(lastElement, { stackLevel: 0 }); + lastLink = { stackLevel: 0 }; + } + // Traverse to find the end of the chain + while (lastLink?.next) { + const nextElement = this.findElementById(lastLink.next); + if (!nextElement) + break; + lastElement = nextElement; + lastLink = this.getStackLink(lastElement); + } + // Link the new event to the end of the chain + const lastElementId = lastElement.dataset.eventId; + this.setStackLink(lastElement, { + ...lastLink, + next: eventId + }); + this.setStackLink(eventElement, { + prev: lastElementId, + stackLevel: stackLevel + }); + } + /** + * Remove stacked styling with proper stack re-linking + */ + removeStackedStyling(eventElement) { + // Clear visual styling + eventElement.style.marginLeft = ''; + eventElement.style.zIndex = ''; + eventElement.style.left = '2px'; + eventElement.style.right = '2px'; + // Handle stack chain re-linking + const link = this.getStackLink(eventElement); + if (link) { + // Re-link prev and next events + if (link.prev && link.next) { + // Middle element - link prev to next + const prevElement = this.findElementById(link.prev); + const nextElement = this.findElementById(link.next); + if (prevElement && nextElement) { + const prevLink = this.getStackLink(prevElement); + const nextLink = this.getStackLink(nextElement); + // CRITICAL: Check if prev and next actually overlap without the middle element + const actuallyOverlap = this.resolveOverlapType(prevElement, nextElement); + if (!actuallyOverlap) { + // CHAIN BREAKING: prev and next don't overlap - break the chain + console.log('Breaking stack chain - events do not overlap directly'); + // Prev element: remove next link (becomes end of its own chain) + this.setStackLink(prevElement, { + ...prevLink, + next: undefined + }); + // Next element: becomes standalone (remove all stack links and styling) + this.setStackLink(nextElement, null); + nextElement.style.marginLeft = ''; + nextElement.style.zIndex = ''; + // If next element had subsequent events, they also become standalone + if (nextLink?.next) { + let subsequentId = nextLink.next; + while (subsequentId) { + const subsequentElement = this.findElementById(subsequentId); + if (!subsequentElement) + break; + const subsequentLink = this.getStackLink(subsequentElement); + this.setStackLink(subsequentElement, null); + subsequentElement.style.marginLeft = ''; + subsequentElement.style.zIndex = ''; + subsequentId = subsequentLink?.next; + } + } + } + else { + // NORMAL STACKING: they overlap, maintain the chain + this.setStackLink(prevElement, { + ...prevLink, + next: link.next + }); + const correctStackLevel = (prevLink?.stackLevel ?? 0) + 1; + this.setStackLink(nextElement, { + ...nextLink, + prev: link.prev, + stackLevel: correctStackLevel + }); + // Update visual styling to match new stackLevel + const marginLeft = correctStackLevel * SimpleEventOverlapManager.STACKING_WIDTH_REDUCTION_PX; + nextElement.style.marginLeft = `${marginLeft}px`; + nextElement.style.zIndex = `${100 + correctStackLevel}`; + } + } + } + else if (link.prev) { + // Last element - remove next link from prev + const prevElement = this.findElementById(link.prev); + if (prevElement) { + const prevLink = this.getStackLink(prevElement); + this.setStackLink(prevElement, { + ...prevLink, + next: undefined + }); + } + } + else if (link.next) { + // First element - remove prev link from next + const nextElement = this.findElementById(link.next); + if (nextElement) { + const nextLink = this.getStackLink(nextElement); + this.setStackLink(nextElement, { + ...nextLink, + prev: undefined, + stackLevel: 0 // Next becomes the base event + }); + } + } + // Only update subsequent stack levels if we didn't break the chain + if (link.prev && link.next) { + const nextElement = this.findElementById(link.next); + const nextLink = nextElement ? this.getStackLink(nextElement) : null; + // If next element still has a stack link, the chain wasn't broken + if (nextLink && nextLink.next) { + this.updateSubsequentStackLevels(nextLink.next, -1); + } + // If nextLink is null, chain was broken - no subsequent updates needed + } + else { + // First or last removal - update all subsequent + this.updateSubsequentStackLevels(link.next, -1); + } + // Clear this element's stack link + this.setStackLink(eventElement, null); + } + } + /** + * Update stack levels for all events following a given event ID + */ + updateSubsequentStackLevels(startEventId, levelDelta) { + let currentId = startEventId; + while (currentId) { + const currentElement = this.findElementById(currentId); + if (!currentElement) + break; + const currentLink = this.getStackLink(currentElement); + if (!currentLink) + break; + // Update stack level + const newLevel = Math.max(0, currentLink.stackLevel + levelDelta); + this.setStackLink(currentElement, { + ...currentLink, + stackLevel: newLevel + }); + // Update visual styling + const marginLeft = newLevel * SimpleEventOverlapManager.STACKING_WIDTH_REDUCTION_PX; + currentElement.style.marginLeft = `${marginLeft}px`; + currentElement.style.zIndex = `${100 + newLevel}`; + currentId = currentLink.next; + } + } + /** + * Check if element is stacked - check both style and data-stack-link + */ + isStackedEvent(element) { + const marginLeft = element.style.marginLeft; + const hasMarginLeft = marginLeft !== '' && marginLeft !== '0px'; + const hasStackLink = this.getStackLink(element) !== null; + return hasMarginLeft || hasStackLink; + } + /** + * Remove event from group with proper cleanup + */ + removeFromEventGroup(container, eventId) { + const eventElement = container.querySelector(`swp-event[data-event-id="${eventId}"]`); + if (!eventElement) + return false; + // Simply remove the element - no position calculation needed since it's being removed + eventElement.remove(); + // Handle remaining events + const remainingEvents = container.querySelectorAll('swp-event'); + const remainingCount = remainingEvents.length; + if (remainingCount === 0) { + container.remove(); + return true; + } + if (remainingCount === 1) { + const remainingEvent = remainingEvents[0]; + // Convert last event back to absolute positioning - use current pixel position + const currentTop = parseInt(remainingEvent.style.top) || 0; + remainingEvent.style.position = 'absolute'; + remainingEvent.style.top = `${currentTop}px`; + remainingEvent.style.left = '2px'; + remainingEvent.style.right = '2px'; + remainingEvent.style.flex = ''; + remainingEvent.style.minWidth = ''; + container.parentElement?.insertBefore(remainingEvent, container); + container.remove(); + return true; + } + return false; + } + /** + * Restack events in container - respects separate stack chains + */ + restackEventsInContainer(container) { + const stackedEvents = Array.from(container.querySelectorAll('swp-event')) + .filter(el => this.isStackedEvent(el)); + if (stackedEvents.length === 0) + return; + // Group events by their stack chains + const processedEventIds = new Set(); + const stackChains = []; + for (const element of stackedEvents) { + const eventId = element.dataset.eventId; + if (!eventId || processedEventIds.has(eventId)) + continue; + // Find the root of this stack chain (stackLevel 0 or no prev link) + let rootElement = element; + let rootLink = this.getStackLink(rootElement); + while (rootLink?.prev) { + const prevElement = this.findElementById(rootLink.prev); + if (!prevElement) + break; + rootElement = prevElement; + rootLink = this.getStackLink(rootElement); + } + // Collect all elements in this chain + const chain = []; + let currentElement = rootElement; + while (currentElement) { + chain.push(currentElement); + processedEventIds.add(currentElement.dataset.eventId); + const currentLink = this.getStackLink(currentElement); + if (!currentLink?.next) + break; + const nextElement = this.findElementById(currentLink.next); + if (!nextElement) + break; + currentElement = nextElement; + } + if (chain.length > 1) { // Only add chains with multiple events + stackChains.push(chain); + } + } + // Re-stack each chain separately + stackChains.forEach(chain => { + chain.forEach((element, index) => { + const marginLeft = index * SimpleEventOverlapManager.STACKING_WIDTH_REDUCTION_PX; + element.style.marginLeft = `${marginLeft}px`; + element.style.zIndex = `${100 + index}`; + // Update the data-stack-link with correct stackLevel + const link = this.getStackLink(element); + if (link) { + this.setStackLink(element, { + ...link, + stackLevel: index + }); + } + }); + }); + } + /** + * Utility methods - simple DOM traversal + */ + getEventGroup(eventElement) { + return eventElement.closest('swp-event-group'); + } + isInEventGroup(element) { + return this.getEventGroup(element) !== null; + } + /** + * Helper methods for data-attribute based stack tracking + */ + getStackLink(element) { + const linkData = element.dataset.stackLink; + if (!linkData) + return null; + try { + return JSON.parse(linkData); + } + catch (e) { + console.warn('Failed to parse stack link data:', linkData, e); + return null; + } + } + setStackLink(element, link) { + if (link === null) { + delete element.dataset.stackLink; + } + else { + element.dataset.stackLink = JSON.stringify(link); + } + } + findElementById(eventId) { + return document.querySelector(`swp-event[data-event-id="${eventId}"]`); + } +} +SimpleEventOverlapManager.STACKING_WIDTH_REDUCTION_PX = 15; +//# sourceMappingURL=SimpleEventOverlapManager.js.map \ No newline at end of file diff --git a/wwwroot/js/managers/SimpleEventOverlapManager.js.map b/wwwroot/js/managers/SimpleEventOverlapManager.js.map new file mode 100644 index 0000000..173a398 --- /dev/null +++ b/wwwroot/js/managers/SimpleEventOverlapManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"SimpleEventOverlapManager.js","sourceRoot":"","sources":["../../../src/managers/SimpleEventOverlapManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,MAAM,CAAN,IAAY,WAIX;AAJD,WAAY,WAAW;IACrB,4BAAa,CAAA;IACb,gDAAiC,CAAA;IACjC,oCAAqB,CAAA;AACvB,CAAC,EAJW,WAAW,KAAX,WAAW,QAItB;AAcD,MAAM,OAAO,yBAAyB;IAGpC;;OAEG;IACI,kBAAkB,CAAC,QAAqB,EAAE,QAAqB;QACpE,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACrD,MAAM,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC;QAE/B,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACrD,MAAM,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC;QAE/B,yCAAyC;QACzC,MAAM,SAAS,GAAG,CAAC,CAAC;QACpB,IAAI,OAAO,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC,IAAI,OAAO,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC,EAAE,CAAC;YACnE,OAAO,WAAW,CAAC,IAAI,CAAC;QAC1B,CAAC;QAED,oEAAoE;QACpE,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;QAE9C,wCAAwC;QACxC,IAAI,eAAe,GAAG,EAAE,EAAE,CAAC;YACzB,OAAO,WAAW,CAAC,QAAQ,CAAC;QAC9B,CAAC;QAED,gDAAgD;QAChD,OAAO,WAAW,CAAC,cAAc,CAAC;IACpC,CAAC;IAGD;;OAEG;IACI,wBAAwB,CAAC,QAAuB;QACrD,MAAM,MAAM,GAAoB,EAAE,CAAC;QACnC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAe,CAAC;QAEzC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,IAAI,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,SAAS;YAErC,+CAA+C;YAC/C,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;gBAC1C,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;oBAAE,OAAO,KAAK,CAAC;gBACvC,OAAO,KAAK,KAAK,OAAO,IAAI,IAAI,CAAC,kBAAkB,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,WAAW,CAAC,IAAI,CAAC;YAC3F,CAAC,CAAC,CAAC;YAEH,wBAAwB;YACxB,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAE3C,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC3B,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;OAEG;IACI,gBAAgB,CAAC,MAAuB,EAAE,QAAyC;QACxF,MAAM,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC;QAC5D,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;OAEG;IACI,eAAe,CAAC,SAAsB,EAAE,YAAyB;QACtE,4BAA4B;QAC5B,MAAM,QAAQ,GAAG,YAAY,CAAC,OAAO,CAAC,QAAQ,CAAC;QAC/C,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,eAAe,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAC3C,MAAM,YAAY,GAAG,cAAc,CAAC,eAAe,EAAE,CAAC;YACtD,MAAM,MAAM,GAAG,CAAC,eAAe,GAAG,EAAE,CAAC,GAAG,YAAY,CAAC,UAAU,CAAC;YAChE,YAAY,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC;QAChD,CAAC;QAED,kBAAkB;QAClB,YAAY,CAAC,KAAK,CAAC,QAAQ,GAAG,UAAU,CAAC;QACzC,YAAY,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,CAAC;QAC9B,YAAY,CAAC,KAAK,CAAC,QAAQ,GAAG,MAAM,CAAC;QAErC,SAAS,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;IACtC,CAAC;IAED;;OAEG;IACI,kBAAkB,CAAC,YAAyB,EAAE,iBAA8B,EAAE,UAAkB;QACrG,MAAM,UAAU,GAAG,UAAU,GAAG,yBAAyB,CAAC,2BAA2B,CAAC;QAEtF,uBAAuB;QACvB,YAAY,CAAC,KAAK,CAAC,UAAU,GAAG,GAAG,UAAU,IAAI,CAAC;QAClD,YAAY,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC;QAChC,YAAY,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;QACjC,YAAY,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,GAAG,GAAG,UAAU,EAAE,CAAC;QAElD,2CAA2C;QAC3C,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC;QAC7C,MAAM,YAAY,GAAG,iBAAiB,CAAC,OAAO,CAAC,OAAO,CAAC;QAEvD,IAAI,CAAC,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC;YAC9B,OAAO,CAAC,IAAI,CAAC,sCAAsC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;YAC5E,OAAO;QACT,CAAC;QAED,yCAAyC;QACzC,IAAI,WAAW,GAAG,iBAAiB,CAAC;QACpC,IAAI,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;QAE9C,uDAAuD;QACvD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC;YAClD,QAAQ,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;QAC/B,CAAC;QAED,wCAAwC;QACxC,OAAO,QAAQ,EAAE,IAAI,EAAE,CAAC;YACtB,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YACxD,IAAI,CAAC,WAAW;gBAAE,MAAM;YACxB,WAAW,GAAG,WAAW,CAAC;YAC1B,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;QAC5C,CAAC;QAED,6CAA6C;QAC7C,MAAM,aAAa,GAAG,WAAW,CAAC,OAAO,CAAC,OAAQ,CAAC;QACnD,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE;YAC7B,GAAG,QAAS;YACZ,IAAI,EAAE,OAAO;SACd,CAAC,CAAC;QAEH,IAAI,CAAC,YAAY,CAAC,YAAY,EAAE;YAC9B,IAAI,EAAE,aAAa;YACnB,UAAU,EAAE,UAAU;SACvB,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACI,oBAAoB,CAAC,YAAyB;QACnD,uBAAuB;QACvB,YAAY,CAAC,KAAK,CAAC,UAAU,GAAG,EAAE,CAAC;QACnC,YAAY,CAAC,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC;QAC/B,YAAY,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC;QAChC,YAAY,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;QAEjC,gCAAgC;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;QAC7C,IAAI,IAAI,EAAE,CAAC;YACT,+BAA+B;YAC/B,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;gBAC3B,qCAAqC;gBACrC,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACpD,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAEpD,IAAI,WAAW,IAAI,WAAW,EAAE,CAAC;oBAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;oBAChD,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;oBAEhD,+EAA+E;oBAC/E,MAAM,eAAe,GAAG,IAAI,CAAC,kBAAkB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;oBAE1E,IAAI,CAAC,eAAe,EAAE,CAAC;wBACrB,gEAAgE;wBAChE,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC;wBAErE,gEAAgE;wBAChE,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE;4BAC7B,GAAG,QAAS;4BACZ,IAAI,EAAE,SAAS;yBAChB,CAAC,CAAC;wBAEH,wEAAwE;wBACxE,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;wBACrC,WAAW,CAAC,KAAK,CAAC,UAAU,GAAG,EAAE,CAAC;wBAClC,WAAW,CAAC,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC;wBAE9B,qEAAqE;wBACrE,IAAI,QAAQ,EAAE,IAAI,EAAE,CAAC;4BACnB,IAAI,YAAY,GAAuB,QAAQ,CAAC,IAAI,CAAC;4BACrD,OAAO,YAAY,EAAE,CAAC;gCACpB,MAAM,iBAAiB,GAAG,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC;gCAC7D,IAAI,CAAC,iBAAiB;oCAAE,MAAM;gCAE9B,MAAM,cAAc,GAAG,IAAI,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC;gCAC5D,IAAI,CAAC,YAAY,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAAC;gCAC3C,iBAAiB,CAAC,KAAK,CAAC,UAAU,GAAG,EAAE,CAAC;gCACxC,iBAAiB,CAAC,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC;gCAEpC,YAAY,GAAG,cAAc,EAAE,IAAI,CAAC;4BACtC,CAAC;wBACH,CAAC;oBACH,CAAC;yBAAM,CAAC;wBACN,oDAAoD;wBACpD,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE;4BAC7B,GAAG,QAAS;4BACZ,IAAI,EAAE,IAAI,CAAC,IAAI;yBAChB,CAAC,CAAC;wBAEH,MAAM,iBAAiB,GAAG,CAAC,QAAQ,EAAE,UAAU,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;wBAC1D,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE;4BAC7B,GAAG,QAAS;4BACZ,IAAI,EAAE,IAAI,CAAC,IAAI;4BACf,UAAU,EAAE,iBAAiB;yBAC9B,CAAC,CAAC;wBAEH,gDAAgD;wBAChD,MAAM,UAAU,GAAG,iBAAiB,GAAG,yBAAyB,CAAC,2BAA2B,CAAC;wBAC7F,WAAW,CAAC,KAAK,CAAC,UAAU,GAAG,GAAG,UAAU,IAAI,CAAC;wBACjD,WAAW,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,GAAG,GAAG,iBAAiB,EAAE,CAAC;oBAC1D,CAAC;gBACH,CAAC;YACH,CAAC;iBAAM,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;gBACrB,4CAA4C;gBAC5C,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACpD,IAAI,WAAW,EAAE,CAAC;oBAChB,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;oBAChD,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE;wBAC7B,GAAG,QAAS;wBACZ,IAAI,EAAE,SAAS;qBAChB,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;iBAAM,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;gBACrB,6CAA6C;gBAC7C,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACpD,IAAI,WAAW,EAAE,CAAC;oBAChB,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;oBAChD,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE;wBAC7B,GAAG,QAAS;wBACZ,IAAI,EAAE,SAAS;wBACf,UAAU,EAAE,CAAC,CAAE,8BAA8B;qBAC9C,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YAED,mEAAmE;YACnE,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;gBAC3B,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACpD,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;gBAErE,kEAAkE;gBAClE,IAAI,QAAQ,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;oBAC9B,IAAI,CAAC,2BAA2B,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;gBACtD,CAAC;gBACD,uEAAuE;YACzE,CAAC;iBAAM,CAAC;gBACN,gDAAgD;gBAChD,IAAI,CAAC,2BAA2B,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YAClD,CAAC;YAED,kCAAkC;YAClC,IAAI,CAAC,YAAY,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED;;OAEG;IACK,2BAA2B,CAAC,YAAgC,EAAE,UAAkB;QACtF,IAAI,SAAS,GAAG,YAAY,CAAC;QAE7B,OAAO,SAAS,EAAE,CAAC;YACjB,MAAM,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;YACvD,IAAI,CAAC,cAAc;gBAAE,MAAM;YAE3B,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC;YACtD,IAAI,CAAC,WAAW;gBAAE,MAAM;YAExB,qBAAqB;YACrB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,CAAC,UAAU,GAAG,UAAU,CAAC,CAAC;YAClE,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE;gBAChC,GAAG,WAAW;gBACd,UAAU,EAAE,QAAQ;aACrB,CAAC,CAAC;YAEH,wBAAwB;YACxB,MAAM,UAAU,GAAG,QAAQ,GAAG,yBAAyB,CAAC,2BAA2B,CAAC;YACpF,cAAc,CAAC,KAAK,CAAC,UAAU,GAAG,GAAG,UAAU,IAAI,CAAC;YACpD,cAAc,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,GAAG,GAAG,QAAQ,EAAE,CAAC;YAElD,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC;QAC/B,CAAC;IACH,CAAC;IAED;;OAEG;IACI,cAAc,CAAC,OAAoB;QACxC,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC;QAC5C,MAAM,aAAa,GAAG,UAAU,KAAK,EAAE,IAAI,UAAU,KAAK,KAAK,CAAC;QAChE,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;QAEzD,OAAO,aAAa,IAAI,YAAY,CAAC;IACvC,CAAC;IAED;;OAEG;IACI,oBAAoB,CAAC,SAAsB,EAAE,OAAe;QACjE,MAAM,YAAY,GAAG,SAAS,CAAC,aAAa,CAAC,4BAA4B,OAAO,IAAI,CAAgB,CAAC;QACrG,IAAI,CAAC,YAAY;YAAE,OAAO,KAAK,CAAC;QAEhC,sFAAsF;QACtF,YAAY,CAAC,MAAM,EAAE,CAAC;QAEtB,0BAA0B;QAC1B,MAAM,eAAe,GAAG,SAAS,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC;QAChE,MAAM,cAAc,GAAG,eAAe,CAAC,MAAM,CAAC;QAE9C,IAAI,cAAc,KAAK,CAAC,EAAE,CAAC;YACzB,SAAS,CAAC,MAAM,EAAE,CAAC;YACnB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,cAAc,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,cAAc,GAAG,eAAe,CAAC,CAAC,CAAgB,CAAC;YAEzD,+EAA+E;YAC/E,MAAM,UAAU,GAAG,QAAQ,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAE3D,cAAc,CAAC,KAAK,CAAC,QAAQ,GAAG,UAAU,CAAC;YAC3C,cAAc,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,UAAU,IAAI,CAAC;YAC7C,cAAc,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC;YAClC,cAAc,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;YACnC,cAAc,CAAC,KAAK,CAAC,IAAI,GAAG,EAAE,CAAC;YAC/B,cAAc,CAAC,KAAK,CAAC,QAAQ,GAAG,EAAE,CAAC;YAEnC,SAAS,CAAC,aAAa,EAAE,YAAY,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;YACjE,SAAS,CAAC,MAAM,EAAE,CAAC;YACnB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;OAEG;IACI,wBAAwB,CAAC,SAAsB;QACpD,MAAM,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC;aACtE,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,EAAiB,CAAC,CAAkB,CAAC;QAEzE,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAEvC,qCAAqC;QACrC,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAU,CAAC;QAC5C,MAAM,WAAW,GAAoB,EAAE,CAAC;QAExC,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;YACpC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;YACxC,IAAI,CAAC,OAAO,IAAI,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,SAAS;YAEzD,mEAAmE;YACnE,IAAI,WAAW,GAAG,OAAO,CAAC;YAC1B,IAAI,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;YAE9C,OAAO,QAAQ,EAAE,IAAI,EAAE,CAAC;gBACtB,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;gBACxD,IAAI,CAAC,WAAW;oBAAE,MAAM;gBACxB,WAAW,GAAG,WAAW,CAAC;gBAC1B,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;YAC5C,CAAC;YAED,qCAAqC;YACrC,MAAM,KAAK,GAAkB,EAAE,CAAC;YAChC,IAAI,cAAc,GAAG,WAAW,CAAC;YAEjC,OAAO,cAAc,EAAE,CAAC;gBACtB,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;gBAC3B,iBAAiB,CAAC,GAAG,CAAC,cAAc,CAAC,OAAO,CAAC,OAAQ,CAAC,CAAC;gBAEvD,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC;gBACtD,IAAI,CAAC,WAAW,EAAE,IAAI;oBAAE,MAAM;gBAE9B,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;gBAC3D,IAAI,CAAC,WAAW;oBAAE,MAAM;gBACxB,cAAc,GAAG,WAAW,CAAC;YAC/B,CAAC;YAED,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC,uCAAuC;gBAC7D,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;QAED,iCAAiC;QACjC,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;YAC1B,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE;gBAC/B,MAAM,UAAU,GAAG,KAAK,GAAG,yBAAyB,CAAC,2BAA2B,CAAC;gBACjF,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,GAAG,UAAU,IAAI,CAAC;gBAC7C,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,GAAG,GAAG,KAAK,EAAE,CAAC;gBAExC,qDAAqD;gBACrD,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;gBACxC,IAAI,IAAI,EAAE,CAAC;oBACT,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE;wBACzB,GAAG,IAAI;wBACP,UAAU,EAAE,KAAK;qBAClB,CAAC,CAAC;gBACL,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAGD;;OAEG;IACI,aAAa,CAAC,YAAyB;QAC5C,OAAO,YAAY,CAAC,OAAO,CAAC,iBAAiB,CAAgB,CAAC;IAChE,CAAC;IAEM,cAAc,CAAC,OAAoB;QACxC,OAAO,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;IAC9C,CAAC;IAED;;OAEG;IACI,YAAY,CAAC,OAAoB;QACtC,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC;QAC3C,IAAI,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAC;QAE3B,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC9B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC,kCAAkC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;YAC9D,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAEO,YAAY,CAAC,OAAoB,EAAE,IAAsB;QAC/D,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC;QACnC,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAEO,eAAe,CAAC,OAAe;QACrC,OAAO,QAAQ,CAAC,aAAa,CAAC,4BAA4B,OAAO,IAAI,CAAgB,CAAC;IACxF,CAAC;;AA3buB,qDAA2B,GAAG,EAAE,CAAC"} \ No newline at end of file diff --git a/wwwroot/js/managers/ViewManager.d.ts b/wwwroot/js/managers/ViewManager.d.ts new file mode 100644 index 0000000..11147b5 --- /dev/null +++ b/wwwroot/js/managers/ViewManager.d.ts @@ -0,0 +1,23 @@ +import { IEventBus } from '../types/CalendarTypes'; +import { Configuration } from '../configurations/CalendarConfig'; +export declare class ViewManager { + private eventBus; + private config; + private currentView; + private buttonListeners; + constructor(eventBus: IEventBus, config: Configuration); + private setupEventListeners; + private setupEventBusListeners; + private setupButtonHandlers; + private setupButtonGroup; + private getViewButtons; + private getWorkweekButtons; + private initializeView; + private changeView; + private changeWorkweek; + private updateAllButtons; + private updateButtonGroup; + private emitViewRendered; + private refreshCurrentView; + private isValidView; +} diff --git a/wwwroot/js/managers/ViewManager.js b/wwwroot/js/managers/ViewManager.js new file mode 100644 index 0000000..7b25515 --- /dev/null +++ b/wwwroot/js/managers/ViewManager.js @@ -0,0 +1,106 @@ +import { ConfigManager } from '../configurations/ConfigManager'; +import { CoreEvents } from '../constants/CoreEvents'; +export class ViewManager { + constructor(eventBus, config) { + this.currentView = 'week'; + this.buttonListeners = new Map(); + this.eventBus = eventBus; + this.config = config; + this.setupEventListeners(); + } + setupEventListeners() { + this.setupEventBusListeners(); + this.setupButtonHandlers(); + } + setupEventBusListeners() { + this.eventBus.on(CoreEvents.INITIALIZED, () => { + this.initializeView(); + }); + this.eventBus.on(CoreEvents.DATE_CHANGED, () => { + this.refreshCurrentView(); + }); + } + setupButtonHandlers() { + this.setupButtonGroup('swp-view-button[data-view]', 'data-view', (value) => { + if (this.isValidView(value)) { + this.changeView(value); + } + }); + this.setupButtonGroup('swp-preset-button[data-workweek]', 'data-workweek', (value) => { + this.changeWorkweek(value); + }); + } + setupButtonGroup(selector, attribute, handler) { + const buttons = document.querySelectorAll(selector); + buttons.forEach(button => { + const clickHandler = (event) => { + event.preventDefault(); + const value = button.getAttribute(attribute); + if (value) { + handler(value); + } + }; + button.addEventListener('click', clickHandler); + this.buttonListeners.set(button, clickHandler); + }); + } + getViewButtons() { + return document.querySelectorAll('swp-view-button[data-view]'); + } + getWorkweekButtons() { + return document.querySelectorAll('swp-preset-button[data-workweek]'); + } + initializeView() { + this.updateAllButtons(); + this.emitViewRendered(); + } + changeView(newView) { + if (newView === this.currentView) + return; + const previousView = this.currentView; + this.currentView = newView; + this.updateAllButtons(); + this.eventBus.emit(CoreEvents.VIEW_CHANGED, { + previousView, + currentView: newView + }); + } + changeWorkweek(workweekId) { + this.config.setWorkWeek(workweekId); + // Update all CSS properties to match new configuration + ConfigManager.updateCSSProperties(this.config); + this.updateAllButtons(); + const settings = this.config.getWorkWeekSettings(); + this.eventBus.emit(CoreEvents.WORKWEEK_CHANGED, { + workWeekId: workweekId, + settings: settings + }); + } + updateAllButtons() { + this.updateButtonGroup(this.getViewButtons(), 'data-view', this.currentView); + this.updateButtonGroup(this.getWorkweekButtons(), 'data-workweek', this.config.currentWorkWeek); + } + updateButtonGroup(buttons, attribute, activeValue) { + buttons.forEach(button => { + const buttonValue = button.getAttribute(attribute); + if (buttonValue === activeValue) { + button.setAttribute('data-active', 'true'); + } + else { + button.removeAttribute('data-active'); + } + }); + } + emitViewRendered() { + this.eventBus.emit(CoreEvents.VIEW_RENDERED, { + view: this.currentView + }); + } + refreshCurrentView() { + this.emitViewRendered(); + } + isValidView(view) { + return ['day', 'week', 'month'].includes(view); + } +} +//# sourceMappingURL=ViewManager.js.map \ No newline at end of file diff --git a/wwwroot/js/managers/ViewManager.js.map b/wwwroot/js/managers/ViewManager.js.map new file mode 100644 index 0000000..9608872 --- /dev/null +++ b/wwwroot/js/managers/ViewManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ViewManager.js","sourceRoot":"","sources":["../../../src/managers/ViewManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/managers/ViewSelectorManager.d.ts b/wwwroot/js/managers/ViewSelectorManager.d.ts new file mode 100644 index 0000000..18a9db6 --- /dev/null +++ b/wwwroot/js/managers/ViewSelectorManager.d.ts @@ -0,0 +1,70 @@ +import { IEventBus } from '../types/CalendarTypes'; +import { Configuration } from '../configurations/CalendarConfig'; +/** + * ViewSelectorManager - Manages view selector UI and state + * + * RESPONSIBILITY: + * =============== + * This manager owns all logic related to the UI element. + * It follows the principle that each functional UI element has its own manager. + * + * RESPONSIBILITIES: + * - Handles button clicks on swp-view-button elements + * - Manages current view state (day/week/month) + * - Validates view values + * - Emits VIEW_CHANGED and VIEW_RENDERED events + * - Updates button UI states (data-active attributes) + * + * EVENT FLOW: + * =========== + * User clicks button → changeView() → validate → update state → emit event → update UI + * + * IMPLEMENTATION STATUS: + * ====================== + * - Week view: FULLY IMPLEMENTED + * - Day view: NOT IMPLEMENTED (button exists but no rendering) + * - Month view: NOT IMPLEMENTED (button exists but no rendering) + * + * SUBSCRIBERS: + * ============ + * - GridRenderer: Uses view parameter (currently only supports 'week') + * - Future: DayRenderer, MonthRenderer when implemented + */ +export declare class ViewSelectorManager { + private eventBus; + private config; + private buttonListeners; + constructor(eventBus: IEventBus, config: Configuration); + /** + * Setup click listeners on all view selector buttons + */ + private setupButtonListeners; + /** + * Setup event bus listeners + */ + private setupEventListeners; + /** + * Change the active view + */ + private changeView; + /** + * Update button states (data-active attributes) + */ + private updateButtonStates; + /** + * Initialize view on INITIALIZED event + */ + private initializeView; + /** + * Emit VIEW_RENDERED event + */ + private emitViewRendered; + /** + * Refresh current view on DATE_CHANGED event + */ + private refreshCurrentView; + /** + * Validate if string is a valid CalendarView type + */ + private isValidView; +} diff --git a/wwwroot/js/managers/ViewSelectorManager.js b/wwwroot/js/managers/ViewSelectorManager.js new file mode 100644 index 0000000..162191c --- /dev/null +++ b/wwwroot/js/managers/ViewSelectorManager.js @@ -0,0 +1,130 @@ +import { CoreEvents } from '../constants/CoreEvents'; +/** + * ViewSelectorManager - Manages view selector UI and state + * + * RESPONSIBILITY: + * =============== + * This manager owns all logic related to the UI element. + * It follows the principle that each functional UI element has its own manager. + * + * RESPONSIBILITIES: + * - Handles button clicks on swp-view-button elements + * - Manages current view state (day/week/month) + * - Validates view values + * - Emits VIEW_CHANGED and VIEW_RENDERED events + * - Updates button UI states (data-active attributes) + * + * EVENT FLOW: + * =========== + * User clicks button → changeView() → validate → update state → emit event → update UI + * + * IMPLEMENTATION STATUS: + * ====================== + * - Week view: FULLY IMPLEMENTED + * - Day view: NOT IMPLEMENTED (button exists but no rendering) + * - Month view: NOT IMPLEMENTED (button exists but no rendering) + * + * SUBSCRIBERS: + * ============ + * - GridRenderer: Uses view parameter (currently only supports 'week') + * - Future: DayRenderer, MonthRenderer when implemented + */ +export class ViewSelectorManager { + constructor(eventBus, config) { + this.buttonListeners = new Map(); + this.eventBus = eventBus; + this.config = config; + this.setupButtonListeners(); + this.setupEventListeners(); + } + /** + * Setup click listeners on all view selector buttons + */ + setupButtonListeners() { + const buttons = document.querySelectorAll('swp-view-button[data-view]'); + buttons.forEach(button => { + const clickHandler = (event) => { + event.preventDefault(); + const view = button.getAttribute('data-view'); + if (view && this.isValidView(view)) { + this.changeView(view); + } + }; + button.addEventListener('click', clickHandler); + this.buttonListeners.set(button, clickHandler); + }); + // Initialize button states + this.updateButtonStates(); + } + /** + * Setup event bus listeners + */ + setupEventListeners() { + this.eventBus.on(CoreEvents.INITIALIZED, () => { + this.initializeView(); + }); + this.eventBus.on(CoreEvents.DATE_CHANGED, () => { + this.refreshCurrentView(); + }); + } + /** + * Change the active view + */ + changeView(newView) { + if (newView === this.config.currentView) { + return; // No change + } + const previousView = this.config.currentView; + this.config.currentView = newView; + // Update button UI states + this.updateButtonStates(); + // Emit event for subscribers + this.eventBus.emit(CoreEvents.VIEW_CHANGED, { + previousView, + currentView: newView + }); + } + /** + * Update button states (data-active attributes) + */ + updateButtonStates() { + const buttons = document.querySelectorAll('swp-view-button[data-view]'); + buttons.forEach(button => { + const buttonView = button.getAttribute('data-view'); + if (buttonView === this.config.currentView) { + button.setAttribute('data-active', 'true'); + } + else { + button.removeAttribute('data-active'); + } + }); + } + /** + * Initialize view on INITIALIZED event + */ + initializeView() { + this.updateButtonStates(); + this.emitViewRendered(); + } + /** + * Emit VIEW_RENDERED event + */ + emitViewRendered() { + this.eventBus.emit(CoreEvents.VIEW_RENDERED, { + view: this.config.currentView + }); + } + /** + * Refresh current view on DATE_CHANGED event + */ + refreshCurrentView() { + this.emitViewRendered(); + } + /** + * Validate if string is a valid CalendarView type + */ + isValidView(view) { + return ['day', 'week', 'month'].includes(view); + } +} +//# sourceMappingURL=ViewSelectorManager.js.map \ No newline at end of file diff --git a/wwwroot/js/managers/ViewSelectorManager.js.map b/wwwroot/js/managers/ViewSelectorManager.js.map new file mode 100644 index 0000000..aa10a71 --- /dev/null +++ b/wwwroot/js/managers/ViewSelectorManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ViewSelectorManager.js","sourceRoot":"","sources":["../../../src/managers/ViewSelectorManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/managers/WorkHoursManager.d.ts b/wwwroot/js/managers/WorkHoursManager.d.ts new file mode 100644 index 0000000..8cd7f19 --- /dev/null +++ b/wwwroot/js/managers/WorkHoursManager.d.ts @@ -0,0 +1,71 @@ +import { DateService } from '../utils/DateService'; +import { Configuration } from '../configurations/CalendarConfig'; +import { PositionUtils } from '../utils/PositionUtils'; +/** + * Work hours for a specific day + */ +export interface IDayWorkHours { + start: number; + end: number; +} +/** + * Work schedule configuration + */ +export interface IWorkScheduleConfig { + weeklyDefault: { + monday: IDayWorkHours | 'off'; + tuesday: IDayWorkHours | 'off'; + wednesday: IDayWorkHours | 'off'; + thursday: IDayWorkHours | 'off'; + friday: IDayWorkHours | 'off'; + saturday: IDayWorkHours | 'off'; + sunday: IDayWorkHours | 'off'; + }; + dateOverrides: { + [dateString: string]: IDayWorkHours | 'off'; + }; +} +/** + * Manages work hours scheduling with weekly defaults and date-specific overrides + */ +export declare class WorkHoursManager { + private dateService; + private config; + private positionUtils; + private workSchedule; + constructor(dateService: DateService, config: Configuration, positionUtils: PositionUtils); + /** + * Get work hours for a specific date + */ + getWorkHoursForDate(date: Date): IDayWorkHours | 'off'; + /** + * Get work hours for multiple dates (used by GridManager) + */ + getWorkHoursForDateRange(dates: Date[]): Map; + /** + * Calculate CSS custom properties for non-work hour overlays using PositionUtils + */ + calculateNonWorkHoursStyle(workHours: IDayWorkHours | 'off'): { + beforeWorkHeight: number; + afterWorkTop: number; + } | null; + /** + * Calculate CSS custom properties for work hours overlay using PositionUtils + */ + calculateWorkHoursStyle(workHours: IDayWorkHours | 'off'): { + top: number; + height: number; + } | null; + /** + * Load work schedule from JSON (future implementation) + */ + loadWorkSchedule(jsonData: IWorkScheduleConfig): Promise; + /** + * Get current work schedule configuration + */ + getWorkSchedule(): IWorkScheduleConfig; + /** + * Convert Date to day name key + */ + private getDayName; +} diff --git a/wwwroot/js/managers/WorkHoursManager.js b/wwwroot/js/managers/WorkHoursManager.js new file mode 100644 index 0000000..b948c0f --- /dev/null +++ b/wwwroot/js/managers/WorkHoursManager.js @@ -0,0 +1,108 @@ +// Work hours management for per-column scheduling +/** + * Manages work hours scheduling with weekly defaults and date-specific overrides + */ +export class WorkHoursManager { + constructor(dateService, config, positionUtils) { + this.dateService = dateService; + this.config = config; + this.positionUtils = positionUtils; + // Default work schedule - will be loaded from JSON later + this.workSchedule = { + weeklyDefault: { + monday: { start: 9, end: 17 }, + tuesday: { start: 9, end: 17 }, + wednesday: { start: 9, end: 17 }, + thursday: { start: 9, end: 17 }, + friday: { start: 9, end: 15 }, + saturday: 'off', + sunday: 'off' + }, + dateOverrides: { + '2025-01-20': { start: 10, end: 16 }, + '2025-01-21': { start: 8, end: 14 }, + '2025-01-22': 'off' + } + }; + } + /** + * Get work hours for a specific date + */ + getWorkHoursForDate(date) { + const dateString = this.dateService.formatISODate(date); + // Check for date-specific override first + if (this.workSchedule.dateOverrides[dateString]) { + return this.workSchedule.dateOverrides[dateString]; + } + // Fall back to weekly default + const dayName = this.getDayName(date); + return this.workSchedule.weeklyDefault[dayName]; + } + /** + * Get work hours for multiple dates (used by GridManager) + */ + getWorkHoursForDateRange(dates) { + const workHoursMap = new Map(); + dates.forEach(date => { + const dateString = this.dateService.formatISODate(date); + const workHours = this.getWorkHoursForDate(date); + workHoursMap.set(dateString, workHours); + }); + return workHoursMap; + } + /** + * Calculate CSS custom properties for non-work hour overlays using PositionUtils + */ + calculateNonWorkHoursStyle(workHours) { + if (workHours === 'off') { + return null; // Full day will be colored via CSS background + } + const gridSettings = this.config.gridSettings; + const dayStartHour = gridSettings.dayStartHour; + const hourHeight = gridSettings.hourHeight; + // Before work: from day start to work start + const beforeWorkHeight = (workHours.start - dayStartHour) * hourHeight; + // After work: from work end to day end + const afterWorkTop = (workHours.end - dayStartHour) * hourHeight; + return { + beforeWorkHeight: Math.max(0, beforeWorkHeight), + afterWorkTop: Math.max(0, afterWorkTop) + }; + } + /** + * Calculate CSS custom properties for work hours overlay using PositionUtils + */ + calculateWorkHoursStyle(workHours) { + if (workHours === 'off') { + return null; + } + // Create dummy time strings for start and end of work hours + const startTime = `${workHours.start.toString().padStart(2, '0')}:00`; + const endTime = `${workHours.end.toString().padStart(2, '0')}:00`; + // Use PositionUtils for consistent position calculation + const position = this.positionUtils.calculateEventPosition(startTime, endTime); + return { top: position.top, height: position.height }; + } + /** + * Load work schedule from JSON (future implementation) + */ + async loadWorkSchedule(jsonData) { + this.workSchedule = jsonData; + } + /** + * Get current work schedule configuration + */ + getWorkSchedule() { + return this.workSchedule; + } + /** + * Convert Date to day name key + */ + getDayName(date) { + const dayNames = [ + 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday' + ]; + return dayNames[date.getDay()]; + } +} +//# sourceMappingURL=WorkHoursManager.js.map \ No newline at end of file diff --git a/wwwroot/js/managers/WorkHoursManager.js.map b/wwwroot/js/managers/WorkHoursManager.js.map new file mode 100644 index 0000000..136e28b --- /dev/null +++ b/wwwroot/js/managers/WorkHoursManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"WorkHoursManager.js","sourceRoot":"","sources":["../../../src/managers/WorkHoursManager.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAgClD;;GAEG;AACH,MAAM,OAAO,gBAAgB;IAM3B,YAAY,WAAwB,EAAE,MAAqB,EAAE,aAA4B;QACvF,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QAEnC,yDAAyD;QACzD,IAAI,CAAC,YAAY,GAAG;YAClB,aAAa,EAAE;gBACb,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;gBAC7B,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;gBAC9B,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;gBAChC,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;gBAC/B,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;gBAC7B,QAAQ,EAAE,KAAK;gBACf,MAAM,EAAE,KAAK;aACd;YACD,aAAa,EAAE;gBACb,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;gBACpC,YAAY,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;gBACnC,YAAY,EAAE,KAAK;aACpB;SACF,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,mBAAmB,CAAC,IAAU;QAC5B,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QAExD,yCAAyC;QACzC,IAAI,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,UAAU,CAAC,EAAE,CAAC;YAChD,OAAO,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QACrD,CAAC;QAED,8BAA8B;QAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACtC,OAAO,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAClD,CAAC;IAED;;OAEG;IACH,wBAAwB,CAAC,KAAa;QACpC,MAAM,YAAY,GAAG,IAAI,GAAG,EAAiC,CAAC;QAE9D,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;YACnB,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YACxD,MAAM,SAAS,GAAG,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;YACjD,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;QAEH,OAAO,YAAY,CAAC;IACtB,CAAC;IAED;;OAEG;IACH,0BAA0B,CAAC,SAAgC;QACzD,IAAI,SAAS,KAAK,KAAK,EAAE,CAAC;YACxB,OAAO,IAAI,CAAC,CAAC,8CAA8C;QAC7D,CAAC;QAED,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC;QAC9C,MAAM,YAAY,GAAG,YAAY,CAAC,YAAY,CAAC;QAC/C,MAAM,UAAU,GAAG,YAAY,CAAC,UAAU,CAAC;QAE3C,4CAA4C;QAC5C,MAAM,gBAAgB,GAAG,CAAC,SAAS,CAAC,KAAK,GAAG,YAAY,CAAC,GAAG,UAAU,CAAC;QAEvE,uCAAuC;QACvC,MAAM,YAAY,GAAG,CAAC,SAAS,CAAC,GAAG,GAAG,YAAY,CAAC,GAAG,UAAU,CAAC;QAEjE,OAAO;YACL,gBAAgB,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,gBAAgB,CAAC;YAC/C,YAAY,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAC;SACxC,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,uBAAuB,CAAC,SAAgC;QACtD,IAAI,SAAS,KAAK,KAAK,EAAE,CAAC;YACxB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,4DAA4D;QAC5D,MAAM,SAAS,GAAG,GAAG,SAAS,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC;QACtE,MAAM,OAAO,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC;QAElE,wDAAwD;QACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,sBAAsB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAE/E,OAAO,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;IACxD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,gBAAgB,CAAC,QAA6B;QAClD,IAAI,CAAC,YAAY,GAAG,QAAQ,CAAC;IAC/B,CAAC;IAED;;OAEG;IACH,eAAe;QACb,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAED;;OAEG;IACK,UAAU,CAAC,IAAU;QAC3B,MAAM,QAAQ,GAAmD;YAC/D,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU;SAC7E,CAAC;QACF,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACjC,CAAC;CACF"} \ No newline at end of file diff --git a/wwwroot/js/managers/WorkweekPresetsManager.d.ts b/wwwroot/js/managers/WorkweekPresetsManager.d.ts new file mode 100644 index 0000000..0251865 --- /dev/null +++ b/wwwroot/js/managers/WorkweekPresetsManager.d.ts @@ -0,0 +1,47 @@ +import { IEventBus } from '../types/CalendarTypes'; +import { Configuration } from '../configurations/CalendarConfig'; +/** + * WorkweekPresetsManager - Manages workweek preset UI and state + * + * RESPONSIBILITY: + * =============== + * This manager owns all logic related to the UI element. + * It follows the principle that each functional UI element has its own manager. + * + * RESPONSIBILITIES: + * - Owns WORK_WEEK_PRESETS data + * - Handles button clicks on swp-preset-button elements + * - Manages current workweek preset state + * - Validates preset IDs + * - Emits WORKWEEK_CHANGED events + * - Updates button UI states (data-active attributes) + * + * EVENT FLOW: + * =========== + * User clicks button → changePreset() → validate → update state → emit event → update UI + * + * SUBSCRIBERS: + * ============ + * - ConfigManager: Updates CSS variables (--grid-columns) + * - GridManager: Re-renders grid with new column count + * - CalendarManager: Relays to header update (via workweek:header-update) + * - HeaderManager: Updates date headers + */ +export declare class WorkweekPresetsManager { + private eventBus; + private config; + private buttonListeners; + constructor(eventBus: IEventBus, config: Configuration); + /** + * Setup click listeners on all workweek preset buttons + */ + private setupButtonListeners; + /** + * Change the active workweek preset + */ + private changePreset; + /** + * Update button states (data-active attributes) + */ + private updateButtonStates; +} diff --git a/wwwroot/js/managers/WorkweekPresetsManager.js b/wwwroot/js/managers/WorkweekPresetsManager.js new file mode 100644 index 0000000..6ebdbc7 --- /dev/null +++ b/wwwroot/js/managers/WorkweekPresetsManager.js @@ -0,0 +1,95 @@ +import { CoreEvents } from '../constants/CoreEvents'; +import { WORK_WEEK_PRESETS } from '../configurations/CalendarConfig'; +/** + * WorkweekPresetsManager - Manages workweek preset UI and state + * + * RESPONSIBILITY: + * =============== + * This manager owns all logic related to the UI element. + * It follows the principle that each functional UI element has its own manager. + * + * RESPONSIBILITIES: + * - Owns WORK_WEEK_PRESETS data + * - Handles button clicks on swp-preset-button elements + * - Manages current workweek preset state + * - Validates preset IDs + * - Emits WORKWEEK_CHANGED events + * - Updates button UI states (data-active attributes) + * + * EVENT FLOW: + * =========== + * User clicks button → changePreset() → validate → update state → emit event → update UI + * + * SUBSCRIBERS: + * ============ + * - ConfigManager: Updates CSS variables (--grid-columns) + * - GridManager: Re-renders grid with new column count + * - CalendarManager: Relays to header update (via workweek:header-update) + * - HeaderManager: Updates date headers + */ +export class WorkweekPresetsManager { + constructor(eventBus, config) { + this.buttonListeners = new Map(); + this.eventBus = eventBus; + this.config = config; + this.setupButtonListeners(); + } + /** + * Setup click listeners on all workweek preset buttons + */ + setupButtonListeners() { + const buttons = document.querySelectorAll('swp-preset-button[data-workweek]'); + buttons.forEach(button => { + const clickHandler = (event) => { + event.preventDefault(); + const presetId = button.getAttribute('data-workweek'); + if (presetId) { + this.changePreset(presetId); + } + }; + button.addEventListener('click', clickHandler); + this.buttonListeners.set(button, clickHandler); + }); + // Initialize button states + this.updateButtonStates(); + } + /** + * Change the active workweek preset + */ + changePreset(presetId) { + if (!WORK_WEEK_PRESETS[presetId]) { + console.warn(`Invalid preset ID "${presetId}"`); + return; + } + if (presetId === this.config.currentWorkWeek) { + return; // No change + } + const previousPresetId = this.config.currentWorkWeek; + this.config.currentWorkWeek = presetId; + const settings = WORK_WEEK_PRESETS[presetId]; + // Update button UI states + this.updateButtonStates(); + // Emit event for subscribers + this.eventBus.emit(CoreEvents.WORKWEEK_CHANGED, { + workWeekId: presetId, + previousWorkWeekId: previousPresetId, + settings: settings + }); + } + /** + * Update button states (data-active attributes) + */ + updateButtonStates() { + const buttons = document.querySelectorAll('swp-preset-button[data-workweek]'); + buttons.forEach(button => { + const buttonPresetId = button.getAttribute('data-workweek'); + if (buttonPresetId === this.config.currentWorkWeek) { + button.setAttribute('data-active', 'true'); + } + else { + button.removeAttribute('data-active'); + } + }); + } +} +//# sourceMappingURL=WorkweekPresetsManager.js.map \ No newline at end of file diff --git a/wwwroot/js/managers/WorkweekPresetsManager.js.map b/wwwroot/js/managers/WorkweekPresetsManager.js.map new file mode 100644 index 0000000..4e7bc85 --- /dev/null +++ b/wwwroot/js/managers/WorkweekPresetsManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"WorkweekPresetsManager.js","sourceRoot":"","sources":["../../../src/managers/WorkweekPresetsManager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD,OAAO,EAAE,iBAAiB,EAAiB,MAAM,kCAAkC,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,OAAO,sBAAsB;IAKjC,YAAY,QAAmB,EAAE,MAAqB;QAF9C,oBAAe,GAAgC,IAAI,GAAG,EAAE,CAAC;QAG/D,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QAErB,IAAI,CAAC,oBAAoB,EAAE,CAAC;IAC9B,CAAC;IAED;;OAEG;IACK,oBAAoB;QAC1B,MAAM,OAAO,GAAG,QAAQ,CAAC,gBAAgB,CAAC,kCAAkC,CAAC,CAAC;QAE9E,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;YACvB,MAAM,YAAY,GAAG,CAAC,KAAY,EAAE,EAAE;gBACpC,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;gBACtD,IAAI,QAAQ,EAAE,CAAC;oBACb,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;gBAC9B,CAAC;YACH,CAAC,CAAC;YAEF,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;YAC/C,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;QAEH,2BAA2B;QAC3B,IAAI,CAAC,kBAAkB,EAAE,CAAC;IAC5B,CAAC;IAED;;OAEG;IACK,YAAY,CAAC,QAAgB;QACnC,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjC,OAAO,CAAC,IAAI,CAAC,sBAAsB,QAAQ,GAAG,CAAC,CAAC;YAChD,OAAO;QACT,CAAC;QAED,IAAI,QAAQ,KAAK,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC;YAC7C,OAAO,CAAC,YAAY;QACtB,CAAC;QAED,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC;QACrD,IAAI,CAAC,MAAM,CAAC,eAAe,GAAG,QAAQ,CAAC;QAEvC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QAE7C,0BAA0B;QAC1B,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAE1B,6BAA6B;QAC7B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,gBAAgB,EAAE;YAC9C,UAAU,EAAE,QAAQ;YACpB,kBAAkB,EAAE,gBAAgB;YACpC,QAAQ,EAAE,QAAQ;SACnB,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACK,kBAAkB;QACxB,MAAM,OAAO,GAAG,QAAQ,CAAC,gBAAgB,CAAC,kCAAkC,CAAC,CAAC;QAE9E,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;YACvB,MAAM,cAAc,GAAG,MAAM,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;YAE5D,IAAI,cAAc,KAAK,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC;gBACnD,MAAM,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;YAC7C,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC;YACxC,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;CAEF"} \ No newline at end of file diff --git a/wwwroot/js/renderers/AllDayEventRenderer.d.ts b/wwwroot/js/renderers/AllDayEventRenderer.d.ts new file mode 100644 index 0000000..588760b --- /dev/null +++ b/wwwroot/js/renderers/AllDayEventRenderer.d.ts @@ -0,0 +1,32 @@ +import { IEventLayout } from '../utils/AllDayLayoutEngine'; +import { IDragStartEventPayload } from '../types/EventTypes'; +export declare class AllDayEventRenderer { + private container; + private originalEvent; + private draggedClone; + constructor(); + private getContainer; + private getAllDayContainer; + /** + * Handle drag start for all-day events + */ + handleDragStart(payload: IDragStartEventPayload): void; + /** + * Render an all-day event with pre-calculated layout + */ + private renderAllDayEventWithLayout; + /** + * Remove an all-day event by ID + */ + removeAllDayEvent(eventId: string): void; + /** + * Clear cache when DOM changes + */ + clearCache(): void; + /** + * Render all-day events for specific period using AllDayEventRenderer + */ + renderAllDayEventsForPeriod(eventLayouts: IEventLayout[]): void; + private clearAllDayEvents; + handleViewChanged(event: CustomEvent): void; +} diff --git a/wwwroot/js/renderers/AllDayEventRenderer.js b/wwwroot/js/renderers/AllDayEventRenderer.js new file mode 100644 index 0000000..bafe6af --- /dev/null +++ b/wwwroot/js/renderers/AllDayEventRenderer.js @@ -0,0 +1,97 @@ +import { SwpAllDayEventElement } from '../elements/SwpEventElement'; +export class AllDayEventRenderer { + constructor() { + this.container = null; + this.originalEvent = null; + this.draggedClone = null; + this.getContainer(); + } + getContainer() { + const header = document.querySelector('swp-calendar-header'); + if (header) { + this.container = header.querySelector('swp-allday-container'); + if (!this.container) { + this.container = document.createElement('swp-allday-container'); + header.appendChild(this.container); + } + } + return this.container; + } + getAllDayContainer() { + return document.querySelector('swp-calendar-header swp-allday-container'); + } + /** + * Handle drag start for all-day events + */ + handleDragStart(payload) { + this.originalEvent = payload.originalElement; + ; + this.draggedClone = payload.draggedClone; + if (this.draggedClone) { + const container = this.getAllDayContainer(); + if (!container) + return; + this.draggedClone.style.gridColumn = this.originalEvent.style.gridColumn; + this.draggedClone.style.gridRow = this.originalEvent.style.gridRow; + console.log('handleDragStart:this.draggedClone', this.draggedClone); + container.appendChild(this.draggedClone); + // Add dragging style + this.draggedClone.classList.add('dragging'); + this.draggedClone.style.zIndex = '1000'; + this.draggedClone.style.cursor = 'grabbing'; + // Make original semi-transparent + this.originalEvent.style.opacity = '0.3'; + this.originalEvent.style.userSelect = 'none'; + } + } + /** + * Render an all-day event with pre-calculated layout + */ + renderAllDayEventWithLayout(event, layout) { + const container = this.getContainer(); + if (!container) + return null; + const dayEvent = SwpAllDayEventElement.fromCalendarEvent(event); + dayEvent.applyGridPositioning(layout.row, layout.startColumn, layout.endColumn); + // Apply highlight class to show events with highlight color + dayEvent.classList.add('highlight'); + container.appendChild(dayEvent); + } + /** + * Remove an all-day event by ID + */ + removeAllDayEvent(eventId) { + const container = this.getContainer(); + if (!container) + return; + const eventElement = container.querySelector(`swp-allday-event[data-event-id="${eventId}"]`); + if (eventElement) { + eventElement.remove(); + } + } + /** + * Clear cache when DOM changes + */ + clearCache() { + this.container = null; + } + /** + * Render all-day events for specific period using AllDayEventRenderer + */ + renderAllDayEventsForPeriod(eventLayouts) { + this.clearAllDayEvents(); + eventLayouts.forEach(layout => { + this.renderAllDayEventWithLayout(layout.calenderEvent, layout); + }); + } + clearAllDayEvents() { + const allDayContainer = document.querySelector('swp-allday-container'); + if (allDayContainer) { + allDayContainer.querySelectorAll('swp-allday-event:not(.max-event-indicator)').forEach(event => event.remove()); + } + } + handleViewChanged(event) { + this.clearAllDayEvents(); + } +} +//# sourceMappingURL=AllDayEventRenderer.js.map \ No newline at end of file diff --git a/wwwroot/js/renderers/AllDayEventRenderer.js.map b/wwwroot/js/renderers/AllDayEventRenderer.js.map new file mode 100644 index 0000000..1a34457 --- /dev/null +++ b/wwwroot/js/renderers/AllDayEventRenderer.js.map @@ -0,0 +1 @@ +{"version":3,"file":"AllDayEventRenderer.js","sourceRoot":"","sources":["../../../src/renderers/AllDayEventRenderer.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/renderers/ColumnRenderer.d.ts b/wwwroot/js/renderers/ColumnRenderer.d.ts new file mode 100644 index 0000000..7bb8239 --- /dev/null +++ b/wwwroot/js/renderers/ColumnRenderer.d.ts @@ -0,0 +1,26 @@ +import { Configuration } from '../configurations/CalendarConfig'; +import { DateService } from '../utils/DateService'; +import { WorkHoursManager } from '../managers/WorkHoursManager'; +/** + * Interface for column rendering strategies + */ +export interface IColumnRenderer { + render(columnContainer: HTMLElement, context: IColumnRenderContext): void; +} +/** + * Context for column rendering + */ +export interface IColumnRenderContext { + currentWeek: Date; + config: Configuration; +} +/** + * Date-based column renderer (original functionality) + */ +export declare class DateColumnRenderer implements IColumnRenderer { + private dateService; + private workHoursManager; + constructor(dateService: DateService, workHoursManager: WorkHoursManager); + render(columnContainer: HTMLElement, context: IColumnRenderContext): void; + private applyWorkHoursToColumn; +} diff --git a/wwwroot/js/renderers/ColumnRenderer.js b/wwwroot/js/renderers/ColumnRenderer.js new file mode 100644 index 0000000..ca17b92 --- /dev/null +++ b/wwwroot/js/renderers/ColumnRenderer.js @@ -0,0 +1,44 @@ +// Column rendering strategy interface and implementations +/** + * Date-based column renderer (original functionality) + */ +export class DateColumnRenderer { + constructor(dateService, workHoursManager) { + this.dateService = dateService; + this.workHoursManager = workHoursManager; + } + render(columnContainer, context) { + const { currentWeek, config } = context; + const workWeekSettings = config.getWorkWeekSettings(); + const dates = this.dateService.getWorkWeekDates(currentWeek, workWeekSettings.workDays); + const dateSettings = config.dateViewSettings; + const daysToShow = dates.slice(0, dateSettings.weekDays); + daysToShow.forEach((date) => { + const column = document.createElement('swp-day-column'); + column.dataset.date = this.dateService.formatISODate(date); + // Apply work hours styling + this.applyWorkHoursToColumn(column, date); + const eventsLayer = document.createElement('swp-events-layer'); + column.appendChild(eventsLayer); + columnContainer.appendChild(column); + }); + } + applyWorkHoursToColumn(column, date) { + const workHours = this.workHoursManager.getWorkHoursForDate(date); + if (workHours === 'off') { + // No work hours - mark as off day (full day will be colored) + column.dataset.workHours = 'off'; + } + else { + // Calculate and apply non-work hours overlays (before and after work) + const nonWorkStyle = this.workHoursManager.calculateNonWorkHoursStyle(workHours); + if (nonWorkStyle) { + // Before work overlay (::before pseudo-element) + column.style.setProperty('--before-work-height', `${nonWorkStyle.beforeWorkHeight}px`); + // After work overlay (::after pseudo-element) + column.style.setProperty('--after-work-top', `${nonWorkStyle.afterWorkTop}px`); + } + } + } +} +//# sourceMappingURL=ColumnRenderer.js.map \ No newline at end of file diff --git a/wwwroot/js/renderers/ColumnRenderer.js.map b/wwwroot/js/renderers/ColumnRenderer.js.map new file mode 100644 index 0000000..634f6f6 --- /dev/null +++ b/wwwroot/js/renderers/ColumnRenderer.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ColumnRenderer.js","sourceRoot":"","sources":["../../../src/renderers/ColumnRenderer.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAqB1D;;GAEG;AACH,MAAM,OAAO,kBAAkB;IAI7B,YACE,WAAwB,EACxB,gBAAkC;QAElC,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;IAC3C,CAAC;IAED,MAAM,CAAC,eAA4B,EAAE,OAA6B;QAChE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAExC,MAAM,gBAAgB,GAAG,MAAM,CAAC,mBAAmB,EAAE,CAAC;QACtD,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,WAAW,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QACxF,MAAM,YAAY,GAAG,MAAM,CAAC,gBAAgB,CAAC;QAC7C,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;QAGzD,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;YAC1B,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC;YACvD,MAAc,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YAEpE,2BAA2B;YAC3B,IAAI,CAAC,sBAAsB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YAE1C,MAAM,WAAW,GAAG,QAAQ,CAAC,aAAa,CAAC,kBAAkB,CAAC,CAAC;YAC/D,MAAM,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;YAEhC,eAAe,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,sBAAsB,CAAC,MAAmB,EAAE,IAAU;QAC5D,MAAM,SAAS,GAAG,IAAI,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAElE,IAAI,SAAS,KAAK,KAAK,EAAE,CAAC;YACxB,6DAA6D;YAC5D,MAAc,CAAC,OAAO,CAAC,SAAS,GAAG,KAAK,CAAC;QAC5C,CAAC;aAAM,CAAC;YACN,sEAAsE;YACtE,MAAM,YAAY,GAAG,IAAI,CAAC,gBAAgB,CAAC,0BAA0B,CAAC,SAAS,CAAC,CAAC;YACjF,IAAI,YAAY,EAAE,CAAC;gBACjB,gDAAgD;gBAChD,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,sBAAsB,EAAE,GAAG,YAAY,CAAC,gBAAgB,IAAI,CAAC,CAAC;gBAEvF,8CAA8C;gBAC9C,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,kBAAkB,EAAE,GAAG,YAAY,CAAC,YAAY,IAAI,CAAC,CAAC;YAEjF,CAAC;QACH,CAAC;IACH,CAAC;CAEF"} \ No newline at end of file diff --git a/wwwroot/js/renderers/DateHeaderRenderer.d.ts b/wwwroot/js/renderers/DateHeaderRenderer.d.ts new file mode 100644 index 0000000..4df75e2 --- /dev/null +++ b/wwwroot/js/renderers/DateHeaderRenderer.d.ts @@ -0,0 +1,21 @@ +import { Configuration } from '../configurations/CalendarConfig'; +/** + * Interface for header rendering strategies + */ +export interface IHeaderRenderer { + render(calendarHeader: HTMLElement, context: IHeaderRenderContext): void; +} +/** + * Context for header rendering + */ +export interface IHeaderRenderContext { + currentWeek: Date; + config: Configuration; +} +/** + * Date-based header renderer (original functionality) + */ +export declare class DateHeaderRenderer implements IHeaderRenderer { + private dateService; + render(calendarHeader: HTMLElement, context: IHeaderRenderContext): void; +} diff --git a/wwwroot/js/renderers/DateHeaderRenderer.js b/wwwroot/js/renderers/DateHeaderRenderer.js new file mode 100644 index 0000000..1787f1c --- /dev/null +++ b/wwwroot/js/renderers/DateHeaderRenderer.js @@ -0,0 +1,35 @@ +// Header rendering strategy interface and implementations +import { DateService } from '../utils/DateService'; +/** + * Date-based header renderer (original functionality) + */ +export class DateHeaderRenderer { + render(calendarHeader, context) { + const { currentWeek, config } = context; + // FIRST: Always create all-day container as part of standard header structure + const allDayContainer = document.createElement('swp-allday-container'); + calendarHeader.appendChild(allDayContainer); + // Initialize date service with timezone and locale from config + const timezone = config.timeFormatConfig.timezone; + const locale = config.timeFormatConfig.locale; + this.dateService = new DateService(config); + const workWeekSettings = config.getWorkWeekSettings(); + const dates = this.dateService.getWorkWeekDates(currentWeek, workWeekSettings.workDays); + const weekDays = config.dateViewSettings.weekDays; + const daysToShow = dates.slice(0, weekDays); + daysToShow.forEach((date, index) => { + const header = document.createElement('swp-day-header'); + if (this.dateService.isSameDay(date, new Date())) { + header.dataset.today = 'true'; + } + const dayName = this.dateService.getDayName(date, 'long', locale).toUpperCase(); + header.innerHTML = ` + ${dayName} + ${date.getDate()} + `; + header.dataset.date = this.dateService.formatISODate(date); + calendarHeader.appendChild(header); + }); + } +} +//# sourceMappingURL=DateHeaderRenderer.js.map \ No newline at end of file diff --git a/wwwroot/js/renderers/DateHeaderRenderer.js.map b/wwwroot/js/renderers/DateHeaderRenderer.js.map new file mode 100644 index 0000000..e3e3bc2 --- /dev/null +++ b/wwwroot/js/renderers/DateHeaderRenderer.js.map @@ -0,0 +1 @@ +{"version":3,"file":"DateHeaderRenderer.js","sourceRoot":"","sources":["../../../src/renderers/DateHeaderRenderer.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAG1D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAkBnD;;GAEG;AACH,MAAM,OAAO,kBAAkB;IAG7B,MAAM,CAAC,cAA2B,EAAE,OAA6B;QAC/D,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAExC,8EAA8E;QAC9E,MAAM,eAAe,GAAG,QAAQ,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC;QACvE,cAAc,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC;QAE5C,+DAA+D;QAC/D,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC;QAClD,MAAM,MAAM,GAAG,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC;QAC9C,IAAI,CAAC,WAAW,GAAG,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC;QAE3C,MAAM,gBAAgB,GAAG,MAAM,CAAC,mBAAmB,EAAE,CAAC;QACtD,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,WAAW,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QACxF,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC;QAClD,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QAE5C,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YACjC,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC;YACxD,IAAI,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,EAAE,CAAC;gBAChD,MAAc,CAAC,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC;YACzC,CAAC;YAED,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;YAEhF,MAAM,CAAC,SAAS,GAAG;wBACD,OAAO;wBACP,IAAI,CAAC,OAAO,EAAE;OAC/B,CAAC;YACD,MAAc,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YAEpE,cAAc,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACL,CAAC;CACF"} \ No newline at end of file diff --git a/wwwroot/js/renderers/EventRenderer.d.ts b/wwwroot/js/renderers/EventRenderer.d.ts new file mode 100644 index 0000000..286119e --- /dev/null +++ b/wwwroot/js/renderers/EventRenderer.d.ts @@ -0,0 +1,96 @@ +import { ICalendarEvent } from '../types/CalendarTypes'; +import { Configuration } from '../configurations/CalendarConfig'; +import { PositionUtils } from '../utils/PositionUtils'; +import { IColumnBounds } from '../utils/ColumnDetectionUtils'; +import { IDragColumnChangeEventPayload, IDragMoveEventPayload, IDragStartEventPayload, IDragMouseEnterColumnEventPayload } from '../types/EventTypes'; +import { DateService } from '../utils/DateService'; +import { EventStackManager } from '../managers/EventStackManager'; +import { EventLayoutCoordinator } from '../managers/EventLayoutCoordinator'; +/** + * Interface for event rendering strategies + */ +export interface IEventRenderer { + renderEvents(events: ICalendarEvent[], container: HTMLElement): void; + clearEvents(container?: HTMLElement): void; + renderSingleColumnEvents?(column: IColumnBounds, events: ICalendarEvent[]): void; + handleDragStart?(payload: IDragStartEventPayload): void; + handleDragMove?(payload: IDragMoveEventPayload): void; + handleDragAutoScroll?(eventId: string, snappedY: number): void; + handleDragEnd?(originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: IColumnBounds, finalY: number): void; + handleEventClick?(eventId: string, originalElement: HTMLElement): void; + handleColumnChange?(payload: IDragColumnChangeEventPayload): void; + handleNavigationCompleted?(): void; + handleConvertAllDayToTimed?(payload: IDragMouseEnterColumnEventPayload): void; +} +/** + * Date-based event renderer + */ +export declare class DateEventRenderer implements IEventRenderer { + private dateService; + private stackManager; + private layoutCoordinator; + private config; + private positionUtils; + private draggedClone; + private originalEvent; + constructor(dateService: DateService, stackManager: EventStackManager, layoutCoordinator: EventLayoutCoordinator, config: Configuration, positionUtils: PositionUtils); + private applyDragStyling; + /** + * Handle drag start event + */ + handleDragStart(payload: IDragStartEventPayload): void; + /** + * Handle drag move event + */ + handleDragMove(payload: IDragMoveEventPayload): void; + /** + * Handle column change during drag + */ + handleColumnChange(payload: IDragColumnChangeEventPayload): void; + /** + * Handle conversion of all-day event to timed event + */ + handleConvertAllDayToTimed(payload: IDragMouseEnterColumnEventPayload): void; + /** + * Handle drag end event + */ + handleDragEnd(originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: IColumnBounds, finalY: number): void; + /** + * Handle navigation completed event + */ + handleNavigationCompleted(): void; + /** + * Fade out and remove element + */ + private fadeOutAndRemove; + renderEvents(events: ICalendarEvent[], container: HTMLElement): void; + /** + * Render events for a single column + */ + renderSingleColumnEvents(column: IColumnBounds, events: ICalendarEvent[]): void; + /** + * Render events in a column using combined stacking + grid algorithm + */ + private renderColumnEvents; + /** + * Render events in a grid container (side-by-side with column sharing) + */ + private renderGridGroup; + /** + * Render a single column within a grid group + * Column may contain multiple events that don't overlap + */ + private renderGridColumn; + /** + * Render event within a grid container (absolute positioning within column) + */ + private renderEventInGrid; + private renderEvent; + protected calculateEventPosition(event: ICalendarEvent): { + top: number; + height: number; + }; + clearEvents(container?: HTMLElement): void; + protected getColumns(container: HTMLElement): HTMLElement[]; + protected getEventsForColumn(column: HTMLElement, events: ICalendarEvent[]): ICalendarEvent[]; +} diff --git a/wwwroot/js/renderers/EventRenderer.js b/wwwroot/js/renderers/EventRenderer.js new file mode 100644 index 0000000..ce026a4 --- /dev/null +++ b/wwwroot/js/renderers/EventRenderer.js @@ -0,0 +1,296 @@ +// Event rendering strategy interface and implementations +import { SwpEventElement } from '../elements/SwpEventElement'; +/** + * Date-based event renderer + */ +export class DateEventRenderer { + constructor(dateService, stackManager, layoutCoordinator, config, positionUtils) { + this.draggedClone = null; + this.originalEvent = null; + this.dateService = dateService; + this.stackManager = stackManager; + this.layoutCoordinator = layoutCoordinator; + this.config = config; + this.positionUtils = positionUtils; + } + applyDragStyling(element) { + element.classList.add('dragging'); + element.style.removeProperty("margin-left"); + } + /** + * Handle drag start event + */ + handleDragStart(payload) { + this.originalEvent = payload.originalElement; + ; + // Use the clone from the payload instead of creating a new one + this.draggedClone = payload.draggedClone; + if (this.draggedClone && payload.columnBounds) { + // Apply drag styling + this.applyDragStyling(this.draggedClone); + // Add to current column's events layer (not directly to column) + const eventsLayer = payload.columnBounds.element.querySelector('swp-events-layer'); + if (eventsLayer) { + eventsLayer.appendChild(this.draggedClone); + // Set initial position to prevent "jump to top" effect + // Calculate absolute Y position from original element + const originalRect = this.originalEvent.getBoundingClientRect(); + const columnRect = payload.columnBounds.boundingClientRect; + const initialTop = originalRect.top - columnRect.top; + this.draggedClone.style.top = `${initialTop}px`; + } + } + // Make original semi-transparent + this.originalEvent.style.opacity = '0.3'; + this.originalEvent.style.userSelect = 'none'; + } + /** + * Handle drag move event + */ + handleDragMove(payload) { + const swpEvent = payload.draggedClone; + const columnDate = this.dateService.parseISO(payload.columnBounds.date); + swpEvent.updatePosition(columnDate, payload.snappedY); + } + /** + * Handle column change during drag + */ + handleColumnChange(payload) { + const eventsLayer = payload.newColumn.element.querySelector('swp-events-layer'); + if (eventsLayer && payload.draggedClone.parentElement !== eventsLayer) { + eventsLayer.appendChild(payload.draggedClone); + // Recalculate timestamps with new column date + const currentTop = parseFloat(payload.draggedClone.style.top) || 0; + const swpEvent = payload.draggedClone; + const columnDate = this.dateService.parseISO(payload.newColumn.date); + swpEvent.updatePosition(columnDate, currentTop); + } + } + /** + * Handle conversion of all-day event to timed event + */ + handleConvertAllDayToTimed(payload) { + console.log('🎯 DateEventRenderer: Converting all-day to timed event', { + eventId: payload.calendarEvent.id, + targetColumn: payload.targetColumn.date, + snappedY: payload.snappedY + }); + let timedClone = SwpEventElement.fromCalendarEvent(payload.calendarEvent); + let position = this.calculateEventPosition(payload.calendarEvent); + // Set position at snapped Y + //timedClone.style.top = `${snappedY}px`; + // Set complete styling for dragged clone (matching normal event rendering) + timedClone.style.height = `${position.height - 3}px`; + timedClone.style.left = '2px'; + timedClone.style.right = '2px'; + timedClone.style.width = 'auto'; + timedClone.style.pointerEvents = 'none'; + // Apply drag styling + this.applyDragStyling(timedClone); + // Find the events layer in the target column + let eventsLayer = payload.targetColumn.element.querySelector('swp-events-layer'); + // Add "clone-" prefix to match clone ID pattern + //timedClone.dataset.eventId = `clone-${payload.calendarEvent.id}`; + // Remove old all-day clone and replace with new timed clone + payload.draggedClone.remove(); + payload.replaceClone(timedClone); + eventsLayer.appendChild(timedClone); + } + /** + * Handle drag end event + */ + handleDragEnd(originalElement, draggedClone, finalColumn, finalY) { + if (!draggedClone || !originalElement) { + console.warn('Missing draggedClone or originalElement'); + return; + } + // Only fade out and remove if it's a swp-event (not swp-allday-event) + // AllDayManager handles removal of swp-allday-event elements + if (originalElement.tagName === 'SWP-EVENT') { + this.fadeOutAndRemove(originalElement); + } + // Remove clone prefix and normalize clone to be a regular event + const cloneId = draggedClone.dataset.eventId; + if (cloneId && cloneId.startsWith('clone-')) { + draggedClone.dataset.eventId = cloneId.replace('clone-', ''); + } + // Fully normalize the clone to be a regular event + draggedClone.classList.remove('dragging'); + draggedClone.style.pointerEvents = ''; // Re-enable pointer events + // Clean up instance state + this.draggedClone = null; + this.originalEvent = null; + // Clean up any remaining day event clones + const dayEventClone = document.querySelector(`swp-event[data-event-id="clone-${cloneId}"]`); + if (dayEventClone) { + dayEventClone.remove(); + } + } + /** + * Handle navigation completed event + */ + handleNavigationCompleted() { + // Default implementation - can be overridden by subclasses + } + /** + * Fade out and remove element + */ + fadeOutAndRemove(element) { + element.style.transition = 'opacity 0.3s ease-out'; + element.style.opacity = '0'; + setTimeout(() => { + element.remove(); + }, 300); + } + renderEvents(events, container) { + // Filter out all-day events - they should be handled by AllDayEventRenderer + const timedEvents = events.filter(event => !event.allDay); + // Find columns in the specific container for regular events + const columns = this.getColumns(container); + columns.forEach(column => { + const columnEvents = this.getEventsForColumn(column, timedEvents); + const eventsLayer = column.querySelector('swp-events-layer'); + if (eventsLayer) { + this.renderColumnEvents(columnEvents, eventsLayer); + } + }); + } + /** + * Render events for a single column + */ + renderSingleColumnEvents(column, events) { + const columnEvents = this.getEventsForColumn(column.element, events); + const eventsLayer = column.element.querySelector('swp-events-layer'); + if (eventsLayer) { + this.renderColumnEvents(columnEvents, eventsLayer); + } + } + /** + * Render events in a column using combined stacking + grid algorithm + */ + renderColumnEvents(columnEvents, eventsLayer) { + if (columnEvents.length === 0) + return; + // Get layout from coordinator + const layout = this.layoutCoordinator.calculateColumnLayout(columnEvents); + // Render grid groups + layout.gridGroups.forEach(gridGroup => { + this.renderGridGroup(gridGroup, eventsLayer); + }); + // Render stacked events + layout.stackedEvents.forEach(stackedEvent => { + const element = this.renderEvent(stackedEvent.event); + this.stackManager.applyStackLinkToElement(element, stackedEvent.stackLink); + this.stackManager.applyVisualStyling(element, stackedEvent.stackLink.stackLevel); + eventsLayer.appendChild(element); + }); + } + /** + * Render events in a grid container (side-by-side with column sharing) + */ + renderGridGroup(gridGroup, eventsLayer) { + const groupElement = document.createElement('swp-event-group'); + // Add grid column class based on number of columns (not events) + const colCount = gridGroup.columns.length; + groupElement.classList.add(`cols-${colCount}`); + // Add stack level class for margin-left offset + groupElement.classList.add(`stack-level-${gridGroup.stackLevel}`); + // Position from layout + groupElement.style.top = `${gridGroup.position.top}px`; + // Add stack-link attribute for drag-drop (group acts as a stacked item) + const stackLink = { + stackLevel: gridGroup.stackLevel + }; + this.stackManager.applyStackLinkToElement(groupElement, stackLink); + // Apply visual styling (margin-left and z-index) using StackManager + this.stackManager.applyVisualStyling(groupElement, gridGroup.stackLevel); + // Render each column + const earliestEvent = gridGroup.events[0]; + gridGroup.columns.forEach((columnEvents) => { + const columnContainer = this.renderGridColumn(columnEvents, earliestEvent.start); + groupElement.appendChild(columnContainer); + }); + eventsLayer.appendChild(groupElement); + } + /** + * Render a single column within a grid group + * Column may contain multiple events that don't overlap + */ + renderGridColumn(columnEvents, containerStart) { + const columnContainer = document.createElement('div'); + columnContainer.style.position = 'relative'; + columnEvents.forEach(event => { + const element = this.renderEventInGrid(event, containerStart); + columnContainer.appendChild(element); + }); + return columnContainer; + } + /** + * Render event within a grid container (absolute positioning within column) + */ + renderEventInGrid(event, containerStart) { + const element = SwpEventElement.fromCalendarEvent(event); + // Calculate event height + const position = this.calculateEventPosition(event); + // Calculate relative top offset if event starts after container start + // (e.g., if container starts at 07:00 and event starts at 08:15, offset = 75 min) + const timeDiffMs = event.start.getTime() - containerStart.getTime(); + const timeDiffMinutes = timeDiffMs / (1000 * 60); + const gridSettings = this.config.gridSettings; + const relativeTop = timeDiffMinutes > 0 ? (timeDiffMinutes / 60) * gridSettings.hourHeight : 0; + // Events in grid columns are positioned absolutely within their column container + element.style.position = 'absolute'; + element.style.top = `${relativeTop}px`; + element.style.height = `${position.height - 3}px`; + element.style.left = '0'; + element.style.right = '0'; + return element; + } + renderEvent(event) { + const element = SwpEventElement.fromCalendarEvent(event); + // Apply positioning (moved from SwpEventElement.applyPositioning) + const position = this.calculateEventPosition(event); + element.style.position = 'absolute'; + element.style.top = `${position.top + 1}px`; + element.style.height = `${position.height - 3}px`; + element.style.left = '2px'; + element.style.right = '2px'; + return element; + } + calculateEventPosition(event) { + // Delegate to PositionUtils for centralized position calculation + return this.positionUtils.calculateEventPosition(event.start, event.end); + } + clearEvents(container) { + const eventSelector = 'swp-event'; + const groupSelector = 'swp-event-group'; + const existingEvents = container + ? container.querySelectorAll(eventSelector) + : document.querySelectorAll(eventSelector); + const existingGroups = container + ? container.querySelectorAll(groupSelector) + : document.querySelectorAll(groupSelector); + existingEvents.forEach(event => event.remove()); + existingGroups.forEach(group => group.remove()); + } + getColumns(container) { + const columns = container.querySelectorAll('swp-day-column'); + return Array.from(columns); + } + getEventsForColumn(column, events) { + const columnDate = column.dataset.date; + if (!columnDate) { + return []; + } + // Create start and end of day for interval overlap check + const columnStart = this.dateService.parseISO(`${columnDate}T00:00:00`); + const columnEnd = this.dateService.parseISO(`${columnDate}T23:59:59.999`); + const columnEvents = events.filter(event => { + // Interval overlap: event overlaps with column day if event.start < columnEnd AND event.end > columnStart + const overlaps = event.start < columnEnd && event.end > columnStart; + return overlaps; + }); + return columnEvents; + } +} +//# sourceMappingURL=EventRenderer.js.map \ No newline at end of file diff --git a/wwwroot/js/renderers/EventRenderer.js.map b/wwwroot/js/renderers/EventRenderer.js.map new file mode 100644 index 0000000..78765f0 --- /dev/null +++ b/wwwroot/js/renderers/EventRenderer.js.map @@ -0,0 +1 @@ +{"version":3,"file":"EventRenderer.js","sourceRoot":"","sources":["../../../src/renderers/EventRenderer.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/renderers/EventRendererManager.d.ts b/wwwroot/js/renderers/EventRendererManager.d.ts new file mode 100644 index 0000000..3a2131c --- /dev/null +++ b/wwwroot/js/renderers/EventRendererManager.d.ts @@ -0,0 +1,55 @@ +import { IEventBus, IRenderContext } from '../types/CalendarTypes'; +import { EventManager } from '../managers/EventManager'; +import { IEventRenderer } from './EventRenderer'; +import { DateService } from '../utils/DateService'; +/** + * EventRenderingService - Render events i DOM med positionering using Strategy Pattern + * Håndterer event positioning og overlap detection + */ +export declare class EventRenderingService { + private eventBus; + private eventManager; + private strategy; + private dateService; + private dragMouseLeaveHeaderListener; + constructor(eventBus: IEventBus, eventManager: EventManager, strategy: IEventRenderer, dateService: DateService); + /** + * Render events in a specific container for a given period + */ + renderEvents(context: IRenderContext): Promise; + private setupEventListeners; + /** + * Handle GRID_RENDERED event - render events in the current grid + */ + private handleGridRendered; + /** + * Handle VIEW_CHANGED event - clear and re-render for new view + */ + private handleViewChanged; + /** + * Setup all drag event listeners - moved from EventRenderer for better separation of concerns + */ + private setupDragEventListeners; + private setupDragStartListener; + private setupDragMoveListener; + private setupDragEndListener; + private setupDragColumnChangeListener; + private setupDragMouseLeaveHeaderListener; + private setupDragMouseEnterColumnListener; + private setupResizeEndListener; + private setupNavigationCompletedListener; + /** + * Re-render affected columns after drag to recalculate stacking/grouping + */ + private reRenderAffectedColumns; + /** + * Clear events in a single column's events layer + */ + private clearColumnEvents; + /** + * Render events for a single column + */ + private renderSingleColumn; + private clearEvents; + refresh(container?: HTMLElement): void; +} diff --git a/wwwroot/js/renderers/EventRendererManager.js b/wwwroot/js/renderers/EventRendererManager.js new file mode 100644 index 0000000..d326ba4 --- /dev/null +++ b/wwwroot/js/renderers/EventRendererManager.js @@ -0,0 +1,264 @@ +import { CoreEvents } from '../constants/CoreEvents'; +import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils'; +/** + * EventRenderingService - Render events i DOM med positionering using Strategy Pattern + * Håndterer event positioning og overlap detection + */ +export class EventRenderingService { + constructor(eventBus, eventManager, strategy, dateService) { + this.dragMouseLeaveHeaderListener = null; + this.eventBus = eventBus; + this.eventManager = eventManager; + this.strategy = strategy; + this.dateService = dateService; + this.setupEventListeners(); + } + /** + * Render events in a specific container for a given period + */ + async renderEvents(context) { + // Clear existing events in the specific container first + this.strategy.clearEvents(context.container); + // Get events from EventManager for the period + const events = await this.eventManager.getEventsForPeriod(context.startDate, context.endDate); + if (events.length === 0) { + return; + } + // Filter events by type - only render timed events here + const timedEvents = events.filter(event => !event.allDay); + console.log('🎯 EventRenderingService: Event filtering', { + totalEvents: events.length, + timedEvents: timedEvents.length, + allDayEvents: events.length - timedEvents.length + }); + // Render timed events using existing strategy + if (timedEvents.length > 0) { + this.strategy.renderEvents(timedEvents, context.container); + } + // Emit EVENTS_RENDERED event for filtering system + this.eventBus.emit(CoreEvents.EVENTS_RENDERED, { + events: events, + container: context.container + }); + } + setupEventListeners() { + this.eventBus.on(CoreEvents.GRID_RENDERED, (event) => { + this.handleGridRendered(event); + }); + this.eventBus.on(CoreEvents.VIEW_CHANGED, (event) => { + this.handleViewChanged(event); + }); + // Handle all drag events and delegate to appropriate renderer + this.setupDragEventListeners(); + } + /** + * Handle GRID_RENDERED event - render events in the current grid + */ + handleGridRendered(event) { + const { container, dates } = event.detail; + if (!container || !dates || dates.length === 0) { + return; + } + // Calculate startDate and endDate from dates array + const startDate = dates[0]; + const endDate = dates[dates.length - 1]; + this.renderEvents({ + container, + startDate, + endDate + }); + } + /** + * Handle VIEW_CHANGED event - clear and re-render for new view + */ + handleViewChanged(event) { + // Clear all existing events since view structure may have changed + this.clearEvents(); + // New rendering will be triggered by subsequent GRID_RENDERED event + } + /** + * Setup all drag event listeners - moved from EventRenderer for better separation of concerns + */ + setupDragEventListeners() { + this.setupDragStartListener(); + this.setupDragMoveListener(); + this.setupDragEndListener(); + this.setupDragColumnChangeListener(); + this.setupDragMouseLeaveHeaderListener(); + this.setupDragMouseEnterColumnListener(); + this.setupResizeEndListener(); + this.setupNavigationCompletedListener(); + } + setupDragStartListener() { + this.eventBus.on('drag:start', (event) => { + const dragStartPayload = event.detail; + if (dragStartPayload.originalElement.hasAttribute('data-allday')) { + return; + } + if (dragStartPayload.originalElement && this.strategy.handleDragStart && dragStartPayload.columnBounds) { + this.strategy.handleDragStart(dragStartPayload); + } + }); + } + setupDragMoveListener() { + this.eventBus.on('drag:move', (event) => { + let dragEvent = event.detail; + if (dragEvent.draggedClone.hasAttribute('data-allday')) { + return; + } + if (this.strategy.handleDragMove) { + this.strategy.handleDragMove(dragEvent); + } + }); + } + setupDragEndListener() { + this.eventBus.on('drag:end', async (event) => { + const { originalElement, draggedClone, originalSourceColumn, finalPosition, target } = event.detail; + const finalColumn = finalPosition.column; + const finalY = finalPosition.snappedY; + let element = draggedClone; + // Only handle day column drops for EventRenderer + if (target === 'swp-day-column' && finalColumn) { + if (originalElement && draggedClone && this.strategy.handleDragEnd) { + this.strategy.handleDragEnd(originalElement, draggedClone, finalColumn, finalY); + } + await this.eventManager.updateEvent(element.eventId, { + start: element.start, + end: element.end, + allDay: false + }); + // Re-render affected columns for stacking/grouping (now with updated data) + await this.reRenderAffectedColumns(originalSourceColumn, finalColumn); + } + }); + } + setupDragColumnChangeListener() { + this.eventBus.on('drag:column-change', (event) => { + let columnChangeEvent = event.detail; + // Filter: Only handle events where clone is NOT an all-day event (normal timed events) + if (columnChangeEvent.draggedClone && columnChangeEvent.draggedClone.hasAttribute('data-allday')) { + return; + } + if (this.strategy.handleColumnChange) { + this.strategy.handleColumnChange(columnChangeEvent); + } + }); + } + setupDragMouseLeaveHeaderListener() { + this.dragMouseLeaveHeaderListener = (event) => { + const { targetDate, mousePosition, originalElement, draggedClone: cloneElement } = event.detail; + if (cloneElement) + cloneElement.style.display = ''; + console.log('🚪 EventRendererManager: Received drag:mouseleave-header', { + targetDate, + originalElement: originalElement, + cloneElement: cloneElement + }); + }; + this.eventBus.on('drag:mouseleave-header', this.dragMouseLeaveHeaderListener); + } + setupDragMouseEnterColumnListener() { + this.eventBus.on('drag:mouseenter-column', (event) => { + const payload = event.detail; + // Only handle if clone is an all-day event + if (!payload.draggedClone.hasAttribute('data-allday')) { + return; + } + console.log('🎯 EventRendererManager: Received drag:mouseenter-column', { + targetColumn: payload.targetColumn, + snappedY: payload.snappedY, + calendarEvent: payload.calendarEvent + }); + // Delegate to strategy for conversion + if (this.strategy.handleConvertAllDayToTimed) { + this.strategy.handleConvertAllDayToTimed(payload); + } + }); + } + setupResizeEndListener() { + this.eventBus.on('resize:end', async (event) => { + const { eventId, element } = event.detail; + // Update event data in EventManager with new end time from resized element + const swpEvent = element; + const newStart = swpEvent.start; + const newEnd = swpEvent.end; + await this.eventManager.updateEvent(eventId, { + start: newStart, + end: newEnd + }); + console.log('📝 EventRendererManager: Updated event after resize', { + eventId, + newStart, + newEnd + }); + let columnBounds = ColumnDetectionUtils.getColumnBoundsByDate(newStart); + if (columnBounds) + await this.renderSingleColumn(columnBounds); + }); + } + setupNavigationCompletedListener() { + this.eventBus.on(CoreEvents.NAVIGATION_COMPLETED, () => { + // Delegate to strategy if it handles navigation + if (this.strategy.handleNavigationCompleted) { + this.strategy.handleNavigationCompleted(); + } + }); + } + /** + * Re-render affected columns after drag to recalculate stacking/grouping + */ + async reRenderAffectedColumns(originalSourceColumn, targetColumn) { + // Re-render original source column if exists + if (originalSourceColumn) { + await this.renderSingleColumn(originalSourceColumn); + } + // Re-render target column if exists and different from source + if (targetColumn && targetColumn.date !== originalSourceColumn?.date) { + await this.renderSingleColumn(targetColumn); + } + } + /** + * Clear events in a single column's events layer + */ + clearColumnEvents(eventsLayer) { + const existingEvents = eventsLayer.querySelectorAll('swp-event'); + const existingGroups = eventsLayer.querySelectorAll('swp-event-group'); + existingEvents.forEach(event => event.remove()); + existingGroups.forEach(group => group.remove()); + } + /** + * Render events for a single column + */ + async renderSingleColumn(column) { + // Get events for just this column's date + const columnStart = this.dateService.parseISO(`${column.date}T00:00:00`); + const columnEnd = this.dateService.parseISO(`${column.date}T23:59:59.999`); + // Get events from EventManager for this single date + const events = await this.eventManager.getEventsForPeriod(columnStart, columnEnd); + // Filter to timed events only + const timedEvents = events.filter(event => !event.allDay); + // Get events layer within this specific column + const eventsLayer = column.element.querySelector('swp-events-layer'); + if (!eventsLayer) { + console.warn('EventRendererManager: Events layer not found in column'); + return; + } + // Clear only this column's events + this.clearColumnEvents(eventsLayer); + // Render events for this column using strategy + if (this.strategy.renderSingleColumnEvents) { + this.strategy.renderSingleColumnEvents(column, timedEvents); + } + console.log('🔄 EventRendererManager: Re-rendered single column', { + columnDate: column.date, + eventsCount: timedEvents.length + }); + } + clearEvents(container) { + this.strategy.clearEvents(container); + } + refresh(container) { + this.clearEvents(container); + } +} +//# sourceMappingURL=EventRendererManager.js.map \ No newline at end of file diff --git a/wwwroot/js/renderers/EventRendererManager.js.map b/wwwroot/js/renderers/EventRendererManager.js.map new file mode 100644 index 0000000..d1cfc77 --- /dev/null +++ b/wwwroot/js/renderers/EventRendererManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"EventRendererManager.js","sourceRoot":"","sources":["../../../src/renderers/EventRendererManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/renderers/GridRenderer.d.ts b/wwwroot/js/renderers/GridRenderer.d.ts new file mode 100644 index 0000000..8613651 --- /dev/null +++ b/wwwroot/js/renderers/GridRenderer.d.ts @@ -0,0 +1,180 @@ +import { Configuration } from '../configurations/CalendarConfig'; +import { CalendarView, ICalendarEvent } from '../types/CalendarTypes'; +import { IColumnRenderer } from './ColumnRenderer'; +import { DateService } from '../utils/DateService'; +import { WorkHoursManager } from '../managers/WorkHoursManager'; +/** + * GridRenderer - Centralized DOM rendering for calendar grid structure + * + * ARCHITECTURE OVERVIEW: + * ===================== + * GridRenderer is responsible for creating and managing the complete DOM structure + * of the calendar grid. It follows the Strategy Pattern by delegating specific + * rendering tasks to specialized renderers (DateHeaderRenderer, ColumnRenderer). + * + * RESPONSIBILITY HIERARCHY: + * ======================== + * GridRenderer (this file) + * ├─ Creates overall grid skeleton + * ├─ Manages time axis (hour markers) + * └─ Delegates to specialized renderers: + * ├─ DateHeaderRenderer → Renders date headers + * └─ ColumnRenderer → Renders day columns + * + * DOM STRUCTURE CREATED: + * ===================== + * + * ← GridRenderer + * ← GridRenderer + * 00:00 ← GridRenderer (iterates hours) + * + * ← GridRenderer + * ← GridRenderer creates container + * ← DateHeaderRenderer (iterates dates) + * + * ← GridRenderer + * ← GridRenderer + * ← GridRenderer + * ← GridRenderer creates container + * ← ColumnRenderer (iterates dates) + * + * + * + * + * + * + * RENDERING FLOW: + * ============== + * 1. renderGrid() - Entry point called by GridManager + * ├─ First render: createCompleteGridStructure() + * └─ Updates: updateGridContent() + * + * 2. createCompleteGridStructure() + * ├─ Creates header spacer + * ├─ Creates time axis (calls createOptimizedTimeAxis) + * └─ Creates grid container (calls createOptimizedGridContainer) + * + * 3. createOptimizedGridContainer() + * ├─ Creates calendar header container + * ├─ Creates scrollable content structure + * └─ Creates column container (calls renderColumnContainer) + * + * 4. renderColumnContainer() + * └─ Delegates to ColumnRenderer.render() + * └─ ColumnRenderer iterates dates and creates columns + * + * OPTIMIZATION STRATEGY: + * ===================== + * - Caches DOM references (cachedGridContainer, cachedTimeAxis) + * - Uses DocumentFragment for batch DOM insertions + * - Only updates changed content on re-renders + * - Delegates specialized tasks to strategy renderers + * + * USAGE EXAMPLE: + * ============= + * const gridRenderer = new GridRenderer(columnRenderer, dateService, config); + * gridRenderer.renderGrid(containerElement, new Date(), 'week'); + */ +export declare class GridRenderer { + private cachedGridContainer; + private cachedTimeAxis; + private dateService; + private columnRenderer; + private config; + private workHoursManager; + constructor(columnRenderer: IColumnRenderer, dateService: DateService, config: Configuration, workHoursManager: WorkHoursManager); + /** + * Main entry point for rendering the complete calendar grid + * + * This method decides between full render (first time) or optimized update. + * It caches the grid reference for performance. + * + * @param grid - Container element where grid will be rendered + * @param currentDate - Base date for the current view (e.g., any date in the week) + * @param view - Calendar view type (day/week/month) + * @param dates - Array of dates to render as columns + * @param events - All events for the period + */ + renderGrid(grid: HTMLElement, currentDate: Date, view?: CalendarView, dates?: Date[], events?: ICalendarEvent[]): void; + /** + * Creates the complete grid structure from scratch + * + * Uses DocumentFragment for optimal performance by minimizing reflows. + * Creates all child elements in memory first, then appends everything at once. + * + * Structure created: + * 1. Header spacer (placeholder for alignment) + * 2. Time axis (hour markers 00:00-23:00) + * 3. Grid container (header + scrollable content) + * + * @param grid - Parent container + * @param currentDate - Current view date + * @param view - View type + * @param dates - Array of dates to render + */ + private createCompleteGridStructure; + /** + * Creates the time axis with hour markers + * + * Iterates from dayStartHour to dayEndHour (configured in GridSettings). + * Each marker shows the hour in the configured time format. + * + * @returns Time axis element with all hour markers + */ + private createOptimizedTimeAxis; + /** + * Creates the main grid container with header and columns + * + * This is the scrollable area containing: + * - Calendar header (dates/resources) - created here, populated by DateHeaderRenderer + * - Time grid (grid lines + day columns) - structure created here + * - Column container - created here, populated by ColumnRenderer + * + * @param currentDate - Current view date + * @param view - View type + * @param dates - Array of dates to render + * @returns Complete grid container element + */ + private createOptimizedGridContainer; + /** + * Renders columns by iterating through dates + * + * GridRenderer creates column structure with work hours styling. + * Event rendering is handled by EventRenderingService listening to GRID_RENDERED. + * + * @param columnContainer - Empty container to populate + * @param dates - Array of dates to render + * @param events - All events for the period (passed through, not used here) + */ + private renderColumnContainer; + /** + * Apply work hours styling to a column + */ + private applyWorkHoursStyling; + /** + * Optimized update of grid content without full rebuild + * + * Only updates the column container content, leaving the structure intact. + * This is much faster than recreating the entire grid. + * + * @param grid - Existing grid element + * @param currentDate - New view date + * @param view - View type + * @param dates - Array of dates to render + * @param events - All events for the period + */ + private updateGridContent; + /** + * Creates a new grid for slide animations during navigation + * + * Used by NavigationManager for smooth week-to-week transitions. + * Creates a complete grid positioned absolutely for animation. + * + * Note: Positioning is handled by Animation API, not here. + * + * @param parentContainer - Container for the new grid + * @param weekStart - Start date of the new week + * @returns New grid element ready for animation + */ + createNavigationGrid(parentContainer: HTMLElement, weekStart: Date): HTMLElement; +} diff --git a/wwwroot/js/renderers/GridRenderer.js b/wwwroot/js/renderers/GridRenderer.js new file mode 100644 index 0000000..0a3a7c9 --- /dev/null +++ b/wwwroot/js/renderers/GridRenderer.js @@ -0,0 +1,289 @@ +import { TimeFormatter } from '../utils/TimeFormatter'; +/** + * GridRenderer - Centralized DOM rendering for calendar grid structure + * + * ARCHITECTURE OVERVIEW: + * ===================== + * GridRenderer is responsible for creating and managing the complete DOM structure + * of the calendar grid. It follows the Strategy Pattern by delegating specific + * rendering tasks to specialized renderers (DateHeaderRenderer, ColumnRenderer). + * + * RESPONSIBILITY HIERARCHY: + * ======================== + * GridRenderer (this file) + * ├─ Creates overall grid skeleton + * ├─ Manages time axis (hour markers) + * └─ Delegates to specialized renderers: + * ├─ DateHeaderRenderer → Renders date headers + * └─ ColumnRenderer → Renders day columns + * + * DOM STRUCTURE CREATED: + * ===================== + * + * ← GridRenderer + * ← GridRenderer + * 00:00 ← GridRenderer (iterates hours) + * + * ← GridRenderer + * ← GridRenderer creates container + * ← DateHeaderRenderer (iterates dates) + * + * ← GridRenderer + * ← GridRenderer + * ← GridRenderer + * ← GridRenderer creates container + * ← ColumnRenderer (iterates dates) + * + * + * + * + * + * + * RENDERING FLOW: + * ============== + * 1. renderGrid() - Entry point called by GridManager + * ├─ First render: createCompleteGridStructure() + * └─ Updates: updateGridContent() + * + * 2. createCompleteGridStructure() + * ├─ Creates header spacer + * ├─ Creates time axis (calls createOptimizedTimeAxis) + * └─ Creates grid container (calls createOptimizedGridContainer) + * + * 3. createOptimizedGridContainer() + * ├─ Creates calendar header container + * ├─ Creates scrollable content structure + * └─ Creates column container (calls renderColumnContainer) + * + * 4. renderColumnContainer() + * └─ Delegates to ColumnRenderer.render() + * └─ ColumnRenderer iterates dates and creates columns + * + * OPTIMIZATION STRATEGY: + * ===================== + * - Caches DOM references (cachedGridContainer, cachedTimeAxis) + * - Uses DocumentFragment for batch DOM insertions + * - Only updates changed content on re-renders + * - Delegates specialized tasks to strategy renderers + * + * USAGE EXAMPLE: + * ============= + * const gridRenderer = new GridRenderer(columnRenderer, dateService, config); + * gridRenderer.renderGrid(containerElement, new Date(), 'week'); + */ +export class GridRenderer { + constructor(columnRenderer, dateService, config, workHoursManager) { + this.cachedGridContainer = null; + this.cachedTimeAxis = null; + this.dateService = dateService; + this.columnRenderer = columnRenderer; + this.config = config; + this.workHoursManager = workHoursManager; + } + /** + * Main entry point for rendering the complete calendar grid + * + * This method decides between full render (first time) or optimized update. + * It caches the grid reference for performance. + * + * @param grid - Container element where grid will be rendered + * @param currentDate - Base date for the current view (e.g., any date in the week) + * @param view - Calendar view type (day/week/month) + * @param dates - Array of dates to render as columns + * @param events - All events for the period + */ + renderGrid(grid, currentDate, view = 'week', dates = [], events = []) { + if (!grid || !currentDate) { + return; + } + // Cache grid reference for performance + this.cachedGridContainer = grid; + // Only clear and rebuild if grid is empty (first render) + if (grid.children.length === 0) { + this.createCompleteGridStructure(grid, currentDate, view, dates, events); + } + else { + // Optimized update - only refresh dynamic content + this.updateGridContent(grid, currentDate, view, dates, events); + } + } + /** + * Creates the complete grid structure from scratch + * + * Uses DocumentFragment for optimal performance by minimizing reflows. + * Creates all child elements in memory first, then appends everything at once. + * + * Structure created: + * 1. Header spacer (placeholder for alignment) + * 2. Time axis (hour markers 00:00-23:00) + * 3. Grid container (header + scrollable content) + * + * @param grid - Parent container + * @param currentDate - Current view date + * @param view - View type + * @param dates - Array of dates to render + */ + createCompleteGridStructure(grid, currentDate, view, dates, events) { + // Create all elements in memory first for better performance + const fragment = document.createDocumentFragment(); + // Create header spacer + const headerSpacer = document.createElement('swp-header-spacer'); + fragment.appendChild(headerSpacer); + // Create time axis with caching + const timeAxis = this.createOptimizedTimeAxis(); + this.cachedTimeAxis = timeAxis; + fragment.appendChild(timeAxis); + // Create grid container with caching + const gridContainer = this.createOptimizedGridContainer(currentDate, view, dates, events); + this.cachedGridContainer = gridContainer; + fragment.appendChild(gridContainer); + // Append all at once to minimize reflows + grid.appendChild(fragment); + } + /** + * Creates the time axis with hour markers + * + * Iterates from dayStartHour to dayEndHour (configured in GridSettings). + * Each marker shows the hour in the configured time format. + * + * @returns Time axis element with all hour markers + */ + createOptimizedTimeAxis() { + const timeAxis = document.createElement('swp-time-axis'); + const timeAxisContent = document.createElement('swp-time-axis-content'); + const gridSettings = this.config.gridSettings; + const startHour = gridSettings.dayStartHour; + const endHour = gridSettings.dayEndHour; + const fragment = document.createDocumentFragment(); + for (let hour = startHour; hour < endHour; hour++) { + const marker = document.createElement('swp-hour-marker'); + const date = new Date(2024, 0, 1, hour, 0); + marker.textContent = TimeFormatter.formatTime(date); + fragment.appendChild(marker); + } + timeAxisContent.appendChild(fragment); + timeAxisContent.style.top = '-1px'; + timeAxis.appendChild(timeAxisContent); + return timeAxis; + } + /** + * Creates the main grid container with header and columns + * + * This is the scrollable area containing: + * - Calendar header (dates/resources) - created here, populated by DateHeaderRenderer + * - Time grid (grid lines + day columns) - structure created here + * - Column container - created here, populated by ColumnRenderer + * + * @param currentDate - Current view date + * @param view - View type + * @param dates - Array of dates to render + * @returns Complete grid container element + */ + createOptimizedGridContainer(dates, events) { + const gridContainer = document.createElement('swp-grid-container'); + // Create calendar header as first child - always exists now! + const calendarHeader = document.createElement('swp-calendar-header'); + gridContainer.appendChild(calendarHeader); + // Create scrollable content structure + const scrollableContent = document.createElement('swp-scrollable-content'); + const timeGrid = document.createElement('swp-time-grid'); + // Add grid lines + const gridLines = document.createElement('swp-grid-lines'); + timeGrid.appendChild(gridLines); + // Create column container + const columnContainer = document.createElement('swp-day-columns'); + this.renderColumnContainer(columnContainer, dates, events); + timeGrid.appendChild(columnContainer); + scrollableContent.appendChild(timeGrid); + gridContainer.appendChild(scrollableContent); + return gridContainer; + } + /** + * Renders columns by iterating through dates + * + * GridRenderer creates column structure with work hours styling. + * Event rendering is handled by EventRenderingService listening to GRID_RENDERED. + * + * @param columnContainer - Empty container to populate + * @param dates - Array of dates to render + * @param events - All events for the period (passed through, not used here) + */ + renderColumnContainer(columnContainer, dates, events) { + // Iterate through dates and render each column structure + dates.forEach(date => { + // Create column with data-date attribute + const column = document.createElement('swp-day-column'); + column.dataset.date = this.dateService.formatISODate(date); + // Apply work hours styling + this.applyWorkHoursStyling(column, date); + // Add events layer (events will be rendered by EventRenderingService) + const eventsLayer = document.createElement('swp-events-layer'); + column.appendChild(eventsLayer); + columnContainer.appendChild(column); + }); + } + /** + * Apply work hours styling to a column + */ + applyWorkHoursStyling(column, date) { + const workHours = this.workHoursManager.getWorkHoursForDate(date); + if (workHours === 'off') { + column.setAttribute('data-day-off', 'true'); + } + else { + column.removeAttribute('data-day-off'); + // Calculate non-work hours overlay positions + const nonWorkStyle = this.workHoursManager.calculateNonWorkHoursStyle(workHours); + if (nonWorkStyle) { + column.style.setProperty('--before-work-height', `${nonWorkStyle.beforeWorkHeight}px`); + column.style.setProperty('--after-work-top', `${nonWorkStyle.afterWorkTop}px`); + } + } + } + /** + * Optimized update of grid content without full rebuild + * + * Only updates the column container content, leaving the structure intact. + * This is much faster than recreating the entire grid. + * + * @param grid - Existing grid element + * @param currentDate - New view date + * @param view - View type + * @param dates - Array of dates to render + * @param events - All events for the period + */ + updateGridContent(grid, currentDate, view, dates, events) { + // Update column container if needed + const columnContainer = grid.querySelector('swp-day-columns'); + if (columnContainer) { + columnContainer.innerHTML = ''; + this.renderColumnContainer(columnContainer, dates, events); + } + } + /** + * Creates a new grid for slide animations during navigation + * + * Used by NavigationManager for smooth week-to-week transitions. + * Creates a complete grid positioned absolutely for animation. + * + * Note: Positioning is handled by Animation API, not here. + * + * @param parentContainer - Container for the new grid + * @param weekStart - Start date of the new week + * @returns New grid element ready for animation + */ + createNavigationGrid(parentContainer, weekStart) { + // Use SAME method as initial load - respects workweek settings + const newGrid = this.createOptimizedGridContainer(weekStart, 'week'); + // Position new grid for animation - NO transform here, let Animation API handle it + newGrid.style.position = 'absolute'; + newGrid.style.top = '0'; + newGrid.style.left = '0'; + newGrid.style.width = '100%'; + newGrid.style.height = '100%'; + // Add to parent container + parentContainer.appendChild(newGrid); + return newGrid; + } +} +//# sourceMappingURL=GridRenderer.js.map \ No newline at end of file diff --git a/wwwroot/js/renderers/GridRenderer.js.map b/wwwroot/js/renderers/GridRenderer.js.map new file mode 100644 index 0000000..6e97412 --- /dev/null +++ b/wwwroot/js/renderers/GridRenderer.js.map @@ -0,0 +1 @@ +{"version":3,"file":"GridRenderer.js","sourceRoot":"","sources":["../../../src/renderers/GridRenderer.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/renderers/GridStyleManager.d.ts b/wwwroot/js/renderers/GridStyleManager.d.ts new file mode 100644 index 0000000..9bad858 --- /dev/null +++ b/wwwroot/js/renderers/GridStyleManager.d.ts @@ -0,0 +1,24 @@ +import { ResourceCalendarData } from '../types/CalendarTypes'; +/** + * GridStyleManager - Manages CSS variables and styling for the grid + * Separated from GridManager to follow Single Responsibility Principle + */ +export declare class GridStyleManager { + constructor(); + /** + * Update all grid CSS variables + */ + updateGridStyles(resourceData?: ResourceCalendarData | null): void; + /** + * Set time-related CSS variables + */ + private setTimeVariables; + /** + * Calculate number of columns based on calendar type and view + */ + private calculateColumnCount; + /** + * Set column width based on fitToWidth setting + */ + private setColumnWidth; +} diff --git a/wwwroot/js/renderers/GridStyleManager.js b/wwwroot/js/renderers/GridStyleManager.js new file mode 100644 index 0000000..c7485da --- /dev/null +++ b/wwwroot/js/renderers/GridStyleManager.js @@ -0,0 +1,76 @@ +import { calendarConfig } from '../core/CalendarConfig'; +/** + * GridStyleManager - Manages CSS variables and styling for the grid + * Separated from GridManager to follow Single Responsibility Principle + */ +export class GridStyleManager { + constructor() { + } + /** + * Update all grid CSS variables + */ + updateGridStyles(resourceData = null) { + const root = document.documentElement; + const gridSettings = calendarConfig.getGridSettings(); + const calendar = document.querySelector('swp-calendar'); + const calendarType = calendarConfig.getCalendarMode(); + // Set CSS variables for time and grid measurements + this.setTimeVariables(root, gridSettings); + // Set column count based on calendar type + const columnCount = this.calculateColumnCount(calendarType, resourceData); + root.style.setProperty('--grid-columns', columnCount.toString()); + // Set column width based on fitToWidth setting + this.setColumnWidth(root, gridSettings); + // Set fitToWidth data attribute for CSS targeting + if (calendar) { + calendar.setAttribute('data-fit-to-width', gridSettings.fitToWidth.toString()); + } + } + /** + * Set time-related CSS variables + */ + setTimeVariables(root, gridSettings) { + root.style.setProperty('--hour-height', `${gridSettings.hourHeight}px`); + root.style.setProperty('--minute-height', `${gridSettings.hourHeight / 60}px`); + root.style.setProperty('--snap-interval', gridSettings.snapInterval.toString()); + root.style.setProperty('--day-start-hour', gridSettings.dayStartHour.toString()); + root.style.setProperty('--day-end-hour', gridSettings.dayEndHour.toString()); + root.style.setProperty('--work-start-hour', gridSettings.workStartHour.toString()); + root.style.setProperty('--work-end-hour', gridSettings.workEndHour.toString()); + } + /** + * Calculate number of columns based on calendar type and view + */ + calculateColumnCount(calendarType, resourceData) { + if (calendarType === 'resource' && resourceData) { + return resourceData.resources.length; + } + else if (calendarType === 'date') { + const dateSettings = calendarConfig.getDateViewSettings(); + const workWeekSettings = calendarConfig.getWorkWeekSettings(); + switch (dateSettings.period) { + case 'day': + return 1; + case 'week': + return workWeekSettings.totalDays; + case 'month': + return workWeekSettings.totalDays; // Use work week for month view too + default: + return workWeekSettings.totalDays; + } + } + return calendarConfig.getWorkWeekSettings().totalDays; // Default to work week + } + /** + * Set column width based on fitToWidth setting + */ + setColumnWidth(root, gridSettings) { + if (gridSettings.fitToWidth) { + root.style.setProperty('--day-column-min-width', '50px'); // Small min-width allows columns to fit available space + } + else { + root.style.setProperty('--day-column-min-width', '250px'); // Default min-width for horizontal scroll mode + } + } +} +//# sourceMappingURL=GridStyleManager.js.map \ No newline at end of file diff --git a/wwwroot/js/renderers/GridStyleManager.js.map b/wwwroot/js/renderers/GridStyleManager.js.map new file mode 100644 index 0000000..f3d9366 --- /dev/null +++ b/wwwroot/js/renderers/GridStyleManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"GridStyleManager.js","sourceRoot":"","sources":["../../../src/renderers/GridStyleManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/renderers/HeaderRenderer.d.ts b/wwwroot/js/renderers/HeaderRenderer.d.ts new file mode 100644 index 0000000..50d0c7b --- /dev/null +++ b/wwwroot/js/renderers/HeaderRenderer.d.ts @@ -0,0 +1,29 @@ +import { CalendarConfig } from '../core/CalendarConfig'; +import { ResourceCalendarData } from '../types/CalendarTypes'; +/** + * Interface for header rendering strategies + */ +export interface HeaderRenderer { + render(calendarHeader: HTMLElement, context: HeaderRenderContext): void; +} +/** + * Context for header rendering + */ +export interface HeaderRenderContext { + currentWeek: Date; + config: CalendarConfig; + resourceData?: ResourceCalendarData | null; +} +/** + * Date-based header renderer (original functionality) + */ +export declare class DateHeaderRenderer implements HeaderRenderer { + private dateService; + render(calendarHeader: HTMLElement, context: HeaderRenderContext): void; +} +/** + * Resource-based header renderer + */ +export declare class ResourceHeaderRenderer implements HeaderRenderer { + render(calendarHeader: HTMLElement, context: HeaderRenderContext): void; +} diff --git a/wwwroot/js/renderers/HeaderRenderer.js b/wwwroot/js/renderers/HeaderRenderer.js new file mode 100644 index 0000000..0acbb43 --- /dev/null +++ b/wwwroot/js/renderers/HeaderRenderer.js @@ -0,0 +1,56 @@ +// Header rendering strategy interface and implementations +import { DateCalculator } from '../utils/DateCalculator'; +/** + * Date-based header renderer (original functionality) + */ +export class DateHeaderRenderer { + render(calendarHeader, context) { + const { currentWeek, config } = context; + // FIRST: Always create all-day container as part of standard header structure + const allDayContainer = document.createElement('swp-allday-container'); + calendarHeader.appendChild(allDayContainer); + // Initialize date calculator with config + DateCalculator.initialize(config); + this.dateCalculator = new DateCalculator(); + const dates = DateCalculator.getWorkWeekDates(currentWeek); + const weekDays = config.getDateViewSettings().weekDays; + const daysToShow = dates.slice(0, weekDays); + daysToShow.forEach((date, index) => { + const header = document.createElement('swp-day-header'); + if (DateCalculator.isToday(date)) { + header.dataset.today = 'true'; + } + const dayName = DateCalculator.getDayName(date, 'short'); + header.innerHTML = ` + ${dayName} + ${date.getDate()} + `; + header.dataset.date = DateCalculator.formatISODate(date); + calendarHeader.appendChild(header); + }); + } +} +/** + * Resource-based header renderer + */ +export class ResourceHeaderRenderer { + render(calendarHeader, context) { + const { resourceData } = context; + if (!resourceData) { + return; + } + resourceData.resources.forEach((resource) => { + const header = document.createElement('swp-resource-header'); + header.setAttribute('data-resource', resource.name); + header.setAttribute('data-employee-id', resource.employeeId); + header.innerHTML = ` + + ${resource.displayName} + + ${resource.displayName} + `; + calendarHeader.appendChild(header); + }); + } +} +//# sourceMappingURL=HeaderRenderer.js.map \ No newline at end of file diff --git a/wwwroot/js/renderers/HeaderRenderer.js.map b/wwwroot/js/renderers/HeaderRenderer.js.map new file mode 100644 index 0000000..a5af7c0 --- /dev/null +++ b/wwwroot/js/renderers/HeaderRenderer.js.map @@ -0,0 +1 @@ +{"version":3,"file":"HeaderRenderer.js","sourceRoot":"","sources":["../../../src/renderers/HeaderRenderer.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAI1D,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAmBzD;;GAEG;AACH,MAAM,OAAO,kBAAkB;IAG7B,MAAM,CAAC,cAA2B,EAAE,OAA4B;QAC9D,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAExC,8EAA8E;QAC9E,MAAM,eAAe,GAAG,QAAQ,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC;QACvE,cAAc,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC;QAE5C,yCAAyC;QACzC,cAAc,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QAClC,IAAI,CAAC,cAAc,GAAG,IAAI,cAAc,EAAE,CAAC;QAE3C,MAAM,KAAK,GAAG,cAAc,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC;QAC3D,MAAM,QAAQ,GAAG,MAAM,CAAC,mBAAmB,EAAE,CAAC,QAAQ,CAAC;QACvD,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QAE5C,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YACjC,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC;YACxD,IAAI,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gBAChC,MAAc,CAAC,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC;YACzC,CAAC;YAED,MAAM,OAAO,GAAG,cAAc,CAAC,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAEzD,MAAM,CAAC,SAAS,GAAG;wBACD,OAAO;wBACP,IAAI,CAAC,OAAO,EAAE;OAC/B,CAAC;YACD,MAAc,CAAC,OAAO,CAAC,IAAI,GAAG,cAAc,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YAElE,cAAc,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,sBAAsB;IACjC,MAAM,CAAC,cAA2B,EAAE,OAA4B;QAC9D,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;QAEjC,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QAED,YAAY,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;YAC1C,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC;YAC7D,MAAM,CAAC,YAAY,CAAC,eAAe,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;YACpD,MAAM,CAAC,YAAY,CAAC,kBAAkB,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;YAE7D,MAAM,CAAC,SAAS,GAAG;;sBAEH,QAAQ,CAAC,SAAS,UAAU,QAAQ,CAAC,WAAW;;6BAEzC,QAAQ,CAAC,WAAW;OAC1C,CAAC;YAEF,cAAc,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACL,CAAC;CACF"} \ No newline at end of file diff --git a/wwwroot/js/renderers/NavigationRenderer.d.ts b/wwwroot/js/renderers/NavigationRenderer.d.ts new file mode 100644 index 0000000..44b4b2d --- /dev/null +++ b/wwwroot/js/renderers/NavigationRenderer.d.ts @@ -0,0 +1,22 @@ +import { IEventBus } from '../types/CalendarTypes'; +import { EventRenderingService } from './EventRendererManager'; +/** + * NavigationRenderer - Handles DOM rendering for navigation containers + * Separated from NavigationManager to follow Single Responsibility Principle + */ +export declare class NavigationRenderer { + private eventBus; + constructor(eventBus: IEventBus, eventRenderer: EventRenderingService); + /** + * Setup event listeners for DOM updates + */ + private setupEventListeners; + private updateWeekInfoInDOM; + /** + * Apply filter state to pre-rendered grids + */ + applyFilterToPreRenderedGrids(filterState: { + active: boolean; + matchingIds: string[]; + }): void; +} diff --git a/wwwroot/js/renderers/NavigationRenderer.js b/wwwroot/js/renderers/NavigationRenderer.js new file mode 100644 index 0000000..8b0382e --- /dev/null +++ b/wwwroot/js/renderers/NavigationRenderer.js @@ -0,0 +1,68 @@ +import { CoreEvents } from '../constants/CoreEvents'; +/** + * NavigationRenderer - Handles DOM rendering for navigation containers + * Separated from NavigationManager to follow Single Responsibility Principle + */ +export class NavigationRenderer { + constructor(eventBus, eventRenderer) { + this.eventBus = eventBus; + this.setupEventListeners(); + } + /** + * Setup event listeners for DOM updates + */ + setupEventListeners() { + this.eventBus.on(CoreEvents.PERIOD_INFO_UPDATE, (event) => { + const customEvent = event; + const { weekNumber, dateRange } = customEvent.detail; + this.updateWeekInfoInDOM(weekNumber, dateRange); + }); + } + updateWeekInfoInDOM(weekNumber, dateRange) { + const weekNumberElement = document.querySelector('swp-week-number'); + const dateRangeElement = document.querySelector('swp-date-range'); + if (weekNumberElement) { + weekNumberElement.textContent = `Week ${weekNumber}`; + } + if (dateRangeElement) { + dateRangeElement.textContent = dateRange; + } + } + /** + * Apply filter state to pre-rendered grids + */ + applyFilterToPreRenderedGrids(filterState) { + // Find all grid containers (including pre-rendered ones) + const allGridContainers = document.querySelectorAll('swp-grid-container'); + allGridContainers.forEach(container => { + const eventsLayers = container.querySelectorAll('swp-events-layer'); + eventsLayers.forEach(layer => { + if (filterState.active) { + // Apply filter active state + layer.setAttribute('data-filter-active', 'true'); + // Mark matching events in this layer + const events = layer.querySelectorAll('swp-event'); + events.forEach(event => { + const eventId = event.getAttribute('data-event-id'); + if (eventId && filterState.matchingIds.includes(eventId)) { + event.setAttribute('data-matches', 'true'); + } + else { + event.removeAttribute('data-matches'); + } + }); + } + else { + // Remove filter state + layer.removeAttribute('data-filter-active'); + // Remove all match attributes + const events = layer.querySelectorAll('swp-event'); + events.forEach(event => { + event.removeAttribute('data-matches'); + }); + } + }); + }); + } +} +//# sourceMappingURL=NavigationRenderer.js.map \ No newline at end of file diff --git a/wwwroot/js/renderers/NavigationRenderer.js.map b/wwwroot/js/renderers/NavigationRenderer.js.map new file mode 100644 index 0000000..84751b8 --- /dev/null +++ b/wwwroot/js/renderers/NavigationRenderer.js.map @@ -0,0 +1 @@ +{"version":3,"file":"NavigationRenderer.js","sourceRoot":"","sources":["../../../src/renderers/NavigationRenderer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAGrD;;;GAGG;AAEH,MAAM,OAAO,kBAAkB;IAG7B,YAAY,QAAmB,EAAE,aAAoC;QACnE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,mBAAmB,EAAE,CAAC;IAC7B,CAAC;IAID;;OAEG;IACK,mBAAmB;QACzB,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC,KAAY,EAAE,EAAE;YAC/D,MAAM,WAAW,GAAG,KAAoB,CAAC;YACzC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC;YACrD,IAAI,CAAC,mBAAmB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;IACL,CAAC;IAGO,mBAAmB,CAAC,UAAkB,EAAE,SAAiB;QAE/D,MAAM,iBAAiB,GAAG,QAAQ,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC;QACpE,MAAM,gBAAgB,GAAG,QAAQ,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC;QAElE,IAAI,iBAAiB,EAAE,CAAC;YACtB,iBAAiB,CAAC,WAAW,GAAG,QAAQ,UAAU,EAAE,CAAC;QACvD,CAAC;QAED,IAAI,gBAAgB,EAAE,CAAC;YACrB,gBAAgB,CAAC,WAAW,GAAG,SAAS,CAAC;QAC3C,CAAC;IACH,CAAC;IAED;;OAEG;IACI,6BAA6B,CAAC,WAAuD;QAC1F,yDAAyD;QACzD,MAAM,iBAAiB,GAAG,QAAQ,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAC;QAE1E,iBAAiB,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE;YACpC,MAAM,YAAY,GAAG,SAAS,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,CAAC;YAEpE,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;gBAC3B,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC;oBACvB,4BAA4B;oBAC5B,KAAK,CAAC,YAAY,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC;oBAEjD,qCAAqC;oBACrC,MAAM,MAAM,GAAG,KAAK,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC;oBACnD,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;wBACrB,MAAM,OAAO,GAAG,KAAK,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;wBACpD,IAAI,OAAO,IAAI,WAAW,CAAC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;4BACzD,KAAK,CAAC,YAAY,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;wBAC7C,CAAC;6BAAM,CAAC;4BACN,KAAK,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC;wBACxC,CAAC;oBACH,CAAC,CAAC,CAAC;gBACL,CAAC;qBAAM,CAAC;oBACN,sBAAsB;oBACtB,KAAK,CAAC,eAAe,CAAC,oBAAoB,CAAC,CAAC;oBAE5C,8BAA8B;oBAC9B,MAAM,MAAM,GAAG,KAAK,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC;oBACnD,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;wBACrB,KAAK,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC;oBACxC,CAAC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;CAEF"} \ No newline at end of file diff --git a/wwwroot/js/renderers/WeekInfoRenderer.d.ts b/wwwroot/js/renderers/WeekInfoRenderer.d.ts new file mode 100644 index 0000000..e244867 --- /dev/null +++ b/wwwroot/js/renderers/WeekInfoRenderer.d.ts @@ -0,0 +1,26 @@ +import { IEventBus } from '../types/CalendarTypes'; +import { EventRenderingService } from './EventRendererManager'; +import { DateService } from '../utils/DateService'; +/** + * WeekInfoRenderer - Handles DOM rendering for week info display + * Updates swp-week-number and swp-date-range elements + * + * Renamed from NavigationRenderer to better reflect its actual responsibility + */ +export declare class WeekInfoRenderer { + private eventBus; + private dateService; + constructor(eventBus: IEventBus, eventRenderer: EventRenderingService, dateService: DateService); + /** + * Setup event listeners for DOM updates + */ + private setupEventListeners; + private updateWeekInfoInDOM; + /** + * Apply filter state to pre-rendered grids + */ + applyFilterToPreRenderedGrids(filterState: { + active: boolean; + matchingIds: string[]; + }): void; +} diff --git a/wwwroot/js/renderers/WeekInfoRenderer.js b/wwwroot/js/renderers/WeekInfoRenderer.js new file mode 100644 index 0000000..cb12aa4 --- /dev/null +++ b/wwwroot/js/renderers/WeekInfoRenderer.js @@ -0,0 +1,75 @@ +import { CoreEvents } from '../constants/CoreEvents'; +/** + * WeekInfoRenderer - Handles DOM rendering for week info display + * Updates swp-week-number and swp-date-range elements + * + * Renamed from NavigationRenderer to better reflect its actual responsibility + */ +export class WeekInfoRenderer { + constructor(eventBus, eventRenderer, dateService) { + this.eventBus = eventBus; + this.dateService = dateService; + this.setupEventListeners(); + } + /** + * Setup event listeners for DOM updates + */ + setupEventListeners() { + this.eventBus.on(CoreEvents.NAVIGATION_COMPLETED, (event) => { + const customEvent = event; + const { newDate } = customEvent.detail; + // Calculate week number and date range from the new date + const weekNumber = this.dateService.getWeekNumber(newDate); + const weekEnd = this.dateService.addDays(newDate, 6); + const dateRange = this.dateService.formatDateRange(newDate, weekEnd); + this.updateWeekInfoInDOM(weekNumber, dateRange); + }); + } + updateWeekInfoInDOM(weekNumber, dateRange) { + const weekNumberElement = document.querySelector('swp-week-number'); + const dateRangeElement = document.querySelector('swp-date-range'); + if (weekNumberElement) { + weekNumberElement.textContent = `Week ${weekNumber}`; + } + if (dateRangeElement) { + dateRangeElement.textContent = dateRange; + } + } + /** + * Apply filter state to pre-rendered grids + */ + applyFilterToPreRenderedGrids(filterState) { + // Find all grid containers (including pre-rendered ones) + const allGridContainers = document.querySelectorAll('swp-grid-container'); + allGridContainers.forEach(container => { + const eventsLayers = container.querySelectorAll('swp-events-layer'); + eventsLayers.forEach(layer => { + if (filterState.active) { + // Apply filter active state + layer.setAttribute('data-filter-active', 'true'); + // Mark matching events in this layer + const events = layer.querySelectorAll('swp-event'); + events.forEach(event => { + const eventId = event.getAttribute('data-event-id'); + if (eventId && filterState.matchingIds.includes(eventId)) { + event.setAttribute('data-matches', 'true'); + } + else { + event.removeAttribute('data-matches'); + } + }); + } + else { + // Remove filter state + layer.removeAttribute('data-filter-active'); + // Remove all match attributes + const events = layer.querySelectorAll('swp-event'); + events.forEach(event => { + event.removeAttribute('data-matches'); + }); + } + }); + }); + } +} +//# sourceMappingURL=WeekInfoRenderer.js.map \ No newline at end of file diff --git a/wwwroot/js/renderers/WeekInfoRenderer.js.map b/wwwroot/js/renderers/WeekInfoRenderer.js.map new file mode 100644 index 0000000..d83cb61 --- /dev/null +++ b/wwwroot/js/renderers/WeekInfoRenderer.js.map @@ -0,0 +1 @@ +{"version":3,"file":"WeekInfoRenderer.js","sourceRoot":"","sources":["../../../src/renderers/WeekInfoRenderer.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/repositories/ApiEventRepository.d.ts b/wwwroot/js/repositories/ApiEventRepository.d.ts new file mode 100644 index 0000000..d7e087d --- /dev/null +++ b/wwwroot/js/repositories/ApiEventRepository.d.ts @@ -0,0 +1,39 @@ +import { ICalendarEvent } from '../types/CalendarTypes'; +import { Configuration } from '../configurations/CalendarConfig'; +/** + * ApiEventRepository + * Handles communication with backend API + * + * Used by SyncManager to send queued operations to the server + * NOT used directly by EventManager (which uses IndexedDBEventRepository) + * + * Future enhancements: + * - SignalR real-time updates + * - Conflict resolution + * - Batch operations + */ +export declare class ApiEventRepository { + private apiEndpoint; + constructor(config: Configuration); + /** + * Send create operation to API + */ + sendCreate(event: ICalendarEvent): Promise; + /** + * Send update operation to API + */ + sendUpdate(id: string, updates: Partial): Promise; + /** + * Send delete operation to API + */ + sendDelete(id: string): Promise; + /** + * Fetch all events from API + */ + fetchAll(): Promise; + /** + * Initialize SignalR connection + * Placeholder for future implementation + */ + initializeSignalR(): Promise; +} diff --git a/wwwroot/js/repositories/ApiEventRepository.js b/wwwroot/js/repositories/ApiEventRepository.js new file mode 100644 index 0000000..b732f80 --- /dev/null +++ b/wwwroot/js/repositories/ApiEventRepository.js @@ -0,0 +1,115 @@ +/** + * ApiEventRepository + * Handles communication with backend API + * + * Used by SyncManager to send queued operations to the server + * NOT used directly by EventManager (which uses IndexedDBEventRepository) + * + * Future enhancements: + * - SignalR real-time updates + * - Conflict resolution + * - Batch operations + */ +export class ApiEventRepository { + constructor(config) { + this.apiEndpoint = config.apiEndpoint; + } + /** + * Send create operation to API + */ + async sendCreate(event) { + // TODO: Implement API call + // const response = await fetch(`${this.apiEndpoint}/events`, { + // method: 'POST', + // headers: { 'Content-Type': 'application/json' }, + // body: JSON.stringify(event) + // }); + // + // if (!response.ok) { + // throw new Error(`API create failed: ${response.statusText}`); + // } + // + // return await response.json(); + throw new Error('ApiEventRepository.sendCreate not implemented yet'); + } + /** + * Send update operation to API + */ + async sendUpdate(id, updates) { + // TODO: Implement API call + // const response = await fetch(`${this.apiEndpoint}/events/${id}`, { + // method: 'PATCH', + // headers: { 'Content-Type': 'application/json' }, + // body: JSON.stringify(updates) + // }); + // + // if (!response.ok) { + // throw new Error(`API update failed: ${response.statusText}`); + // } + // + // return await response.json(); + throw new Error('ApiEventRepository.sendUpdate not implemented yet'); + } + /** + * Send delete operation to API + */ + async sendDelete(id) { + // TODO: Implement API call + // const response = await fetch(`${this.apiEndpoint}/events/${id}`, { + // method: 'DELETE' + // }); + // + // if (!response.ok) { + // throw new Error(`API delete failed: ${response.statusText}`); + // } + throw new Error('ApiEventRepository.sendDelete not implemented yet'); + } + /** + * Fetch all events from API + */ + async fetchAll() { + // TODO: Implement API call + // const response = await fetch(`${this.apiEndpoint}/events`); + // + // if (!response.ok) { + // throw new Error(`API fetch failed: ${response.statusText}`); + // } + // + // return await response.json(); + throw new Error('ApiEventRepository.fetchAll not implemented yet'); + } + // ======================================== + // Future: SignalR Integration + // ======================================== + /** + * Initialize SignalR connection + * Placeholder for future implementation + */ + async initializeSignalR() { + // TODO: Setup SignalR connection + // - Connect to hub + // - Register event handlers + // - Handle reconnection + // + // Example: + // const connection = new signalR.HubConnectionBuilder() + // .withUrl(`${this.apiEndpoint}/hubs/calendar`) + // .build(); + // + // connection.on('EventCreated', (event: ICalendarEvent) => { + // // Handle remote create + // }); + // + // connection.on('EventUpdated', (event: ICalendarEvent) => { + // // Handle remote update + // }); + // + // connection.on('EventDeleted', (eventId: string) => { + // // Handle remote delete + // }); + // + // await connection.start(); + throw new Error('SignalR not implemented yet'); + } +} +//# sourceMappingURL=ApiEventRepository.js.map \ No newline at end of file diff --git a/wwwroot/js/repositories/ApiEventRepository.js.map b/wwwroot/js/repositories/ApiEventRepository.js.map new file mode 100644 index 0000000..cf892a1 --- /dev/null +++ b/wwwroot/js/repositories/ApiEventRepository.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ApiEventRepository.js","sourceRoot":"","sources":["../../../src/repositories/ApiEventRepository.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,kBAAkB;IAG7B,YAAY,MAAqB;QAC/B,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;IACxC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU,CAAC,KAAqB;QACpC,2BAA2B;QAC3B,+DAA+D;QAC/D,oBAAoB;QACpB,qDAAqD;QACrD,gCAAgC;QAChC,MAAM;QACN,EAAE;QACF,sBAAsB;QACtB,kEAAkE;QAClE,IAAI;QACJ,EAAE;QACF,gCAAgC;QAEhC,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IACvE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU,CAAC,EAAU,EAAE,OAAgC;QAC3D,2BAA2B;QAC3B,qEAAqE;QACrE,qBAAqB;QACrB,qDAAqD;QACrD,kCAAkC;QAClC,MAAM;QACN,EAAE;QACF,sBAAsB;QACtB,kEAAkE;QAClE,IAAI;QACJ,EAAE;QACF,gCAAgC;QAEhC,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IACvE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU,CAAC,EAAU;QACzB,2BAA2B;QAC3B,qEAAqE;QACrE,qBAAqB;QACrB,MAAM;QACN,EAAE;QACF,sBAAsB;QACtB,kEAAkE;QAClE,IAAI;QAEJ,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IACvE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,QAAQ;QACZ,2BAA2B;QAC3B,8DAA8D;QAC9D,EAAE;QACF,sBAAsB;QACtB,iEAAiE;QACjE,IAAI;QACJ,EAAE;QACF,gCAAgC;QAEhC,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IACrE,CAAC;IAED,2CAA2C;IAC3C,8BAA8B;IAC9B,2CAA2C;IAE3C;;;OAGG;IACH,KAAK,CAAC,iBAAiB;QACrB,iCAAiC;QACjC,mBAAmB;QACnB,4BAA4B;QAC5B,wBAAwB;QACxB,EAAE;QACF,WAAW;QACX,wDAAwD;QACxD,kDAAkD;QAClD,cAAc;QACd,EAAE;QACF,6DAA6D;QAC7D,4BAA4B;QAC5B,MAAM;QACN,EAAE;QACF,6DAA6D;QAC7D,4BAA4B;QAC5B,MAAM;QACN,EAAE;QACF,uDAAuD;QACvD,4BAA4B;QAC5B,MAAM;QACN,EAAE;QACF,4BAA4B;QAE5B,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACjD,CAAC;CACF"} \ No newline at end of file diff --git a/wwwroot/js/repositories/IEventRepository.d.ts b/wwwroot/js/repositories/IEventRepository.d.ts new file mode 100644 index 0000000..2bd6a5e --- /dev/null +++ b/wwwroot/js/repositories/IEventRepository.d.ts @@ -0,0 +1,51 @@ +import { ICalendarEvent } from '../types/CalendarTypes'; +/** + * Update source type + * - 'local': Changes made by the user locally (needs sync) + * - 'remote': Changes from API/SignalR (already synced) + */ +export type UpdateSource = 'local' | 'remote'; +/** + * IEventRepository - Interface for event data access + * + * Abstracts the data source for calendar events, allowing easy switching + * between IndexedDB, REST API, GraphQL, or other data sources. + * + * Implementations: + * - IndexedDBEventRepository: Local storage with offline support + * - MockEventRepository: (Legacy) Loads from local JSON file + * - ApiEventRepository: (Future) Loads from backend API + */ +export interface IEventRepository { + /** + * Load all calendar events from the data source + * @returns Promise resolving to array of ICalendarEvent objects + * @throws Error if loading fails + */ + loadEvents(): Promise; + /** + * Create a new event + * @param event - Event to create (without ID, will be generated) + * @param source - Source of the update ('local' or 'remote') + * @returns Promise resolving to the created event with generated ID + * @throws Error if creation fails + */ + createEvent(event: Omit, source?: UpdateSource): Promise; + /** + * Update an existing event + * @param id - ID of the event to update + * @param updates - Partial event data to update + * @param source - Source of the update ('local' or 'remote') + * @returns Promise resolving to the updated event + * @throws Error if update fails or event not found + */ + updateEvent(id: string, updates: Partial, source?: UpdateSource): Promise; + /** + * Delete an event + * @param id - ID of the event to delete + * @param source - Source of the update ('local' or 'remote') + * @returns Promise resolving when deletion is complete + * @throws Error if deletion fails or event not found + */ + deleteEvent(id: string, source?: UpdateSource): Promise; +} diff --git a/wwwroot/js/repositories/IEventRepository.js b/wwwroot/js/repositories/IEventRepository.js new file mode 100644 index 0000000..fd60757 --- /dev/null +++ b/wwwroot/js/repositories/IEventRepository.js @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=IEventRepository.js.map \ No newline at end of file diff --git a/wwwroot/js/repositories/IEventRepository.js.map b/wwwroot/js/repositories/IEventRepository.js.map new file mode 100644 index 0000000..fc02973 --- /dev/null +++ b/wwwroot/js/repositories/IEventRepository.js.map @@ -0,0 +1 @@ +{"version":3,"file":"IEventRepository.js","sourceRoot":"","sources":["../../../src/repositories/IEventRepository.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/wwwroot/js/repositories/IndexedDBEventRepository.d.ts b/wwwroot/js/repositories/IndexedDBEventRepository.d.ts new file mode 100644 index 0000000..575264a --- /dev/null +++ b/wwwroot/js/repositories/IndexedDBEventRepository.d.ts @@ -0,0 +1,47 @@ +import { ICalendarEvent } from '../types/CalendarTypes'; +import { IEventRepository, UpdateSource } from './IEventRepository'; +import { IndexedDBService } from '../storage/IndexedDBService'; +import { OperationQueue } from '../storage/OperationQueue'; +/** + * IndexedDBEventRepository + * Offline-first repository using IndexedDB as single source of truth + * + * All CRUD operations: + * - Save to IndexedDB immediately (always succeeds) + * - Add to sync queue if source is 'local' + * - Background SyncManager processes queue to sync with API + */ +export declare class IndexedDBEventRepository implements IEventRepository { + private indexedDB; + private queue; + constructor(indexedDB: IndexedDBService, queue: OperationQueue); + /** + * Load all events from IndexedDB + * Ensures IndexedDB is initialized and seeded on first call + */ + loadEvents(): Promise; + /** + * Create a new event + * - Generates ID + * - Saves to IndexedDB + * - Adds to queue if local (needs sync) + */ + createEvent(event: Omit, source?: UpdateSource): Promise; + /** + * Update an existing event + * - Updates in IndexedDB + * - Adds to queue if local (needs sync) + */ + updateEvent(id: string, updates: Partial, source?: UpdateSource): Promise; + /** + * Delete an event + * - Removes from IndexedDB + * - Adds to queue if local (needs sync) + */ + deleteEvent(id: string, source?: UpdateSource): Promise; + /** + * Generate unique event ID + * Format: {timestamp}-{random} + */ + private generateEventId; +} diff --git a/wwwroot/js/repositories/IndexedDBEventRepository.js b/wwwroot/js/repositories/IndexedDBEventRepository.js new file mode 100644 index 0000000..c09245e --- /dev/null +++ b/wwwroot/js/repositories/IndexedDBEventRepository.js @@ -0,0 +1,127 @@ +/** + * IndexedDBEventRepository + * Offline-first repository using IndexedDB as single source of truth + * + * All CRUD operations: + * - Save to IndexedDB immediately (always succeeds) + * - Add to sync queue if source is 'local' + * - Background SyncManager processes queue to sync with API + */ +export class IndexedDBEventRepository { + constructor(indexedDB, queue) { + this.indexedDB = indexedDB; + this.queue = queue; + } + /** + * Load all events from IndexedDB + * Ensures IndexedDB is initialized and seeded on first call + */ + async loadEvents() { + // Lazy initialization on first data load + if (!this.indexedDB.isInitialized()) { + await this.indexedDB.initialize(); + await this.indexedDB.seedIfEmpty(); + } + return await this.indexedDB.getAllEvents(); + } + /** + * Create a new event + * - Generates ID + * - Saves to IndexedDB + * - Adds to queue if local (needs sync) + */ + async createEvent(event, source = 'local') { + // Generate unique ID + const id = this.generateEventId(); + // Determine sync status based on source + const syncStatus = source === 'local' ? 'pending' : 'synced'; + // Create full event object + const newEvent = { + ...event, + id, + syncStatus + }; + // Save to IndexedDB + await this.indexedDB.saveEvent(newEvent); + // If local change, add to sync queue + if (source === 'local') { + await this.queue.enqueue({ + type: 'create', + eventId: id, + data: newEvent, + timestamp: Date.now(), + retryCount: 0 + }); + } + return newEvent; + } + /** + * Update an existing event + * - Updates in IndexedDB + * - Adds to queue if local (needs sync) + */ + async updateEvent(id, updates, source = 'local') { + // Get existing event + const existingEvent = await this.indexedDB.getEvent(id); + if (!existingEvent) { + throw new Error(`Event with ID ${id} not found`); + } + // Determine sync status based on source + const syncStatus = source === 'local' ? 'pending' : 'synced'; + // Merge updates + const updatedEvent = { + ...existingEvent, + ...updates, + id, // Ensure ID doesn't change + syncStatus + }; + // Save to IndexedDB + await this.indexedDB.saveEvent(updatedEvent); + // If local change, add to sync queue + if (source === 'local') { + await this.queue.enqueue({ + type: 'update', + eventId: id, + data: updates, + timestamp: Date.now(), + retryCount: 0 + }); + } + return updatedEvent; + } + /** + * Delete an event + * - Removes from IndexedDB + * - Adds to queue if local (needs sync) + */ + async deleteEvent(id, source = 'local') { + // Check if event exists + const existingEvent = await this.indexedDB.getEvent(id); + if (!existingEvent) { + throw new Error(`Event with ID ${id} not found`); + } + // If local change, add to sync queue BEFORE deleting + // (so we can send the delete operation to API later) + if (source === 'local') { + await this.queue.enqueue({ + type: 'delete', + eventId: id, + data: {}, // No data needed for delete + timestamp: Date.now(), + retryCount: 0 + }); + } + // Delete from IndexedDB + await this.indexedDB.deleteEvent(id); + } + /** + * Generate unique event ID + * Format: {timestamp}-{random} + */ + generateEventId() { + const timestamp = Date.now(); + const random = Math.random().toString(36).substring(2, 9); + return `${timestamp}-${random}`; + } +} +//# sourceMappingURL=IndexedDBEventRepository.js.map \ No newline at end of file diff --git a/wwwroot/js/repositories/IndexedDBEventRepository.js.map b/wwwroot/js/repositories/IndexedDBEventRepository.js.map new file mode 100644 index 0000000..82835e7 --- /dev/null +++ b/wwwroot/js/repositories/IndexedDBEventRepository.js.map @@ -0,0 +1 @@ +{"version":3,"file":"IndexedDBEventRepository.js","sourceRoot":"","sources":["../../../src/repositories/IndexedDBEventRepository.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/repositories/MockEventRepository.d.ts b/wwwroot/js/repositories/MockEventRepository.d.ts new file mode 100644 index 0000000..3e3d5cd --- /dev/null +++ b/wwwroot/js/repositories/MockEventRepository.d.ts @@ -0,0 +1,33 @@ +import { ICalendarEvent } from '../types/CalendarTypes'; +import { IEventRepository, UpdateSource } from './IEventRepository'; +/** + * MockEventRepository - Loads event data from local JSON file (LEGACY) + * + * This repository implementation fetches mock event data from a static JSON file. + * DEPRECATED: Use IndexedDBEventRepository for offline-first functionality. + * + * Data Source: data/mock-events.json + * + * NOTE: Create/Update/Delete operations are not supported - throws errors. + * This is intentional to encourage migration to IndexedDBEventRepository. + */ +export declare class MockEventRepository implements IEventRepository { + private readonly dataUrl; + loadEvents(): Promise; + /** + * NOT SUPPORTED - MockEventRepository is read-only + * Use IndexedDBEventRepository instead + */ + createEvent(event: Omit, source?: UpdateSource): Promise; + /** + * NOT SUPPORTED - MockEventRepository is read-only + * Use IndexedDBEventRepository instead + */ + updateEvent(id: string, updates: Partial, source?: UpdateSource): Promise; + /** + * NOT SUPPORTED - MockEventRepository is read-only + * Use IndexedDBEventRepository instead + */ + deleteEvent(id: string, source?: UpdateSource): Promise; + private processCalendarData; +} diff --git a/wwwroot/js/repositories/MockEventRepository.js b/wwwroot/js/repositories/MockEventRepository.js new file mode 100644 index 0000000..e43f8cb --- /dev/null +++ b/wwwroot/js/repositories/MockEventRepository.js @@ -0,0 +1,62 @@ +/** + * MockEventRepository - Loads event data from local JSON file (LEGACY) + * + * This repository implementation fetches mock event data from a static JSON file. + * DEPRECATED: Use IndexedDBEventRepository for offline-first functionality. + * + * Data Source: data/mock-events.json + * + * NOTE: Create/Update/Delete operations are not supported - throws errors. + * This is intentional to encourage migration to IndexedDBEventRepository. + */ +export class MockEventRepository { + constructor() { + this.dataUrl = 'data/mock-events.json'; + } + async loadEvents() { + try { + const response = await fetch(this.dataUrl); + if (!response.ok) { + throw new Error(`Failed to load mock events: ${response.status} ${response.statusText}`); + } + const rawData = await response.json(); + return this.processCalendarData(rawData); + } + catch (error) { + console.error('Failed to load event data:', error); + throw error; + } + } + /** + * NOT SUPPORTED - MockEventRepository is read-only + * Use IndexedDBEventRepository instead + */ + async createEvent(event, source) { + throw new Error('MockEventRepository does not support createEvent. Use IndexedDBEventRepository instead.'); + } + /** + * NOT SUPPORTED - MockEventRepository is read-only + * Use IndexedDBEventRepository instead + */ + async updateEvent(id, updates, source) { + throw new Error('MockEventRepository does not support updateEvent. Use IndexedDBEventRepository instead.'); + } + /** + * NOT SUPPORTED - MockEventRepository is read-only + * Use IndexedDBEventRepository instead + */ + async deleteEvent(id, source) { + throw new Error('MockEventRepository does not support deleteEvent. Use IndexedDBEventRepository instead.'); + } + processCalendarData(data) { + return data.map((event) => ({ + ...event, + start: new Date(event.start), + end: new Date(event.end), + type: event.type, + allDay: event.allDay || false, + syncStatus: 'synced' + })); + } +} +//# sourceMappingURL=MockEventRepository.js.map \ No newline at end of file diff --git a/wwwroot/js/repositories/MockEventRepository.js.map b/wwwroot/js/repositories/MockEventRepository.js.map new file mode 100644 index 0000000..f2909a6 --- /dev/null +++ b/wwwroot/js/repositories/MockEventRepository.js.map @@ -0,0 +1 @@ +{"version":3,"file":"MockEventRepository.js","sourceRoot":"","sources":["../../../src/repositories/MockEventRepository.ts"],"names":[],"mappings":"AAcA;;;;;;;;;;GAUG;AACH,MAAM,OAAO,mBAAmB;IAAhC;QACmB,YAAO,GAAG,uBAAuB,CAAC;IAqDrD,CAAC;IAnDQ,KAAK,CAAC,UAAU;QACrB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAE3C,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CAAC,+BAA+B,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;YAC3F,CAAC;YAED,MAAM,OAAO,GAAmB,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YAEtD,OAAO,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC;QAC3C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC,CAAC;YACnD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,WAAW,CAAC,KAAiC,EAAE,MAAqB;QAC/E,MAAM,IAAI,KAAK,CAAC,yFAAyF,CAAC,CAAC;IAC7G,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,WAAW,CAAC,EAAU,EAAE,OAAgC,EAAE,MAAqB;QAC1F,MAAM,IAAI,KAAK,CAAC,yFAAyF,CAAC,CAAC;IAC7G,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,WAAW,CAAC,EAAU,EAAE,MAAqB;QACxD,MAAM,IAAI,KAAK,CAAC,yFAAyF,CAAC,CAAC;IAC7G,CAAC;IAEO,mBAAmB,CAAC,IAAoB;QAC9C,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAkB,EAAE,CAAC,CAAC;YAC1C,GAAG,KAAK;YACR,KAAK,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;YAC5B,GAAG,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC;YACxB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,KAAK;YAC7B,UAAU,EAAE,QAAiB;SAC9B,CAAC,CAAC,CAAC;IACN,CAAC;CACF"} \ No newline at end of file diff --git a/wwwroot/js/storage/IndexedDBService.d.ts b/wwwroot/js/storage/IndexedDBService.d.ts new file mode 100644 index 0000000..d40c72a --- /dev/null +++ b/wwwroot/js/storage/IndexedDBService.d.ts @@ -0,0 +1,97 @@ +import { ICalendarEvent } from '../types/CalendarTypes'; +/** + * Operation for the sync queue + */ +export interface IQueueOperation { + id: string; + type: 'create' | 'update' | 'delete'; + eventId: string; + data: Partial | ICalendarEvent; + timestamp: number; + retryCount: number; +} +/** + * IndexedDB Service for Calendar App + * Handles local storage of events and sync queue + */ +export declare class IndexedDBService { + private static readonly DB_NAME; + private static readonly DB_VERSION; + private static readonly EVENTS_STORE; + private static readonly QUEUE_STORE; + private static readonly SYNC_STATE_STORE; + private db; + private initialized; + /** + * Initialize and open the database + */ + initialize(): Promise; + /** + * Check if database is initialized + */ + isInitialized(): boolean; + /** + * Ensure database is initialized + */ + private ensureDB; + /** + * Get a single event by ID + */ + getEvent(id: string): Promise; + /** + * Get all events + */ + getAllEvents(): Promise; + /** + * Save an event (create or update) + */ + saveEvent(event: ICalendarEvent): Promise; + /** + * Delete an event + */ + deleteEvent(id: string): Promise; + /** + * Add operation to queue + */ + addToQueue(operation: Omit): Promise; + /** + * Get all queue operations (sorted by timestamp) + */ + getQueue(): Promise; + /** + * Remove operation from queue + */ + removeFromQueue(id: string): Promise; + /** + * Clear entire queue + */ + clearQueue(): Promise; + /** + * Save sync state value + */ + setSyncState(key: string, value: any): Promise; + /** + * Get sync state value + */ + getSyncState(key: string): Promise; + /** + * Serialize event for IndexedDB storage (convert Dates to ISO strings) + */ + private serializeEvent; + /** + * Deserialize event from IndexedDB (convert ISO strings to Dates) + */ + private deserializeEvent; + /** + * Close database connection + */ + close(): void; + /** + * Delete entire database (for testing/reset) + */ + static deleteDatabase(): Promise; + /** + * Seed IndexedDB with mock data if empty + */ + seedIfEmpty(mockDataUrl?: string): Promise; +} diff --git a/wwwroot/js/storage/IndexedDBService.js b/wwwroot/js/storage/IndexedDBService.js new file mode 100644 index 0000000..0f07270 --- /dev/null +++ b/wwwroot/js/storage/IndexedDBService.js @@ -0,0 +1,340 @@ +/** + * IndexedDB Service for Calendar App + * Handles local storage of events and sync queue + */ +export class IndexedDBService { + constructor() { + this.db = null; + this.initialized = false; + } + /** + * Initialize and open the database + */ + async initialize() { + return new Promise((resolve, reject) => { + const request = indexedDB.open(IndexedDBService.DB_NAME, IndexedDBService.DB_VERSION); + request.onerror = () => { + reject(new Error(`Failed to open IndexedDB: ${request.error}`)); + }; + request.onsuccess = () => { + this.db = request.result; + this.initialized = true; + resolve(); + }; + request.onupgradeneeded = (event) => { + const db = event.target.result; + // Create events store + if (!db.objectStoreNames.contains(IndexedDBService.EVENTS_STORE)) { + const eventsStore = db.createObjectStore(IndexedDBService.EVENTS_STORE, { keyPath: 'id' }); + eventsStore.createIndex('start', 'start', { unique: false }); + eventsStore.createIndex('end', 'end', { unique: false }); + eventsStore.createIndex('syncStatus', 'syncStatus', { unique: false }); + } + // Create operation queue store + if (!db.objectStoreNames.contains(IndexedDBService.QUEUE_STORE)) { + const queueStore = db.createObjectStore(IndexedDBService.QUEUE_STORE, { keyPath: 'id' }); + queueStore.createIndex('timestamp', 'timestamp', { unique: false }); + } + // Create sync state store + if (!db.objectStoreNames.contains(IndexedDBService.SYNC_STATE_STORE)) { + db.createObjectStore(IndexedDBService.SYNC_STATE_STORE, { keyPath: 'key' }); + } + }; + }); + } + /** + * Check if database is initialized + */ + isInitialized() { + return this.initialized; + } + /** + * Ensure database is initialized + */ + ensureDB() { + if (!this.db) { + throw new Error('IndexedDB not initialized. Call initialize() first.'); + } + return this.db; + } + // ======================================== + // Event CRUD Operations + // ======================================== + /** + * Get a single event by ID + */ + async getEvent(id) { + const db = this.ensureDB(); + return new Promise((resolve, reject) => { + const transaction = db.transaction([IndexedDBService.EVENTS_STORE], 'readonly'); + const store = transaction.objectStore(IndexedDBService.EVENTS_STORE); + const request = store.get(id); + request.onsuccess = () => { + const event = request.result; + resolve(event ? this.deserializeEvent(event) : null); + }; + request.onerror = () => { + reject(new Error(`Failed to get event ${id}: ${request.error}`)); + }; + }); + } + /** + * Get all events + */ + async getAllEvents() { + const db = this.ensureDB(); + return new Promise((resolve, reject) => { + const transaction = db.transaction([IndexedDBService.EVENTS_STORE], 'readonly'); + const store = transaction.objectStore(IndexedDBService.EVENTS_STORE); + const request = store.getAll(); + request.onsuccess = () => { + const events = request.result; + resolve(events.map(e => this.deserializeEvent(e))); + }; + request.onerror = () => { + reject(new Error(`Failed to get all events: ${request.error}`)); + }; + }); + } + /** + * Save an event (create or update) + */ + async saveEvent(event) { + const db = this.ensureDB(); + const serialized = this.serializeEvent(event); + return new Promise((resolve, reject) => { + const transaction = db.transaction([IndexedDBService.EVENTS_STORE], 'readwrite'); + const store = transaction.objectStore(IndexedDBService.EVENTS_STORE); + const request = store.put(serialized); + request.onsuccess = () => { + resolve(); + }; + request.onerror = () => { + reject(new Error(`Failed to save event ${event.id}: ${request.error}`)); + }; + }); + } + /** + * Delete an event + */ + async deleteEvent(id) { + const db = this.ensureDB(); + return new Promise((resolve, reject) => { + const transaction = db.transaction([IndexedDBService.EVENTS_STORE], 'readwrite'); + const store = transaction.objectStore(IndexedDBService.EVENTS_STORE); + const request = store.delete(id); + request.onsuccess = () => { + resolve(); + }; + request.onerror = () => { + reject(new Error(`Failed to delete event ${id}: ${request.error}`)); + }; + }); + } + // ======================================== + // Queue Operations + // ======================================== + /** + * Add operation to queue + */ + async addToQueue(operation) { + const db = this.ensureDB(); + const queueItem = { + ...operation, + id: `${operation.type}-${operation.eventId}-${Date.now()}` + }; + return new Promise((resolve, reject) => { + const transaction = db.transaction([IndexedDBService.QUEUE_STORE], 'readwrite'); + const store = transaction.objectStore(IndexedDBService.QUEUE_STORE); + const request = store.put(queueItem); + request.onsuccess = () => { + resolve(); + }; + request.onerror = () => { + reject(new Error(`Failed to add to queue: ${request.error}`)); + }; + }); + } + /** + * Get all queue operations (sorted by timestamp) + */ + async getQueue() { + const db = this.ensureDB(); + return new Promise((resolve, reject) => { + const transaction = db.transaction([IndexedDBService.QUEUE_STORE], 'readonly'); + const store = transaction.objectStore(IndexedDBService.QUEUE_STORE); + const index = store.index('timestamp'); + const request = index.getAll(); + request.onsuccess = () => { + resolve(request.result); + }; + request.onerror = () => { + reject(new Error(`Failed to get queue: ${request.error}`)); + }; + }); + } + /** + * Remove operation from queue + */ + async removeFromQueue(id) { + const db = this.ensureDB(); + return new Promise((resolve, reject) => { + const transaction = db.transaction([IndexedDBService.QUEUE_STORE], 'readwrite'); + const store = transaction.objectStore(IndexedDBService.QUEUE_STORE); + const request = store.delete(id); + request.onsuccess = () => { + resolve(); + }; + request.onerror = () => { + reject(new Error(`Failed to remove from queue: ${request.error}`)); + }; + }); + } + /** + * Clear entire queue + */ + async clearQueue() { + const db = this.ensureDB(); + return new Promise((resolve, reject) => { + const transaction = db.transaction([IndexedDBService.QUEUE_STORE], 'readwrite'); + const store = transaction.objectStore(IndexedDBService.QUEUE_STORE); + const request = store.clear(); + request.onsuccess = () => { + resolve(); + }; + request.onerror = () => { + reject(new Error(`Failed to clear queue: ${request.error}`)); + }; + }); + } + // ======================================== + // Sync State Operations + // ======================================== + /** + * Save sync state value + */ + async setSyncState(key, value) { + const db = this.ensureDB(); + return new Promise((resolve, reject) => { + const transaction = db.transaction([IndexedDBService.SYNC_STATE_STORE], 'readwrite'); + const store = transaction.objectStore(IndexedDBService.SYNC_STATE_STORE); + const request = store.put({ key, value }); + request.onsuccess = () => { + resolve(); + }; + request.onerror = () => { + reject(new Error(`Failed to set sync state ${key}: ${request.error}`)); + }; + }); + } + /** + * Get sync state value + */ + async getSyncState(key) { + const db = this.ensureDB(); + return new Promise((resolve, reject) => { + const transaction = db.transaction([IndexedDBService.SYNC_STATE_STORE], 'readonly'); + const store = transaction.objectStore(IndexedDBService.SYNC_STATE_STORE); + const request = store.get(key); + request.onsuccess = () => { + const result = request.result; + resolve(result ? result.value : null); + }; + request.onerror = () => { + reject(new Error(`Failed to get sync state ${key}: ${request.error}`)); + }; + }); + } + // ======================================== + // Serialization Helpers + // ======================================== + /** + * Serialize event for IndexedDB storage (convert Dates to ISO strings) + */ + serializeEvent(event) { + return { + ...event, + start: event.start instanceof Date ? event.start.toISOString() : event.start, + end: event.end instanceof Date ? event.end.toISOString() : event.end + }; + } + /** + * Deserialize event from IndexedDB (convert ISO strings to Dates) + */ + deserializeEvent(event) { + return { + ...event, + start: typeof event.start === 'string' ? new Date(event.start) : event.start, + end: typeof event.end === 'string' ? new Date(event.end) : event.end + }; + } + /** + * Close database connection + */ + close() { + if (this.db) { + this.db.close(); + this.db = null; + } + } + /** + * Delete entire database (for testing/reset) + */ + static async deleteDatabase() { + return new Promise((resolve, reject) => { + const request = indexedDB.deleteDatabase(IndexedDBService.DB_NAME); + request.onsuccess = () => { + resolve(); + }; + request.onerror = () => { + reject(new Error(`Failed to delete database: ${request.error}`)); + }; + }); + } + /** + * Seed IndexedDB with mock data if empty + */ + async seedIfEmpty(mockDataUrl = 'data/mock-events.json') { + try { + const existingEvents = await this.getAllEvents(); + if (existingEvents.length > 0) { + console.log(`IndexedDB already has ${existingEvents.length} events - skipping seed`); + return; + } + console.log('IndexedDB is empty - seeding with mock data'); + // Check if online to fetch mock data + if (!navigator.onLine) { + console.warn('Offline and IndexedDB empty - starting with no events'); + return; + } + // Fetch mock events + const response = await fetch(mockDataUrl); + if (!response.ok) { + throw new Error(`Failed to fetch mock events: ${response.statusText}`); + } + const mockEvents = await response.json(); + // Convert and save to IndexedDB + for (const event of mockEvents) { + const calendarEvent = { + ...event, + start: new Date(event.start), + end: new Date(event.end), + allDay: event.allDay || false, + syncStatus: 'synced' + }; + await this.saveEvent(calendarEvent); + } + console.log(`Seeded IndexedDB with ${mockEvents.length} mock events`); + } + catch (error) { + console.error('Failed to seed IndexedDB:', error); + // Don't throw - allow app to start with empty calendar + } + } +} +IndexedDBService.DB_NAME = 'CalendarDB'; +IndexedDBService.DB_VERSION = 1; +IndexedDBService.EVENTS_STORE = 'events'; +IndexedDBService.QUEUE_STORE = 'operationQueue'; +IndexedDBService.SYNC_STATE_STORE = 'syncState'; +//# sourceMappingURL=IndexedDBService.js.map \ No newline at end of file diff --git a/wwwroot/js/storage/IndexedDBService.js.map b/wwwroot/js/storage/IndexedDBService.js.map new file mode 100644 index 0000000..488a2dd --- /dev/null +++ b/wwwroot/js/storage/IndexedDBService.js.map @@ -0,0 +1 @@ +{"version":3,"file":"IndexedDBService.js","sourceRoot":"","sources":["../../../src/storage/IndexedDBService.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/storage/OperationQueue.d.ts b/wwwroot/js/storage/OperationQueue.d.ts new file mode 100644 index 0000000..50018e6 --- /dev/null +++ b/wwwroot/js/storage/OperationQueue.d.ts @@ -0,0 +1,55 @@ +import { IndexedDBService, IQueueOperation } from './IndexedDBService'; +/** + * Operation Queue Manager + * Handles FIFO queue of pending sync operations + */ +export declare class OperationQueue { + private indexedDB; + constructor(indexedDB: IndexedDBService); + /** + * Add operation to the end of the queue + */ + enqueue(operation: Omit): Promise; + /** + * Get the first operation from the queue (without removing it) + * Returns null if queue is empty + */ + peek(): Promise; + /** + * Get all operations in the queue (sorted by timestamp FIFO) + */ + getAll(): Promise; + /** + * Remove a specific operation from the queue + */ + remove(operationId: string): Promise; + /** + * Remove the first operation from the queue and return it + * Returns null if queue is empty + */ + dequeue(): Promise; + /** + * Clear all operations from the queue + */ + clear(): Promise; + /** + * Get the number of operations in the queue + */ + size(): Promise; + /** + * Check if queue is empty + */ + isEmpty(): Promise; + /** + * Get operations for a specific event ID + */ + getOperationsForEvent(eventId: string): Promise; + /** + * Remove all operations for a specific event ID + */ + removeOperationsForEvent(eventId: string): Promise; + /** + * Update retry count for an operation + */ + incrementRetryCount(operationId: string): Promise; +} diff --git a/wwwroot/js/storage/OperationQueue.js b/wwwroot/js/storage/OperationQueue.js new file mode 100644 index 0000000..eb1b740 --- /dev/null +++ b/wwwroot/js/storage/OperationQueue.js @@ -0,0 +1,96 @@ +/** + * Operation Queue Manager + * Handles FIFO queue of pending sync operations + */ +export class OperationQueue { + constructor(indexedDB) { + this.indexedDB = indexedDB; + } + /** + * Add operation to the end of the queue + */ + async enqueue(operation) { + await this.indexedDB.addToQueue(operation); + } + /** + * Get the first operation from the queue (without removing it) + * Returns null if queue is empty + */ + async peek() { + const queue = await this.indexedDB.getQueue(); + return queue.length > 0 ? queue[0] : null; + } + /** + * Get all operations in the queue (sorted by timestamp FIFO) + */ + async getAll() { + return await this.indexedDB.getQueue(); + } + /** + * Remove a specific operation from the queue + */ + async remove(operationId) { + await this.indexedDB.removeFromQueue(operationId); + } + /** + * Remove the first operation from the queue and return it + * Returns null if queue is empty + */ + async dequeue() { + const operation = await this.peek(); + if (operation) { + await this.remove(operation.id); + } + return operation; + } + /** + * Clear all operations from the queue + */ + async clear() { + await this.indexedDB.clearQueue(); + } + /** + * Get the number of operations in the queue + */ + async size() { + const queue = await this.getAll(); + return queue.length; + } + /** + * Check if queue is empty + */ + async isEmpty() { + const size = await this.size(); + return size === 0; + } + /** + * Get operations for a specific event ID + */ + async getOperationsForEvent(eventId) { + const queue = await this.getAll(); + return queue.filter(op => op.eventId === eventId); + } + /** + * Remove all operations for a specific event ID + */ + async removeOperationsForEvent(eventId) { + const operations = await this.getOperationsForEvent(eventId); + for (const op of operations) { + await this.remove(op.id); + } + } + /** + * Update retry count for an operation + */ + async incrementRetryCount(operationId) { + const queue = await this.getAll(); + const operation = queue.find(op => op.id === operationId); + if (operation) { + operation.retryCount++; + // Re-add to queue with updated retry count + await this.remove(operationId); + await this.enqueue(operation); + } + } +} +//# sourceMappingURL=OperationQueue.js.map \ No newline at end of file diff --git a/wwwroot/js/storage/OperationQueue.js.map b/wwwroot/js/storage/OperationQueue.js.map new file mode 100644 index 0000000..572a8ac --- /dev/null +++ b/wwwroot/js/storage/OperationQueue.js.map @@ -0,0 +1 @@ +{"version":3,"file":"OperationQueue.js","sourceRoot":"","sources":["../../../src/storage/OperationQueue.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,OAAO,cAAc;IAGzB,YAAY,SAA2B;QACrC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO,CAAC,SAAsC;QAClD,MAAM,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,IAAI;QACR,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC;QAC9C,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,MAAM;QACV,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC;IACzC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,MAAM,CAAC,WAAmB;QAC9B,MAAM,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;IACpD,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,OAAO;QACX,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QACpC,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QAClC,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,CAAC;IACpC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,IAAI;QACR,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QAClC,OAAO,KAAK,CAAC,MAAM,CAAC;IACtB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO;QACX,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,OAAO,IAAI,KAAK,CAAC,CAAC;IACpB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,qBAAqB,CAAC,OAAe;QACzC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QAClC,OAAO,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC;IACpD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,wBAAwB,CAAC,OAAe;QAC5C,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC;QAC7D,KAAK,MAAM,EAAE,IAAI,UAAU,EAAE,CAAC;YAC5B,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,mBAAmB,CAAC,WAAmB;QAC3C,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QAClC,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,WAAW,CAAC,CAAC;QAE1D,IAAI,SAAS,EAAE,CAAC;YACd,SAAS,CAAC,UAAU,EAAE,CAAC;YACvB,2CAA2C;YAC3C,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;YAC/B,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;CACF"} \ No newline at end of file diff --git a/wwwroot/js/strategies/MonthViewStrategy.d.ts b/wwwroot/js/strategies/MonthViewStrategy.d.ts new file mode 100644 index 0000000..3e782fb --- /dev/null +++ b/wwwroot/js/strategies/MonthViewStrategy.d.ts @@ -0,0 +1,25 @@ +/** + * MonthViewStrategy - Strategy for month view rendering + * Completely different from week view - no time axis, cell-based events + */ +import { ViewStrategy, ViewContext, ViewLayoutConfig } from './ViewStrategy'; +export declare class MonthViewStrategy implements ViewStrategy { + private dateCalculator; + constructor(); + getLayoutConfig(): ViewLayoutConfig; + renderGrid(context: ViewContext): void; + private createMonthGrid; + private createDayHeaders; + private createDayCells; + private getMonthDates; + private renderMonthEvents; + getNextPeriod(currentDate: Date): Date; + getPreviousPeriod(currentDate: Date): Date; + getPeriodLabel(date: Date): string; + getDisplayDates(baseDate: Date): Date[]; + getPeriodRange(baseDate: Date): { + startDate: Date; + endDate: Date; + }; + destroy(): void; +} diff --git a/wwwroot/js/strategies/MonthViewStrategy.js b/wwwroot/js/strategies/MonthViewStrategy.js new file mode 100644 index 0000000..670878d --- /dev/null +++ b/wwwroot/js/strategies/MonthViewStrategy.js @@ -0,0 +1,124 @@ +/** + * MonthViewStrategy - Strategy for month view rendering + * Completely different from week view - no time axis, cell-based events + */ +import { DateCalculator } from '../utils/DateCalculator'; +import { calendarConfig } from '../core/CalendarConfig'; +export class MonthViewStrategy { + constructor() { + DateCalculator.initialize(calendarConfig); + this.dateCalculator = new DateCalculator(); + } + getLayoutConfig() { + return { + needsTimeAxis: false, // No time axis in month view! + columnCount: 7, // Always 7 days (Mon-Sun) + scrollable: false, // Month fits in viewport + eventPositioning: 'cell-based' // Events go in day cells + }; + } + renderGrid(context) { + // Clear existing content + context.container.innerHTML = ''; + // Create month grid (completely different from week!) + this.createMonthGrid(context); + } + createMonthGrid(context) { + const monthGrid = document.createElement('div'); + monthGrid.className = 'month-grid'; + monthGrid.style.display = 'grid'; + monthGrid.style.gridTemplateColumns = 'repeat(7, 1fr)'; + monthGrid.style.gridTemplateRows = 'auto repeat(6, 1fr)'; + monthGrid.style.height = '100%'; + // Add day headers (Mon, Tue, Wed, etc.) + this.createDayHeaders(monthGrid); + // Add 6 weeks of day cells + this.createDayCells(monthGrid, context.currentDate); + // Render events in day cells (will be handled by EventRendererManager) + // this.renderMonthEvents(monthGrid, context.allDayEvents); + context.container.appendChild(monthGrid); + } + createDayHeaders(container) { + const dayNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + dayNames.forEach(dayName => { + const header = document.createElement('div'); + header.className = 'month-day-header'; + header.textContent = dayName; + header.style.padding = '8px'; + header.style.fontWeight = 'bold'; + header.style.textAlign = 'center'; + header.style.borderBottom = '1px solid #e0e0e0'; + container.appendChild(header); + }); + } + createDayCells(container, monthDate) { + const dates = this.getMonthDates(monthDate); + dates.forEach(date => { + const cell = document.createElement('div'); + cell.className = 'month-day-cell'; + cell.dataset.date = DateCalculator.formatISODate(date); + cell.style.border = '1px solid #e0e0e0'; + cell.style.minHeight = '100px'; + cell.style.padding = '4px'; + cell.style.position = 'relative'; + // Day number + const dayNumber = document.createElement('div'); + dayNumber.className = 'month-day-number'; + dayNumber.textContent = date.getDate().toString(); + dayNumber.style.fontWeight = 'bold'; + dayNumber.style.marginBottom = '4px'; + // Check if today + if (DateCalculator.isToday(date)) { + dayNumber.style.color = '#1976d2'; + cell.style.backgroundColor = '#f5f5f5'; + } + cell.appendChild(dayNumber); + container.appendChild(cell); + }); + } + getMonthDates(monthDate) { + // Get first day of month + const firstOfMonth = new Date(monthDate.getFullYear(), monthDate.getMonth(), 1); + // Get Monday of the week containing first day + const startDate = DateCalculator.getISOWeekStart(firstOfMonth); + // Generate 42 days (6 weeks) + const dates = []; + for (let i = 0; i < 42; i++) { + dates.push(DateCalculator.addDays(startDate, i)); + } + return dates; + } + renderMonthEvents(container, events) { + // TODO: Implement month event rendering + // Events will be small blocks in day cells + } + getNextPeriod(currentDate) { + return new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1); + } + getPreviousPeriod(currentDate) { + return new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1); + } + getPeriodLabel(date) { + const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December']; + return `${monthNames[date.getMonth()]} ${date.getFullYear()}`; + } + getDisplayDates(baseDate) { + return this.getMonthDates(baseDate); + } + getPeriodRange(baseDate) { + // Month view shows events for the entire month grid (including partial weeks) + const firstOfMonth = new Date(baseDate.getFullYear(), baseDate.getMonth(), 1); + // Get Monday of the week containing first day + const startDate = DateCalculator.getISOWeekStart(firstOfMonth); + // End date is 41 days after start (42 total days) + const endDate = DateCalculator.addDays(startDate, 41); + return { + startDate, + endDate + }; + } + destroy() { + } +} +//# sourceMappingURL=MonthViewStrategy.js.map \ No newline at end of file diff --git a/wwwroot/js/strategies/MonthViewStrategy.js.map b/wwwroot/js/strategies/MonthViewStrategy.js.map new file mode 100644 index 0000000..04383f6 --- /dev/null +++ b/wwwroot/js/strategies/MonthViewStrategy.js.map @@ -0,0 +1 @@ +{"version":3,"file":"MonthViewStrategy.js","sourceRoot":"","sources":["../../../src/strategies/MonthViewStrategy.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/strategies/ViewStrategy.d.ts b/wwwroot/js/strategies/ViewStrategy.d.ts new file mode 100644 index 0000000..6ce1eee --- /dev/null +++ b/wwwroot/js/strategies/ViewStrategy.d.ts @@ -0,0 +1,58 @@ +/** + * ViewStrategy - Strategy pattern for different calendar view types + * Allows clean separation between week view, month view, day view etc. + */ +import { ResourceCalendarData } from '../types/CalendarTypes'; +/** + * Context object passed to strategy methods + */ +export interface ViewContext { + currentDate: Date; + container: HTMLElement; + resourceData: ResourceCalendarData | null; +} +/** + * Layout configuration specific to each view type + */ +export interface ViewLayoutConfig { + needsTimeAxis: boolean; + columnCount: number; + scrollable: boolean; + eventPositioning: 'time-based' | 'cell-based'; +} +/** + * Base strategy interface for all view types + */ +export interface ViewStrategy { + /** + * Get the layout configuration for this view + */ + getLayoutConfig(): ViewLayoutConfig; + /** + * Render the grid structure for this view + */ + renderGrid(context: ViewContext): void; + /** + * Calculate next period for navigation + */ + getNextPeriod(currentDate: Date): Date; + /** + * Calculate previous period for navigation + */ + getPreviousPeriod(currentDate: Date): Date; + /** + * Get display label for current period + */ + getPeriodLabel(date: Date): string; + /** + * Get the dates that should be displayed in this view + */ + getDisplayDates(baseDate: Date): Date[]; + /** + * Get the period start and end dates for event filtering + */ + getPeriodRange(baseDate: Date): { + startDate: Date; + endDate: Date; + }; +} diff --git a/wwwroot/js/strategies/ViewStrategy.js b/wwwroot/js/strategies/ViewStrategy.js new file mode 100644 index 0000000..6185c60 --- /dev/null +++ b/wwwroot/js/strategies/ViewStrategy.js @@ -0,0 +1,6 @@ +/** + * ViewStrategy - Strategy pattern for different calendar view types + * Allows clean separation between week view, month view, day view etc. + */ +export {}; +//# sourceMappingURL=ViewStrategy.js.map \ No newline at end of file diff --git a/wwwroot/js/strategies/ViewStrategy.js.map b/wwwroot/js/strategies/ViewStrategy.js.map new file mode 100644 index 0000000..e58f7ec --- /dev/null +++ b/wwwroot/js/strategies/ViewStrategy.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ViewStrategy.js","sourceRoot":"","sources":["../../../src/strategies/ViewStrategy.ts"],"names":[],"mappings":"AAAA;;;GAGG"} \ No newline at end of file diff --git a/wwwroot/js/strategies/WeekViewStrategy.d.ts b/wwwroot/js/strategies/WeekViewStrategy.d.ts new file mode 100644 index 0000000..3307dfc --- /dev/null +++ b/wwwroot/js/strategies/WeekViewStrategy.d.ts @@ -0,0 +1,22 @@ +/** + * WeekViewStrategy - Strategy for week/day view rendering + * Extracts the time-based grid logic from GridManager + */ +import { ViewStrategy, ViewContext, ViewLayoutConfig } from './ViewStrategy'; +export declare class WeekViewStrategy implements ViewStrategy { + private dateCalculator; + private gridRenderer; + private styleManager; + constructor(); + getLayoutConfig(): ViewLayoutConfig; + renderGrid(context: ViewContext): void; + getNextPeriod(currentDate: Date): Date; + getPreviousPeriod(currentDate: Date): Date; + getPeriodLabel(date: Date): string; + getDisplayDates(baseDate: Date): Date[]; + getPeriodRange(baseDate: Date): { + startDate: Date; + endDate: Date; + }; + destroy(): void; +} diff --git a/wwwroot/js/strategies/WeekViewStrategy.js b/wwwroot/js/strategies/WeekViewStrategy.js new file mode 100644 index 0000000..d5130d9 --- /dev/null +++ b/wwwroot/js/strategies/WeekViewStrategy.js @@ -0,0 +1,57 @@ +/** + * WeekViewStrategy - Strategy for week/day view rendering + * Extracts the time-based grid logic from GridManager + */ +import { DateCalculator } from '../utils/DateCalculator'; +import { calendarConfig } from '../core/CalendarConfig'; +import { GridRenderer } from '../renderers/GridRenderer'; +import { GridStyleManager } from '../renderers/GridStyleManager'; +export class WeekViewStrategy { + constructor() { + DateCalculator.initialize(calendarConfig); + this.dateCalculator = new DateCalculator(); + this.gridRenderer = new GridRenderer(); + this.styleManager = new GridStyleManager(); + } + getLayoutConfig() { + return { + needsTimeAxis: true, + columnCount: calendarConfig.getWorkWeekSettings().totalDays, + scrollable: true, + eventPositioning: 'time-based' + }; + } + renderGrid(context) { + // Update grid styles + this.styleManager.updateGridStyles(context.resourceData); + // Render the grid structure (time axis + day columns) + this.gridRenderer.renderGrid(context.container, context.currentDate, context.resourceData); + } + getNextPeriod(currentDate) { + return DateCalculator.addWeeks(currentDate, 1); + } + getPreviousPeriod(currentDate) { + return DateCalculator.addWeeks(currentDate, -1); + } + getPeriodLabel(date) { + const weekStart = DateCalculator.getISOWeekStart(date); + const weekEnd = DateCalculator.addDays(weekStart, 6); + const weekNumber = DateCalculator.getWeekNumber(date); + return `Week ${weekNumber}: ${DateCalculator.formatDateRange(weekStart, weekEnd)}`; + } + getDisplayDates(baseDate) { + return DateCalculator.getWorkWeekDates(baseDate); + } + getPeriodRange(baseDate) { + const weekStart = DateCalculator.getISOWeekStart(baseDate); + const weekEnd = DateCalculator.addDays(weekStart, 6); + return { + startDate: weekStart, + endDate: weekEnd + }; + } + destroy() { + // Clean up any week-specific resources + } +} +//# sourceMappingURL=WeekViewStrategy.js.map \ No newline at end of file diff --git a/wwwroot/js/strategies/WeekViewStrategy.js.map b/wwwroot/js/strategies/WeekViewStrategy.js.map new file mode 100644 index 0000000..fff7d39 --- /dev/null +++ b/wwwroot/js/strategies/WeekViewStrategy.js.map @@ -0,0 +1 @@ +{"version":3,"file":"WeekViewStrategy.js","sourceRoot":"","sources":["../../../src/strategies/WeekViewStrategy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAEjE,MAAM,OAAO,gBAAgB;IAK3B;QACE,cAAc,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;QAC1C,IAAI,CAAC,cAAc,GAAG,IAAI,cAAc,EAAE,CAAC;QAC3C,IAAI,CAAC,YAAY,GAAG,IAAI,YAAY,EAAE,CAAC;QACvC,IAAI,CAAC,YAAY,GAAG,IAAI,gBAAgB,EAAE,CAAC;IAC7C,CAAC;IAED,eAAe;QACb,OAAO;YACL,aAAa,EAAE,IAAI;YACnB,WAAW,EAAE,cAAc,CAAC,mBAAmB,EAAE,CAAC,SAAS;YAC3D,UAAU,EAAE,IAAI;YAChB,gBAAgB,EAAE,YAAY;SAC/B,CAAC;IACJ,CAAC;IAED,UAAU,CAAC,OAAoB;QAC7B,qBAAqB;QACrB,IAAI,CAAC,YAAY,CAAC,gBAAgB,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAEzD,sDAAsD;QACtD,IAAI,CAAC,YAAY,CAAC,UAAU,CAC1B,OAAO,CAAC,SAAS,EACjB,OAAO,CAAC,WAAW,EACnB,OAAO,CAAC,YAAY,CACrB,CAAC;IACJ,CAAC;IAED,aAAa,CAAC,WAAiB;QAC7B,OAAO,cAAc,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,iBAAiB,CAAC,WAAiB;QACjC,OAAO,cAAc,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC;IAED,cAAc,CAAC,IAAU;QACvB,MAAM,SAAS,GAAG,cAAc,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QACvD,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;QACrD,MAAM,UAAU,GAAG,cAAc,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QAEtD,OAAO,QAAQ,UAAU,KAAK,cAAc,CAAC,eAAe,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC;IACrF,CAAC;IAED,eAAe,CAAC,QAAc;QAC5B,OAAO,cAAc,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACnD,CAAC;IAED,cAAc,CAAC,QAAc;QAC3B,MAAM,SAAS,GAAG,cAAc,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QAC3D,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;QAErD,OAAO;YACL,SAAS,EAAE,SAAS;YACpB,OAAO,EAAE,OAAO;SACjB,CAAC;IACJ,CAAC;IAED,OAAO;QACL,uCAAuC;IACzC,CAAC;CACF"} \ No newline at end of file diff --git a/wwwroot/js/types/CalendarTypes.d.ts b/wwwroot/js/types/CalendarTypes.d.ts new file mode 100644 index 0000000..d5efb9f --- /dev/null +++ b/wwwroot/js/types/CalendarTypes.d.ts @@ -0,0 +1,56 @@ +export type ViewPeriod = 'day' | 'week' | 'month'; +export type CalendarView = ViewPeriod; +export type SyncStatus = 'synced' | 'pending' | 'error'; +export interface IRenderContext { + container: HTMLElement; + startDate: Date; + endDate: Date; +} +export interface ICalendarEvent { + id: string; + title: string; + description?: string; + start: Date; + end: Date; + type: string; + allDay: boolean; + syncStatus: SyncStatus; + recurringId?: string; + metadata?: Record; +} +export interface ICalendarConfig { + scrollbarWidth: number; + scrollbarColor: string; + scrollbarTrackColor: string; + scrollbarHoverColor: string; + scrollbarBorderRadius: number; + allowDrag: boolean; + allowResize: boolean; + allowCreate: boolean; + apiEndpoint: string; + dateFormat: string; + timeFormat: string; + enableSearch: boolean; + enableTouch: boolean; + defaultEventDuration: number; + minEventDuration: number; + maxEventDuration: number; +} +export interface IEventLogEntry { + type: string; + detail: unknown; + timestamp: number; +} +export interface IListenerEntry { + eventType: string; + handler: EventListener; + options?: AddEventListenerOptions; +} +export interface IEventBus { + on(eventType: string, handler: EventListener, options?: AddEventListenerOptions): () => void; + once(eventType: string, handler: EventListener): () => void; + off(eventType: string, handler: EventListener): void; + emit(eventType: string, detail?: unknown): boolean; + getEventLog(eventType?: string): IEventLogEntry[]; + setDebug(enabled: boolean): void; +} diff --git a/wwwroot/js/types/CalendarTypes.js b/wwwroot/js/types/CalendarTypes.js new file mode 100644 index 0000000..a86177f --- /dev/null +++ b/wwwroot/js/types/CalendarTypes.js @@ -0,0 +1,3 @@ +// Calendar type definitions +export {}; +//# sourceMappingURL=CalendarTypes.js.map \ No newline at end of file diff --git a/wwwroot/js/types/CalendarTypes.js.map b/wwwroot/js/types/CalendarTypes.js.map new file mode 100644 index 0000000..6bb92ea --- /dev/null +++ b/wwwroot/js/types/CalendarTypes.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CalendarTypes.js","sourceRoot":"","sources":["../../../src/types/CalendarTypes.ts"],"names":[],"mappings":"AAAA,4BAA4B"} \ No newline at end of file diff --git a/wwwroot/js/types/ColumnDataSource.d.ts b/wwwroot/js/types/ColumnDataSource.d.ts new file mode 100644 index 0000000..269fc8e --- /dev/null +++ b/wwwroot/js/types/ColumnDataSource.d.ts @@ -0,0 +1,17 @@ +/** + * IColumnDataSource - Defines the contract for providing column data + * + * This interface abstracts away whether columns represent dates or resources, + * allowing the calendar to switch between date-based and resource-based views. + */ +export interface IColumnDataSource { + /** + * Get the list of column identifiers to render + * @returns Array of identifiers (dates or resource IDs) + */ + getColumns(): Date[]; + /** + * Get the type of columns this datasource provides + */ + getType(): 'date' | 'resource'; +} diff --git a/wwwroot/js/types/ColumnDataSource.js b/wwwroot/js/types/ColumnDataSource.js new file mode 100644 index 0000000..1fd57b8 --- /dev/null +++ b/wwwroot/js/types/ColumnDataSource.js @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=ColumnDataSource.js.map \ No newline at end of file diff --git a/wwwroot/js/types/ColumnDataSource.js.map b/wwwroot/js/types/ColumnDataSource.js.map new file mode 100644 index 0000000..2d1395f --- /dev/null +++ b/wwwroot/js/types/ColumnDataSource.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ColumnDataSource.js","sourceRoot":"","sources":["../../../src/types/ColumnDataSource.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/wwwroot/js/types/DragDropTypes.d.ts b/wwwroot/js/types/DragDropTypes.d.ts new file mode 100644 index 0000000..da16c45 --- /dev/null +++ b/wwwroot/js/types/DragDropTypes.d.ts @@ -0,0 +1,41 @@ +/** + * Type definitions for drag and drop functionality + */ +export interface IMousePosition { + x: number; + y: number; + clientX?: number; + clientY?: number; +} +export interface IDragOffset { + x: number; + y: number; + offsetX?: number; + offsetY?: number; +} +export interface IDragState { + isDragging: boolean; + draggedElement: HTMLElement | null; + draggedClone: HTMLElement | null; + eventId: string | null; + startColumn: string | null; + currentColumn: string | null; + mouseOffset: IDragOffset; +} +export interface IDragEndPosition { + column: string; + y: number; + snappedY: number; + time?: Date; +} +export interface IStackLinkData { + prev?: string; + next?: string; + isFirst?: boolean; + isLast?: boolean; +} +export interface IDragEventHandlers { + handleDragStart?(originalElement: HTMLElement, eventId: string, mouseOffset: IDragOffset, column: string): void; + handleDragMove?(eventId: string, snappedY: number, column: string, mouseOffset: IDragOffset): void; + handleDragEnd?(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: string, finalY: number): void; +} diff --git a/wwwroot/js/types/DragDropTypes.js b/wwwroot/js/types/DragDropTypes.js new file mode 100644 index 0000000..d892616 --- /dev/null +++ b/wwwroot/js/types/DragDropTypes.js @@ -0,0 +1,5 @@ +/** + * Type definitions for drag and drop functionality + */ +export {}; +//# sourceMappingURL=DragDropTypes.js.map \ No newline at end of file diff --git a/wwwroot/js/types/DragDropTypes.js.map b/wwwroot/js/types/DragDropTypes.js.map new file mode 100644 index 0000000..2272daa --- /dev/null +++ b/wwwroot/js/types/DragDropTypes.js.map @@ -0,0 +1 @@ +{"version":3,"file":"DragDropTypes.js","sourceRoot":"","sources":["../../../src/types/DragDropTypes.ts"],"names":[],"mappings":"AAAA;;GAEG"} \ No newline at end of file diff --git a/wwwroot/js/types/EventPayloadMap.d.ts b/wwwroot/js/types/EventPayloadMap.d.ts new file mode 100644 index 0000000..d35b9d7 --- /dev/null +++ b/wwwroot/js/types/EventPayloadMap.d.ts @@ -0,0 +1,133 @@ +import { CalendarEvent, CalendarView } from './CalendarTypes'; +import { DragStartEventPayload, DragMoveEventPayload, DragEndEventPayload, DragMouseEnterHeaderEventPayload, DragMouseLeaveHeaderEventPayload, HeaderReadyEventPayload } from './EventTypes'; +import { CoreEvents } from '../constants/CoreEvents'; +/** + * Complete type mapping for all calendar events + * This enables type-safe event emission and handling + */ +export interface CalendarEventPayloadMap { + [CoreEvents.INITIALIZED]: { + initialized: boolean; + timestamp: number; + }; + [CoreEvents.READY]: undefined; + [CoreEvents.DESTROYED]: undefined; + [CoreEvents.VIEW_CHANGED]: { + view: CalendarView; + previousView?: CalendarView; + }; + [CoreEvents.VIEW_RENDERED]: { + view: CalendarView; + }; + [CoreEvents.WORKWEEK_CHANGED]: { + settings: unknown; + }; + [CoreEvents.DATE_CHANGED]: { + date: Date; + view?: CalendarView; + }; + [CoreEvents.NAVIGATION_COMPLETED]: { + direction: 'previous' | 'next' | 'today'; + }; + [CoreEvents.PERIOD_INFO_UPDATE]: { + label: string; + startDate: Date; + endDate: Date; + }; + [CoreEvents.NAVIGATE_TO_EVENT]: { + eventId: string; + }; + [CoreEvents.DATA_LOADING]: undefined; + [CoreEvents.DATA_LOADED]: { + events: CalendarEvent[]; + count: number; + }; + [CoreEvents.DATA_ERROR]: { + error: Error; + }; + [CoreEvents.EVENTS_FILTERED]: { + filteredEvents: CalendarEvent[]; + }; + [CoreEvents.GRID_RENDERED]: { + container: HTMLElement; + currentDate: Date; + startDate: Date; + endDate: Date; + columnCount: number; + }; + [CoreEvents.GRID_CLICKED]: { + column: string; + row: number; + }; + [CoreEvents.CELL_SELECTED]: { + cell: HTMLElement; + }; + [CoreEvents.EVENT_CREATED]: { + event: CalendarEvent; + }; + [CoreEvents.EVENT_UPDATED]: { + event: CalendarEvent; + previousData?: Partial; + }; + [CoreEvents.EVENT_DELETED]: { + eventId: string; + }; + [CoreEvents.EVENT_SELECTED]: { + eventId: string; + event?: CalendarEvent; + }; + [CoreEvents.ERROR]: { + error: Error; + context?: string; + }; + [CoreEvents.REFRESH_REQUESTED]: { + view?: CalendarView; + date?: Date; + }; + [CoreEvents.FILTER_CHANGED]: { + activeFilters: string[]; + visibleEvents: CalendarEvent[]; + }; + [CoreEvents.EVENTS_RENDERED]: { + eventCount: number; + }; + 'drag:start': DragStartEventPayload; + 'drag:move': DragMoveEventPayload; + 'drag:end': DragEndEventPayload; + 'drag:mouseenter-header': DragMouseEnterHeaderEventPayload; + 'drag:mouseleave-header': DragMouseLeaveHeaderEventPayload; + 'drag:cancelled': { + reason: string; + }; + 'header:ready': HeaderReadyEventPayload; + 'header:height-changed': { + height: number; + rowCount: number; + }; + 'allday:checkHeight': undefined; + 'allday:convert-to-allday': { + eventId: string; + element: HTMLElement; + }; + 'allday:convert-from-allday': { + eventId: string; + element: HTMLElement; + }; + 'scroll:sync': { + scrollTop: number; + source: string; + }; + 'scroll:to-hour': { + hour: number; + }; + 'filter:updated': { + activeFilters: string[]; + visibleEvents: CalendarEvent[]; + }; + 'filter:search': { + query: string; + results: CalendarEvent[]; + }; +} +export type EventPayload = CalendarEventPayloadMap[T]; +export declare function hasPayload(eventType: T, payload: unknown): payload is CalendarEventPayloadMap[T]; diff --git a/wwwroot/js/types/EventPayloadMap.js b/wwwroot/js/types/EventPayloadMap.js new file mode 100644 index 0000000..8738d5e --- /dev/null +++ b/wwwroot/js/types/EventPayloadMap.js @@ -0,0 +1,6 @@ +import { CoreEvents } from '../constants/CoreEvents'; +// Type guard to check if an event has a payload +export function hasPayload(eventType, payload) { + return payload !== undefined; +} +//# sourceMappingURL=EventPayloadMap.js.map \ No newline at end of file diff --git a/wwwroot/js/types/EventPayloadMap.js.map b/wwwroot/js/types/EventPayloadMap.js.map new file mode 100644 index 0000000..89f465c --- /dev/null +++ b/wwwroot/js/types/EventPayloadMap.js.map @@ -0,0 +1 @@ +{"version":3,"file":"EventPayloadMap.js","sourceRoot":"","sources":["../../../src/types/EventPayloadMap.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAiKrD,gDAAgD;AAChD,MAAM,UAAU,UAAU,CACxB,SAAY,EACZ,OAAgB;IAEhB,OAAO,OAAO,KAAK,SAAS,CAAC;AAC/B,CAAC"} \ No newline at end of file diff --git a/wwwroot/js/types/EventTypes.d.ts b/wwwroot/js/types/EventTypes.d.ts new file mode 100644 index 0000000..c99f970 --- /dev/null +++ b/wwwroot/js/types/EventTypes.d.ts @@ -0,0 +1,81 @@ +/** + * Type definitions for calendar events and drag operations + */ +import { IColumnBounds } from "../utils/ColumnDetectionUtils"; +import { ICalendarEvent } from "./CalendarTypes"; +/** + * Drag Event Payload Interfaces + * Type-safe interfaces for drag and drop events + */ +export interface IMousePosition { + x: number; + y: number; +} +export interface IDragStartEventPayload { + originalElement: HTMLElement; + draggedClone: HTMLElement | null; + mousePosition: IMousePosition; + mouseOffset: IMousePosition; + columnBounds: IColumnBounds | null; +} +export interface IDragMoveEventPayload { + originalElement: HTMLElement; + draggedClone: HTMLElement; + mousePosition: IMousePosition; + mouseOffset: IMousePosition; + columnBounds: IColumnBounds | null; + snappedY: number; +} +export interface IDragEndEventPayload { + originalElement: HTMLElement; + draggedClone: HTMLElement | null; + mousePosition: IMousePosition; + originalSourceColumn: IColumnBounds; + finalPosition: { + column: IColumnBounds | null; + snappedY: number; + }; + target: 'swp-day-column' | 'swp-day-header' | null; +} +export interface IDragMouseEnterHeaderEventPayload { + targetColumn: IColumnBounds; + mousePosition: IMousePosition; + originalElement: HTMLElement | null; + draggedClone: HTMLElement; + calendarEvent: ICalendarEvent; + replaceClone: (newClone: HTMLElement) => void; +} +export interface IDragMouseLeaveHeaderEventPayload { + targetDate: string | null; + mousePosition: IMousePosition; + originalElement: HTMLElement | null; + draggedClone: HTMLElement | null; +} +export interface IDragMouseEnterColumnEventPayload { + targetColumn: IColumnBounds; + mousePosition: IMousePosition; + snappedY: number; + originalElement: HTMLElement | null; + draggedClone: HTMLElement; + calendarEvent: ICalendarEvent; + replaceClone: (newClone: HTMLElement) => void; +} +export interface IDragColumnChangeEventPayload { + originalElement: HTMLElement; + draggedClone: HTMLElement; + previousColumn: IColumnBounds | null; + newColumn: IColumnBounds; + mousePosition: IMousePosition; +} +export interface IHeaderReadyEventPayload { + headerElements: IColumnBounds[]; +} +export interface IResizeEndEventPayload { + eventId: string; + element: HTMLElement; + finalHeight: number; +} +export interface INavButtonClickedEventPayload { + direction: 'next' | 'previous' | 'today'; + newDate: Date; +} diff --git a/wwwroot/js/types/EventTypes.js b/wwwroot/js/types/EventTypes.js new file mode 100644 index 0000000..db1af83 --- /dev/null +++ b/wwwroot/js/types/EventTypes.js @@ -0,0 +1,5 @@ +/** + * Type definitions for calendar events and drag operations + */ +export {}; +//# sourceMappingURL=EventTypes.js.map \ No newline at end of file diff --git a/wwwroot/js/types/EventTypes.js.map b/wwwroot/js/types/EventTypes.js.map new file mode 100644 index 0000000..30cdf68 --- /dev/null +++ b/wwwroot/js/types/EventTypes.js.map @@ -0,0 +1 @@ +{"version":3,"file":"EventTypes.js","sourceRoot":"","sources":["../../../src/types/EventTypes.ts"],"names":[],"mappings":"AAAA;;GAEG"} \ No newline at end of file diff --git a/wwwroot/js/types/ManagerTypes.d.ts b/wwwroot/js/types/ManagerTypes.d.ts new file mode 100644 index 0000000..9af0be9 --- /dev/null +++ b/wwwroot/js/types/ManagerTypes.d.ts @@ -0,0 +1,59 @@ +import { ICalendarEvent, CalendarView } from './CalendarTypes'; +/** + * Complete type definition for all managers returned by ManagerFactory + */ +export interface ICalendarManagers { + eventManager: IEventManager; + eventRenderer: IEventRenderingService; + gridManager: IGridManager; + scrollManager: IScrollManager; + navigationManager: unknown; + viewManager: IViewManager; + calendarManager: ICalendarManager; + dragDropManager: unknown; + allDayManager: unknown; + resizeHandleManager: IResizeHandleManager; + edgeScrollManager: unknown; + dragHoverManager: unknown; + headerManager: unknown; +} +/** + * Base interface for managers with optional initialization and refresh + */ +interface IManager { + initialize?(): Promise | void; + refresh?(): void; +} +export interface IEventManager extends IManager { + loadData(): Promise; + getEvents(): ICalendarEvent[]; + getEventsForPeriod(startDate: Date, endDate: Date): ICalendarEvent[]; + navigateToEvent(eventId: string): boolean; +} +export interface IEventRenderingService extends IManager { +} +export interface IGridManager extends IManager { + render(): Promise; +} +export interface IScrollManager extends IManager { + scrollTo(scrollTop: number): void; + scrollToHour(hour: number): void; +} +export interface INavigationManager extends IManager { + [key: string]: unknown; +} +export interface IViewManager extends IManager { + getCurrentView?(): CalendarView; +} +export interface ICalendarManager extends IManager { + setView(view: CalendarView): void; + setCurrentDate(date: Date): void; +} +export interface IDragDropManager extends IManager { +} +export interface IAllDayManager extends IManager { + [key: string]: unknown; +} +export interface IResizeHandleManager extends IManager { +} +export {}; diff --git a/wwwroot/js/types/ManagerTypes.js b/wwwroot/js/types/ManagerTypes.js new file mode 100644 index 0000000..8e31fa0 --- /dev/null +++ b/wwwroot/js/types/ManagerTypes.js @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=ManagerTypes.js.map \ No newline at end of file diff --git a/wwwroot/js/types/ManagerTypes.js.map b/wwwroot/js/types/ManagerTypes.js.map new file mode 100644 index 0000000..a63646f --- /dev/null +++ b/wwwroot/js/types/ManagerTypes.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ManagerTypes.js","sourceRoot":"","sources":["../../../src/types/ManagerTypes.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/wwwroot/js/utils/AllDayLayoutEngine.d.ts b/wwwroot/js/utils/AllDayLayoutEngine.d.ts new file mode 100644 index 0000000..caff572 --- /dev/null +++ b/wwwroot/js/utils/AllDayLayoutEngine.d.ts @@ -0,0 +1,42 @@ +import { ICalendarEvent } from '../types/CalendarTypes'; +export interface IEventLayout { + calenderEvent: ICalendarEvent; + gridArea: string; + startColumn: number; + endColumn: number; + row: number; + columnSpan: number; +} +export declare class AllDayLayoutEngine { + private weekDates; + private tracks; + constructor(weekDates: string[]); + /** + * Calculate layout for all events using clean day-based logic + */ + calculateLayout(events: ICalendarEvent[]): IEventLayout[]; + /** + * Find available track for event spanning from startDay to endDay (0-based indices) + */ + private findAvailableTrack; + /** + * Check if track is available for the given day range (0-based indices) + */ + private isTrackAvailable; + /** + * Get start day index for event (1-based, 0 if not visible) + */ + private getEventStartDay; + /** + * Get end day index for event (1-based, 0 if not visible) + */ + private getEventEndDay; + /** + * Check if event is visible in the current date range + */ + private isEventVisible; + /** + * Format date to YYYY-MM-DD string using local date + */ + private formatDate; +} diff --git a/wwwroot/js/utils/AllDayLayoutEngine.js b/wwwroot/js/utils/AllDayLayoutEngine.js new file mode 100644 index 0000000..c939563 --- /dev/null +++ b/wwwroot/js/utils/AllDayLayoutEngine.js @@ -0,0 +1,108 @@ +export class AllDayLayoutEngine { + constructor(weekDates) { + this.weekDates = weekDates; + this.tracks = []; + } + /** + * Calculate layout for all events using clean day-based logic + */ + calculateLayout(events) { + let layouts = []; + // Reset tracks for new calculation + this.tracks = [new Array(this.weekDates.length).fill(false)]; + // Filter to only visible events + const visibleEvents = events.filter(event => this.isEventVisible(event)); + // Process events in input order (no sorting) + for (const event of visibleEvents) { + const startDay = this.getEventStartDay(event); + const endDay = this.getEventEndDay(event); + if (startDay > 0 && endDay > 0) { + const track = this.findAvailableTrack(startDay - 1, endDay - 1); // Convert to 0-based for tracks + // Mark days as occupied + for (let day = startDay - 1; day <= endDay - 1; day++) { + this.tracks[track][day] = true; + } + const layout = { + calenderEvent: event, + gridArea: `${track + 1} / ${startDay} / ${track + 2} / ${endDay + 1}`, + startColumn: startDay, + endColumn: endDay, + row: track + 1, + columnSpan: endDay - startDay + 1 + }; + layouts.push(layout); + } + } + return layouts; + } + /** + * Find available track for event spanning from startDay to endDay (0-based indices) + */ + findAvailableTrack(startDay, endDay) { + for (let trackIndex = 0; trackIndex < this.tracks.length; trackIndex++) { + if (this.isTrackAvailable(trackIndex, startDay, endDay)) { + return trackIndex; + } + } + // Create new track if none available + this.tracks.push(new Array(this.weekDates.length).fill(false)); + return this.tracks.length - 1; + } + /** + * Check if track is available for the given day range (0-based indices) + */ + isTrackAvailable(trackIndex, startDay, endDay) { + for (let day = startDay; day <= endDay; day++) { + if (this.tracks[trackIndex][day]) { + return false; + } + } + return true; + } + /** + * Get start day index for event (1-based, 0 if not visible) + */ + getEventStartDay(event) { + const eventStartDate = this.formatDate(event.start); + const firstVisibleDate = this.weekDates[0]; + // If event starts before visible range, clip to first visible day + const clippedStartDate = eventStartDate < firstVisibleDate ? firstVisibleDate : eventStartDate; + const dayIndex = this.weekDates.indexOf(clippedStartDate); + return dayIndex >= 0 ? dayIndex + 1 : 0; + } + /** + * Get end day index for event (1-based, 0 if not visible) + */ + getEventEndDay(event) { + const eventEndDate = this.formatDate(event.end); + const lastVisibleDate = this.weekDates[this.weekDates.length - 1]; + // If event ends after visible range, clip to last visible day + const clippedEndDate = eventEndDate > lastVisibleDate ? lastVisibleDate : eventEndDate; + const dayIndex = this.weekDates.indexOf(clippedEndDate); + return dayIndex >= 0 ? dayIndex + 1 : 0; + } + /** + * Check if event is visible in the current date range + */ + isEventVisible(event) { + if (this.weekDates.length === 0) + return false; + const eventStartDate = this.formatDate(event.start); + const eventEndDate = this.formatDate(event.end); + const firstVisibleDate = this.weekDates[0]; + const lastVisibleDate = this.weekDates[this.weekDates.length - 1]; + // Event overlaps if it doesn't end before visible range starts + // AND doesn't start after visible range ends + return !(eventEndDate < firstVisibleDate || eventStartDate > lastVisibleDate); + } + /** + * Format date to YYYY-MM-DD string using local date + */ + formatDate(date) { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; + } +} +//# sourceMappingURL=AllDayLayoutEngine.js.map \ No newline at end of file diff --git a/wwwroot/js/utils/AllDayLayoutEngine.js.map b/wwwroot/js/utils/AllDayLayoutEngine.js.map new file mode 100644 index 0000000..a6d6e7b --- /dev/null +++ b/wwwroot/js/utils/AllDayLayoutEngine.js.map @@ -0,0 +1 @@ +{"version":3,"file":"AllDayLayoutEngine.js","sourceRoot":"","sources":["../../../src/utils/AllDayLayoutEngine.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/utils/ColumnDetectionUtils.d.ts b/wwwroot/js/utils/ColumnDetectionUtils.d.ts new file mode 100644 index 0000000..04e1552 --- /dev/null +++ b/wwwroot/js/utils/ColumnDetectionUtils.d.ts @@ -0,0 +1,30 @@ +/** + * ColumnDetectionUtils - Shared utility for column detection and caching + * Used by both DragDropManager and AllDayManager for consistent column detection + */ +import { IMousePosition } from "../types/DragDropTypes"; +export interface IColumnBounds { + date: string; + left: number; + right: number; + boundingClientRect: DOMRect; + element: HTMLElement; + index: number; +} +export declare class ColumnDetectionUtils { + private static columnBoundsCache; + /** + * Update column bounds cache for coordinate-based column detection + */ + static updateColumnBoundsCache(): void; + /** + * Get column date from X coordinate using cached bounds + */ + static getColumnBounds(position: IMousePosition): IColumnBounds | null; + /** + * Get column bounds by Date + */ + static getColumnBoundsByDate(date: Date): IColumnBounds | null; + static getColumns(): IColumnBounds[]; + static getHeaderColumns(): IColumnBounds[]; +} diff --git a/wwwroot/js/utils/ColumnDetectionUtils.js b/wwwroot/js/utils/ColumnDetectionUtils.js new file mode 100644 index 0000000..552638b --- /dev/null +++ b/wwwroot/js/utils/ColumnDetectionUtils.js @@ -0,0 +1,87 @@ +/** + * ColumnDetectionUtils - Shared utility for column detection and caching + * Used by both DragDropManager and AllDayManager for consistent column detection + */ +export class ColumnDetectionUtils { + /** + * Update column bounds cache for coordinate-based column detection + */ + static updateColumnBoundsCache() { + // Reset cache + this.columnBoundsCache = []; + // Find alle kolonner + const columns = document.querySelectorAll('swp-day-column'); + let index = 1; + // Cache hver kolonnes x-grænser + columns.forEach(column => { + const rect = column.getBoundingClientRect(); + const date = column.dataset.date; + if (date) { + this.columnBoundsCache.push({ + boundingClientRect: rect, + element: column, + date, + left: rect.left, + right: rect.right, + index: index++ + }); + } + }); + // Sorter efter x-position (fra venstre til højre) + this.columnBoundsCache.sort((a, b) => a.left - b.left); + } + /** + * Get column date from X coordinate using cached bounds + */ + static getColumnBounds(position) { + if (this.columnBoundsCache.length === 0) { + this.updateColumnBoundsCache(); + } + // Find den kolonne hvor x-koordinaten er indenfor grænserne + let column = this.columnBoundsCache.find(col => position.x >= col.left && position.x <= col.right); + if (column) + return column; + return null; + } + /** + * Get column bounds by Date + */ + static getColumnBoundsByDate(date) { + if (this.columnBoundsCache.length === 0) { + this.updateColumnBoundsCache(); + } + // Convert Date to YYYY-MM-DD format + let dateString = date.toISOString().split('T')[0]; + // Find column that matches the date + let column = this.columnBoundsCache.find(col => col.date === dateString); + return column || null; + } + static getColumns() { + return [...this.columnBoundsCache]; + } + static getHeaderColumns() { + let dayHeaders = []; + const dayColumns = document.querySelectorAll('swp-calendar-header swp-day-header'); + let index = 1; + // Cache hver kolonnes x-grænser + dayColumns.forEach(column => { + const rect = column.getBoundingClientRect(); + const date = column.dataset.date; + if (date) { + dayHeaders.push({ + boundingClientRect: rect, + element: column, + date, + left: rect.left, + right: rect.right, + index: index++ + }); + } + }); + // Sorter efter x-position (fra venstre til højre) + dayHeaders.sort((a, b) => a.left - b.left); + return dayHeaders; + } +} +ColumnDetectionUtils.columnBoundsCache = []; +//# sourceMappingURL=ColumnDetectionUtils.js.map \ No newline at end of file diff --git a/wwwroot/js/utils/ColumnDetectionUtils.js.map b/wwwroot/js/utils/ColumnDetectionUtils.js.map new file mode 100644 index 0000000..21aeb66 --- /dev/null +++ b/wwwroot/js/utils/ColumnDetectionUtils.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ColumnDetectionUtils.js","sourceRoot":"","sources":["../../../src/utils/ColumnDetectionUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAcH,MAAM,OAAO,oBAAoB;IAG7B;;OAEG;IACI,MAAM,CAAC,uBAAuB;QACjC,cAAc;QACd,IAAI,CAAC,iBAAiB,GAAG,EAAE,CAAC;QAE5B,qBAAqB;QACrB,MAAM,OAAO,GAAG,QAAQ,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,CAAC;QAC5D,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,gCAAgC;QAChC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;YACrB,MAAM,IAAI,GAAG,MAAM,CAAC,qBAAqB,EAAE,CAAC;YAC5C,MAAM,IAAI,GAAI,MAAsB,CAAC,OAAO,CAAC,IAAI,CAAC;YAElD,IAAI,IAAI,EAAE,CAAC;gBACP,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC;oBACxB,kBAAkB,EAAG,IAAI;oBACzB,OAAO,EAAE,MAAqB;oBAC9B,IAAI;oBACJ,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,KAAK,EAAE,KAAK,EAAE;iBACjB,CAAC,CAAC;YACP,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,kDAAkD;QAClD,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;IAC3D,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,eAAe,CAAC,QAAwB;QAClD,IAAI,IAAI,CAAC,iBAAiB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtC,IAAI,CAAC,uBAAuB,EAAE,CAAC;QACnC,CAAC;QAED,4DAA4D;QAC5D,IAAI,MAAM,GAAG,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAC3C,QAAQ,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,IAAI,QAAQ,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CACpD,CAAC;QACF,IAAI,MAAM;YACN,OAAO,MAAM,CAAC;QAElB,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,qBAAqB,CAAC,IAAU;QAC1C,IAAI,IAAI,CAAC,iBAAiB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtC,IAAI,CAAC,uBAAuB,EAAE,CAAC;QACnC,CAAC;QAED,oCAAoC;QACpC,IAAI,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAElD,oCAAoC;QACpC,IAAI,MAAM,GAAG,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;QACzE,OAAO,MAAM,IAAI,IAAI,CAAC;IAC1B,CAAC;IAGM,MAAM,CAAC,UAAU;QACpB,OAAO,CAAC,GAAG,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACvC,CAAC;IACM,MAAM,CAAC,gBAAgB;QAE1B,IAAI,UAAU,GAAoB,EAAE,CAAC;QAErC,MAAM,UAAU,GAAG,QAAQ,CAAC,gBAAgB,CAAC,oCAAoC,CAAC,CAAC;QACnF,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,gCAAgC;QAChC,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;YACxB,MAAM,IAAI,GAAG,MAAM,CAAC,qBAAqB,EAAE,CAAC;YAC5C,MAAM,IAAI,GAAI,MAAsB,CAAC,OAAO,CAAC,IAAI,CAAC;YAElD,IAAI,IAAI,EAAE,CAAC;gBACP,UAAU,CAAC,IAAI,CAAC;oBACZ,kBAAkB,EAAG,IAAI;oBACzB,OAAO,EAAE,MAAqB;oBAC9B,IAAI;oBACJ,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,KAAK,EAAE,KAAK,EAAE;iBACjB,CAAC,CAAC;YACP,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,kDAAkD;QAClD,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;QAC3C,OAAO,UAAU,CAAC;IAEtB,CAAC;;AAlGc,sCAAiB,GAAoB,EAAE,CAAC"} \ No newline at end of file diff --git a/wwwroot/js/utils/DateCalculator.d.ts b/wwwroot/js/utils/DateCalculator.d.ts new file mode 100644 index 0000000..74e3a54 --- /dev/null +++ b/wwwroot/js/utils/DateCalculator.d.ts @@ -0,0 +1,149 @@ +/** + * DateCalculator - Centralized date calculation logic for calendar + * Handles all date computations with proper week start handling + */ +import { CalendarConfig } from '../core/CalendarConfig'; +export declare class DateCalculator { + private static config; + /** + * Initialize DateCalculator with configuration + * @param config - Calendar configuration + */ + static initialize(config: CalendarConfig): void; + /** + * Validate that a date is valid + * @param date - Date to validate + * @param methodName - Name of calling method for error messages + * @throws Error if date is invalid + */ + private static validateDate; + /** + * Get dates for work week using ISO 8601 day numbering (Monday=1, Sunday=7) + * @param weekStart - Any date in the week + * @returns Array of dates for the configured work days + */ + static getWorkWeekDates(weekStart: Date): Date[]; + /** + * Get the start of the ISO week (Monday) for a given date + * @param date - Any date in the week + * @returns The Monday of the ISO week + */ + static getISOWeekStart(date: Date): Date; + /** + * Get the end of the ISO week for a given date + * @param date - Any date in the week + * @returns The end date of the ISO week (Sunday) + */ + static getWeekEnd(date: Date): Date; + /** + * Get week number for a date (ISO 8601) + * @param date - The date to get week number for + * @returns Week number (1-53) + */ + static getWeekNumber(date: Date): number; + /** + * Format a date range with customizable options + * @param start - Start date + * @param end - End date + * @param options - Formatting options + * @returns Formatted date range string + */ + static formatDateRange(start: Date, end: Date, options?: { + locale?: string; + month?: 'numeric' | '2-digit' | 'long' | 'short' | 'narrow'; + day?: 'numeric' | '2-digit'; + year?: 'numeric' | '2-digit'; + }): string; + /** + * Format a date to ISO date string (YYYY-MM-DD) + * @param date - Date to format + * @returns ISO date string + */ + static formatISODate(date: Date): string; + /** + * Check if a date is today + * @param date - Date to check + * @returns True if the date is today + */ + static isToday(date: Date): boolean; + /** + * Add days to a date + * @param date - Base date + * @param days - Number of days to add (can be negative) + * @returns New date + */ + static addDays(date: Date, days: number): Date; + /** + * Add weeks to a date + * @param date - Base date + * @param weeks - Number of weeks to add (can be negative) + * @returns New date + */ + static addWeeks(date: Date, weeks: number): Date; + /** + * Get all dates in a week + * @param weekStart - Start of the week + * @returns Array of 7 dates for the full week + */ + static getFullWeekDates(weekStart: Date): Date[]; + /** + * Get the day name for a date using Intl.DateTimeFormat + * @param date - Date to get day name for + * @param format - 'short' or 'long' + * @returns Day name + */ + static getDayName(date: Date, format?: 'short' | 'long'): string; + /** + * Format time to HH:MM + * @param date - Date to format + * @returns Time string + */ + static formatTime(date: Date): string; + /** + * Format time to 12-hour format + * @param date - Date to format + * @returns 12-hour time string + */ + static formatTime12(date: Date): string; + /** + * Convert minutes since midnight to time string + * @param minutes - Minutes since midnight + * @returns Time string + */ + static minutesToTime(minutes: number): string; + /** + * Convert time string to minutes since midnight + * @param timeStr - Time string + * @returns Minutes since midnight + */ + static timeToMinutes(timeStr: string): number; + /** + * Get minutes since start of day + * @param date - Date or ISO string + * @returns Minutes since midnight + */ + static getMinutesSinceMidnight(date: Date | string): number; + /** + * Calculate duration in minutes between two dates + * @param start - Start date or ISO string + * @param end - End date or ISO string + * @returns Duration in minutes + */ + static getDurationMinutes(start: Date | string, end: Date | string): number; + /** + * Check if two dates are on the same day + * @param date1 - First date + * @param date2 - Second date + * @returns True if same day + */ + static isSameDay(date1: Date, date2: Date): boolean; + /** + * Check if event spans multiple days + * @param start - Start date or ISO string + * @param end - End date or ISO string + * @returns True if spans multiple days + */ + static isMultiDay(start: Date | string, end: Date | string): boolean; + constructor(); +} +export declare function createDateCalculator(config: CalendarConfig): DateCalculator; diff --git a/wwwroot/js/utils/DateCalculator.js b/wwwroot/js/utils/DateCalculator.js new file mode 100644 index 0000000..4941202 --- /dev/null +++ b/wwwroot/js/utils/DateCalculator.js @@ -0,0 +1,260 @@ +/** + * DateCalculator - Centralized date calculation logic for calendar + * Handles all date computations with proper week start handling + */ +export class DateCalculator { + /** + * Initialize DateCalculator with configuration + * @param config - Calendar configuration + */ + static initialize(config) { + DateCalculator.config = config; + } + /** + * Validate that a date is valid + * @param date - Date to validate + * @param methodName - Name of calling method for error messages + * @throws Error if date is invalid + */ + static validateDate(date, methodName) { + if (!date || !(date instanceof Date) || isNaN(date.getTime())) { + throw new Error(`${methodName}: Invalid date provided - ${date}`); + } + } + /** + * Get dates for work week using ISO 8601 day numbering (Monday=1, Sunday=7) + * @param weekStart - Any date in the week + * @returns Array of dates for the configured work days + */ + static getWorkWeekDates(weekStart) { + DateCalculator.validateDate(weekStart, 'getWorkWeekDates'); + const dates = []; + const workWeekSettings = DateCalculator.config.getWorkWeekSettings(); + // Always use ISO week start (Monday) + const mondayOfWeek = DateCalculator.getISOWeekStart(weekStart); + // Calculate dates for each work day using ISO numbering + workWeekSettings.workDays.forEach(isoDay => { + const date = new Date(mondayOfWeek); + // ISO day 1=Monday is +0 days, ISO day 7=Sunday is +6 days + const daysFromMonday = isoDay === 7 ? 6 : isoDay - 1; + date.setDate(mondayOfWeek.getDate() + daysFromMonday); + dates.push(date); + }); + return dates; + } + /** + * Get the start of the ISO week (Monday) for a given date + * @param date - Any date in the week + * @returns The Monday of the ISO week + */ + static getISOWeekStart(date) { + DateCalculator.validateDate(date, 'getISOWeekStart'); + const monday = new Date(date); + const currentDay = monday.getDay(); + const daysToSubtract = currentDay === 0 ? 6 : currentDay - 1; + monday.setDate(monday.getDate() - daysToSubtract); + monday.setHours(0, 0, 0, 0); + return monday; + } + /** + * Get the end of the ISO week for a given date + * @param date - Any date in the week + * @returns The end date of the ISO week (Sunday) + */ + static getWeekEnd(date) { + DateCalculator.validateDate(date, 'getWeekEnd'); + const weekStart = DateCalculator.getISOWeekStart(date); + const weekEnd = new Date(weekStart); + weekEnd.setDate(weekStart.getDate() + 6); + weekEnd.setHours(23, 59, 59, 999); + return weekEnd; + } + /** + * Get week number for a date (ISO 8601) + * @param date - The date to get week number for + * @returns Week number (1-53) + */ + static getWeekNumber(date) { + const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); + const dayNum = d.getUTCDay() || 7; + d.setUTCDate(d.getUTCDate() + 4 - dayNum); + const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); + return Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7); + } + /** + * Format a date range with customizable options + * @param start - Start date + * @param end - End date + * @param options - Formatting options + * @returns Formatted date range string + */ + static formatDateRange(start, end, options = {}) { + const { locale = 'en-US', month = 'short', day = 'numeric' } = options; + const startYear = start.getFullYear(); + const endYear = end.getFullYear(); + const formatter = new Intl.DateTimeFormat(locale, { + month, + day, + year: startYear !== endYear ? 'numeric' : undefined + }); + // @ts-ignore + if (typeof formatter.formatRange === 'function') { + // @ts-ignore + return formatter.formatRange(start, end); + } + return `${formatter.format(start)} - ${formatter.format(end)}`; + } + /** + * Format a date to ISO date string (YYYY-MM-DD) + * @param date - Date to format + * @returns ISO date string + */ + static formatISODate(date) { + return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; + } + /** + * Check if a date is today + * @param date - Date to check + * @returns True if the date is today + */ + static isToday(date) { + const today = new Date(); + return date.toDateString() === today.toDateString(); + } + /** + * Add days to a date + * @param date - Base date + * @param days - Number of days to add (can be negative) + * @returns New date + */ + static addDays(date, days) { + const result = new Date(date); + result.setDate(result.getDate() + days); + return result; + } + /** + * Add weeks to a date + * @param date - Base date + * @param weeks - Number of weeks to add (can be negative) + * @returns New date + */ + static addWeeks(date, weeks) { + return DateCalculator.addDays(date, weeks * 7); + } + /** + * Get all dates in a week + * @param weekStart - Start of the week + * @returns Array of 7 dates for the full week + */ + static getFullWeekDates(weekStart) { + const dates = []; + for (let i = 0; i < 7; i++) { + dates.push(DateCalculator.addDays(weekStart, i)); + } + return dates; + } + /** + * Get the day name for a date using Intl.DateTimeFormat + * @param date - Date to get day name for + * @param format - 'short' or 'long' + * @returns Day name + */ + static getDayName(date, format = 'short') { + const formatter = new Intl.DateTimeFormat('en-US', { + weekday: format + }); + return formatter.format(date); + } + /** + * Format time to HH:MM + * @param date - Date to format + * @returns Time string + */ + static formatTime(date) { + return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`; + } + /** + * Format time to 12-hour format + * @param date - Date to format + * @returns 12-hour time string + */ + static formatTime12(date) { + const hours = date.getHours(); + const minutes = date.getMinutes(); + const period = hours >= 12 ? 'PM' : 'AM'; + const displayHours = hours % 12 || 12; + return `${displayHours}:${String(minutes).padStart(2, '0')} ${period}`; + } + /** + * Convert minutes since midnight to time string + * @param minutes - Minutes since midnight + * @returns Time string + */ + static minutesToTime(minutes) { + const hours = Math.floor(minutes / 60); + const mins = minutes % 60; + const period = hours >= 12 ? 'PM' : 'AM'; + const displayHours = hours % 12 || 12; + return `${displayHours}:${String(mins).padStart(2, '0')} ${period}`; + } + /** + * Convert time string to minutes since midnight + * @param timeStr - Time string + * @returns Minutes since midnight + */ + static timeToMinutes(timeStr) { + const [time] = timeStr.split('T').pop().split('.'); + const [hours, minutes] = time.split(':').map(Number); + return hours * 60 + minutes; + } + /** + * Get minutes since start of day + * @param date - Date or ISO string + * @returns Minutes since midnight + */ + static getMinutesSinceMidnight(date) { + const d = typeof date === 'string' ? new Date(date) : date; + return d.getHours() * 60 + d.getMinutes(); + } + /** + * Calculate duration in minutes between two dates + * @param start - Start date or ISO string + * @param end - End date or ISO string + * @returns Duration in minutes + */ + static getDurationMinutes(start, end) { + const startDate = typeof start === 'string' ? new Date(start) : start; + const endDate = typeof end === 'string' ? new Date(end) : end; + return Math.floor((endDate.getTime() - startDate.getTime()) / 60000); + } + /** + * Check if two dates are on the same day + * @param date1 - First date + * @param date2 - Second date + * @returns True if same day + */ + static isSameDay(date1, date2) { + return date1.toDateString() === date2.toDateString(); + } + /** + * Check if event spans multiple days + * @param start - Start date or ISO string + * @param end - End date or ISO string + * @returns True if spans multiple days + */ + static isMultiDay(start, end) { + const startDate = typeof start === 'string' ? new Date(start) : start; + const endDate = typeof end === 'string' ? new Date(end) : end; + return !DateCalculator.isSameDay(startDate, endDate); + } + // Legacy constructor for backward compatibility + constructor() { + // Empty constructor - all methods are now static + } +} +// Legacy factory function - deprecated, use static methods instead +export function createDateCalculator(config) { + DateCalculator.initialize(config); + return new DateCalculator(); +} +//# sourceMappingURL=DateCalculator.js.map \ No newline at end of file diff --git a/wwwroot/js/utils/DateCalculator.js.map b/wwwroot/js/utils/DateCalculator.js.map new file mode 100644 index 0000000..b21a322 --- /dev/null +++ b/wwwroot/js/utils/DateCalculator.js.map @@ -0,0 +1 @@ +{"version":3,"file":"DateCalculator.js","sourceRoot":"","sources":["../../../src/utils/DateCalculator.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/utils/DateService.d.ts b/wwwroot/js/utils/DateService.d.ts new file mode 100644 index 0000000..3d26c55 --- /dev/null +++ b/wwwroot/js/utils/DateService.d.ts @@ -0,0 +1,254 @@ +/** + * DateService - Unified date/time service using day.js + * Handles all date operations, timezone conversions, and formatting + */ +import { Configuration } from '../configurations/CalendarConfig'; +export declare class DateService { + private timezone; + constructor(config: Configuration); + /** + * Convert local date to UTC ISO string + * @param localDate - Date in local timezone + * @returns ISO string in UTC (with 'Z' suffix) + */ + toUTC(localDate: Date): string; + /** + * Convert UTC ISO string to local date + * @param utcString - ISO string in UTC + * @returns Date in local timezone + */ + fromUTC(utcString: string): Date; + /** + * Format time as HH:mm or HH:mm:ss + * @param date - Date to format + * @param showSeconds - Include seconds in output + * @returns Formatted time string + */ + formatTime(date: Date, showSeconds?: boolean): string; + /** + * Format time range as "HH:mm - HH:mm" + * @param start - Start date + * @param end - End date + * @returns Formatted time range + */ + formatTimeRange(start: Date, end: Date): string; + /** + * Format date and time in technical format: yyyy-MM-dd HH:mm:ss + * @param date - Date to format + * @returns Technical datetime string + */ + formatTechnicalDateTime(date: Date): string; + /** + * Format date as yyyy-MM-dd + * @param date - Date to format + * @returns ISO date string + */ + formatDate(date: Date): string; + /** + * Format date as "Month Year" (e.g., "January 2025") + * @param date - Date to format + * @param locale - Locale for month name (default: 'en-US') + * @returns Formatted month and year + */ + formatMonthYear(date: Date, locale?: string): string; + /** + * Format date as ISO string (same as formatDate for compatibility) + * @param date - Date to format + * @returns ISO date string + */ + formatISODate(date: Date): string; + /** + * Format time in 12-hour format with AM/PM + * @param date - Date to format + * @returns Time string in 12-hour format (e.g., "2:30 PM") + */ + formatTime12(date: Date): string; + /** + * Get day name for a date + * @param date - Date to get day name for + * @param format - 'short' (e.g., 'Mon') or 'long' (e.g., 'Monday') + * @param locale - Locale for day name (default: 'da-DK') + * @returns Day name + */ + getDayName(date: Date, format?: 'short' | 'long', locale?: string): string; + /** + * Format a date range with customizable options + * @param start - Start date + * @param end - End date + * @param options - Formatting options + * @returns Formatted date range string + */ + formatDateRange(start: Date, end: Date, options?: { + locale?: string; + month?: 'numeric' | '2-digit' | 'long' | 'short' | 'narrow'; + day?: 'numeric' | '2-digit'; + year?: 'numeric' | '2-digit'; + }): string; + /** + * Convert time string (HH:mm or HH:mm:ss) to total minutes since midnight + * @param timeString - Time in format HH:mm or HH:mm:ss + * @returns Total minutes since midnight + */ + timeToMinutes(timeString: string): number; + /** + * Convert total minutes since midnight to time string HH:mm + * @param totalMinutes - Minutes since midnight + * @returns Time string in format HH:mm + */ + minutesToTime(totalMinutes: number): string; + /** + * Format time from total minutes (alias for minutesToTime) + * @param totalMinutes - Minutes since midnight + * @returns Time string in format HH:mm + */ + formatTimeFromMinutes(totalMinutes: number): string; + /** + * Get minutes since midnight for a given date + * @param date - Date to calculate from + * @returns Minutes since midnight + */ + getMinutesSinceMidnight(date: Date): number; + /** + * Calculate duration in minutes between two dates + * @param start - Start date or ISO string + * @param end - End date or ISO string + * @returns Duration in minutes + */ + getDurationMinutes(start: Date | string, end: Date | string): number; + /** + * Get start and end of week (Monday to Sunday) + * @param date - Reference date + * @returns Object with start and end dates + */ + getWeekBounds(date: Date): { + start: Date; + end: Date; + }; + /** + * Add weeks to a date + * @param date - Base date + * @param weeks - Number of weeks to add (can be negative) + * @returns New date + */ + addWeeks(date: Date, weeks: number): Date; + /** + * Add months to a date + * @param date - Base date + * @param months - Number of months to add (can be negative) + * @returns New date + */ + addMonths(date: Date, months: number): Date; + /** + * Get ISO week number (1-53) + * @param date - Date to get week number for + * @returns ISO week number + */ + getWeekNumber(date: Date): number; + /** + * Get all dates in a full week (7 days starting from given date) + * @param weekStart - Start date of the week + * @returns Array of 7 dates + */ + getFullWeekDates(weekStart: Date): Date[]; + /** + * Get dates for work week using ISO 8601 day numbering (Monday=1, Sunday=7) + * @param weekStart - Any date in the week + * @param workDays - Array of ISO day numbers (1=Monday, 7=Sunday) + * @returns Array of dates for the specified work days + */ + getWorkWeekDates(weekStart: Date, workDays: number[]): Date[]; + /** + * Create a date at a specific time (minutes since midnight) + * @param baseDate - Base date (date component) + * @param totalMinutes - Minutes since midnight + * @returns New date with specified time + */ + createDateAtTime(baseDate: Date, totalMinutes: number): Date; + /** + * Snap date to nearest interval + * @param date - Date to snap + * @param intervalMinutes - Snap interval in minutes + * @returns Snapped date + */ + snapToInterval(date: Date, intervalMinutes: number): Date; + /** + * Check if two dates are the same day + * @param date1 - First date + * @param date2 - Second date + * @returns True if same day + */ + isSameDay(date1: Date, date2: Date): boolean; + /** + * Get start of day + * @param date - Date + * @returns Start of day (00:00:00) + */ + startOfDay(date: Date): Date; + /** + * Get end of day + * @param date - Date + * @returns End of day (23:59:59.999) + */ + endOfDay(date: Date): Date; + /** + * Add days to a date + * @param date - Base date + * @param days - Number of days to add (can be negative) + * @returns New date + */ + addDays(date: Date, days: number): Date; + /** + * Add minutes to a date + * @param date - Base date + * @param minutes - Number of minutes to add (can be negative) + * @returns New date + */ + addMinutes(date: Date, minutes: number): Date; + /** + * Parse ISO string to date + * @param isoString - ISO date string + * @returns Parsed date + */ + parseISO(isoString: string): Date; + /** + * Check if date is valid + * @param date - Date to check + * @returns True if valid + */ + isValid(date: Date): boolean; + /** + * Calculate difference in calendar days between two dates + * @param date1 - First date + * @param date2 - Second date + * @returns Number of calendar days between dates (can be negative) + */ + differenceInCalendarDays(date1: Date, date2: Date): number; + /** + * Validate date range (start must be before or equal to end) + * @param start - Start date + * @param end - End date + * @returns True if valid range + */ + isValidRange(start: Date, end: Date): boolean; + /** + * Check if date is within reasonable bounds (1900-2100) + * @param date - Date to check + * @returns True if within bounds + */ + isWithinBounds(date: Date): boolean; + /** + * Validate date with comprehensive checks + * @param date - Date to validate + * @param options - Validation options + * @returns Validation result with error message + */ + validateDate(date: Date, options?: { + requireFuture?: boolean; + requirePast?: boolean; + minDate?: Date; + maxDate?: Date; + }): { + valid: boolean; + error?: string; + }; +} diff --git a/wwwroot/js/utils/DateService.js b/wwwroot/js/utils/DateService.js new file mode 100644 index 0000000..a3eb134 --- /dev/null +++ b/wwwroot/js/utils/DateService.js @@ -0,0 +1,418 @@ +/** + * DateService - Unified date/time service using day.js + * Handles all date operations, timezone conversions, and formatting + */ +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import timezone from 'dayjs/plugin/timezone'; +import isoWeek from 'dayjs/plugin/isoWeek'; +import customParseFormat from 'dayjs/plugin/customParseFormat'; +import isSameOrAfter from 'dayjs/plugin/isSameOrAfter'; +import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'; +// Enable day.js plugins +dayjs.extend(utc); +dayjs.extend(timezone); +dayjs.extend(isoWeek); +dayjs.extend(customParseFormat); +dayjs.extend(isSameOrAfter); +dayjs.extend(isSameOrBefore); +export class DateService { + constructor(config) { + this.timezone = config.timeFormatConfig.timezone; + } + // ============================================ + // CORE CONVERSIONS + // ============================================ + /** + * Convert local date to UTC ISO string + * @param localDate - Date in local timezone + * @returns ISO string in UTC (with 'Z' suffix) + */ + toUTC(localDate) { + return dayjs.tz(localDate, this.timezone).utc().toISOString(); + } + /** + * Convert UTC ISO string to local date + * @param utcString - ISO string in UTC + * @returns Date in local timezone + */ + fromUTC(utcString) { + return dayjs.utc(utcString).tz(this.timezone).toDate(); + } + // ============================================ + // FORMATTING + // ============================================ + /** + * Format time as HH:mm or HH:mm:ss + * @param date - Date to format + * @param showSeconds - Include seconds in output + * @returns Formatted time string + */ + formatTime(date, showSeconds = false) { + const pattern = showSeconds ? 'HH:mm:ss' : 'HH:mm'; + return dayjs(date).format(pattern); + } + /** + * Format time range as "HH:mm - HH:mm" + * @param start - Start date + * @param end - End date + * @returns Formatted time range + */ + formatTimeRange(start, end) { + return `${this.formatTime(start)} - ${this.formatTime(end)}`; + } + /** + * Format date and time in technical format: yyyy-MM-dd HH:mm:ss + * @param date - Date to format + * @returns Technical datetime string + */ + formatTechnicalDateTime(date) { + return dayjs(date).format('YYYY-MM-DD HH:mm:ss'); + } + /** + * Format date as yyyy-MM-dd + * @param date - Date to format + * @returns ISO date string + */ + formatDate(date) { + return dayjs(date).format('YYYY-MM-DD'); + } + /** + * Format date as "Month Year" (e.g., "January 2025") + * @param date - Date to format + * @param locale - Locale for month name (default: 'en-US') + * @returns Formatted month and year + */ + formatMonthYear(date, locale = 'en-US') { + return date.toLocaleDateString(locale, { month: 'long', year: 'numeric' }); + } + /** + * Format date as ISO string (same as formatDate for compatibility) + * @param date - Date to format + * @returns ISO date string + */ + formatISODate(date) { + return this.formatDate(date); + } + /** + * Format time in 12-hour format with AM/PM + * @param date - Date to format + * @returns Time string in 12-hour format (e.g., "2:30 PM") + */ + formatTime12(date) { + return dayjs(date).format('h:mm A'); + } + /** + * Get day name for a date + * @param date - Date to get day name for + * @param format - 'short' (e.g., 'Mon') or 'long' (e.g., 'Monday') + * @param locale - Locale for day name (default: 'da-DK') + * @returns Day name + */ + getDayName(date, format = 'short', locale = 'da-DK') { + const formatter = new Intl.DateTimeFormat(locale, { + weekday: format + }); + return formatter.format(date); + } + /** + * Format a date range with customizable options + * @param start - Start date + * @param end - End date + * @param options - Formatting options + * @returns Formatted date range string + */ + formatDateRange(start, end, options = {}) { + const { locale = 'en-US', month = 'short', day = 'numeric' } = options; + const startYear = start.getFullYear(); + const endYear = end.getFullYear(); + const formatter = new Intl.DateTimeFormat(locale, { + month, + day, + year: startYear !== endYear ? 'numeric' : undefined + }); + // @ts-ignore - formatRange is available in modern browsers + if (typeof formatter.formatRange === 'function') { + // @ts-ignore + return formatter.formatRange(start, end); + } + return `${formatter.format(start)} - ${formatter.format(end)}`; + } + // ============================================ + // TIME CALCULATIONS + // ============================================ + /** + * Convert time string (HH:mm or HH:mm:ss) to total minutes since midnight + * @param timeString - Time in format HH:mm or HH:mm:ss + * @returns Total minutes since midnight + */ + timeToMinutes(timeString) { + const parts = timeString.split(':').map(Number); + const hours = parts[0] || 0; + const minutes = parts[1] || 0; + return hours * 60 + minutes; + } + /** + * Convert total minutes since midnight to time string HH:mm + * @param totalMinutes - Minutes since midnight + * @returns Time string in format HH:mm + */ + minutesToTime(totalMinutes) { + const hours = Math.floor(totalMinutes / 60); + const minutes = totalMinutes % 60; + return dayjs().hour(hours).minute(minutes).format('HH:mm'); + } + /** + * Format time from total minutes (alias for minutesToTime) + * @param totalMinutes - Minutes since midnight + * @returns Time string in format HH:mm + */ + formatTimeFromMinutes(totalMinutes) { + return this.minutesToTime(totalMinutes); + } + /** + * Get minutes since midnight for a given date + * @param date - Date to calculate from + * @returns Minutes since midnight + */ + getMinutesSinceMidnight(date) { + const d = dayjs(date); + return d.hour() * 60 + d.minute(); + } + /** + * Calculate duration in minutes between two dates + * @param start - Start date or ISO string + * @param end - End date or ISO string + * @returns Duration in minutes + */ + getDurationMinutes(start, end) { + const startDate = dayjs(start); + const endDate = dayjs(end); + return endDate.diff(startDate, 'minute'); + } + // ============================================ + // WEEK OPERATIONS + // ============================================ + /** + * Get start and end of week (Monday to Sunday) + * @param date - Reference date + * @returns Object with start and end dates + */ + getWeekBounds(date) { + const d = dayjs(date); + return { + start: d.startOf('week').add(1, 'day').toDate(), // Monday (day.js week starts on Sunday) + end: d.endOf('week').add(1, 'day').toDate() // Sunday + }; + } + /** + * Add weeks to a date + * @param date - Base date + * @param weeks - Number of weeks to add (can be negative) + * @returns New date + */ + addWeeks(date, weeks) { + return dayjs(date).add(weeks, 'week').toDate(); + } + /** + * Add months to a date + * @param date - Base date + * @param months - Number of months to add (can be negative) + * @returns New date + */ + addMonths(date, months) { + return dayjs(date).add(months, 'month').toDate(); + } + /** + * Get ISO week number (1-53) + * @param date - Date to get week number for + * @returns ISO week number + */ + getWeekNumber(date) { + return dayjs(date).isoWeek(); + } + /** + * Get all dates in a full week (7 days starting from given date) + * @param weekStart - Start date of the week + * @returns Array of 7 dates + */ + getFullWeekDates(weekStart) { + const dates = []; + for (let i = 0; i < 7; i++) { + dates.push(this.addDays(weekStart, i)); + } + return dates; + } + /** + * Get dates for work week using ISO 8601 day numbering (Monday=1, Sunday=7) + * @param weekStart - Any date in the week + * @param workDays - Array of ISO day numbers (1=Monday, 7=Sunday) + * @returns Array of dates for the specified work days + */ + getWorkWeekDates(weekStart, workDays) { + const dates = []; + // Get Monday of the week + const weekBounds = this.getWeekBounds(weekStart); + const mondayOfWeek = this.startOfDay(weekBounds.start); + // Calculate dates for each work day using ISO numbering + workDays.forEach(isoDay => { + const date = new Date(mondayOfWeek); + // ISO day 1=Monday is +0 days, ISO day 7=Sunday is +6 days + const daysFromMonday = isoDay === 7 ? 6 : isoDay - 1; + date.setDate(mondayOfWeek.getDate() + daysFromMonday); + dates.push(date); + }); + return dates; + } + // ============================================ + // GRID HELPERS + // ============================================ + /** + * Create a date at a specific time (minutes since midnight) + * @param baseDate - Base date (date component) + * @param totalMinutes - Minutes since midnight + * @returns New date with specified time + */ + createDateAtTime(baseDate, totalMinutes) { + const hours = Math.floor(totalMinutes / 60); + const minutes = totalMinutes % 60; + return dayjs(baseDate).startOf('day').hour(hours).minute(minutes).toDate(); + } + /** + * Snap date to nearest interval + * @param date - Date to snap + * @param intervalMinutes - Snap interval in minutes + * @returns Snapped date + */ + snapToInterval(date, intervalMinutes) { + const minutes = this.getMinutesSinceMidnight(date); + const snappedMinutes = Math.round(minutes / intervalMinutes) * intervalMinutes; + return this.createDateAtTime(date, snappedMinutes); + } + // ============================================ + // UTILITY METHODS + // ============================================ + /** + * Check if two dates are the same day + * @param date1 - First date + * @param date2 - Second date + * @returns True if same day + */ + isSameDay(date1, date2) { + return dayjs(date1).isSame(date2, 'day'); + } + /** + * Get start of day + * @param date - Date + * @returns Start of day (00:00:00) + */ + startOfDay(date) { + return dayjs(date).startOf('day').toDate(); + } + /** + * Get end of day + * @param date - Date + * @returns End of day (23:59:59.999) + */ + endOfDay(date) { + return dayjs(date).endOf('day').toDate(); + } + /** + * Add days to a date + * @param date - Base date + * @param days - Number of days to add (can be negative) + * @returns New date + */ + addDays(date, days) { + return dayjs(date).add(days, 'day').toDate(); + } + /** + * Add minutes to a date + * @param date - Base date + * @param minutes - Number of minutes to add (can be negative) + * @returns New date + */ + addMinutes(date, minutes) { + return dayjs(date).add(minutes, 'minute').toDate(); + } + /** + * Parse ISO string to date + * @param isoString - ISO date string + * @returns Parsed date + */ + parseISO(isoString) { + return dayjs(isoString).toDate(); + } + /** + * Check if date is valid + * @param date - Date to check + * @returns True if valid + */ + isValid(date) { + return dayjs(date).isValid(); + } + /** + * Calculate difference in calendar days between two dates + * @param date1 - First date + * @param date2 - Second date + * @returns Number of calendar days between dates (can be negative) + */ + differenceInCalendarDays(date1, date2) { + const d1 = dayjs(date1).startOf('day'); + const d2 = dayjs(date2).startOf('day'); + return d1.diff(d2, 'day'); + } + /** + * Validate date range (start must be before or equal to end) + * @param start - Start date + * @param end - End date + * @returns True if valid range + */ + isValidRange(start, end) { + if (!this.isValid(start) || !this.isValid(end)) { + return false; + } + return start.getTime() <= end.getTime(); + } + /** + * Check if date is within reasonable bounds (1900-2100) + * @param date - Date to check + * @returns True if within bounds + */ + isWithinBounds(date) { + if (!this.isValid(date)) { + return false; + } + const year = date.getFullYear(); + return year >= 1900 && year <= 2100; + } + /** + * Validate date with comprehensive checks + * @param date - Date to validate + * @param options - Validation options + * @returns Validation result with error message + */ + validateDate(date, options = {}) { + if (!this.isValid(date)) { + return { valid: false, error: 'Invalid date' }; + } + if (!this.isWithinBounds(date)) { + return { valid: false, error: 'Date out of bounds (1900-2100)' }; + } + const now = new Date(); + if (options.requireFuture && date <= now) { + return { valid: false, error: 'Date must be in the future' }; + } + if (options.requirePast && date >= now) { + return { valid: false, error: 'Date must be in the past' }; + } + if (options.minDate && date < options.minDate) { + return { valid: false, error: `Date must be after ${this.formatDate(options.minDate)}` }; + } + if (options.maxDate && date > options.maxDate) { + return { valid: false, error: `Date must be before ${this.formatDate(options.maxDate)}` }; + } + return { valid: true }; + } +} +//# sourceMappingURL=DateService.js.map \ No newline at end of file diff --git a/wwwroot/js/utils/DateService.js.map b/wwwroot/js/utils/DateService.js.map new file mode 100644 index 0000000..e976a16 --- /dev/null +++ b/wwwroot/js/utils/DateService.js.map @@ -0,0 +1 @@ +{"version":3,"file":"DateService.js","sourceRoot":"","sources":["../../../src/utils/DateService.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/utils/OverlapDetector.d.ts b/wwwroot/js/utils/OverlapDetector.d.ts new file mode 100644 index 0000000..b4a6994 --- /dev/null +++ b/wwwroot/js/utils/OverlapDetector.d.ts @@ -0,0 +1,33 @@ +/** + * OverlapDetector - Ren tidbaseret overlap detection + * Ingen DOM manipulation, kun tidsberegninger + */ +import { CalendarEvent } from '../types/CalendarTypes'; +export type EventId = string & { + readonly __brand: 'EventId'; +}; +export type OverlapResult = { + overlappingEvents: CalendarEvent[]; + stackLinks: Map; +}; +export interface StackLink { + prev?: EventId; + next?: EventId; + stackLevel: number; +} +export declare class OverlapDetector { + /** + * Resolver hvilke events et givent event overlapper med i en kolonne + * @param event - CalendarEvent der skal checkes for overlap + * @param columnEvents - Array af CalendarEvent objekter i kolonnen + * @returns Array af events som det givne event overlapper med + */ + resolveOverlap(event: CalendarEvent, columnEvents: CalendarEvent[]): CalendarEvent[]; + /** + * Dekorerer events med stack linking data + * @param newEvent - Det nye event der skal tilføjes + * @param overlappingEvents - Events som det nye event overlapper med + * @returns OverlapResult med overlappende events og stack links + */ + decorateWithStackLinks(newEvent: CalendarEvent, overlappingEvents: CalendarEvent[]): OverlapResult; +} diff --git a/wwwroot/js/utils/OverlapDetector.js b/wwwroot/js/utils/OverlapDetector.js new file mode 100644 index 0000000..09ddd6b --- /dev/null +++ b/wwwroot/js/utils/OverlapDetector.js @@ -0,0 +1,52 @@ +/** + * OverlapDetector - Ren tidbaseret overlap detection + * Ingen DOM manipulation, kun tidsberegninger + */ +export class OverlapDetector { + /** + * Resolver hvilke events et givent event overlapper med i en kolonne + * @param event - CalendarEvent der skal checkes for overlap + * @param columnEvents - Array af CalendarEvent objekter i kolonnen + * @returns Array af events som det givne event overlapper med + */ + resolveOverlap(event, columnEvents) { + return columnEvents.filter(existingEvent => { + // To events overlapper hvis: + // event starter før existing slutter OG + // event slutter efter existing starter + return event.start < existingEvent.end && event.end > existingEvent.start; + }); + } + /** + * Dekorerer events med stack linking data + * @param newEvent - Det nye event der skal tilføjes + * @param overlappingEvents - Events som det nye event overlapper med + * @returns OverlapResult med overlappende events og stack links + */ + decorateWithStackLinks(newEvent, overlappingEvents) { + const stackLinks = new Map(); + if (overlappingEvents.length === 0) { + return { + overlappingEvents: [], + stackLinks + }; + } + // Kombiner nyt event med eksisterende og sortér efter start tid (tidligste første) + const allEvents = [...overlappingEvents, newEvent].sort((a, b) => a.start.getTime() - b.start.getTime()); + // Opret sammenhængende kæde - alle events bindes sammen + allEvents.forEach((event, index) => { + const stackLink = { + stackLevel: index, + prev: index > 0 ? allEvents[index - 1].id : undefined, + next: index < allEvents.length - 1 ? allEvents[index + 1].id : undefined + }; + stackLinks.set(event.id, stackLink); + }); + overlappingEvents.push(newEvent); + return { + overlappingEvents, + stackLinks + }; + } +} +//# sourceMappingURL=OverlapDetector.js.map \ No newline at end of file diff --git a/wwwroot/js/utils/OverlapDetector.js.map b/wwwroot/js/utils/OverlapDetector.js.map new file mode 100644 index 0000000..941cbb5 --- /dev/null +++ b/wwwroot/js/utils/OverlapDetector.js.map @@ -0,0 +1 @@ +{"version":3,"file":"OverlapDetector.js","sourceRoot":"","sources":["../../../src/utils/OverlapDetector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAkBH,MAAM,OAAO,eAAe;IAE1B;;;;;OAKG;IACI,cAAc,CAAC,KAAoB,EAAE,YAA6B;QACvE,OAAO,YAAY,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE;YACzC,6BAA6B;YAC7B,wCAAwC;YACxC,uCAAuC;YACvC,OAAO,KAAK,CAAC,KAAK,GAAG,aAAa,CAAC,GAAG,IAAI,KAAK,CAAC,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC;QAC5E,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACI,sBAAsB,CAAC,QAAuB,EAAE,iBAAkC;QACvF,MAAM,UAAU,GAAG,IAAI,GAAG,EAAsB,CAAC;QAEjD,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACnC,OAAO;gBACL,iBAAiB,EAAE,EAAE;gBACrB,UAAU;aACX,CAAC;QACJ,CAAC;QAED,mFAAmF;QACnF,MAAM,SAAS,GAAG,CAAC,GAAG,iBAAiB,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAC/D,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,CACtC,CAAC;QAEF,wDAAwD;QACxD,SAAS,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;YACjC,MAAM,SAAS,GAAc;gBAC3B,UAAU,EAAE,KAAK;gBACjB,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,EAAa,CAAC,CAAC,CAAC,SAAS;gBAChE,IAAI,EAAE,KAAK,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,EAAa,CAAC,CAAC,CAAC,SAAS;aACpF,CAAC;YACF,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,EAAa,EAAE,SAAS,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;QACH,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjC,OAAO;YACL,iBAAiB;YACjB,UAAU;SACX,CAAC;IACJ,CAAC;CACF"} \ No newline at end of file diff --git a/wwwroot/js/utils/PositionUtils.d.ts b/wwwroot/js/utils/PositionUtils.d.ts new file mode 100644 index 0000000..8171427 --- /dev/null +++ b/wwwroot/js/utils/PositionUtils.d.ts @@ -0,0 +1,101 @@ +import { Configuration } from '../configurations/CalendarConfig'; +import { IColumnBounds } from './ColumnDetectionUtils'; +import { DateService } from './DateService'; +/** + * PositionUtils - Positioning utilities with dependency injection + * Focuses on pixel/position calculations while delegating date operations + * + * Note: Uses DateService with date-fns for all date/time operations + */ +export declare class PositionUtils { + private dateService; + private config; + constructor(dateService: DateService, config: Configuration); + /** + * Convert minutes to pixels + */ + minutesToPixels(minutes: number): number; + /** + * Convert pixels to minutes + */ + pixelsToMinutes(pixels: number): number; + /** + * Convert time (HH:MM) to pixels from day start using DateService + */ + timeToPixels(timeString: string): number; + /** + * Convert Date object to pixels from day start using DateService + */ + dateToPixels(date: Date): number; + /** + * Convert pixels to time using DateService + */ + pixelsToTime(pixels: number): string; + /** + * Beregn event position og størrelse + */ + calculateEventPosition(startTime: string | Date, endTime: string | Date): { + top: number; + height: number; + duration: number; + }; + /** + * Snap position til grid interval + */ + snapToGrid(pixels: number): number; + /** + * Snap time to interval using DateService + */ + snapTimeToInterval(timeString: string): string; + /** + * Beregn kolonne position for overlappende events + */ + calculateColumnPosition(eventIndex: number, totalColumns: number, containerWidth: number): { + left: number; + width: number; + }; + /** + * Check om to events overlapper i tid + */ + eventsOverlap(start1: string | Date, end1: string | Date, start2: string | Date, end2: string | Date): boolean; + /** + * Beregn Y position fra mouse/touch koordinat + */ + getPositionFromCoordinate(clientY: number, column: IColumnBounds): number; + /** + * Valider at tid er inden for arbejdstimer + */ + isWithinWorkHours(timeString: string): boolean; + /** + * Valider at tid er inden for dag grænser + */ + isWithinDayBounds(timeString: string): boolean; + /** + * Hent minimum event højde i pixels + */ + getMinimumEventHeight(): number; + /** + * Hent maksimum event højde i pixels (hele dagen) + */ + getMaximumEventHeight(): number; + /** + * Beregn total kalender højde + */ + getTotalCalendarHeight(): number; + /** + * Convert ISO datetime to time string with UTC-to-local conversion + */ + isoToTimeString(isoString: string): string; + /** + * Convert time string to ISO datetime using DateService with timezone handling + */ + timeStringToIso(timeString: string, date?: Date): string; + /** + * Calculate event duration using DateService + */ + calculateDuration(startTime: string | Date, endTime: string | Date): number; + /** + * Format duration to readable text (Danish) + */ + formatDuration(minutes: number): string; +} diff --git a/wwwroot/js/utils/PositionUtils.js b/wwwroot/js/utils/PositionUtils.js new file mode 100644 index 0000000..9dd1956 --- /dev/null +++ b/wwwroot/js/utils/PositionUtils.js @@ -0,0 +1,209 @@ +import { TimeFormatter } from './TimeFormatter'; +/** + * PositionUtils - Positioning utilities with dependency injection + * Focuses on pixel/position calculations while delegating date operations + * + * Note: Uses DateService with date-fns for all date/time operations + */ +export class PositionUtils { + constructor(dateService, config) { + this.dateService = dateService; + this.config = config; + } + /** + * Convert minutes to pixels + */ + minutesToPixels(minutes) { + const gridSettings = this.config.gridSettings; + const pixelsPerHour = gridSettings.hourHeight; + return (minutes / 60) * pixelsPerHour; + } + /** + * Convert pixels to minutes + */ + pixelsToMinutes(pixels) { + const gridSettings = this.config.gridSettings; + const pixelsPerHour = gridSettings.hourHeight; + return (pixels / pixelsPerHour) * 60; + } + /** + * Convert time (HH:MM) to pixels from day start using DateService + */ + timeToPixels(timeString) { + const totalMinutes = this.dateService.timeToMinutes(timeString); + const gridSettings = this.config.gridSettings; + const dayStartMinutes = gridSettings.dayStartHour * 60; + const minutesFromDayStart = totalMinutes - dayStartMinutes; + return this.minutesToPixels(minutesFromDayStart); + } + /** + * Convert Date object to pixels from day start using DateService + */ + dateToPixels(date) { + const totalMinutes = this.dateService.getMinutesSinceMidnight(date); + const gridSettings = this.config.gridSettings; + const dayStartMinutes = gridSettings.dayStartHour * 60; + const minutesFromDayStart = totalMinutes - dayStartMinutes; + return this.minutesToPixels(minutesFromDayStart); + } + /** + * Convert pixels to time using DateService + */ + pixelsToTime(pixels) { + const minutes = this.pixelsToMinutes(pixels); + const gridSettings = this.config.gridSettings; + const dayStartMinutes = gridSettings.dayStartHour * 60; + const totalMinutes = dayStartMinutes + minutes; + return this.dateService.minutesToTime(totalMinutes); + } + /** + * Beregn event position og størrelse + */ + calculateEventPosition(startTime, endTime) { + let startPixels; + let endPixels; + if (typeof startTime === 'string') { + startPixels = this.timeToPixels(startTime); + } + else { + startPixels = this.dateToPixels(startTime); + } + if (typeof endTime === 'string') { + endPixels = this.timeToPixels(endTime); + } + else { + endPixels = this.dateToPixels(endTime); + } + const height = Math.max(endPixels - startPixels, this.getMinimumEventHeight()); + const duration = this.pixelsToMinutes(height); + return { + top: startPixels, + height, + duration + }; + } + /** + * Snap position til grid interval + */ + snapToGrid(pixels) { + const gridSettings = this.config.gridSettings; + const snapInterval = gridSettings.snapInterval; + const snapPixels = this.minutesToPixels(snapInterval); + return Math.round(pixels / snapPixels) * snapPixels; + } + /** + * Snap time to interval using DateService + */ + snapTimeToInterval(timeString) { + const totalMinutes = this.dateService.timeToMinutes(timeString); + const gridSettings = this.config.gridSettings; + const snapInterval = gridSettings.snapInterval; + const snappedMinutes = Math.round(totalMinutes / snapInterval) * snapInterval; + return this.dateService.minutesToTime(snappedMinutes); + } + /** + * Beregn kolonne position for overlappende events + */ + calculateColumnPosition(eventIndex, totalColumns, containerWidth) { + const columnWidth = containerWidth / totalColumns; + const left = eventIndex * columnWidth; + // Lav lidt margin mellem kolonnerne + const margin = 2; + const adjustedWidth = columnWidth - margin; + return { + left: left + (margin / 2), + width: Math.max(adjustedWidth, 50) // Minimum width + }; + } + /** + * Check om to events overlapper i tid + */ + eventsOverlap(start1, end1, start2, end2) { + const pos1 = this.calculateEventPosition(start1, end1); + const pos2 = this.calculateEventPosition(start2, end2); + const event1End = pos1.top + pos1.height; + const event2End = pos2.top + pos2.height; + return !(event1End <= pos2.top || event2End <= pos1.top); + } + /** + * Beregn Y position fra mouse/touch koordinat + */ + getPositionFromCoordinate(clientY, column) { + const relativeY = clientY - column.boundingClientRect.top; + // Snap til grid + return this.snapToGrid(relativeY); + } + /** + * Valider at tid er inden for arbejdstimer + */ + isWithinWorkHours(timeString) { + const [hours] = timeString.split(':').map(Number); + const gridSettings = this.config.gridSettings; + return hours >= gridSettings.workStartHour && hours < gridSettings.workEndHour; + } + /** + * Valider at tid er inden for dag grænser + */ + isWithinDayBounds(timeString) { + const [hours] = timeString.split(':').map(Number); + const gridSettings = this.config.gridSettings; + return hours >= gridSettings.dayStartHour && hours < gridSettings.dayEndHour; + } + /** + * Hent minimum event højde i pixels + */ + getMinimumEventHeight() { + // Minimum 15 minutter + return this.minutesToPixels(15); + } + /** + * Hent maksimum event højde i pixels (hele dagen) + */ + getMaximumEventHeight() { + const gridSettings = this.config.gridSettings; + const dayDurationHours = gridSettings.dayEndHour - gridSettings.dayStartHour; + return dayDurationHours * gridSettings.hourHeight; + } + /** + * Beregn total kalender højde + */ + getTotalCalendarHeight() { + return this.getMaximumEventHeight(); + } + /** + * Convert ISO datetime to time string with UTC-to-local conversion + */ + isoToTimeString(isoString) { + const date = new Date(isoString); + return TimeFormatter.formatTime(date); + } + /** + * Convert time string to ISO datetime using DateService with timezone handling + */ + timeStringToIso(timeString, date = new Date()) { + const totalMinutes = this.dateService.timeToMinutes(timeString); + const newDate = this.dateService.createDateAtTime(date, totalMinutes); + return this.dateService.toUTC(newDate); + } + /** + * Calculate event duration using DateService + */ + calculateDuration(startTime, endTime) { + return this.dateService.getDurationMinutes(startTime, endTime); + } + /** + * Format duration to readable text (Danish) + */ + formatDuration(minutes) { + if (minutes < 60) { + return `${minutes} min`; + } + const hours = Math.floor(minutes / 60); + const remainingMinutes = minutes % 60; + if (remainingMinutes === 0) { + return `${hours} time${hours !== 1 ? 'r' : ''}`; + } + return `${hours}t ${remainingMinutes}m`; + } +} +//# sourceMappingURL=PositionUtils.js.map \ No newline at end of file diff --git a/wwwroot/js/utils/PositionUtils.js.map b/wwwroot/js/utils/PositionUtils.js.map new file mode 100644 index 0000000..4d1125c --- /dev/null +++ b/wwwroot/js/utils/PositionUtils.js.map @@ -0,0 +1 @@ +{"version":3,"file":"PositionUtils.js","sourceRoot":"","sources":["../../../src/utils/PositionUtils.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/js/utils/TimeFormatter.d.ts b/wwwroot/js/utils/TimeFormatter.d.ts new file mode 100644 index 0000000..d52a9f2 --- /dev/null +++ b/wwwroot/js/utils/TimeFormatter.d.ts @@ -0,0 +1,45 @@ +/** + * TimeFormatter - Centralized time formatting with timezone support + * Now uses DateService internally for all date/time operations + * + * Handles conversion from UTC/Zulu time to configured timezone (default: Europe/Copenhagen) + * Supports both 12-hour and 24-hour format configuration + * + * All events in the system are stored in UTC and must be converted to local timezone + */ +import { ITimeFormatConfig } from '../configurations/TimeFormatConfig'; +export declare class TimeFormatter { + private static settings; + private static dateService; + private static getDateService; + /** + * Configure time formatting settings + * Must be called before using TimeFormatter + */ + static configure(settings: ITimeFormatConfig): void; + /** + * Convert UTC date to configured timezone (internal helper) + * @param utcDate - Date in UTC (or ISO string) + * @returns Date object adjusted to configured timezone + */ + private static convertToLocalTime; + /** + * Format time in 24-hour format using DateService (internal helper) + * @param date - Date to format + * @returns Formatted time string (e.g., "09:00") + */ + private static format24Hour; + /** + * Format time according to current configuration + * @param date - Date to format + * @returns Formatted time string + */ + static formatTime(date: Date): string; + /** + * Format time range (start - end) using DateService + * @param startDate - Start date + * @param endDate - End date + * @returns Formatted time range string (e.g., "09:00 - 10:30") + */ + static formatTimeRange(startDate: Date, endDate: Date): string; +} diff --git a/wwwroot/js/utils/TimeFormatter.js b/wwwroot/js/utils/TimeFormatter.js new file mode 100644 index 0000000..72ab72c --- /dev/null +++ b/wwwroot/js/utils/TimeFormatter.js @@ -0,0 +1,92 @@ +/** + * TimeFormatter - Centralized time formatting with timezone support + * Now uses DateService internally for all date/time operations + * + * Handles conversion from UTC/Zulu time to configured timezone (default: Europe/Copenhagen) + * Supports both 12-hour and 24-hour format configuration + * + * All events in the system are stored in UTC and must be converted to local timezone + */ +import { DateService } from './DateService'; +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import timezone from 'dayjs/plugin/timezone'; +// Enable day.js plugins for timezone formatting +dayjs.extend(utc); +dayjs.extend(timezone); +export class TimeFormatter { + static getDateService() { + if (!TimeFormatter.dateService) { + if (!TimeFormatter.settings) { + throw new Error('TimeFormatter must be configured before use. Call TimeFormatter.configure() first.'); + } + // Create a minimal config object for DateService + const config = { + timeFormatConfig: { + timezone: TimeFormatter.settings.timezone + } + }; + TimeFormatter.dateService = new DateService(config); + } + return TimeFormatter.dateService; + } + /** + * Configure time formatting settings + * Must be called before using TimeFormatter + */ + static configure(settings) { + TimeFormatter.settings = settings; + // Reset DateService to pick up new timezone + TimeFormatter.dateService = null; + } + /** + * Convert UTC date to configured timezone (internal helper) + * @param utcDate - Date in UTC (or ISO string) + * @returns Date object adjusted to configured timezone + */ + static convertToLocalTime(utcDate) { + if (typeof utcDate === 'string') { + return TimeFormatter.getDateService().fromUTC(utcDate); + } + // If it's already a Date object, convert to UTC string first, then back to local + const utcString = utcDate.toISOString(); + return TimeFormatter.getDateService().fromUTC(utcString); + } + /** + * Format time in 24-hour format using DateService (internal helper) + * @param date - Date to format + * @returns Formatted time string (e.g., "09:00") + */ + static format24Hour(date) { + if (!TimeFormatter.settings) { + throw new Error('TimeFormatter must be configured before use. Call TimeFormatter.configure() first.'); + } + // Use day.js directly to format with timezone awareness + const pattern = TimeFormatter.settings.showSeconds ? 'HH:mm:ss' : 'HH:mm'; + return dayjs.utc(date).tz(TimeFormatter.settings.timezone).format(pattern); + } + /** + * Format time according to current configuration + * @param date - Date to format + * @returns Formatted time string + */ + static formatTime(date) { + // Always use 24-hour format (12-hour support removed as unused) + return TimeFormatter.format24Hour(date); + } + /** + * Format time range (start - end) using DateService + * @param startDate - Start date + * @param endDate - End date + * @returns Formatted time range string (e.g., "09:00 - 10:30") + */ + static formatTimeRange(startDate, endDate) { + const localStart = TimeFormatter.convertToLocalTime(startDate); + const localEnd = TimeFormatter.convertToLocalTime(endDate); + return TimeFormatter.getDateService().formatTimeRange(localStart, localEnd); + } +} +TimeFormatter.settings = null; +// DateService will be initialized lazily to avoid circular dependency with CalendarConfig +TimeFormatter.dateService = null; +//# sourceMappingURL=TimeFormatter.js.map \ No newline at end of file diff --git a/wwwroot/js/utils/TimeFormatter.js.map b/wwwroot/js/utils/TimeFormatter.js.map new file mode 100644 index 0000000..e5a05ec --- /dev/null +++ b/wwwroot/js/utils/TimeFormatter.js.map @@ -0,0 +1 @@ +{"version":3,"file":"TimeFormatter.js","sourceRoot":"","sources":["../../../src/utils/TimeFormatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,kBAAkB,CAAC;AACnC,OAAO,QAAQ,MAAM,uBAAuB,CAAC;AAE7C,gDAAgD;AAChD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAClB,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAEvB,MAAM,OAAO,aAAa;IAMhB,MAAM,CAAC,cAAc;QAC3B,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,CAAC;YAC/B,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,CAAC;gBAC5B,MAAM,IAAI,KAAK,CAAC,oFAAoF,CAAC,CAAC;YACxG,CAAC;YACD,iDAAiD;YACjD,MAAM,MAAM,GAAG;gBACb,gBAAgB,EAAE;oBAChB,QAAQ,EAAE,aAAa,CAAC,QAAQ,CAAC,QAAQ;iBAC1C;aACF,CAAC;YACF,aAAa,CAAC,WAAW,GAAG,IAAI,WAAW,CAAC,MAAa,CAAC,CAAC;QAC7D,CAAC;QACD,OAAO,aAAa,CAAC,WAAW,CAAC;IACnC,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,SAAS,CAAC,QAA2B;QAC1C,aAAa,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAClC,4CAA4C;QAC5C,aAAa,CAAC,WAAW,GAAG,IAAI,CAAC;IACnC,CAAC;IAED;;;;OAIG;IACK,MAAM,CAAC,kBAAkB,CAAC,OAAsB;QACtD,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YAChC,OAAO,aAAa,CAAC,cAAc,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACzD,CAAC;QAED,iFAAiF;QACjF,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;QACxC,OAAO,aAAa,CAAC,cAAc,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC3D,CAAC;IAED;;;;OAIG;IACK,MAAM,CAAC,YAAY,CAAC,IAAU;QACpC,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,oFAAoF,CAAC,CAAC;QACxG,CAAC;QAED,wDAAwD;QACxD,MAAM,OAAO,GAAG,aAAa,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC;QAC1E,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7E,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,UAAU,CAAC,IAAU;QAC1B,gEAAgE;QAChE,OAAO,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAC1C,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,eAAe,CAAC,SAAe,EAAE,OAAa;QACnD,MAAM,UAAU,GAAG,aAAa,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC;QAC/D,MAAM,QAAQ,GAAG,aAAa,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;QAC3D,OAAO,aAAa,CAAC,cAAc,EAAE,CAAC,eAAe,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC9E,CAAC;;AAjFc,sBAAQ,GAA6B,IAAI,CAAC;AAEzD,0FAA0F;AAC3E,yBAAW,GAAuB,IAAI,CAAC"} \ No newline at end of file diff --git a/wwwroot/js/utils/URLManager.d.ts b/wwwroot/js/utils/URLManager.d.ts new file mode 100644 index 0000000..1b4d811 --- /dev/null +++ b/wwwroot/js/utils/URLManager.d.ts @@ -0,0 +1,29 @@ +import { IEventBus } from '../types/CalendarTypes'; +/** + * URLManager handles URL query parameter parsing and deep linking functionality + * Follows event-driven architecture with no global state + */ +export declare class URLManager { + private eventBus; + constructor(eventBus: IEventBus); + /** + * Parse eventId from URL query parameters + * @returns eventId string or null if not found + */ + parseEventIdFromURL(): string | null; + /** + * Get all query parameters as an object + * @returns object with all query parameters + */ + getAllQueryParams(): Record; + /** + * Update URL without page reload (for future use) + * @param params object with parameters to update + */ + updateURL(params: Record): void; + /** + * Check if current URL has any query parameters + * @returns true if URL has query parameters + */ + hasQueryParams(): boolean; +} diff --git a/wwwroot/js/utils/URLManager.js b/wwwroot/js/utils/URLManager.js new file mode 100644 index 0000000..472dce8 --- /dev/null +++ b/wwwroot/js/utils/URLManager.js @@ -0,0 +1,76 @@ +/** + * URLManager handles URL query parameter parsing and deep linking functionality + * Follows event-driven architecture with no global state + */ +export class URLManager { + constructor(eventBus) { + this.eventBus = eventBus; + } + /** + * Parse eventId from URL query parameters + * @returns eventId string or null if not found + */ + parseEventIdFromURL() { + try { + const urlParams = new URLSearchParams(window.location.search); + const eventId = urlParams.get('eventId'); + if (eventId && eventId.trim() !== '') { + return eventId.trim(); + } + return null; + } + catch (error) { + console.warn('URLManager: Failed to parse URL parameters:', error); + return null; + } + } + /** + * Get all query parameters as an object + * @returns object with all query parameters + */ + getAllQueryParams() { + try { + const urlParams = new URLSearchParams(window.location.search); + const params = {}; + for (const [key, value] of urlParams.entries()) { + params[key] = value; + } + return params; + } + catch (error) { + console.warn('URLManager: Failed to parse URL parameters:', error); + return {}; + } + } + /** + * Update URL without page reload (for future use) + * @param params object with parameters to update + */ + updateURL(params) { + try { + const url = new URL(window.location.href); + // Update or remove parameters + Object.entries(params).forEach(([key, value]) => { + if (value === null) { + url.searchParams.delete(key); + } + else { + url.searchParams.set(key, value); + } + }); + // Update URL without page reload + window.history.replaceState({}, '', url.toString()); + } + catch (error) { + console.warn('URLManager: Failed to update URL:', error); + } + } + /** + * Check if current URL has any query parameters + * @returns true if URL has query parameters + */ + hasQueryParams() { + return window.location.search.length > 0; + } +} +//# sourceMappingURL=URLManager.js.map \ No newline at end of file diff --git a/wwwroot/js/utils/URLManager.js.map b/wwwroot/js/utils/URLManager.js.map new file mode 100644 index 0000000..5cd8f88 --- /dev/null +++ b/wwwroot/js/utils/URLManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"URLManager.js","sourceRoot":"","sources":["../../../src/utils/URLManager.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,OAAO,UAAU;IAGnB,YAAY,QAAmB;QAC3B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC7B,CAAC;IAED;;;OAGG;IACI,mBAAmB;QACtB,IAAI,CAAC;YACD,MAAM,SAAS,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YAC9D,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAEzC,IAAI,OAAO,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;gBACnC,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;YAC1B,CAAC;YAED,OAAO,IAAI,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,6CAA6C,EAAE,KAAK,CAAC,CAAC;YACnE,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;IAED;;;OAGG;IACI,iBAAiB;QACpB,IAAI,CAAC;YACD,MAAM,SAAS,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YAC9D,MAAM,MAAM,GAA2B,EAAE,CAAC;YAE1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,SAAS,CAAC,OAAO,EAAE,EAAE,CAAC;gBAC7C,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YACxB,CAAC;YAED,OAAO,MAAM,CAAC;QAClB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,6CAA6C,EAAE,KAAK,CAAC,CAAC;YACnE,OAAO,EAAE,CAAC;QACd,CAAC;IACL,CAAC;IAED;;;OAGG;IACI,SAAS,CAAC,MAAqC;QAClD,IAAI,CAAC;YACD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAE1C,8BAA8B;YAC9B,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;gBAC5C,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;oBACjB,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACjC,CAAC;qBAAM,CAAC;oBACJ,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;gBACrC,CAAC;YACL,CAAC,CAAC,CAAC;YAEH,iCAAiC;YACjC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;QACxD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,mCAAmC,EAAE,KAAK,CAAC,CAAC;QAC7D,CAAC;IACL,CAAC;IAED;;;OAGG;IACI,cAAc;QACjB,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAC7C,CAAC;CACJ"} \ No newline at end of file diff --git a/wwwroot/js/v2-demo.js b/wwwroot/js/v2-demo.js new file mode 100644 index 0000000..9fde2c8 --- /dev/null +++ b/wwwroot/js/v2-demo.js @@ -0,0 +1,6463 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); + +// node_modules/dayjs/dayjs.min.js +var require_dayjs_min = __commonJS({ + "node_modules/dayjs/dayjs.min.js"(exports, module) { + !function(t, e) { + "object" == typeof exports && "undefined" != typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define(e) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs = e(); + }(exports, function() { + "use strict"; + var t = 1e3, e = 6e4, n = 36e5, r = "millisecond", i = "second", s = "minute", u = "hour", a = "day", o = "week", c = "month", f = "quarter", h = "year", d = "date", l = "Invalid Date", $ = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/, y = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g, M = { name: "en", weekdays: "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), months: "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), ordinal: function(t2) { + var e2 = ["th", "st", "nd", "rd"], n2 = t2 % 100; + return "[" + t2 + (e2[(n2 - 20) % 10] || e2[n2] || e2[0]) + "]"; + } }, m = /* @__PURE__ */ __name(function(t2, e2, n2) { + var r2 = String(t2); + return !r2 || r2.length >= e2 ? t2 : "" + Array(e2 + 1 - r2.length).join(n2) + t2; + }, "m"), v = { s: m, z: function(t2) { + var e2 = -t2.utcOffset(), n2 = Math.abs(e2), r2 = Math.floor(n2 / 60), i2 = n2 % 60; + return (e2 <= 0 ? "+" : "-") + m(r2, 2, "0") + ":" + m(i2, 2, "0"); + }, m: /* @__PURE__ */ __name(function t2(e2, n2) { + if (e2.date() < n2.date()) + return -t2(n2, e2); + var r2 = 12 * (n2.year() - e2.year()) + (n2.month() - e2.month()), i2 = e2.clone().add(r2, c), s2 = n2 - i2 < 0, u2 = e2.clone().add(r2 + (s2 ? -1 : 1), c); + return +(-(r2 + (n2 - i2) / (s2 ? i2 - u2 : u2 - i2)) || 0); + }, "t"), a: function(t2) { + return t2 < 0 ? Math.ceil(t2) || 0 : Math.floor(t2); + }, p: function(t2) { + return { M: c, y: h, w: o, d: a, D: d, h: u, m: s, s: i, ms: r, Q: f }[t2] || String(t2 || "").toLowerCase().replace(/s$/, ""); + }, u: function(t2) { + return void 0 === t2; + } }, g = "en", D = {}; + D[g] = M; + var p = "$isDayjsObject", S = /* @__PURE__ */ __name(function(t2) { + return t2 instanceof _ || !(!t2 || !t2[p]); + }, "S"), w = /* @__PURE__ */ __name(function t2(e2, n2, r2) { + var i2; + if (!e2) + return g; + if ("string" == typeof e2) { + var s2 = e2.toLowerCase(); + D[s2] && (i2 = s2), n2 && (D[s2] = n2, i2 = s2); + var u2 = e2.split("-"); + if (!i2 && u2.length > 1) + return t2(u2[0]); + } else { + var a2 = e2.name; + D[a2] = e2, i2 = a2; + } + return !r2 && i2 && (g = i2), i2 || !r2 && g; + }, "t"), O = /* @__PURE__ */ __name(function(t2, e2) { + if (S(t2)) + return t2.clone(); + var n2 = "object" == typeof e2 ? e2 : {}; + return n2.date = t2, n2.args = arguments, new _(n2); + }, "O"), b = v; + b.l = w, b.i = S, b.w = function(t2, e2) { + return O(t2, { locale: e2.$L, utc: e2.$u, x: e2.$x, $offset: e2.$offset }); + }; + var _ = function() { + function M2(t2) { + this.$L = w(t2.locale, null, true), this.parse(t2), this.$x = this.$x || t2.x || {}, this[p] = true; + } + __name(M2, "M"); + var m2 = M2.prototype; + return m2.parse = function(t2) { + this.$d = function(t3) { + var e2 = t3.date, n2 = t3.utc; + if (null === e2) + return /* @__PURE__ */ new Date(NaN); + if (b.u(e2)) + return /* @__PURE__ */ new Date(); + if (e2 instanceof Date) + return new Date(e2); + if ("string" == typeof e2 && !/Z$/i.test(e2)) { + var r2 = e2.match($); + if (r2) { + var i2 = r2[2] - 1 || 0, s2 = (r2[7] || "0").substring(0, 3); + return n2 ? new Date(Date.UTC(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2)) : new Date(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2); + } + } + return new Date(e2); + }(t2), this.init(); + }, m2.init = function() { + var t2 = this.$d; + this.$y = t2.getFullYear(), this.$M = t2.getMonth(), this.$D = t2.getDate(), this.$W = t2.getDay(), this.$H = t2.getHours(), this.$m = t2.getMinutes(), this.$s = t2.getSeconds(), this.$ms = t2.getMilliseconds(); + }, m2.$utils = function() { + return b; + }, m2.isValid = function() { + return !(this.$d.toString() === l); + }, m2.isSame = function(t2, e2) { + var n2 = O(t2); + return this.startOf(e2) <= n2 && n2 <= this.endOf(e2); + }, m2.isAfter = function(t2, e2) { + return O(t2) < this.startOf(e2); + }, m2.isBefore = function(t2, e2) { + return this.endOf(e2) < O(t2); + }, m2.$g = function(t2, e2, n2) { + return b.u(t2) ? this[e2] : this.set(n2, t2); + }, m2.unix = function() { + return Math.floor(this.valueOf() / 1e3); + }, m2.valueOf = function() { + return this.$d.getTime(); + }, m2.startOf = function(t2, e2) { + var n2 = this, r2 = !!b.u(e2) || e2, f2 = b.p(t2), l2 = /* @__PURE__ */ __name(function(t3, e3) { + var i2 = b.w(n2.$u ? Date.UTC(n2.$y, e3, t3) : new Date(n2.$y, e3, t3), n2); + return r2 ? i2 : i2.endOf(a); + }, "l"), $2 = /* @__PURE__ */ __name(function(t3, e3) { + return b.w(n2.toDate()[t3].apply(n2.toDate("s"), (r2 ? [0, 0, 0, 0] : [23, 59, 59, 999]).slice(e3)), n2); + }, "$"), y2 = this.$W, M3 = this.$M, m3 = this.$D, v2 = "set" + (this.$u ? "UTC" : ""); + switch (f2) { + case h: + return r2 ? l2(1, 0) : l2(31, 11); + case c: + return r2 ? l2(1, M3) : l2(0, M3 + 1); + case o: + var g2 = this.$locale().weekStart || 0, D2 = (y2 < g2 ? y2 + 7 : y2) - g2; + return l2(r2 ? m3 - D2 : m3 + (6 - D2), M3); + case a: + case d: + return $2(v2 + "Hours", 0); + case u: + return $2(v2 + "Minutes", 1); + case s: + return $2(v2 + "Seconds", 2); + case i: + return $2(v2 + "Milliseconds", 3); + default: + return this.clone(); + } + }, m2.endOf = function(t2) { + return this.startOf(t2, false); + }, m2.$set = function(t2, e2) { + var n2, o2 = b.p(t2), f2 = "set" + (this.$u ? "UTC" : ""), l2 = (n2 = {}, n2[a] = f2 + "Date", n2[d] = f2 + "Date", n2[c] = f2 + "Month", n2[h] = f2 + "FullYear", n2[u] = f2 + "Hours", n2[s] = f2 + "Minutes", n2[i] = f2 + "Seconds", n2[r] = f2 + "Milliseconds", n2)[o2], $2 = o2 === a ? this.$D + (e2 - this.$W) : e2; + if (o2 === c || o2 === h) { + var y2 = this.clone().set(d, 1); + y2.$d[l2]($2), y2.init(), this.$d = y2.set(d, Math.min(this.$D, y2.daysInMonth())).$d; + } else + l2 && this.$d[l2]($2); + return this.init(), this; + }, m2.set = function(t2, e2) { + return this.clone().$set(t2, e2); + }, m2.get = function(t2) { + return this[b.p(t2)](); + }, m2.add = function(r2, f2) { + var d2, l2 = this; + r2 = Number(r2); + var $2 = b.p(f2), y2 = /* @__PURE__ */ __name(function(t2) { + var e2 = O(l2); + return b.w(e2.date(e2.date() + Math.round(t2 * r2)), l2); + }, "y"); + if ($2 === c) + return this.set(c, this.$M + r2); + if ($2 === h) + return this.set(h, this.$y + r2); + if ($2 === a) + return y2(1); + if ($2 === o) + return y2(7); + var M3 = (d2 = {}, d2[s] = e, d2[u] = n, d2[i] = t, d2)[$2] || 1, m3 = this.$d.getTime() + r2 * M3; + return b.w(m3, this); + }, m2.subtract = function(t2, e2) { + return this.add(-1 * t2, e2); + }, m2.format = function(t2) { + var e2 = this, n2 = this.$locale(); + if (!this.isValid()) + return n2.invalidDate || l; + var r2 = t2 || "YYYY-MM-DDTHH:mm:ssZ", i2 = b.z(this), s2 = this.$H, u2 = this.$m, a2 = this.$M, o2 = n2.weekdays, c2 = n2.months, f2 = n2.meridiem, h2 = /* @__PURE__ */ __name(function(t3, n3, i3, s3) { + return t3 && (t3[n3] || t3(e2, r2)) || i3[n3].slice(0, s3); + }, "h"), d2 = /* @__PURE__ */ __name(function(t3) { + return b.s(s2 % 12 || 12, t3, "0"); + }, "d"), $2 = f2 || function(t3, e3, n3) { + var r3 = t3 < 12 ? "AM" : "PM"; + return n3 ? r3.toLowerCase() : r3; + }; + return r2.replace(y, function(t3, r3) { + return r3 || function(t4) { + switch (t4) { + case "YY": + return String(e2.$y).slice(-2); + case "YYYY": + return b.s(e2.$y, 4, "0"); + case "M": + return a2 + 1; + case "MM": + return b.s(a2 + 1, 2, "0"); + case "MMM": + return h2(n2.monthsShort, a2, c2, 3); + case "MMMM": + return h2(c2, a2); + case "D": + return e2.$D; + case "DD": + return b.s(e2.$D, 2, "0"); + case "d": + return String(e2.$W); + case "dd": + return h2(n2.weekdaysMin, e2.$W, o2, 2); + case "ddd": + return h2(n2.weekdaysShort, e2.$W, o2, 3); + case "dddd": + return o2[e2.$W]; + case "H": + return String(s2); + case "HH": + return b.s(s2, 2, "0"); + case "h": + return d2(1); + case "hh": + return d2(2); + case "a": + return $2(s2, u2, true); + case "A": + return $2(s2, u2, false); + case "m": + return String(u2); + case "mm": + return b.s(u2, 2, "0"); + case "s": + return String(e2.$s); + case "ss": + return b.s(e2.$s, 2, "0"); + case "SSS": + return b.s(e2.$ms, 3, "0"); + case "Z": + return i2; + } + return null; + }(t3) || i2.replace(":", ""); + }); + }, m2.utcOffset = function() { + return 15 * -Math.round(this.$d.getTimezoneOffset() / 15); + }, m2.diff = function(r2, d2, l2) { + var $2, y2 = this, M3 = b.p(d2), m3 = O(r2), v2 = (m3.utcOffset() - this.utcOffset()) * e, g2 = this - m3, D2 = /* @__PURE__ */ __name(function() { + return b.m(y2, m3); + }, "D"); + switch (M3) { + case h: + $2 = D2() / 12; + break; + case c: + $2 = D2(); + break; + case f: + $2 = D2() / 3; + break; + case o: + $2 = (g2 - v2) / 6048e5; + break; + case a: + $2 = (g2 - v2) / 864e5; + break; + case u: + $2 = g2 / n; + break; + case s: + $2 = g2 / e; + break; + case i: + $2 = g2 / t; + break; + default: + $2 = g2; + } + return l2 ? $2 : b.a($2); + }, m2.daysInMonth = function() { + return this.endOf(c).$D; + }, m2.$locale = function() { + return D[this.$L]; + }, m2.locale = function(t2, e2) { + if (!t2) + return this.$L; + var n2 = this.clone(), r2 = w(t2, e2, true); + return r2 && (n2.$L = r2), n2; + }, m2.clone = function() { + return b.w(this.$d, this); + }, m2.toDate = function() { + return new Date(this.valueOf()); + }, m2.toJSON = function() { + return this.isValid() ? this.toISOString() : null; + }, m2.toISOString = function() { + return this.$d.toISOString(); + }, m2.toString = function() { + return this.$d.toUTCString(); + }, M2; + }(), k = _.prototype; + return O.prototype = k, [["$ms", r], ["$s", i], ["$m", s], ["$H", u], ["$W", a], ["$M", c], ["$y", h], ["$D", d]].forEach(function(t2) { + k[t2[1]] = function(e2) { + return this.$g(e2, t2[0], t2[1]); + }; + }), O.extend = function(t2, e2) { + return t2.$i || (t2(e2, _, O), t2.$i = true), O; + }, O.locale = w, O.isDayjs = S, O.unix = function(t2) { + return O(1e3 * t2); + }, O.en = D[g], O.Ls = D, O.p = {}, O; + }); + } +}); + +// node_modules/dayjs/plugin/utc.js +var require_utc = __commonJS({ + "node_modules/dayjs/plugin/utc.js"(exports, module) { + !function(t, i) { + "object" == typeof exports && "undefined" != typeof module ? module.exports = i() : "function" == typeof define && define.amd ? define(i) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs_plugin_utc = i(); + }(exports, function() { + "use strict"; + var t = "minute", i = /[+-]\d\d(?::?\d\d)?/g, e = /([+-]|\d\d)/g; + return function(s, f, n) { + var u = f.prototype; + n.utc = function(t2) { + var i2 = { date: t2, utc: true, args: arguments }; + return new f(i2); + }, u.utc = function(i2) { + var e2 = n(this.toDate(), { locale: this.$L, utc: true }); + return i2 ? e2.add(this.utcOffset(), t) : e2; + }, u.local = function() { + return n(this.toDate(), { locale: this.$L, utc: false }); + }; + var r = u.parse; + u.parse = function(t2) { + t2.utc && (this.$u = true), this.$utils().u(t2.$offset) || (this.$offset = t2.$offset), r.call(this, t2); + }; + var o = u.init; + u.init = function() { + if (this.$u) { + var t2 = this.$d; + this.$y = t2.getUTCFullYear(), this.$M = t2.getUTCMonth(), this.$D = t2.getUTCDate(), this.$W = t2.getUTCDay(), this.$H = t2.getUTCHours(), this.$m = t2.getUTCMinutes(), this.$s = t2.getUTCSeconds(), this.$ms = t2.getUTCMilliseconds(); + } else + o.call(this); + }; + var a = u.utcOffset; + u.utcOffset = function(s2, f2) { + var n2 = this.$utils().u; + if (n2(s2)) + return this.$u ? 0 : n2(this.$offset) ? a.call(this) : this.$offset; + if ("string" == typeof s2 && (s2 = function(t2) { + void 0 === t2 && (t2 = ""); + var s3 = t2.match(i); + if (!s3) + return null; + var f3 = ("" + s3[0]).match(e) || ["-", 0, 0], n3 = f3[0], u3 = 60 * +f3[1] + +f3[2]; + return 0 === u3 ? 0 : "+" === n3 ? u3 : -u3; + }(s2), null === s2)) + return this; + var u2 = Math.abs(s2) <= 16 ? 60 * s2 : s2; + if (0 === u2) + return this.utc(f2); + var r2 = this.clone(); + if (f2) + return r2.$offset = u2, r2.$u = false, r2; + var o2 = this.$u ? this.toDate().getTimezoneOffset() : -1 * this.utcOffset(); + return (r2 = this.local().add(u2 + o2, t)).$offset = u2, r2.$x.$localOffset = o2, r2; + }; + var h = u.format; + u.format = function(t2) { + var i2 = t2 || (this.$u ? "YYYY-MM-DDTHH:mm:ss[Z]" : ""); + return h.call(this, i2); + }, u.valueOf = function() { + var t2 = this.$utils().u(this.$offset) ? 0 : this.$offset + (this.$x.$localOffset || this.$d.getTimezoneOffset()); + return this.$d.valueOf() - 6e4 * t2; + }, u.isUTC = function() { + return !!this.$u; + }, u.toISOString = function() { + return this.toDate().toISOString(); + }, u.toString = function() { + return this.toDate().toUTCString(); + }; + var l = u.toDate; + u.toDate = function(t2) { + return "s" === t2 && this.$offset ? n(this.format("YYYY-MM-DD HH:mm:ss:SSS")).toDate() : l.call(this); + }; + var c = u.diff; + u.diff = function(t2, i2, e2) { + if (t2 && this.$u === t2.$u) + return c.call(this, t2, i2, e2); + var s2 = this.local(), f2 = n(t2).local(); + return c.call(s2, f2, i2, e2); + }; + }; + }); + } +}); + +// node_modules/dayjs/plugin/timezone.js +var require_timezone = __commonJS({ + "node_modules/dayjs/plugin/timezone.js"(exports, module) { + !function(t, e) { + "object" == typeof exports && "undefined" != typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define(e) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs_plugin_timezone = e(); + }(exports, function() { + "use strict"; + var t = { year: 0, month: 1, day: 2, hour: 3, minute: 4, second: 5 }, e = {}; + return function(n, i, o) { + var r, a = /* @__PURE__ */ __name(function(t2, n2, i2) { + void 0 === i2 && (i2 = {}); + var o2 = new Date(t2), r2 = function(t3, n3) { + void 0 === n3 && (n3 = {}); + var i3 = n3.timeZoneName || "short", o3 = t3 + "|" + i3, r3 = e[o3]; + return r3 || (r3 = new Intl.DateTimeFormat("en-US", { hour12: false, timeZone: t3, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", timeZoneName: i3 }), e[o3] = r3), r3; + }(n2, i2); + return r2.formatToParts(o2); + }, "a"), u = /* @__PURE__ */ __name(function(e2, n2) { + for (var i2 = a(e2, n2), r2 = [], u2 = 0; u2 < i2.length; u2 += 1) { + var f2 = i2[u2], s2 = f2.type, m = f2.value, c = t[s2]; + c >= 0 && (r2[c] = parseInt(m, 10)); + } + var d = r2[3], l = 24 === d ? 0 : d, h = r2[0] + "-" + r2[1] + "-" + r2[2] + " " + l + ":" + r2[4] + ":" + r2[5] + ":000", v = +e2; + return (o.utc(h).valueOf() - (v -= v % 1e3)) / 6e4; + }, "u"), f = i.prototype; + f.tz = function(t2, e2) { + void 0 === t2 && (t2 = r); + var n2, i2 = this.utcOffset(), a2 = this.toDate(), u2 = a2.toLocaleString("en-US", { timeZone: t2 }), f2 = Math.round((a2 - new Date(u2)) / 1e3 / 60), s2 = 15 * -Math.round(a2.getTimezoneOffset() / 15) - f2; + if (!Number(s2)) + n2 = this.utcOffset(0, e2); + else if (n2 = o(u2, { locale: this.$L }).$set("millisecond", this.$ms).utcOffset(s2, true), e2) { + var m = n2.utcOffset(); + n2 = n2.add(i2 - m, "minute"); + } + return n2.$x.$timezone = t2, n2; + }, f.offsetName = function(t2) { + var e2 = this.$x.$timezone || o.tz.guess(), n2 = a(this.valueOf(), e2, { timeZoneName: t2 }).find(function(t3) { + return "timezonename" === t3.type.toLowerCase(); + }); + return n2 && n2.value; + }; + var s = f.startOf; + f.startOf = function(t2, e2) { + if (!this.$x || !this.$x.$timezone) + return s.call(this, t2, e2); + var n2 = o(this.format("YYYY-MM-DD HH:mm:ss:SSS"), { locale: this.$L }); + return s.call(n2, t2, e2).tz(this.$x.$timezone, true); + }, o.tz = function(t2, e2, n2) { + var i2 = n2 && e2, a2 = n2 || e2 || r, f2 = u(+o(), a2); + if ("string" != typeof t2) + return o(t2).tz(a2); + var s2 = function(t3, e3, n3) { + var i3 = t3 - 60 * e3 * 1e3, o2 = u(i3, n3); + if (e3 === o2) + return [i3, e3]; + var r2 = u(i3 -= 60 * (o2 - e3) * 1e3, n3); + return o2 === r2 ? [i3, o2] : [t3 - 60 * Math.min(o2, r2) * 1e3, Math.max(o2, r2)]; + }(o.utc(t2, i2).valueOf(), f2, a2), m = s2[0], c = s2[1], d = o(m).utcOffset(c); + return d.$x.$timezone = a2, d; + }, o.tz.guess = function() { + return Intl.DateTimeFormat().resolvedOptions().timeZone; + }, o.tz.setDefault = function(t2) { + r = t2; + }; + }; + }); + } +}); + +// node_modules/dayjs/plugin/isoWeek.js +var require_isoWeek = __commonJS({ + "node_modules/dayjs/plugin/isoWeek.js"(exports, module) { + !function(e, t) { + "object" == typeof exports && "undefined" != typeof module ? module.exports = t() : "function" == typeof define && define.amd ? define(t) : (e = "undefined" != typeof globalThis ? globalThis : e || self).dayjs_plugin_isoWeek = t(); + }(exports, function() { + "use strict"; + var e = "day"; + return function(t, i, s) { + var a = /* @__PURE__ */ __name(function(t2) { + return t2.add(4 - t2.isoWeekday(), e); + }, "a"), d = i.prototype; + d.isoWeekYear = function() { + return a(this).year(); + }, d.isoWeek = function(t2) { + if (!this.$utils().u(t2)) + return this.add(7 * (t2 - this.isoWeek()), e); + var i2, d2, n2, o, r = a(this), u = (i2 = this.isoWeekYear(), d2 = this.$u, n2 = (d2 ? s.utc : s)().year(i2).startOf("year"), o = 4 - n2.isoWeekday(), n2.isoWeekday() > 4 && (o += 7), n2.add(o, e)); + return r.diff(u, "week") + 1; + }, d.isoWeekday = function(e2) { + return this.$utils().u(e2) ? this.day() || 7 : this.day(this.day() % 7 ? e2 : e2 - 7); + }; + var n = d.startOf; + d.startOf = function(e2, t2) { + var i2 = this.$utils(), s2 = !!i2.u(t2) || t2; + return "isoweek" === i2.p(e2) ? s2 ? this.date(this.date() - (this.isoWeekday() - 1)).startOf("day") : this.date(this.date() - 1 - (this.isoWeekday() - 1) + 7).endOf("day") : n.bind(this)(e2, t2); + }; + }; + }); + } +}); + +// node_modules/@novadi/core/dist/token.js +var tokenCounter = 0; +function Token(description) { + const id = ++tokenCounter; + const sym = Symbol(description ? `Token(${description})` : `Token#${id}`); + const token2 = { + symbol: sym, + description, + toString() { + return description ? `Token<${description}>` : `Token<#${id}>`; + } + }; + return token2; +} +__name(Token, "Token"); + +// node_modules/@novadi/core/dist/errors.js +var _ContainerError = class _ContainerError extends Error { + constructor(message) { + super(message); + this.name = "ContainerError"; + } +}; +__name(_ContainerError, "ContainerError"); +var ContainerError = _ContainerError; +var _BindingNotFoundError = class _BindingNotFoundError extends ContainerError { + constructor(tokenDescription, path = []) { + const pathStr = path.length > 0 ? ` + Dependency path: ${path.join(" -> ")}` : ""; + super(`Token "${tokenDescription}" is not bound or registered in the container.${pathStr}`); + this.name = "BindingNotFoundError"; + } +}; +__name(_BindingNotFoundError, "BindingNotFoundError"); +var BindingNotFoundError = _BindingNotFoundError; +var _CircularDependencyError = class _CircularDependencyError extends ContainerError { + constructor(path) { + super(`Circular dependency detected: ${path.join(" -> ")}`); + this.name = "CircularDependencyError"; + } +}; +__name(_CircularDependencyError, "CircularDependencyError"); +var CircularDependencyError = _CircularDependencyError; + +// node_modules/@novadi/core/dist/autowire.js +var paramNameCache = /* @__PURE__ */ new WeakMap(); +function extractParameterNames(constructor) { + const cached = paramNameCache.get(constructor); + if (cached) { + return cached; + } + const fnStr = constructor.toString(); + const match = fnStr.match(/constructor\s*\(([^)]*)\)/) || fnStr.match(/^[^(]*\(([^)]*)\)/); + if (!match || !match[1]) { + return []; + } + const params = match[1].split(",").map((param) => param.trim()).filter((param) => param.length > 0).map((param) => { + let name = param.split(/[:=]/)[0].trim(); + name = name.replace(/^((public|private|protected|readonly)\s+)+/, ""); + if (name.includes("{") || name.includes("[")) { + return null; + } + return name; + }).filter((name) => name !== null); + paramNameCache.set(constructor, params); + return params; +} +__name(extractParameterNames, "extractParameterNames"); +function resolveByMap(constructor, container2, options) { + if (!options.map) { + throw new Error("AutoWire map strategy requires options.map to be defined"); + } + const paramNames = extractParameterNames(constructor); + const resolvedDeps = []; + for (const paramName of paramNames) { + const resolver = options.map[paramName]; + if (resolver === void 0) { + if (options.strict) { + throw new Error(`Cannot resolve parameter "${paramName}" on ${constructor.name}. Not found in autowire map. Add it to the map: .autoWire({ map: { ${paramName}: ... } })`); + } else { + resolvedDeps.push(void 0); + } + continue; + } + if (typeof resolver === "function") { + resolvedDeps.push(resolver(container2)); + } else { + resolvedDeps.push(container2.resolve(resolver)); + } + } + return resolvedDeps; +} +__name(resolveByMap, "resolveByMap"); +function resolveByMapResolvers(_constructor, container2, options) { + if (!options.mapResolvers || options.mapResolvers.length === 0) { + return []; + } + const resolvedDeps = []; + for (let i = 0; i < options.mapResolvers.length; i++) { + const resolver = options.mapResolvers[i]; + if (resolver === void 0) { + resolvedDeps.push(void 0); + } else if (typeof resolver === "function") { + resolvedDeps.push(resolver(container2)); + } else { + resolvedDeps.push(container2.resolve(resolver)); + } + } + return resolvedDeps; +} +__name(resolveByMapResolvers, "resolveByMapResolvers"); +function autowire(constructor, container2, options) { + const opts = { + by: "paramName", + strict: false, + ...options + }; + if (opts.mapResolvers && opts.mapResolvers.length > 0) { + return resolveByMapResolvers(constructor, container2, opts); + } + if (opts.map && Object.keys(opts.map).length > 0) { + return resolveByMap(constructor, container2, opts); + } + return []; +} +__name(autowire, "autowire"); + +// node_modules/@novadi/core/dist/builder.js +var _RegistrationBuilder = class _RegistrationBuilder { + constructor(pending, registrations) { + this.registrations = registrations; + this.configs = []; + this.defaultLifetime = "singleton"; + this.pending = pending; + } + /** + * Bind this registration to a token or interface type + * + * @overload + * @param {Token} token - Explicit token for binding + * + * @overload + * @param {string} typeName - Interface type name (auto-generated by transformer) + */ + as(tokenOrTypeName) { + if (tokenOrTypeName && typeof tokenOrTypeName === "object" && "symbol" in tokenOrTypeName) { + const config = { + token: tokenOrTypeName, + type: this.pending.type, + value: this.pending.value, + factory: this.pending.factory, + constructor: this.pending.constructor, + lifetime: this.defaultLifetime + }; + this.configs.push(config); + this.registrations.push(config); + return this; + } else { + const config = { + token: null, + // Will be set during build() + type: this.pending.type, + value: this.pending.value, + factory: this.pending.factory, + constructor: this.pending.constructor, + lifetime: this.defaultLifetime, + interfaceType: tokenOrTypeName + }; + this.configs.push(config); + this.registrations.push(config); + return this; + } + } + /** + * Register as default implementation for an interface + * Combines as() + asDefault() + */ + asDefaultInterface(typeName) { + this.as("TInterface", typeName); + return this.asDefault(); + } + /** + * Register as a keyed interface implementation + * Combines as() + keyed() + */ + asKeyedInterface(key, typeName) { + this.as("TInterface", typeName); + return this.keyed(key); + } + /** + * Register as multiple implemented interfaces + */ + asImplementedInterfaces(tokens) { + if (tokens.length === 0) { + return this; + } + if (this.configs.length > 0) { + for (const config of this.configs) { + config.lifetime = "singleton"; + config.additionalTokens = config.additionalTokens || []; + config.additionalTokens.push(...tokens); + } + return this; + } + const firstConfig = { + token: tokens[0], + type: this.pending.type, + value: this.pending.value, + factory: this.pending.factory, + constructor: this.pending.constructor, + lifetime: "singleton" + }; + this.configs.push(firstConfig); + this.registrations.push(firstConfig); + for (let i = 1; i < tokens.length; i++) { + firstConfig.additionalTokens = firstConfig.additionalTokens || []; + firstConfig.additionalTokens.push(tokens[i]); + } + return this; + } + /** + * Set singleton lifetime (one instance for entire container) + */ + singleInstance() { + for (const config of this.configs) { + config.lifetime = "singleton"; + } + return this; + } + /** + * Set per-request lifetime (one instance per resolve call tree) + */ + instancePerRequest() { + for (const config of this.configs) { + config.lifetime = "per-request"; + } + return this; + } + /** + * Set transient lifetime (new instance every time) + * Alias for default behavior + */ + instancePerDependency() { + for (const config of this.configs) { + config.lifetime = "transient"; + } + return this; + } + /** + * Name this registration for named resolution + */ + named(name) { + for (const config of this.configs) { + config.name = name; + } + return this; + } + /** + * Key this registration for keyed resolution + */ + keyed(key) { + for (const config of this.configs) { + config.key = key; + } + return this; + } + /** + * Mark this as default registration + * Default registrations don't override existing ones + */ + asDefault() { + for (const config of this.configs) { + config.isDefault = true; + } + return this; + } + /** + * Only register if token not already registered + */ + ifNotRegistered() { + for (const config of this.configs) { + config.ifNotRegistered = true; + } + return this; + } + /** + * Specify parameter values for constructor (primitives and constants) + * Use this for non-DI parameters like strings, numbers, config values + */ + withParameters(parameters) { + for (const config of this.configs) { + config.parameterValues = parameters; + } + return this; + } + /** + * Enable automatic dependency injection (autowiring) + * Supports three strategies: paramName (default), map, and class + * + * @example + * ```ts + * // Strategy 1: paramName (default, requires non-minified code in dev) + * builder.registerType(EventBus).as().autoWire() + * + * // Strategy 2: map (minify-safe, explicit) + * builder.registerType(EventBus).as().autoWire({ + * map: { + * logger: (c) => c.resolveType() + * } + * }) + * + * // Strategy 3: class (requires build-time codegen) + * builder.registerType(EventBus).as().autoWire({ by: 'class' }) + * ``` + */ + autoWire(options) { + for (const config of this.configs) { + config.autowireOptions = options || { by: "paramName", strict: false }; + } + return this; + } +}; +__name(_RegistrationBuilder, "RegistrationBuilder"); +var RegistrationBuilder = _RegistrationBuilder; +var _Builder = class _Builder { + constructor(baseContainer) { + this.baseContainer = baseContainer; + this.registrations = []; + } + /** + * Register a class constructor + */ + registerType(constructor) { + const pending = { + type: "type", + value: null, + constructor + }; + return new RegistrationBuilder(pending, this.registrations); + } + /** + * Register a pre-created instance + */ + registerInstance(instance) { + const pending = { + type: "instance", + value: instance, + constructor: void 0 + }; + return new RegistrationBuilder(pending, this.registrations); + } + /** + * Register a factory function + */ + register(factory) { + const pending = { + type: "factory", + value: null, + factory, + constructor: void 0 + }; + return new RegistrationBuilder(pending, this.registrations); + } + /** + * Register a module (function that adds multiple registrations) + */ + module(moduleFunc) { + moduleFunc(this); + return this; + } + /** + * Resolve interface type names to tokens + * @internal + */ + resolveInterfaceTokens(container2) { + for (const config of this.registrations) { + if (config.interfaceType !== void 0 && !config.token) { + config.token = container2.interfaceToken(config.interfaceType); + } + } + } + /** + * Identify tokens that have non-default registrations + * @internal + */ + identifyNonDefaultTokens() { + const tokensWithNonDefaults = /* @__PURE__ */ new Set(); + for (const config of this.registrations) { + if (!config.isDefault && !config.name && config.key === void 0) { + tokensWithNonDefaults.add(config.token); + } + } + return tokensWithNonDefaults; + } + /** + * Check if registration should be skipped + * @internal + */ + shouldSkipRegistration(config, tokensWithNonDefaults, registeredTokens) { + if (config.isDefault && !config.name && config.key === void 0 && tokensWithNonDefaults.has(config.token)) { + return true; + } + if (config.ifNotRegistered && registeredTokens.has(config.token)) { + return true; + } + if (config.isDefault && registeredTokens.has(config.token)) { + return true; + } + return false; + } + /** + * Create binding token for registration (named, keyed, or multi) + * @internal + */ + createBindingToken(config, namedRegistrations, keyedRegistrations, multiRegistrations) { + if (config.name) { + const bindingToken = Token(`__named_${config.name}`); + namedRegistrations.set(config.name, { ...config, token: bindingToken }); + return bindingToken; + } else if (config.key !== void 0) { + const keyStr = typeof config.key === "symbol" ? config.key.toString() : config.key; + const bindingToken = Token(`__keyed_${keyStr}`); + keyedRegistrations.set(config.key, { ...config, token: bindingToken }); + return bindingToken; + } else { + if (multiRegistrations.has(config.token)) { + const bindingToken = Token(`__multi_${config.token.toString()}_${multiRegistrations.get(config.token).length}`); + multiRegistrations.get(config.token).push(bindingToken); + return bindingToken; + } else { + multiRegistrations.set(config.token, [config.token]); + return config.token; + } + } + } + /** + * Register additional interfaces for a config + * @internal + */ + registerAdditionalInterfaces(container2, config, bindingToken, registeredTokens) { + if (config.additionalTokens) { + for (const additionalToken of config.additionalTokens) { + container2.bindFactory(additionalToken, (c) => c.resolve(bindingToken), { lifetime: config.lifetime }); + registeredTokens.add(additionalToken); + } + } + } + /** + * Build the container with all registered bindings + */ + build() { + const container2 = this.baseContainer.createChild(); + this.resolveInterfaceTokens(container2); + const registeredTokens = /* @__PURE__ */ new Set(); + const namedRegistrations = /* @__PURE__ */ new Map(); + const keyedRegistrations = /* @__PURE__ */ new Map(); + const multiRegistrations = /* @__PURE__ */ new Map(); + const tokensWithNonDefaults = this.identifyNonDefaultTokens(); + for (const config of this.registrations) { + if (this.shouldSkipRegistration(config, tokensWithNonDefaults, registeredTokens)) { + continue; + } + const bindingToken = this.createBindingToken(config, namedRegistrations, keyedRegistrations, multiRegistrations); + this.applyRegistration(container2, { ...config, token: bindingToken }); + registeredTokens.add(config.token); + this.registerAdditionalInterfaces(container2, config, bindingToken, registeredTokens); + } + ; + container2.__namedRegistrations = namedRegistrations; + container2.__keyedRegistrations = keyedRegistrations; + container2.__multiRegistrations = multiRegistrations; + return container2; + } + /** + * Analyze constructor to detect dependencies + * @internal + */ + analyzeConstructor(constructor) { + const constructorStr = constructor.toString(); + const hasDependencies = /constructor\s*\([^)]+\)/.test(constructorStr); + return { hasDependencies }; + } + /** + * Create optimized factory for zero-dependency constructors + * @internal + */ + createOptimizedFactory(container2, config, options) { + if (config.lifetime === "singleton") { + const instance = new config.constructor(); + container2.bindValue(config.token, instance); + } else if (config.lifetime === "transient") { + const ctor = config.constructor; + const fastFactory = /* @__PURE__ */ __name(() => new ctor(), "fastFactory"); + container2.fastTransientCache.set(config.token, fastFactory); + container2.bindFactory(config.token, fastFactory, options); + } else { + const factory = /* @__PURE__ */ __name(() => new config.constructor(), "factory"); + container2.bindFactory(config.token, factory, options); + } + } + /** + * Create autowire factory + * @internal + */ + createAutoWireFactory(container2, config, options) { + const factory = /* @__PURE__ */ __name((c) => { + const resolvedDeps = autowire(config.constructor, c, config.autowireOptions); + return new config.constructor(...resolvedDeps); + }, "factory"); + container2.bindFactory(config.token, factory, options); + } + /** + * Create withParameters factory + * @internal + */ + createParameterFactory(container2, config, options) { + const factory = /* @__PURE__ */ __name(() => { + const values = Object.values(config.parameterValues); + return new config.constructor(...values); + }, "factory"); + container2.bindFactory(config.token, factory, options); + } + /** + * Apply type registration (class constructor) + * @internal + */ + applyTypeRegistration(container2, config, options) { + const { hasDependencies } = this.analyzeConstructor(config.constructor); + if (!hasDependencies && !config.autowireOptions && !config.parameterValues) { + this.createOptimizedFactory(container2, config, options); + return; + } + if (config.autowireOptions) { + this.createAutoWireFactory(container2, config, options); + return; + } + if (config.parameterValues) { + this.createParameterFactory(container2, config, options); + return; + } + if (hasDependencies) { + const className = config.constructor.name || "UnnamedClass"; + throw new Error(`Service "${className}" has constructor dependencies but no autowiring configuration. + +Solutions: + 1. \u2B50 Use the NovaDI transformer (recommended): + - Add "@novadi/core/unplugin" to your build config + - Transformer automatically generates .autoWire() for all dependencies + + 2. Add manual autowiring: + .autoWire({ map: { /* param: resolver */ } }) + + 3. Use a factory function: + .register((c) => new ${className}(...)) + +See docs: https://github.com/janus007/NovaDI#autowire`); + } + const factory = /* @__PURE__ */ __name(() => new config.constructor(), "factory"); + container2.bindFactory(config.token, factory, options); + } + applyRegistration(container2, config) { + const options = { lifetime: config.lifetime }; + switch (config.type) { + case "instance": + container2.bindValue(config.token, config.value); + break; + case "factory": + container2.bindFactory(config.token, config.factory, options); + break; + case "type": + this.applyTypeRegistration(container2, config, options); + break; + } + } +}; +__name(_Builder, "Builder"); +var Builder = _Builder; + +// node_modules/@novadi/core/dist/container.js +function isDisposable(obj) { + return obj && typeof obj.dispose === "function"; +} +__name(isDisposable, "isDisposable"); +var _ResolutionContext = class _ResolutionContext { + constructor() { + this.resolvingStack = /* @__PURE__ */ new Set(); + this.perRequestCache = /* @__PURE__ */ new Map(); + } + isResolving(token2) { + return this.resolvingStack.has(token2); + } + enterResolve(token2) { + this.resolvingStack.add(token2); + } + exitResolve(token2) { + this.resolvingStack.delete(token2); + this.path = void 0; + } + getPath() { + if (!this.path) { + this.path = Array.from(this.resolvingStack).map((t) => t.toString()); + } + return [...this.path]; + } + cachePerRequest(token2, instance) { + this.perRequestCache.set(token2, instance); + } + getPerRequest(token2) { + return this.perRequestCache.get(token2); + } + hasPerRequest(token2) { + return this.perRequestCache.has(token2); + } + /** + * Reset context for reuse in object pool + * Performance: Reusing contexts avoids heap allocations + */ + reset() { + this.resolvingStack.clear(); + this.perRequestCache.clear(); + this.path = void 0; + } +}; +__name(_ResolutionContext, "ResolutionContext"); +var ResolutionContext = _ResolutionContext; +var _ResolutionContextPool = class _ResolutionContextPool { + constructor() { + this.pool = []; + this.maxSize = 10; + } + acquire() { + const context = this.pool.pop(); + if (context) { + context.reset(); + return context; + } + return new ResolutionContext(); + } + release(context) { + if (this.pool.length < this.maxSize) { + this.pool.push(context); + } + } +}; +__name(_ResolutionContextPool, "ResolutionContextPool"); +var ResolutionContextPool = _ResolutionContextPool; +var _Container = class _Container { + constructor(parent) { + this.bindings = /* @__PURE__ */ new Map(); + this.singletonCache = /* @__PURE__ */ new Map(); + this.singletonOrder = []; + this.interfaceRegistry = /* @__PURE__ */ new Map(); + this.interfaceTokenCache = /* @__PURE__ */ new Map(); + this.fastTransientCache = /* @__PURE__ */ new Map(); + this.ultraFastSingletonCache = /* @__PURE__ */ new Map(); + this.parent = parent; + } + /** + * Bind a pre-created value to a token + */ + bindValue(token2, value) { + this.bindings.set(token2, { + type: "value", + lifetime: "singleton", + value, + constructor: void 0 + }); + this.invalidateBindingCache(); + } + /** + * Bind a factory function to a token + */ + bindFactory(token2, factory, options) { + this.bindings.set(token2, { + type: "factory", + lifetime: options?.lifetime || "transient", + factory, + dependencies: options?.dependencies, + constructor: void 0 + }); + this.invalidateBindingCache(); + } + /** + * Bind a class constructor to a token + */ + bindClass(token2, constructor, options) { + const binding = { + type: "class", + lifetime: options?.lifetime || "transient", + constructor, + dependencies: options?.dependencies + }; + this.bindings.set(token2, binding); + this.invalidateBindingCache(); + if (binding.lifetime === "transient" && (!binding.dependencies || binding.dependencies.length === 0)) { + this.fastTransientCache.set(token2, () => new constructor()); + } + } + /** + * Resolve a dependency synchronously + * Performance optimized with multiple fast paths + */ + resolve(token2) { + const cached = this.tryGetFromCaches(token2); + if (cached !== void 0) { + return cached; + } + if (this.currentContext) { + return this.resolveWithContext(token2, this.currentContext); + } + const context = _Container.contextPool.acquire(); + this.currentContext = context; + try { + return this.resolveWithContext(token2, context); + } finally { + this.currentContext = void 0; + _Container.contextPool.release(context); + } + } + /** + * SPECIALIZED: Ultra-fast singleton resolve (no safety checks) + * Use ONLY when you're 100% sure the token is a registered singleton + * @internal For performance-critical paths only + */ + resolveSingletonUnsafe(token2) { + return this.ultraFastSingletonCache.get(token2) ?? this.singletonCache.get(token2); + } + /** + * SPECIALIZED: Fast transient resolve for zero-dependency classes + * Skips all context creation and circular dependency checks + * @internal For performance-critical paths only + */ + resolveTransientSimple(token2) { + const factory = this.fastTransientCache.get(token2); + if (factory) { + return factory(); + } + return this.resolve(token2); + } + /** + * SPECIALIZED: Batch resolve multiple dependencies at once + * More efficient than multiple individual resolves + */ + resolveBatch(tokens) { + const wasResolving = !!this.currentContext; + const context = this.currentContext || _Container.contextPool.acquire(); + if (!wasResolving) { + this.currentContext = context; + } + try { + const results = tokens.map((token2) => { + const cached = this.tryGetFromCaches(token2); + if (cached !== void 0) + return cached; + return this.resolveWithContext(token2, context); + }); + return results; + } finally { + if (!wasResolving) { + this.currentContext = void 0; + _Container.contextPool.release(context); + } + } + } + /** + * Resolve a dependency asynchronously (supports async factories) + */ + async resolveAsync(token2) { + if (this.currentContext) { + return this.resolveAsyncWithContext(token2, this.currentContext); + } + const context = _Container.contextPool.acquire(); + this.currentContext = context; + try { + return await this.resolveAsyncWithContext(token2, context); + } finally { + this.currentContext = void 0; + _Container.contextPool.release(context); + } + } + /** + * Try to get instance from all cache levels + * Returns undefined if not cached + * @internal + */ + tryGetFromCaches(token2) { + const ultraFast = this.ultraFastSingletonCache.get(token2); + if (ultraFast !== void 0) { + return ultraFast; + } + if (this.singletonCache.has(token2)) { + const cached = this.singletonCache.get(token2); + this.ultraFastSingletonCache.set(token2, cached); + return cached; + } + const fastFactory = this.fastTransientCache.get(token2); + if (fastFactory) { + return fastFactory(); + } + return void 0; + } + /** + * Cache instance based on lifetime strategy + * @internal + */ + cacheInstance(token2, instance, lifetime, context) { + if (lifetime === "singleton") { + this.singletonCache.set(token2, instance); + this.singletonOrder.push(token2); + this.ultraFastSingletonCache.set(token2, instance); + } else if (lifetime === "per-request" && context) { + context.cachePerRequest(token2, instance); + } + } + /** + * Validate and get binding with circular dependency check + * Returns binding or throws error + * @internal + */ + validateAndGetBinding(token2, context) { + if (context.isResolving(token2)) { + throw new CircularDependencyError([...context.getPath(), token2.toString()]); + } + const binding = this.getBinding(token2); + if (!binding) { + throw new BindingNotFoundError(token2.toString(), context.getPath()); + } + return binding; + } + /** + * Instantiate from binding synchronously + * @internal + */ + instantiateBindingSync(binding, token2, context) { + switch (binding.type) { + case "value": + return binding.value; + case "factory": + const result = binding.factory(this); + if (result instanceof Promise) { + throw new Error(`Async factory detected for ${token2.toString()}. Use resolveAsync() instead.`); + } + return result; + case "class": + const deps = binding.dependencies || []; + const resolvedDeps = deps.map((dep) => this.resolveWithContext(dep, context)); + return new binding.constructor(...resolvedDeps); + case "inline-class": + return new binding.constructor(); + default: + throw new Error(`Unknown binding type: ${binding.type}`); + } + } + /** + * Instantiate from binding asynchronously + * @internal + */ + async instantiateBindingAsync(binding, context) { + switch (binding.type) { + case "value": + return binding.value; + case "factory": + return await Promise.resolve(binding.factory(this)); + case "class": + const deps = binding.dependencies || []; + const resolvedDeps = await Promise.all(deps.map((dep) => this.resolveAsyncWithContext(dep, context))); + return new binding.constructor(...resolvedDeps); + case "inline-class": + return new binding.constructor(); + default: + throw new Error(`Unknown binding type: ${binding.type}`); + } + } + /** + * Create a child container that inherits bindings from this container + */ + createChild() { + return new _Container(this); + } + /** + * Dispose all singleton instances in reverse registration order + */ + async dispose() { + const errors = []; + for (let i = this.singletonOrder.length - 1; i >= 0; i--) { + const token2 = this.singletonOrder[i]; + const instance = this.singletonCache.get(token2); + if (instance && isDisposable(instance)) { + try { + await instance.dispose(); + } catch (error) { + errors.push(error); + } + } + } + this.singletonCache.clear(); + this.singletonOrder.length = 0; + } + /** + * Create a fluent builder for registering dependencies + */ + builder() { + return new Builder(this); + } + /** + * Resolve a named service + */ + resolveNamed(name) { + const namedRegistrations = this.__namedRegistrations; + if (!namedRegistrations) { + throw new Error(`Named service "${name}" not found. No named registrations exist.`); + } + const config = namedRegistrations.get(name); + if (!config) { + throw new Error(`Named service "${name}" not found`); + } + return this.resolve(config.token); + } + /** + * Resolve a keyed service + */ + resolveKeyed(key) { + const keyedRegistrations = this.__keyedRegistrations; + if (!keyedRegistrations) { + throw new Error(`Keyed service not found. No keyed registrations exist.`); + } + const config = keyedRegistrations.get(key); + if (!config) { + const keyStr = typeof key === "symbol" ? key.toString() : `"${key}"`; + throw new Error(`Keyed service ${keyStr} not found`); + } + return this.resolve(config.token); + } + /** + * Resolve all registrations for a token + */ + resolveAll(token2) { + const multiRegistrations = this.__multiRegistrations; + if (!multiRegistrations) { + return []; + } + const tokens = multiRegistrations.get(token2); + if (!tokens || tokens.length === 0) { + return []; + } + return tokens.map((t) => this.resolve(t)); + } + /** + * Get registry information for debugging/visualization + * Returns array of binding information + */ + getRegistry() { + const registry = []; + this.bindings.forEach((binding, token2) => { + registry.push({ + token: token2.description || token2.symbol.toString(), + type: binding.type, + lifetime: binding.lifetime, + dependencies: binding.dependencies?.map((d) => d.description || d.symbol.toString()) + }); + }); + return registry; + } + /** + * Get or create a token for an interface type + * Uses a type name hash as key for the interface registry + */ + interfaceToken(typeName) { + const key = typeName || `Interface_${Math.random().toString(36).substr(2, 9)}`; + if (this.interfaceRegistry.has(key)) { + return this.interfaceRegistry.get(key); + } + if (this.parent) { + const parentToken = this.parent.interfaceToken(key); + return parentToken; + } + const token2 = Token(key); + this.interfaceRegistry.set(key, token2); + return token2; + } + /** + * Resolve a dependency by interface type without explicit token + */ + resolveType(typeName) { + const key = typeName || ""; + let token2 = this.interfaceTokenCache.get(key); + if (!token2) { + token2 = this.interfaceToken(typeName); + this.interfaceTokenCache.set(key, token2); + } + return this.resolve(token2); + } + /** + * Resolve a keyed interface + */ + resolveTypeKeyed(key, _typeName) { + return this.resolveKeyed(key); + } + /** + * Resolve all registrations for an interface type + */ + resolveTypeAll(typeName) { + const token2 = this.interfaceToken(typeName); + return this.resolveAll(token2); + } + /** + * Internal: Resolve with context for circular dependency detection + */ + resolveWithContext(token2, context) { + const binding = this.validateAndGetBinding(token2, context); + if (binding.lifetime === "per-request" && context.hasPerRequest(token2)) { + return context.getPerRequest(token2); + } + if (binding.lifetime === "singleton" && this.singletonCache.has(token2)) { + return this.singletonCache.get(token2); + } + context.enterResolve(token2); + try { + const instance = this.instantiateBindingSync(binding, token2, context); + this.cacheInstance(token2, instance, binding.lifetime, context); + return instance; + } finally { + context.exitResolve(token2); + } + } + /** + * Internal: Async resolve with context + */ + async resolveAsyncWithContext(token2, context) { + const binding = this.validateAndGetBinding(token2, context); + if (binding.lifetime === "per-request" && context.hasPerRequest(token2)) { + return context.getPerRequest(token2); + } + if (binding.lifetime === "singleton" && this.singletonCache.has(token2)) { + return this.singletonCache.get(token2); + } + context.enterResolve(token2); + try { + const instance = await this.instantiateBindingAsync(binding, context); + this.cacheInstance(token2, instance, binding.lifetime, context); + return instance; + } finally { + context.exitResolve(token2); + } + } + /** + * Get binding from this container or parent chain + * Performance optimized: Uses flat cache to avoid recursive parent lookups + */ + getBinding(token2) { + if (!this.bindingCache) { + this.buildBindingCache(); + } + return this.bindingCache.get(token2); + } + /** + * Build flat cache of all bindings including parent chain + * This converts O(n) parent chain traversal to O(1) lookup + */ + buildBindingCache() { + this.bindingCache = /* @__PURE__ */ new Map(); + let current = this; + while (current) { + current.bindings.forEach((binding, token2) => { + if (!this.bindingCache.has(token2)) { + this.bindingCache.set(token2, binding); + } + }); + current = current.parent; + } + } + /** + * Invalidate binding cache when new bindings are added + * Called by bindValue, bindFactory, bindClass + */ + invalidateBindingCache() { + this.bindingCache = void 0; + this.ultraFastSingletonCache.clear(); + } +}; +__name(_Container, "Container"); +var Container = _Container; +Container.contextPool = new ResolutionContextPool(); + +// src/v2/features/date/DateRenderer.ts +var _DateRenderer = class _DateRenderer { + constructor(dateService) { + this.dateService = dateService; + this.type = "date"; + } + render(context) { + const dates = context.filter["date"] || []; + const resourceIds = context.filter["resource"] || []; + const dateGrouping = context.groupings?.find((g) => g.type === "date"); + const hideHeader = dateGrouping?.hideHeader === true; + const iterations = resourceIds.length || 1; + let columnCount = 0; + for (let r = 0; r < iterations; r++) { + const resourceId = resourceIds[r]; + for (const dateStr of dates) { + const date = this.dateService.parseISO(dateStr); + const segments = { date: dateStr }; + if (resourceId) + segments.resource = resourceId; + const columnKey = this.dateService.buildColumnKey(segments); + const header = document.createElement("swp-day-header"); + header.dataset.date = dateStr; + header.dataset.columnKey = columnKey; + if (resourceId) { + header.dataset.resourceId = resourceId; + } + if (hideHeader) { + header.dataset.hidden = "true"; + } + header.innerHTML = ` + ${this.dateService.getDayName(date, "short")} + ${date.getDate()} + `; + context.headerContainer.appendChild(header); + const column = document.createElement("swp-day-column"); + column.dataset.date = dateStr; + column.dataset.columnKey = columnKey; + if (resourceId) { + column.dataset.resourceId = resourceId; + } + column.innerHTML = ""; + context.columnContainer.appendChild(column); + columnCount++; + } + } + const container2 = context.columnContainer.closest("swp-calendar-container"); + if (container2) { + container2.style.setProperty("--grid-columns", String(columnCount)); + } + } +}; +__name(_DateRenderer, "DateRenderer"); +var DateRenderer = _DateRenderer; + +// src/v2/core/DateService.ts +var import_dayjs = __toESM(require_dayjs_min(), 1); +var import_utc = __toESM(require_utc(), 1); +var import_timezone = __toESM(require_timezone(), 1); +var import_isoWeek = __toESM(require_isoWeek(), 1); +import_dayjs.default.extend(import_utc.default); +import_dayjs.default.extend(import_timezone.default); +import_dayjs.default.extend(import_isoWeek.default); +var _DateService = class _DateService { + constructor(config, baseDate) { + this.config = config; + this.timezone = config.timezone; + this.baseDate = baseDate ? (0, import_dayjs.default)(baseDate) : (0, import_dayjs.default)(); + } + /** + * Set a fixed base date (useful for demos with static mock data) + */ + setBaseDate(date) { + this.baseDate = (0, import_dayjs.default)(date); + } + /** + * Get the current base date (either fixed or today) + */ + getBaseDate() { + return this.baseDate.toDate(); + } + parseISO(isoString) { + return (0, import_dayjs.default)(isoString).toDate(); + } + getDayName(date, format = "short") { + return new Intl.DateTimeFormat(this.config.locale, { weekday: format }).format(date); + } + getWeekDates(offset = 0, days = 7) { + const monday = this.baseDate.startOf("week").add(1, "day").add(offset, "week"); + return Array.from({ length: days }, (_, i) => monday.add(i, "day").format("YYYY-MM-DD")); + } + /** + * Get dates for specific weekdays within a week + * @param offset - Week offset from base date (0 = current week) + * @param workDays - Array of ISO weekday numbers (1=Monday, 7=Sunday) + * @returns Array of date strings in YYYY-MM-DD format + */ + getWorkWeekDates(offset, workDays) { + const monday = this.baseDate.startOf("week").add(1, "day").add(offset, "week"); + return workDays.map((isoDay) => { + const daysFromMonday = isoDay === 7 ? 6 : isoDay - 1; + return monday.add(daysFromMonday, "day").format("YYYY-MM-DD"); + }); + } + // ============================================ + // FORMATTING + // ============================================ + formatTime(date, showSeconds = false) { + const pattern = showSeconds ? "HH:mm:ss" : "HH:mm"; + return (0, import_dayjs.default)(date).format(pattern); + } + formatTimeRange(start, end) { + return `${this.formatTime(start)} - ${this.formatTime(end)}`; + } + formatDate(date) { + return (0, import_dayjs.default)(date).format("YYYY-MM-DD"); + } + getDateKey(date) { + return this.formatDate(date); + } + // ============================================ + // COLUMN KEY + // ============================================ + /** + * Build a uniform columnKey from grouping segments + * Handles any combination of date, resource, team, etc. + * + * @example + * buildColumnKey({ date: '2025-12-09' }) → "2025-12-09" + * buildColumnKey({ date: '2025-12-09', resource: 'EMP001' }) → "2025-12-09:EMP001" + */ + buildColumnKey(segments) { + const date = segments.date; + const others = Object.entries(segments).filter(([k]) => k !== "date").sort(([a], [b]) => a.localeCompare(b)).map(([, v]) => v); + return date ? [date, ...others].join(":") : others.join(":"); + } + /** + * Parse a columnKey back into segments + * Assumes format: "date:resource:..." or just "date" + */ + parseColumnKey(columnKey) { + const parts = columnKey.split(":"); + return { + date: parts[0], + resource: parts[1] + }; + } + /** + * Extract dateKey from columnKey (first segment) + */ + getDateFromColumnKey(columnKey) { + return columnKey.split(":")[0]; + } + // ============================================ + // TIME CALCULATIONS + // ============================================ + timeToMinutes(timeString) { + const parts = timeString.split(":").map(Number); + const hours = parts[0] || 0; + const minutes = parts[1] || 0; + return hours * 60 + minutes; + } + minutesToTime(totalMinutes) { + const hours = Math.floor(totalMinutes / 60); + const minutes = totalMinutes % 60; + return (0, import_dayjs.default)().hour(hours).minute(minutes).format("HH:mm"); + } + getMinutesSinceMidnight(date) { + const d = (0, import_dayjs.default)(date); + return d.hour() * 60 + d.minute(); + } + // ============================================ + // UTC CONVERSIONS + // ============================================ + toUTC(localDate) { + return import_dayjs.default.tz(localDate, this.timezone).utc().toISOString(); + } + fromUTC(utcString) { + return import_dayjs.default.utc(utcString).tz(this.timezone).toDate(); + } + // ============================================ + // DATE CREATION + // ============================================ + createDateAtTime(baseDate, timeString) { + const totalMinutes = this.timeToMinutes(timeString); + const hours = Math.floor(totalMinutes / 60); + const minutes = totalMinutes % 60; + return (0, import_dayjs.default)(baseDate).startOf("day").hour(hours).minute(minutes).toDate(); + } + getISOWeekDay(date) { + return (0, import_dayjs.default)(date).isoWeekday(); + } +}; +__name(_DateService, "DateService"); +var DateService = _DateService; + +// src/v2/core/BaseGroupingRenderer.ts +var _BaseGroupingRenderer = class _BaseGroupingRenderer { + /** + * Main render method - handles common logic + */ + async render(context) { + const allowedIds = context.filter[this.type] || []; + if (allowedIds.length === 0) + return; + const entities = await this.getEntities(allowedIds); + const dateCount = context.filter["date"]?.length || 1; + const childIds = context.childType ? context.filter[context.childType] || [] : []; + for (const entity of entities) { + const entityChildIds = context.parentChildMap?.[entity.id] || []; + const childCount = entityChildIds.filter((id) => childIds.includes(id)).length; + const colspan = childCount * dateCount; + const header = document.createElement(this.config.elementTag); + header.dataset[this.config.idAttribute] = entity.id; + header.style.setProperty(this.config.colspanVar, String(colspan)); + this.renderHeader(entity, header, context); + context.headerContainer.appendChild(header); + } + } + /** + * Override this method for custom header rendering + * Default: just sets textContent to display name + */ + renderHeader(entity, header, _context) { + header.textContent = this.getDisplayName(entity); + } + /** + * Helper to render a single entity header. + * Can be used by subclasses that override render() but want consistent header creation. + */ + createHeader(entity, context) { + const header = document.createElement(this.config.elementTag); + header.dataset[this.config.idAttribute] = entity.id; + this.renderHeader(entity, header, context); + return header; + } +}; +__name(_BaseGroupingRenderer, "BaseGroupingRenderer"); +var BaseGroupingRenderer = _BaseGroupingRenderer; + +// src/v2/features/resource/ResourceRenderer.ts +var _ResourceRenderer = class _ResourceRenderer extends BaseGroupingRenderer { + constructor(resourceService) { + super(); + this.resourceService = resourceService; + this.type = "resource"; + this.config = { + elementTag: "swp-resource-header", + idAttribute: "resourceId", + colspanVar: "--resource-cols" + }; + } + getEntities(ids) { + return this.resourceService.getByIds(ids); + } + getDisplayName(entity) { + return entity.displayName; + } + /** + * Override render to handle: + * 1. Special ordering when parentChildMap exists (resources grouped by parent) + * 2. Different colspan calculation (just dateCount, not childCount * dateCount) + */ + async render(context) { + const resourceIds = context.filter["resource"] || []; + const dateCount = context.filter["date"]?.length || 1; + let orderedResourceIds; + if (context.parentChildMap) { + orderedResourceIds = []; + for (const childIds of Object.values(context.parentChildMap)) { + for (const childId of childIds) { + if (resourceIds.includes(childId)) { + orderedResourceIds.push(childId); + } + } + } + } else { + orderedResourceIds = resourceIds; + } + const resources = await this.getEntities(orderedResourceIds); + const resourceMap = new Map(resources.map((r) => [r.id, r])); + for (const resourceId of orderedResourceIds) { + const resource = resourceMap.get(resourceId); + if (!resource) + continue; + const header = this.createHeader(resource, context); + header.style.gridColumn = `span ${dateCount}`; + context.headerContainer.appendChild(header); + } + } +}; +__name(_ResourceRenderer, "ResourceRenderer"); +var ResourceRenderer = _ResourceRenderer; + +// src/v2/features/team/TeamRenderer.ts +var _TeamRenderer = class _TeamRenderer extends BaseGroupingRenderer { + constructor(teamService) { + super(); + this.teamService = teamService; + this.type = "team"; + this.config = { + elementTag: "swp-team-header", + idAttribute: "teamId", + colspanVar: "--team-cols" + }; + } + getEntities(ids) { + return this.teamService.getByIds(ids); + } + getDisplayName(entity) { + return entity.name; + } +}; +__name(_TeamRenderer, "TeamRenderer"); +var TeamRenderer = _TeamRenderer; + +// src/v2/features/department/DepartmentRenderer.ts +var _DepartmentRenderer = class _DepartmentRenderer extends BaseGroupingRenderer { + constructor(departmentService) { + super(); + this.departmentService = departmentService; + this.type = "department"; + this.config = { + elementTag: "swp-department-header", + idAttribute: "departmentId", + colspanVar: "--department-cols" + }; + } + getEntities(ids) { + return this.departmentService.getByIds(ids); + } + getDisplayName(entity) { + return entity.name; + } +}; +__name(_DepartmentRenderer, "DepartmentRenderer"); +var DepartmentRenderer = _DepartmentRenderer; + +// src/v2/core/RenderBuilder.ts +function buildPipeline(renderers) { + return { + async run(context) { + for (const renderer of renderers) { + await renderer.render(context); + } + } + }; +} +__name(buildPipeline, "buildPipeline"); + +// src/v2/core/FilterTemplate.ts +var _FilterTemplate = class _FilterTemplate { + constructor(dateService, entityResolver) { + this.dateService = dateService; + this.entityResolver = entityResolver; + this.fields = []; + } + /** + * Tilføj felt til template + * @param idProperty - Property-navn (bruges på både event og column.dataset) + * @param derivedFrom - Hvis feltet udledes fra anden property (f.eks. date fra start) + */ + addField(idProperty, derivedFrom) { + this.fields.push({ idProperty, derivedFrom }); + return this; + } + /** + * Parse dot-notation string into components + * @example 'resource.teamId' → { entityType: 'resource', property: 'teamId', foreignKey: 'resourceId' } + */ + parseDotNotation(idProperty) { + if (!idProperty.includes(".")) + return null; + const [entityType, property] = idProperty.split("."); + return { + entityType, + property, + foreignKey: entityType + "Id" + // Convention: resource → resourceId + }; + } + /** + * Get dataset key for column lookup + * For dot-notation 'resource.teamId', we look for 'teamId' in dataset + */ + getDatasetKey(idProperty) { + const dotNotation = this.parseDotNotation(idProperty); + if (dotNotation) { + return dotNotation.property; + } + return idProperty; + } + /** + * Byg nøgle fra kolonne + * Læser værdier fra column.dataset[idProperty] + * For dot-notation, uses the property part (resource.teamId → teamId) + */ + buildKeyFromColumn(column) { + return this.fields.map((f) => { + const key = this.getDatasetKey(f.idProperty); + return column.dataset[key] || ""; + }).join(":"); + } + /** + * Byg nøgle fra event + * Læser værdier fra event[idProperty] eller udleder fra derivedFrom + * For dot-notation, resolves via EntityResolver + */ + buildKeyFromEvent(event) { + const eventRecord = event; + return this.fields.map((f) => { + const dotNotation = this.parseDotNotation(f.idProperty); + if (dotNotation) { + return this.resolveDotNotation(eventRecord, dotNotation); + } + if (f.derivedFrom) { + const sourceValue = eventRecord[f.derivedFrom]; + if (sourceValue instanceof Date) { + return this.dateService.getDateKey(sourceValue); + } + return String(sourceValue || ""); + } + return String(eventRecord[f.idProperty] || ""); + }).join(":"); + } + /** + * Resolve dot-notation reference via EntityResolver + */ + resolveDotNotation(eventRecord, dotNotation) { + if (!this.entityResolver) { + console.warn(`FilterTemplate: EntityResolver required for dot-notation '${dotNotation.entityType}.${dotNotation.property}'`); + return ""; + } + const foreignId = eventRecord[dotNotation.foreignKey]; + if (!foreignId) + return ""; + const entity = this.entityResolver.resolve(dotNotation.entityType, String(foreignId)); + if (!entity) + return ""; + return String(entity[dotNotation.property] || ""); + } + /** + * Match event mod kolonne + */ + matches(event, column) { + return this.buildKeyFromEvent(event) === this.buildKeyFromColumn(column); + } +}; +__name(_FilterTemplate, "FilterTemplate"); +var FilterTemplate = _FilterTemplate; + +// src/v2/core/CalendarOrchestrator.ts +var _CalendarOrchestrator = class _CalendarOrchestrator { + constructor(allRenderers, eventRenderer, scheduleRenderer, headerDrawerRenderer, dateService, entityServices) { + this.allRenderers = allRenderers; + this.eventRenderer = eventRenderer; + this.scheduleRenderer = scheduleRenderer; + this.headerDrawerRenderer = headerDrawerRenderer; + this.dateService = dateService; + this.entityServices = entityServices; + } + async render(viewConfig, container2) { + const headerContainer = container2.querySelector("swp-calendar-header"); + const columnContainer = container2.querySelector("swp-day-columns"); + if (!headerContainer || !columnContainer) { + throw new Error("Missing swp-calendar-header or swp-day-columns"); + } + const filter = {}; + for (const grouping of viewConfig.groupings) { + filter[grouping.type] = grouping.values; + } + const filterTemplate = new FilterTemplate(this.dateService); + for (const grouping of viewConfig.groupings) { + if (grouping.idProperty) { + filterTemplate.addField(grouping.idProperty, grouping.derivedFrom); + } + } + const { parentChildMap, childType } = await this.resolveBelongsTo(viewConfig.groupings, filter); + const context = { headerContainer, columnContainer, filter, groupings: viewConfig.groupings, parentChildMap, childType }; + headerContainer.innerHTML = ""; + columnContainer.innerHTML = ""; + const levels = viewConfig.groupings.map((g) => g.type).join(" "); + headerContainer.dataset.levels = levels; + const activeRenderers = this.selectRenderers(viewConfig); + const pipeline = buildPipeline(activeRenderers); + await pipeline.run(context); + await this.scheduleRenderer.render(container2, filter); + await this.eventRenderer.render(container2, filter, filterTemplate); + await this.headerDrawerRenderer.render(container2, filter, filterTemplate); + } + selectRenderers(viewConfig) { + const types = viewConfig.groupings.map((g) => g.type); + return types.map((type) => this.allRenderers.find((r) => r.type === type)).filter((r) => r !== void 0); + } + /** + * Resolve belongsTo relations to build parent-child map + * e.g., belongsTo: 'team.resourceIds' → { team1: ['EMP001', 'EMP002'], team2: [...] } + * Also returns the childType (the grouping type that has belongsTo) + */ + async resolveBelongsTo(groupings, filter) { + const childGrouping = groupings.find((g) => g.belongsTo); + if (!childGrouping?.belongsTo) + return {}; + const [entityType, property] = childGrouping.belongsTo.split("."); + if (!entityType || !property) + return {}; + const parentIds = filter[entityType] || []; + if (parentIds.length === 0) + return {}; + const service = this.entityServices.find((s) => s.entityType.toLowerCase() === entityType); + if (!service) + return {}; + const allEntities = await service.getAll(); + const entities = allEntities.filter((e) => parentIds.includes(e.id)); + const map = {}; + for (const entity of entities) { + const entityRecord = entity; + const children = entityRecord[property] || []; + map[entityRecord.id] = children; + } + return { parentChildMap: map, childType: childGrouping.type }; + } +}; +__name(_CalendarOrchestrator, "CalendarOrchestrator"); +var CalendarOrchestrator = _CalendarOrchestrator; + +// src/v2/core/NavigationAnimator.ts +var _NavigationAnimator = class _NavigationAnimator { + constructor(headerTrack, contentTrack) { + this.headerTrack = headerTrack; + this.contentTrack = contentTrack; + } + async slide(direction, renderFn) { + const out = direction === "left" ? "-100%" : "100%"; + const into = direction === "left" ? "100%" : "-100%"; + await this.animateOut(out); + await renderFn(); + await this.animateIn(into); + } + async animateOut(translate) { + await Promise.all([ + this.headerTrack.animate([{ transform: "translateX(0)" }, { transform: `translateX(${translate})` }], { duration: 200, easing: "ease-in" }).finished, + this.contentTrack.animate([{ transform: "translateX(0)" }, { transform: `translateX(${translate})` }], { duration: 200, easing: "ease-in" }).finished + ]); + } + async animateIn(translate) { + await Promise.all([ + this.headerTrack.animate([{ transform: `translateX(${translate})` }, { transform: "translateX(0)" }], { duration: 200, easing: "ease-out" }).finished, + this.contentTrack.animate([{ transform: `translateX(${translate})` }, { transform: "translateX(0)" }], { duration: 200, easing: "ease-out" }).finished + ]); + } +}; +__name(_NavigationAnimator, "NavigationAnimator"); +var NavigationAnimator = _NavigationAnimator; + +// src/v2/core/CalendarEvents.ts +var CalendarEvents = { + // Command events (host → calendar) + CMD_NAVIGATE_PREV: "calendar:cmd:navigate:prev", + CMD_NAVIGATE_NEXT: "calendar:cmd:navigate:next", + CMD_DRAWER_TOGGLE: "calendar:cmd:drawer:toggle", + CMD_RENDER: "calendar:cmd:render", + CMD_WORKWEEK_CHANGE: "calendar:cmd:workweek:change", + CMD_VIEW_UPDATE: "calendar:cmd:view:update" +}; + +// src/v2/core/CalendarApp.ts +var _CalendarApp = class _CalendarApp { + constructor(orchestrator, timeAxisRenderer, dateService, scrollManager, headerDrawerManager, dragDropManager, edgeScrollManager, resizeManager, headerDrawerRenderer, eventPersistenceManager, settingsService, viewConfigService, eventBus) { + this.orchestrator = orchestrator; + this.timeAxisRenderer = timeAxisRenderer; + this.dateService = dateService; + this.scrollManager = scrollManager; + this.headerDrawerManager = headerDrawerManager; + this.dragDropManager = dragDropManager; + this.edgeScrollManager = edgeScrollManager; + this.resizeManager = resizeManager; + this.headerDrawerRenderer = headerDrawerRenderer; + this.eventPersistenceManager = eventPersistenceManager; + this.settingsService = settingsService; + this.viewConfigService = viewConfigService; + this.eventBus = eventBus; + this.weekOffset = 0; + this.currentViewId = "simple"; + this.workweekPreset = null; + this.groupingOverrides = /* @__PURE__ */ new Map(); + } + async init(container2) { + this.container = container2; + const gridSettings = await this.settingsService.getGridSettings(); + if (!gridSettings) { + throw new Error("GridSettings not found"); + } + this.workweekPreset = await this.settingsService.getDefaultWorkweekPreset(); + this.animator = new NavigationAnimator(container2.querySelector("swp-header-track"), container2.querySelector("swp-content-track")); + this.timeAxisRenderer.render(container2.querySelector("#time-axis"), gridSettings.dayStartHour, gridSettings.dayEndHour); + this.scrollManager.init(container2); + this.headerDrawerManager.init(container2); + this.dragDropManager.init(container2); + this.resizeManager.init(container2); + const scrollableContent = container2.querySelector("swp-scrollable-content"); + this.edgeScrollManager.init(scrollableContent); + this.setupEventListeners(); + this.emitStatus("ready"); + } + setupEventListeners() { + this.eventBus.on(CalendarEvents.CMD_NAVIGATE_PREV, () => { + this.handleNavigatePrev(); + }); + this.eventBus.on(CalendarEvents.CMD_NAVIGATE_NEXT, () => { + this.handleNavigateNext(); + }); + this.eventBus.on(CalendarEvents.CMD_DRAWER_TOGGLE, () => { + this.headerDrawerManager.toggle(); + }); + this.eventBus.on(CalendarEvents.CMD_RENDER, (e) => { + const { viewId } = e.detail; + this.handleRenderCommand(viewId); + }); + this.eventBus.on(CalendarEvents.CMD_WORKWEEK_CHANGE, (e) => { + const { presetId } = e.detail; + this.handleWorkweekChange(presetId); + }); + this.eventBus.on(CalendarEvents.CMD_VIEW_UPDATE, (e) => { + const { type, values } = e.detail; + this.handleViewUpdate(type, values); + }); + } + async handleRenderCommand(viewId) { + this.currentViewId = viewId; + await this.render(); + this.emitStatus("rendered", { viewId }); + } + async handleNavigatePrev() { + this.weekOffset--; + await this.animator.slide("right", () => this.render()); + this.emitStatus("rendered", { viewId: this.currentViewId }); + } + async handleNavigateNext() { + this.weekOffset++; + await this.animator.slide("left", () => this.render()); + this.emitStatus("rendered", { viewId: this.currentViewId }); + } + async handleWorkweekChange(presetId) { + const preset = await this.settingsService.getWorkweekPreset(presetId); + if (preset) { + this.workweekPreset = preset; + await this.render(); + this.emitStatus("rendered", { viewId: this.currentViewId }); + } + } + async handleViewUpdate(type, values) { + this.groupingOverrides.set(type, values); + await this.render(); + this.emitStatus("rendered", { viewId: this.currentViewId }); + } + async render() { + const storedConfig = await this.viewConfigService.getById(this.currentViewId); + if (!storedConfig) { + this.emitStatus("error", { message: `ViewConfig not found: ${this.currentViewId}` }); + return; + } + const workDays = this.workweekPreset?.workDays || [1, 2, 3, 4, 5]; + const dates = this.currentViewId === "day" ? this.dateService.getWeekDates(this.weekOffset, 1) : this.dateService.getWorkWeekDates(this.weekOffset, workDays); + const viewConfig = { + ...storedConfig, + groupings: storedConfig.groupings.map((g) => { + if (g.type === "date") { + return { ...g, values: dates }; + } + const override = this.groupingOverrides.get(g.type); + if (override) { + return { ...g, values: override }; + } + return g; + }) + }; + await this.orchestrator.render(viewConfig, this.container); + } + emitStatus(status, detail) { + this.container.dispatchEvent(new CustomEvent(`calendar:status:${status}`, { + detail, + bubbles: true + })); + } +}; +__name(_CalendarApp, "CalendarApp"); +var CalendarApp = _CalendarApp; + +// src/v2/features/timeaxis/TimeAxisRenderer.ts +var _TimeAxisRenderer = class _TimeAxisRenderer { + render(container2, startHour = 6, endHour = 20) { + container2.innerHTML = ""; + for (let hour = startHour; hour <= endHour; hour++) { + const marker = document.createElement("swp-hour-marker"); + marker.textContent = `${hour.toString().padStart(2, "0")}:00`; + container2.appendChild(marker); + } + } +}; +__name(_TimeAxisRenderer, "TimeAxisRenderer"); +var TimeAxisRenderer = _TimeAxisRenderer; + +// src/v2/core/ScrollManager.ts +var _ScrollManager = class _ScrollManager { + init(container2) { + this.scrollableContent = container2.querySelector("swp-scrollable-content"); + this.timeAxisContent = container2.querySelector("swp-time-axis-content"); + this.calendarHeader = container2.querySelector("swp-calendar-header"); + this.headerDrawer = container2.querySelector("swp-header-drawer"); + this.headerViewport = container2.querySelector("swp-header-viewport"); + this.headerSpacer = container2.querySelector("swp-header-spacer"); + this.scrollableContent.addEventListener("scroll", () => this.onScroll()); + this.resizeObserver = new ResizeObserver(() => this.syncHeaderSpacerHeight()); + this.resizeObserver.observe(this.headerViewport); + this.syncHeaderSpacerHeight(); + } + syncHeaderSpacerHeight() { + const computedHeight = getComputedStyle(this.headerViewport).height; + this.headerSpacer.style.height = computedHeight; + } + onScroll() { + const { scrollTop, scrollLeft } = this.scrollableContent; + this.timeAxisContent.style.transform = `translateY(-${scrollTop}px)`; + this.calendarHeader.style.transform = `translateX(-${scrollLeft}px)`; + this.headerDrawer.style.transform = `translateX(-${scrollLeft}px)`; + } +}; +__name(_ScrollManager, "ScrollManager"); +var ScrollManager = _ScrollManager; + +// src/v2/core/HeaderDrawerManager.ts +var _HeaderDrawerManager = class _HeaderDrawerManager { + constructor() { + this.expanded = false; + this.currentRows = 0; + this.rowHeight = 25; + this.duration = 200; + } + init(container2) { + this.drawer = container2.querySelector("swp-header-drawer"); + if (!this.drawer) + console.error("HeaderDrawerManager: swp-header-drawer not found"); + } + toggle() { + this.expanded ? this.collapse() : this.expand(); + } + /** + * Expand drawer to single row (legacy support) + */ + expand() { + this.expandToRows(1); + } + /** + * Expand drawer to fit specified number of rows + */ + expandToRows(rowCount) { + const targetHeight = rowCount * this.rowHeight; + const currentHeight = this.expanded ? this.currentRows * this.rowHeight : 0; + if (this.expanded && this.currentRows === rowCount) + return; + this.currentRows = rowCount; + this.expanded = true; + this.animate(currentHeight, targetHeight); + } + collapse() { + if (!this.expanded) + return; + const currentHeight = this.currentRows * this.rowHeight; + this.expanded = false; + this.currentRows = 0; + this.animate(currentHeight, 0); + } + animate(from, to) { + const keyframes = [ + { height: `${from}px` }, + { height: `${to}px` } + ]; + const options = { + duration: this.duration, + easing: "ease", + fill: "forwards" + }; + this.drawer.animate(keyframes, options); + } + isExpanded() { + return this.expanded; + } + getRowCount() { + return this.currentRows; + } +}; +__name(_HeaderDrawerManager, "HeaderDrawerManager"); +var HeaderDrawerManager = _HeaderDrawerManager; + +// src/v2/demo/MockStores.ts +var _MockTeamStore = class _MockTeamStore { + constructor() { + this.type = "team"; + this.teams = [ + { id: "alpha", name: "Team Alpha" }, + { id: "beta", name: "Team Beta" } + ]; + } + getByIds(ids) { + return this.teams.filter((t) => ids.includes(t.id)); + } +}; +__name(_MockTeamStore, "MockTeamStore"); +var MockTeamStore = _MockTeamStore; +var _MockResourceStore = class _MockResourceStore { + constructor() { + this.type = "resource"; + this.resources = [ + { id: "alice", name: "Alice", teamId: "alpha" }, + { id: "bob", name: "Bob", teamId: "alpha" }, + { id: "carol", name: "Carol", teamId: "beta" }, + { id: "dave", name: "Dave", teamId: "beta" } + ]; + } + getByIds(ids) { + return this.resources.filter((r) => ids.includes(r.id)); + } +}; +__name(_MockResourceStore, "MockResourceStore"); +var MockResourceStore = _MockResourceStore; + +// src/v2/demo/DemoApp.ts +var _DemoApp = class _DemoApp { + constructor(indexedDBContext, dataSeeder, auditService, calendarApp, dateService, resourceService, eventBus) { + this.indexedDBContext = indexedDBContext; + this.dataSeeder = dataSeeder; + this.auditService = auditService; + this.calendarApp = calendarApp; + this.dateService = dateService; + this.resourceService = resourceService; + this.eventBus = eventBus; + this.currentView = "simple"; + } + async init() { + this.dateService.setBaseDate(/* @__PURE__ */ new Date("2025-12-08")); + await this.indexedDBContext.initialize(); + console.log("[DemoApp] IndexedDB initialized"); + await this.dataSeeder.seedIfEmpty(); + console.log("[DemoApp] Data seeding complete"); + this.container = document.querySelector("swp-calendar-container"); + await this.calendarApp.init(this.container); + console.log("[DemoApp] CalendarApp initialized"); + this.setupNavigation(); + this.setupDrawerToggle(); + this.setupViewSwitching(); + this.setupWorkweekSelector(); + await this.setupResourceSelector(); + this.setupStatusListeners(); + this.eventBus.emit(CalendarEvents.CMD_RENDER, { viewId: this.currentView }); + } + setupNavigation() { + document.getElementById("btn-prev").onclick = () => { + this.eventBus.emit(CalendarEvents.CMD_NAVIGATE_PREV); + }; + document.getElementById("btn-next").onclick = () => { + this.eventBus.emit(CalendarEvents.CMD_NAVIGATE_NEXT); + }; + } + setupViewSwitching() { + const chips = document.querySelectorAll(".view-chip"); + chips.forEach((chip) => { + chip.addEventListener("click", () => { + chips.forEach((c) => c.classList.remove("active")); + chip.classList.add("active"); + const view = chip.dataset.view; + if (view) { + this.currentView = view; + this.updateSelectorVisibility(); + this.eventBus.emit(CalendarEvents.CMD_RENDER, { viewId: view }); + } + }); + }); + } + updateSelectorVisibility() { + const selector = document.querySelector("swp-resource-selector"); + const showSelector = this.currentView === "picker" || this.currentView === "day"; + selector?.classList.toggle("hidden", !showSelector); + } + setupDrawerToggle() { + document.getElementById("btn-drawer").onclick = () => { + this.eventBus.emit(CalendarEvents.CMD_DRAWER_TOGGLE); + }; + } + setupWorkweekSelector() { + const workweekSelect = document.getElementById("workweek-select"); + workweekSelect?.addEventListener("change", () => { + const presetId = workweekSelect.value; + this.eventBus.emit(CalendarEvents.CMD_WORKWEEK_CHANGE, { presetId }); + }); + } + async setupResourceSelector() { + const resources = await this.resourceService.getAll(); + const container2 = document.querySelector(".resource-checkboxes"); + if (!container2) + return; + container2.innerHTML = ""; + resources.forEach((r) => { + const label = document.createElement("label"); + label.innerHTML = ` + + ${r.displayName} + `; + container2.appendChild(label); + }); + container2.addEventListener("change", () => { + const checked = container2.querySelectorAll("input:checked"); + const values = Array.from(checked).map((cb) => cb.value); + this.eventBus.emit(CalendarEvents.CMD_VIEW_UPDATE, { type: "resource", values }); + }); + } + setupStatusListeners() { + this.container.addEventListener("calendar:status:ready", () => { + console.log("[DemoApp] Calendar ready"); + }); + this.container.addEventListener("calendar:status:rendered", (e) => { + console.log("[DemoApp] Calendar rendered:", e.detail.viewId); + }); + this.container.addEventListener("calendar:status:error", (e) => { + console.error("[DemoApp] Calendar error:", e.detail.message); + }); + } +}; +__name(_DemoApp, "DemoApp"); +var DemoApp = _DemoApp; + +// src/v2/core/EventBus.ts +var _EventBus = class _EventBus { + constructor() { + this.eventLog = []; + this.debug = false; + this.listeners = /* @__PURE__ */ new Set(); + this.logConfig = { + calendar: true, + grid: true, + event: true, + scroll: true, + navigation: true, + view: true, + default: true + }; + } + /** + * Subscribe to an event via DOM addEventListener + */ + on(eventType, handler, options) { + document.addEventListener(eventType, handler, options); + this.listeners.add({ eventType, handler, options }); + return () => this.off(eventType, handler); + } + /** + * Subscribe to an event once + */ + once(eventType, handler) { + return this.on(eventType, handler, { once: true }); + } + /** + * Unsubscribe from an event + */ + off(eventType, handler) { + document.removeEventListener(eventType, handler); + for (const listener of this.listeners) { + if (listener.eventType === eventType && listener.handler === handler) { + this.listeners.delete(listener); + break; + } + } + } + /** + * Emit an event via DOM CustomEvent + */ + emit(eventType, detail = {}) { + if (!eventType) { + return false; + } + const event = new CustomEvent(eventType, { + detail: detail ?? {}, + bubbles: true, + cancelable: true + }); + if (this.debug) { + this.logEventWithGrouping(eventType, detail); + } + this.eventLog.push({ + type: eventType, + detail: detail ?? {}, + timestamp: Date.now() + }); + return !document.dispatchEvent(event); + } + /** + * Log event with console grouping + */ + logEventWithGrouping(eventType, _detail) { + const category = this.extractCategory(eventType); + if (!this.logConfig[category]) { + return; + } + this.getCategoryStyle(category); + } + /** + * Extract category from event type + */ + extractCategory(eventType) { + if (!eventType) { + return "unknown"; + } + if (eventType.includes(":")) { + return eventType.split(":")[0]; + } + const lowerType = eventType.toLowerCase(); + if (lowerType.includes("grid") || lowerType.includes("rendered")) + return "grid"; + if (lowerType.includes("event") || lowerType.includes("sync")) + return "event"; + if (lowerType.includes("scroll")) + return "scroll"; + if (lowerType.includes("nav") || lowerType.includes("date")) + return "navigation"; + if (lowerType.includes("view")) + return "view"; + return "default"; + } + /** + * Get styling for different categories + */ + getCategoryStyle(category) { + const styles = { + calendar: { emoji: "\u{1F4C5}", color: "#2196F3" }, + grid: { emoji: "\u{1F4CA}", color: "#4CAF50" }, + event: { emoji: "\u{1F4CC}", color: "#FF9800" }, + scroll: { emoji: "\u{1F4DC}", color: "#9C27B0" }, + navigation: { emoji: "\u{1F9ED}", color: "#F44336" }, + view: { emoji: "\u{1F441}", color: "#00BCD4" }, + default: { emoji: "\u{1F4E2}", color: "#607D8B" } + }; + return styles[category] || styles.default; + } + /** + * Configure logging for specific categories + */ + setLogConfig(config) { + this.logConfig = { ...this.logConfig, ...config }; + } + /** + * Get current log configuration + */ + getLogConfig() { + return { ...this.logConfig }; + } + /** + * Get event history + */ + getEventLog(eventType) { + if (eventType) { + return this.eventLog.filter((e) => e.type === eventType); + } + return this.eventLog; + } + /** + * Enable/disable debug mode + */ + setDebug(enabled) { + this.debug = enabled; + } +}; +__name(_EventBus, "EventBus"); +var EventBus = _EventBus; + +// src/v2/storage/IndexedDBContext.ts +var _IndexedDBContext = class _IndexedDBContext { + constructor(stores) { + this.db = null; + this.initialized = false; + this.stores = stores; + } + /** + * Initialize and open the database + */ + async initialize() { + return new Promise((resolve, reject) => { + const request = indexedDB.open(_IndexedDBContext.DB_NAME, _IndexedDBContext.DB_VERSION); + request.onerror = () => { + reject(new Error(`Failed to open IndexedDB: ${request.error}`)); + }; + request.onsuccess = () => { + this.db = request.result; + this.initialized = true; + resolve(); + }; + request.onupgradeneeded = (event) => { + const db = event.target.result; + this.stores.forEach((store) => { + if (!db.objectStoreNames.contains(store.storeName)) { + store.create(db); + } + }); + }; + }); + } + /** + * Check if database is initialized + */ + isInitialized() { + return this.initialized; + } + /** + * Get IDBDatabase instance + */ + getDatabase() { + if (!this.db) { + throw new Error("IndexedDB not initialized. Call initialize() first."); + } + return this.db; + } + /** + * Close database connection + */ + close() { + if (this.db) { + this.db.close(); + this.db = null; + this.initialized = false; + } + } + /** + * Delete entire database (for testing/reset) + */ + static async deleteDatabase() { + return new Promise((resolve, reject) => { + const request = indexedDB.deleteDatabase(_IndexedDBContext.DB_NAME); + request.onsuccess = () => resolve(); + request.onerror = () => reject(new Error(`Failed to delete database: ${request.error}`)); + }); + } +}; +__name(_IndexedDBContext, "IndexedDBContext"); +var IndexedDBContext = _IndexedDBContext; +IndexedDBContext.DB_NAME = "CalendarV2DB"; +IndexedDBContext.DB_VERSION = 4; + +// src/v2/storage/events/EventStore.ts +var _EventStore = class _EventStore { + constructor() { + this.storeName = _EventStore.STORE_NAME; + } + /** + * Create the events ObjectStore with indexes + */ + create(db) { + const store = db.createObjectStore(_EventStore.STORE_NAME, { keyPath: "id" }); + store.createIndex("start", "start", { unique: false }); + store.createIndex("end", "end", { unique: false }); + store.createIndex("syncStatus", "syncStatus", { unique: false }); + store.createIndex("resourceId", "resourceId", { unique: false }); + store.createIndex("customerId", "customerId", { unique: false }); + store.createIndex("bookingId", "bookingId", { unique: false }); + store.createIndex("startEnd", ["start", "end"], { unique: false }); + } +}; +__name(_EventStore, "EventStore"); +var EventStore = _EventStore; +EventStore.STORE_NAME = "events"; + +// src/v2/storage/events/EventSerialization.ts +var _EventSerialization = class _EventSerialization { + /** + * Serialize event for IndexedDB storage + */ + static serialize(event) { + return { + ...event, + start: event.start instanceof Date ? event.start.toISOString() : event.start, + end: event.end instanceof Date ? event.end.toISOString() : event.end + }; + } + /** + * Deserialize event from IndexedDB storage + */ + static deserialize(data) { + return { + ...data, + start: typeof data.start === "string" ? new Date(data.start) : data.start, + end: typeof data.end === "string" ? new Date(data.end) : data.end + }; + } +}; +__name(_EventSerialization, "EventSerialization"); +var EventSerialization = _EventSerialization; + +// src/v2/storage/SyncPlugin.ts +var _SyncPlugin = class _SyncPlugin { + constructor(service) { + this.service = service; + } + /** + * Mark entity as successfully synced + */ + async markAsSynced(id) { + const entity = await this.service.get(id); + if (entity) { + entity.syncStatus = "synced"; + await this.service.save(entity); + } + } + /** + * Mark entity as sync error + */ + async markAsError(id) { + const entity = await this.service.get(id); + if (entity) { + entity.syncStatus = "error"; + await this.service.save(entity); + } + } + /** + * Get current sync status for an entity + */ + async getSyncStatus(id) { + const entity = await this.service.get(id); + return entity ? entity.syncStatus : null; + } + /** + * Get entities by sync status using IndexedDB index + */ + async getBySyncStatus(syncStatus) { + return new Promise((resolve, reject) => { + const transaction = this.service.db.transaction([this.service.storeName], "readonly"); + const store = transaction.objectStore(this.service.storeName); + const index = store.index("syncStatus"); + const request = index.getAll(syncStatus); + request.onsuccess = () => { + const data = request.result; + const entities = data.map((item) => this.service.deserialize(item)); + resolve(entities); + }; + request.onerror = () => { + reject(new Error(`Failed to get by sync status ${syncStatus}: ${request.error}`)); + }; + }); + } +}; +__name(_SyncPlugin, "SyncPlugin"); +var SyncPlugin = _SyncPlugin; + +// src/v2/constants/CoreEvents.ts +var CoreEvents = { + // Lifecycle events + INITIALIZED: "core:initialized", + READY: "core:ready", + DESTROYED: "core:destroyed", + // View events + VIEW_CHANGED: "view:changed", + VIEW_RENDERED: "view:rendered", + // Navigation events + DATE_CHANGED: "nav:date-changed", + NAVIGATION_COMPLETED: "nav:navigation-completed", + // Data events + DATA_LOADING: "data:loading", + DATA_LOADED: "data:loaded", + DATA_ERROR: "data:error", + // Grid events + GRID_RENDERED: "grid:rendered", + GRID_CLICKED: "grid:clicked", + // Event management + EVENT_CREATED: "event:created", + EVENT_UPDATED: "event:updated", + EVENT_DELETED: "event:deleted", + EVENT_SELECTED: "event:selected", + // Event drag-drop + EVENT_DRAG_START: "event:drag-start", + EVENT_DRAG_MOVE: "event:drag-move", + EVENT_DRAG_END: "event:drag-end", + EVENT_DRAG_CANCEL: "event:drag-cancel", + EVENT_DRAG_COLUMN_CHANGE: "event:drag-column-change", + // Header drag (timed → header conversion) + EVENT_DRAG_ENTER_HEADER: "event:drag-enter-header", + EVENT_DRAG_MOVE_HEADER: "event:drag-move-header", + EVENT_DRAG_LEAVE_HEADER: "event:drag-leave-header", + // Event resize + EVENT_RESIZE_START: "event:resize-start", + EVENT_RESIZE_END: "event:resize-end", + // Edge scroll + EDGE_SCROLL_TICK: "edge-scroll:tick", + EDGE_SCROLL_STARTED: "edge-scroll:started", + EDGE_SCROLL_STOPPED: "edge-scroll:stopped", + // System events + ERROR: "system:error", + // Sync events + SYNC_STARTED: "sync:started", + SYNC_COMPLETED: "sync:completed", + SYNC_FAILED: "sync:failed", + // Entity events - for audit and sync + ENTITY_SAVED: "entity:saved", + ENTITY_DELETED: "entity:deleted", + // Audit events + AUDIT_LOGGED: "audit:logged", + // Rendering events + EVENTS_RENDERED: "events:rendered" +}; + +// node_modules/json-diff-ts/dist/index.js +function arrayDifference(first, second) { + const secondSet = new Set(second); + return first.filter((item) => !secondSet.has(item)); +} +__name(arrayDifference, "arrayDifference"); +function arrayIntersection(first, second) { + const secondSet = new Set(second); + return first.filter((item) => secondSet.has(item)); +} +__name(arrayIntersection, "arrayIntersection"); +function keyBy(arr, getKey2) { + const result = {}; + for (const item of arr) { + result[String(getKey2(item))] = item; + } + return result; +} +__name(keyBy, "keyBy"); +function diff(oldObj, newObj, options = {}) { + let { embeddedObjKeys } = options; + const { keysToSkip, treatTypeChangeAsReplace } = options; + if (embeddedObjKeys instanceof Map) { + embeddedObjKeys = new Map( + Array.from(embeddedObjKeys.entries()).map(([key, value]) => [ + key instanceof RegExp ? key : key.replace(/^\./, ""), + value + ]) + ); + } else if (embeddedObjKeys) { + embeddedObjKeys = Object.fromEntries( + Object.entries(embeddedObjKeys).map(([key, value]) => [key.replace(/^\./, ""), value]) + ); + } + return compare(oldObj, newObj, [], [], { + embeddedObjKeys, + keysToSkip: keysToSkip ?? [], + treatTypeChangeAsReplace: treatTypeChangeAsReplace ?? true + }); +} +__name(diff, "diff"); +var getTypeOfObj = /* @__PURE__ */ __name((obj) => { + if (typeof obj === "undefined") { + return "undefined"; + } + if (obj === null) { + return null; + } + return Object.prototype.toString.call(obj).match(/^\[object\s(.*)\]$/)[1]; +}, "getTypeOfObj"); +var getKey = /* @__PURE__ */ __name((path) => { + const left = path[path.length - 1]; + return left != null ? left : "$root"; +}, "getKey"); +var compare = /* @__PURE__ */ __name((oldObj, newObj, path, keyPath, options) => { + let changes = []; + const currentPath = keyPath.join("."); + if (options.keysToSkip?.some((skipPath) => { + if (currentPath === skipPath) { + return true; + } + if (skipPath.includes(".") && skipPath.startsWith(currentPath + ".")) { + return false; + } + if (skipPath.includes(".")) { + const skipParts = skipPath.split("."); + const currentParts = currentPath.split("."); + if (currentParts.length >= skipParts.length) { + for (let i = 0; i < skipParts.length; i++) { + if (skipParts[i] !== currentParts[i]) { + return false; + } + } + return true; + } + } + return false; + })) { + return changes; + } + const typeOfOldObj = getTypeOfObj(oldObj); + const typeOfNewObj = getTypeOfObj(newObj); + if (options.treatTypeChangeAsReplace && typeOfOldObj !== typeOfNewObj) { + if (typeOfOldObj !== "undefined") { + changes.push({ type: "REMOVE", key: getKey(path), value: oldObj }); + } + if (typeOfNewObj !== "undefined") { + changes.push({ type: "ADD", key: getKey(path), value: newObj }); + } + return changes; + } + if (typeOfNewObj === "undefined" && typeOfOldObj !== "undefined") { + changes.push({ type: "REMOVE", key: getKey(path), value: oldObj }); + return changes; + } + if (typeOfNewObj === "Object" && typeOfOldObj === "Array") { + changes.push({ type: "UPDATE", key: getKey(path), value: newObj, oldValue: oldObj }); + return changes; + } + if (typeOfNewObj === null) { + if (typeOfOldObj !== null) { + changes.push({ type: "UPDATE", key: getKey(path), value: newObj, oldValue: oldObj }); + } + return changes; + } + switch (typeOfOldObj) { + case "Date": + if (typeOfNewObj === "Date") { + changes = changes.concat( + comparePrimitives(oldObj.getTime(), newObj.getTime(), path).map((x) => ({ + ...x, + value: new Date(x.value), + oldValue: new Date(x.oldValue) + })) + ); + } else { + changes = changes.concat(comparePrimitives(oldObj, newObj, path)); + } + break; + case "Object": { + const diffs = compareObject(oldObj, newObj, path, keyPath, false, options); + if (diffs.length) { + if (path.length) { + changes.push({ + type: "UPDATE", + key: getKey(path), + changes: diffs + }); + } else { + changes = changes.concat(diffs); + } + } + break; + } + case "Array": + changes = changes.concat(compareArray(oldObj, newObj, path, keyPath, options)); + break; + case "Function": + break; + default: + changes = changes.concat(comparePrimitives(oldObj, newObj, path)); + } + return changes; +}, "compare"); +var compareObject = /* @__PURE__ */ __name((oldObj, newObj, path, keyPath, skipPath = false, options = {}) => { + let k; + let newKeyPath; + let newPath; + if (skipPath == null) { + skipPath = false; + } + let changes = []; + const oldObjKeys = Object.keys(oldObj); + const newObjKeys = Object.keys(newObj); + const intersectionKeys = arrayIntersection(oldObjKeys, newObjKeys); + for (k of intersectionKeys) { + newPath = path.concat([k]); + newKeyPath = skipPath ? keyPath : keyPath.concat([k]); + const diffs = compare(oldObj[k], newObj[k], newPath, newKeyPath, options); + if (diffs.length) { + changes = changes.concat(diffs); + } + } + const addedKeys = arrayDifference(newObjKeys, oldObjKeys); + for (k of addedKeys) { + newPath = path.concat([k]); + newKeyPath = skipPath ? keyPath : keyPath.concat([k]); + const currentPath = newKeyPath.join("."); + if (options.keysToSkip?.some((skipPath2) => currentPath === skipPath2 || currentPath.startsWith(skipPath2 + "."))) { + continue; + } + changes.push({ + type: "ADD", + key: getKey(newPath), + value: newObj[k] + }); + } + const deletedKeys = arrayDifference(oldObjKeys, newObjKeys); + for (k of deletedKeys) { + newPath = path.concat([k]); + newKeyPath = skipPath ? keyPath : keyPath.concat([k]); + const currentPath = newKeyPath.join("."); + if (options.keysToSkip?.some((skipPath2) => currentPath === skipPath2 || currentPath.startsWith(skipPath2 + "."))) { + continue; + } + changes.push({ + type: "REMOVE", + key: getKey(newPath), + value: oldObj[k] + }); + } + return changes; +}, "compareObject"); +var compareArray = /* @__PURE__ */ __name((oldObj, newObj, path, keyPath, options) => { + if (getTypeOfObj(newObj) !== "Array") { + return [{ type: "UPDATE", key: getKey(path), value: newObj, oldValue: oldObj }]; + } + const left = getObjectKey(options.embeddedObjKeys, keyPath); + const uniqKey = left != null ? left : "$index"; + const indexedOldObj = convertArrayToObj(oldObj, uniqKey); + const indexedNewObj = convertArrayToObj(newObj, uniqKey); + const diffs = compareObject(indexedOldObj, indexedNewObj, path, keyPath, true, options); + if (diffs.length) { + return [ + { + type: "UPDATE", + key: getKey(path), + embeddedKey: typeof uniqKey === "function" && uniqKey.length === 2 ? uniqKey(newObj[0], true) : uniqKey, + changes: diffs + } + ]; + } else { + return []; + } +}, "compareArray"); +var getObjectKey = /* @__PURE__ */ __name((embeddedObjKeys, keyPath) => { + if (embeddedObjKeys != null) { + const path = keyPath.join("."); + if (embeddedObjKeys instanceof Map) { + for (const [key2, value] of embeddedObjKeys.entries()) { + if (key2 instanceof RegExp) { + if (path.match(key2)) { + return value; + } + } else if (path === key2) { + return value; + } + } + } + const key = embeddedObjKeys[path]; + if (key != null) { + return key; + } + } + return void 0; +}, "getObjectKey"); +var convertArrayToObj = /* @__PURE__ */ __name((arr, uniqKey) => { + let obj = {}; + if (uniqKey === "$value") { + arr.forEach((value) => { + obj[value] = value; + }); + } else if (uniqKey !== "$index") { + const keyFunction = typeof uniqKey === "string" ? (item) => item[uniqKey] : uniqKey; + obj = keyBy(arr, keyFunction); + } else { + for (let i = 0; i < arr.length; i++) { + const value = arr[i]; + obj[i] = value; + } + } + return obj; +}, "convertArrayToObj"); +var comparePrimitives = /* @__PURE__ */ __name((oldObj, newObj, path) => { + const changes = []; + if (oldObj !== newObj) { + changes.push({ + type: "UPDATE", + key: getKey(path), + value: newObj, + oldValue: oldObj + }); + } + return changes; +}, "comparePrimitives"); + +// src/v2/storage/BaseEntityService.ts +var _BaseEntityService = class _BaseEntityService { + constructor(context, eventBus) { + this.context = context; + this.eventBus = eventBus; + this.syncPlugin = new SyncPlugin(this); + } + get db() { + return this.context.getDatabase(); + } + /** + * Serialize entity before storing in IndexedDB + */ + serialize(entity) { + return entity; + } + /** + * Deserialize data from IndexedDB back to entity + */ + deserialize(data) { + return data; + } + /** + * Get a single entity by ID + */ + async get(id) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const request = store.get(id); + request.onsuccess = () => { + const data = request.result; + resolve(data ? this.deserialize(data) : null); + }; + request.onerror = () => { + reject(new Error(`Failed to get ${this.entityType} ${id}: ${request.error}`)); + }; + }); + } + /** + * Get all entities + */ + async getAll() { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const request = store.getAll(); + request.onsuccess = () => { + const data = request.result; + const entities = data.map((item) => this.deserialize(item)); + resolve(entities); + }; + request.onerror = () => { + reject(new Error(`Failed to get all ${this.entityType}s: ${request.error}`)); + }; + }); + } + /** + * Save an entity (create or update) + * Emits ENTITY_SAVED event with operation type and changes (diff for updates) + * @param entity - Entity to save + * @param silent - If true, skip event emission (used for seeding) + */ + async save(entity, silent = false) { + const entityId = entity.id; + const existingEntity = await this.get(entityId); + const isCreate = existingEntity === null; + let changes; + if (isCreate) { + changes = entity; + } else { + const existingSerialized = this.serialize(existingEntity); + const newSerialized = this.serialize(entity); + changes = diff(existingSerialized, newSerialized); + } + const serialized = this.serialize(entity); + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readwrite"); + const store = transaction.objectStore(this.storeName); + const request = store.put(serialized); + request.onsuccess = () => { + if (!silent) { + const payload = { + entityType: this.entityType, + entityId, + operation: isCreate ? "create" : "update", + changes, + timestamp: Date.now() + }; + this.eventBus.emit(CoreEvents.ENTITY_SAVED, payload); + } + resolve(); + }; + request.onerror = () => { + reject(new Error(`Failed to save ${this.entityType} ${entityId}: ${request.error}`)); + }; + }); + } + /** + * Delete an entity + * Emits ENTITY_DELETED event + */ + async delete(id) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readwrite"); + const store = transaction.objectStore(this.storeName); + const request = store.delete(id); + request.onsuccess = () => { + const payload = { + entityType: this.entityType, + entityId: id, + operation: "delete", + timestamp: Date.now() + }; + this.eventBus.emit(CoreEvents.ENTITY_DELETED, payload); + resolve(); + }; + request.onerror = () => { + reject(new Error(`Failed to delete ${this.entityType} ${id}: ${request.error}`)); + }; + }); + } + // Sync methods - delegate to SyncPlugin + async markAsSynced(id) { + return this.syncPlugin.markAsSynced(id); + } + async markAsError(id) { + return this.syncPlugin.markAsError(id); + } + async getSyncStatus(id) { + return this.syncPlugin.getSyncStatus(id); + } + async getBySyncStatus(syncStatus) { + return this.syncPlugin.getBySyncStatus(syncStatus); + } +}; +__name(_BaseEntityService, "BaseEntityService"); +var BaseEntityService = _BaseEntityService; + +// src/v2/storage/events/EventService.ts +var _EventService = class _EventService extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = EventStore.STORE_NAME; + this.entityType = "Event"; + } + serialize(event) { + return EventSerialization.serialize(event); + } + deserialize(data) { + return EventSerialization.deserialize(data); + } + /** + * Get events within a date range + */ + async getByDateRange(start, end) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const index = store.index("start"); + const range = IDBKeyRange.lowerBound(start.toISOString()); + const request = index.getAll(range); + request.onsuccess = () => { + const data = request.result; + const events = data.map((item) => this.deserialize(item)).filter((event) => event.start <= end); + resolve(events); + }; + request.onerror = () => { + reject(new Error(`Failed to get events by date range: ${request.error}`)); + }; + }); + } + /** + * Get events for a specific resource + */ + async getByResource(resourceId) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const index = store.index("resourceId"); + const request = index.getAll(resourceId); + request.onsuccess = () => { + const data = request.result; + const events = data.map((item) => this.deserialize(item)); + resolve(events); + }; + request.onerror = () => { + reject(new Error(`Failed to get events for resource ${resourceId}: ${request.error}`)); + }; + }); + } + /** + * Get events for a resource within a date range + */ + async getByResourceAndDateRange(resourceId, start, end) { + const resourceEvents = await this.getByResource(resourceId); + return resourceEvents.filter((event) => event.start >= start && event.start <= end); + } +}; +__name(_EventService, "EventService"); +var EventService = _EventService; + +// src/v2/storage/resources/ResourceStore.ts +var _ResourceStore = class _ResourceStore { + constructor() { + this.storeName = _ResourceStore.STORE_NAME; + } + create(db) { + const store = db.createObjectStore(_ResourceStore.STORE_NAME, { keyPath: "id" }); + store.createIndex("type", "type", { unique: false }); + store.createIndex("syncStatus", "syncStatus", { unique: false }); + store.createIndex("isActive", "isActive", { unique: false }); + } +}; +__name(_ResourceStore, "ResourceStore"); +var ResourceStore = _ResourceStore; +ResourceStore.STORE_NAME = "resources"; + +// src/v2/storage/resources/ResourceService.ts +var _ResourceService = class _ResourceService extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = ResourceStore.STORE_NAME; + this.entityType = "Resource"; + } + /** + * Get all active resources + */ + async getActive() { + const all = await this.getAll(); + return all.filter((r) => r.isActive !== false); + } + /** + * Get resources by IDs + */ + async getByIds(ids) { + if (ids.length === 0) + return []; + const results = await Promise.all(ids.map((id) => this.get(id))); + return results.filter((r) => r !== null); + } + /** + * Get resources by type + */ + async getByType(type) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const index = store.index("type"); + const request = index.getAll(type); + request.onsuccess = () => { + const data = request.result; + resolve(data); + }; + request.onerror = () => { + reject(new Error(`Failed to get resources by type ${type}: ${request.error}`)); + }; + }); + } +}; +__name(_ResourceService, "ResourceService"); +var ResourceService = _ResourceService; + +// src/v2/storage/bookings/BookingStore.ts +var _BookingStore = class _BookingStore { + constructor() { + this.storeName = _BookingStore.STORE_NAME; + } + create(db) { + const store = db.createObjectStore(_BookingStore.STORE_NAME, { keyPath: "id" }); + store.createIndex("customerId", "customerId", { unique: false }); + store.createIndex("status", "status", { unique: false }); + store.createIndex("syncStatus", "syncStatus", { unique: false }); + store.createIndex("createdAt", "createdAt", { unique: false }); + } +}; +__name(_BookingStore, "BookingStore"); +var BookingStore = _BookingStore; +BookingStore.STORE_NAME = "bookings"; + +// src/v2/storage/bookings/BookingService.ts +var _BookingService = class _BookingService extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = BookingStore.STORE_NAME; + this.entityType = "Booking"; + } + serialize(booking) { + return { + ...booking, + createdAt: booking.createdAt.toISOString() + }; + } + deserialize(data) { + const raw = data; + return { + ...raw, + createdAt: new Date(raw.createdAt) + }; + } + /** + * Get bookings for a customer + */ + async getByCustomer(customerId) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const index = store.index("customerId"); + const request = index.getAll(customerId); + request.onsuccess = () => { + const data = request.result; + const bookings = data.map((item) => this.deserialize(item)); + resolve(bookings); + }; + request.onerror = () => { + reject(new Error(`Failed to get bookings for customer ${customerId}: ${request.error}`)); + }; + }); + } + /** + * Get bookings by status + */ + async getByStatus(status) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const index = store.index("status"); + const request = index.getAll(status); + request.onsuccess = () => { + const data = request.result; + const bookings = data.map((item) => this.deserialize(item)); + resolve(bookings); + }; + request.onerror = () => { + reject(new Error(`Failed to get bookings with status ${status}: ${request.error}`)); + }; + }); + } +}; +__name(_BookingService, "BookingService"); +var BookingService = _BookingService; + +// src/v2/storage/customers/CustomerStore.ts +var _CustomerStore = class _CustomerStore { + constructor() { + this.storeName = _CustomerStore.STORE_NAME; + } + create(db) { + const store = db.createObjectStore(_CustomerStore.STORE_NAME, { keyPath: "id" }); + store.createIndex("name", "name", { unique: false }); + store.createIndex("phone", "phone", { unique: false }); + store.createIndex("syncStatus", "syncStatus", { unique: false }); + } +}; +__name(_CustomerStore, "CustomerStore"); +var CustomerStore = _CustomerStore; +CustomerStore.STORE_NAME = "customers"; + +// src/v2/storage/customers/CustomerService.ts +var _CustomerService = class _CustomerService extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = CustomerStore.STORE_NAME; + this.entityType = "Customer"; + } + /** + * Search customers by name (case-insensitive contains) + */ + async searchByName(query) { + const all = await this.getAll(); + const lowerQuery = query.toLowerCase(); + return all.filter((c) => c.name.toLowerCase().includes(lowerQuery)); + } + /** + * Find customer by phone + */ + async getByPhone(phone) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const index = store.index("phone"); + const request = index.get(phone); + request.onsuccess = () => { + const data = request.result; + resolve(data ? data : null); + }; + request.onerror = () => { + reject(new Error(`Failed to find customer by phone ${phone}: ${request.error}`)); + }; + }); + } +}; +__name(_CustomerService, "CustomerService"); +var CustomerService = _CustomerService; + +// src/v2/storage/teams/TeamStore.ts +var _TeamStore = class _TeamStore { + constructor() { + this.storeName = _TeamStore.STORE_NAME; + } + create(db) { + db.createObjectStore(_TeamStore.STORE_NAME, { keyPath: "id" }); + } +}; +__name(_TeamStore, "TeamStore"); +var TeamStore = _TeamStore; +TeamStore.STORE_NAME = "teams"; + +// src/v2/storage/teams/TeamService.ts +var _TeamService = class _TeamService extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = TeamStore.STORE_NAME; + this.entityType = "Team"; + } + /** + * Get teams by IDs + */ + async getByIds(ids) { + if (ids.length === 0) + return []; + const results = await Promise.all(ids.map((id) => this.get(id))); + return results.filter((t) => t !== null); + } + /** + * Build reverse lookup: resourceId → teamId + */ + async buildResourceToTeamMap() { + const teams = await this.getAll(); + const map = {}; + for (const team of teams) { + for (const resourceId of team.resourceIds) { + map[resourceId] = team.id; + } + } + return map; + } +}; +__name(_TeamService, "TeamService"); +var TeamService = _TeamService; + +// src/v2/storage/departments/DepartmentStore.ts +var _DepartmentStore = class _DepartmentStore { + constructor() { + this.storeName = _DepartmentStore.STORE_NAME; + } + create(db) { + db.createObjectStore(_DepartmentStore.STORE_NAME, { keyPath: "id" }); + } +}; +__name(_DepartmentStore, "DepartmentStore"); +var DepartmentStore = _DepartmentStore; +DepartmentStore.STORE_NAME = "departments"; + +// src/v2/storage/departments/DepartmentService.ts +var _DepartmentService = class _DepartmentService extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = DepartmentStore.STORE_NAME; + this.entityType = "Department"; + } + /** + * Get departments by IDs + */ + async getByIds(ids) { + if (ids.length === 0) + return []; + const results = await Promise.all(ids.map((id) => this.get(id))); + return results.filter((d) => d !== null); + } +}; +__name(_DepartmentService, "DepartmentService"); +var DepartmentService = _DepartmentService; + +// src/v2/storage/settings/SettingsStore.ts +var _SettingsStore = class _SettingsStore { + constructor() { + this.storeName = _SettingsStore.STORE_NAME; + } + create(db) { + db.createObjectStore(_SettingsStore.STORE_NAME, { keyPath: "id" }); + } +}; +__name(_SettingsStore, "SettingsStore"); +var SettingsStore = _SettingsStore; +SettingsStore.STORE_NAME = "settings"; + +// src/v2/types/SettingsTypes.ts +var SettingsIds = { + WORKWEEK: "workweek", + GRID: "grid", + TIME_FORMAT: "timeFormat", + VIEWS: "views" +}; + +// src/v2/storage/settings/SettingsService.ts +var _SettingsService = class _SettingsService extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = SettingsStore.STORE_NAME; + this.entityType = "Settings"; + } + /** + * Get workweek settings + */ + async getWorkweekSettings() { + return this.get(SettingsIds.WORKWEEK); + } + /** + * Get grid settings + */ + async getGridSettings() { + return this.get(SettingsIds.GRID); + } + /** + * Get time format settings + */ + async getTimeFormatSettings() { + return this.get(SettingsIds.TIME_FORMAT); + } + /** + * Get view settings + */ + async getViewSettings() { + return this.get(SettingsIds.VIEWS); + } + /** + * Get workweek preset by ID + */ + async getWorkweekPreset(presetId) { + const settings = await this.getWorkweekSettings(); + if (!settings) + return null; + return settings.presets[presetId] || null; + } + /** + * Get the default workweek preset + */ + async getDefaultWorkweekPreset() { + const settings = await this.getWorkweekSettings(); + if (!settings) + return null; + return settings.presets[settings.defaultPreset] || null; + } + /** + * Get all available workweek presets + */ + async getWorkweekPresets() { + const settings = await this.getWorkweekSettings(); + if (!settings) + return []; + return Object.values(settings.presets); + } +}; +__name(_SettingsService, "SettingsService"); +var SettingsService = _SettingsService; + +// src/v2/storage/viewconfigs/ViewConfigStore.ts +var _ViewConfigStore = class _ViewConfigStore { + constructor() { + this.storeName = _ViewConfigStore.STORE_NAME; + } + create(db) { + db.createObjectStore(_ViewConfigStore.STORE_NAME, { keyPath: "id" }); + } +}; +__name(_ViewConfigStore, "ViewConfigStore"); +var ViewConfigStore = _ViewConfigStore; +ViewConfigStore.STORE_NAME = "viewconfigs"; + +// src/v2/storage/viewconfigs/ViewConfigService.ts +var _ViewConfigService = class _ViewConfigService extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = ViewConfigStore.STORE_NAME; + this.entityType = "ViewConfig"; + } + async getById(id) { + return this.get(id); + } +}; +__name(_ViewConfigService, "ViewConfigService"); +var ViewConfigService = _ViewConfigService; + +// src/v2/storage/audit/AuditStore.ts +var _AuditStore = class _AuditStore { + constructor() { + this.storeName = "audit"; + } + create(db) { + const store = db.createObjectStore(this.storeName, { keyPath: "id" }); + store.createIndex("syncStatus", "syncStatus", { unique: false }); + store.createIndex("synced", "synced", { unique: false }); + store.createIndex("entityId", "entityId", { unique: false }); + store.createIndex("timestamp", "timestamp", { unique: false }); + } +}; +__name(_AuditStore, "AuditStore"); +var AuditStore = _AuditStore; + +// src/v2/storage/audit/AuditService.ts +var _AuditService = class _AuditService extends BaseEntityService { + constructor(context, eventBus) { + super(context, eventBus); + this.storeName = "audit"; + this.entityType = "Audit"; + this.setupEventListeners(); + } + /** + * Setup listeners for ENTITY_SAVED and ENTITY_DELETED events + */ + setupEventListeners() { + this.eventBus.on(CoreEvents.ENTITY_SAVED, (event) => { + const detail = event.detail; + this.handleEntitySaved(detail); + }); + this.eventBus.on(CoreEvents.ENTITY_DELETED, (event) => { + const detail = event.detail; + this.handleEntityDeleted(detail); + }); + } + /** + * Handle ENTITY_SAVED event - create audit entry + */ + async handleEntitySaved(payload) { + if (payload.entityType === "Audit") + return; + const auditEntry = { + id: crypto.randomUUID(), + entityType: payload.entityType, + entityId: payload.entityId, + operation: payload.operation, + userId: _AuditService.DEFAULT_USER_ID, + timestamp: payload.timestamp, + changes: payload.changes, + synced: false, + syncStatus: "pending" + }; + await this.save(auditEntry); + } + /** + * Handle ENTITY_DELETED event - create audit entry + */ + async handleEntityDeleted(payload) { + if (payload.entityType === "Audit") + return; + const auditEntry = { + id: crypto.randomUUID(), + entityType: payload.entityType, + entityId: payload.entityId, + operation: "delete", + userId: _AuditService.DEFAULT_USER_ID, + timestamp: payload.timestamp, + changes: { id: payload.entityId }, + // For delete, just store the ID + synced: false, + syncStatus: "pending" + }; + await this.save(auditEntry); + } + /** + * Override save to NOT trigger ENTITY_SAVED event + * Instead, emits AUDIT_LOGGED for SyncManager to listen + * + * This prevents infinite loops: + * - BaseEntityService.save() emits ENTITY_SAVED + * - AuditService listens to ENTITY_SAVED and creates audit + * - If AuditService.save() also emitted ENTITY_SAVED, it would loop + */ + async save(entity) { + const serialized = this.serialize(entity); + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readwrite"); + const store = transaction.objectStore(this.storeName); + const request = store.put(serialized); + request.onsuccess = () => { + const payload = { + auditId: entity.id, + entityType: entity.entityType, + entityId: entity.entityId, + operation: entity.operation, + timestamp: entity.timestamp + }; + this.eventBus.emit(CoreEvents.AUDIT_LOGGED, payload); + resolve(); + }; + request.onerror = () => { + reject(new Error(`Failed to save audit entry ${entity.id}: ${request.error}`)); + }; + }); + } + /** + * Override delete to NOT trigger ENTITY_DELETED event + * Audit entries should never be deleted (compliance requirement) + */ + async delete(_id) { + throw new Error("Audit entries cannot be deleted (compliance requirement)"); + } + /** + * Get pending audit entries (for sync) + */ + async getPendingAudits() { + return this.getBySyncStatus("pending"); + } + /** + * Get audit entries for a specific entity + */ + async getByEntityId(entityId) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + const index = store.index("entityId"); + const request = index.getAll(entityId); + request.onsuccess = () => { + const entries = request.result; + resolve(entries); + }; + request.onerror = () => { + reject(new Error(`Failed to get audit entries for entity ${entityId}: ${request.error}`)); + }; + }); + } +}; +__name(_AuditService, "AuditService"); +var AuditService = _AuditService; +AuditService.DEFAULT_USER_ID = "00000000-0000-0000-0000-000000000001"; + +// src/v2/repositories/MockEventRepository.ts +var _MockEventRepository = class _MockEventRepository { + constructor() { + this.entityType = "Event"; + this.dataUrl = "data/mock-events.json"; + } + /** + * Fetch all events from mock JSON file + */ + async fetchAll() { + try { + const response = await fetch(this.dataUrl); + if (!response.ok) { + throw new Error(`Failed to load mock events: ${response.status} ${response.statusText}`); + } + const rawData = await response.json(); + return this.processCalendarData(rawData); + } catch (error) { + console.error("Failed to load event data:", error); + throw error; + } + } + async sendCreate(_event) { + throw new Error("MockEventRepository does not support sendCreate. Mock data is read-only."); + } + async sendUpdate(_id, _updates) { + throw new Error("MockEventRepository does not support sendUpdate. Mock data is read-only."); + } + async sendDelete(_id) { + throw new Error("MockEventRepository does not support sendDelete. Mock data is read-only."); + } + processCalendarData(data) { + return data.map((event) => { + if (event.type === "customer") { + if (!event.bookingId) + console.warn(`Customer event ${event.id} missing bookingId`); + if (!event.resourceId) + console.warn(`Customer event ${event.id} missing resourceId`); + if (!event.customerId) + console.warn(`Customer event ${event.id} missing customerId`); + } + return { + id: event.id, + title: event.title, + description: event.description, + start: new Date(event.start), + end: new Date(event.end), + type: event.type, + allDay: event.allDay || false, + bookingId: event.bookingId, + resourceId: event.resourceId, + customerId: event.customerId, + recurringId: event.recurringId, + metadata: event.metadata, + syncStatus: "synced" + }; + }); + } +}; +__name(_MockEventRepository, "MockEventRepository"); +var MockEventRepository = _MockEventRepository; + +// src/v2/repositories/MockResourceRepository.ts +var _MockResourceRepository = class _MockResourceRepository { + constructor() { + this.entityType = "Resource"; + this.dataUrl = "data/mock-resources.json"; + } + async fetchAll() { + try { + const response = await fetch(this.dataUrl); + if (!response.ok) { + throw new Error(`Failed to load mock resources: ${response.status} ${response.statusText}`); + } + const rawData = await response.json(); + return this.processResourceData(rawData); + } catch (error) { + console.error("Failed to load resource data:", error); + throw error; + } + } + async sendCreate(_resource) { + throw new Error("MockResourceRepository does not support sendCreate. Mock data is read-only."); + } + async sendUpdate(_id, _updates) { + throw new Error("MockResourceRepository does not support sendUpdate. Mock data is read-only."); + } + async sendDelete(_id) { + throw new Error("MockResourceRepository does not support sendDelete. Mock data is read-only."); + } + processResourceData(data) { + return data.map((resource) => ({ + id: resource.id, + name: resource.name, + displayName: resource.displayName, + type: resource.type, + avatarUrl: resource.avatarUrl, + color: resource.color, + isActive: resource.isActive, + defaultSchedule: resource.defaultSchedule, + metadata: resource.metadata, + syncStatus: "synced" + })); + } +}; +__name(_MockResourceRepository, "MockResourceRepository"); +var MockResourceRepository = _MockResourceRepository; + +// src/v2/repositories/MockBookingRepository.ts +var _MockBookingRepository = class _MockBookingRepository { + constructor() { + this.entityType = "Booking"; + this.dataUrl = "data/mock-bookings.json"; + } + async fetchAll() { + try { + const response = await fetch(this.dataUrl); + if (!response.ok) { + throw new Error(`Failed to load mock bookings: ${response.status} ${response.statusText}`); + } + const rawData = await response.json(); + return this.processBookingData(rawData); + } catch (error) { + console.error("Failed to load booking data:", error); + throw error; + } + } + async sendCreate(_booking) { + throw new Error("MockBookingRepository does not support sendCreate. Mock data is read-only."); + } + async sendUpdate(_id, _updates) { + throw new Error("MockBookingRepository does not support sendUpdate. Mock data is read-only."); + } + async sendDelete(_id) { + throw new Error("MockBookingRepository does not support sendDelete. Mock data is read-only."); + } + processBookingData(data) { + return data.map((booking) => ({ + id: booking.id, + customerId: booking.customerId, + status: booking.status, + createdAt: new Date(booking.createdAt), + services: booking.services, + totalPrice: booking.totalPrice, + tags: booking.tags, + notes: booking.notes, + syncStatus: "synced" + })); + } +}; +__name(_MockBookingRepository, "MockBookingRepository"); +var MockBookingRepository = _MockBookingRepository; + +// src/v2/repositories/MockCustomerRepository.ts +var _MockCustomerRepository = class _MockCustomerRepository { + constructor() { + this.entityType = "Customer"; + this.dataUrl = "data/mock-customers.json"; + } + async fetchAll() { + try { + const response = await fetch(this.dataUrl); + if (!response.ok) { + throw new Error(`Failed to load mock customers: ${response.status} ${response.statusText}`); + } + const rawData = await response.json(); + return this.processCustomerData(rawData); + } catch (error) { + console.error("Failed to load customer data:", error); + throw error; + } + } + async sendCreate(_customer) { + throw new Error("MockCustomerRepository does not support sendCreate. Mock data is read-only."); + } + async sendUpdate(_id, _updates) { + throw new Error("MockCustomerRepository does not support sendUpdate. Mock data is read-only."); + } + async sendDelete(_id) { + throw new Error("MockCustomerRepository does not support sendDelete. Mock data is read-only."); + } + processCustomerData(data) { + return data.map((customer) => ({ + id: customer.id, + name: customer.name, + phone: customer.phone, + email: customer.email, + metadata: customer.metadata, + syncStatus: "synced" + })); + } +}; +__name(_MockCustomerRepository, "MockCustomerRepository"); +var MockCustomerRepository = _MockCustomerRepository; + +// src/v2/repositories/MockAuditRepository.ts +var _MockAuditRepository = class _MockAuditRepository { + constructor() { + this.entityType = "Audit"; + } + async sendCreate(entity) { + await new Promise((resolve) => setTimeout(resolve, 100)); + console.log("MockAuditRepository: Audit entry synced to backend:", { + id: entity.id, + entityType: entity.entityType, + entityId: entity.entityId, + operation: entity.operation, + timestamp: new Date(entity.timestamp).toISOString() + }); + return entity; + } + async sendUpdate(_id, _entity) { + throw new Error("Audit entries cannot be updated"); + } + async sendDelete(_id) { + throw new Error("Audit entries cannot be deleted"); + } + async fetchAll() { + return []; + } + async fetchById(_id) { + return null; + } +}; +__name(_MockAuditRepository, "MockAuditRepository"); +var MockAuditRepository = _MockAuditRepository; + +// src/v2/repositories/MockTeamRepository.ts +var _MockTeamRepository = class _MockTeamRepository { + constructor() { + this.entityType = "Team"; + this.dataUrl = "data/mock-teams.json"; + } + async fetchAll() { + try { + const response = await fetch(this.dataUrl); + if (!response.ok) { + throw new Error(`Failed to load mock teams: ${response.status} ${response.statusText}`); + } + const rawData = await response.json(); + return this.processTeamData(rawData); + } catch (error) { + console.error("Failed to load team data:", error); + throw error; + } + } + async sendCreate(_team) { + throw new Error("MockTeamRepository does not support sendCreate. Mock data is read-only."); + } + async sendUpdate(_id, _updates) { + throw new Error("MockTeamRepository does not support sendUpdate. Mock data is read-only."); + } + async sendDelete(_id) { + throw new Error("MockTeamRepository does not support sendDelete. Mock data is read-only."); + } + processTeamData(data) { + return data.map((team) => ({ + id: team.id, + name: team.name, + resourceIds: team.resourceIds, + syncStatus: "synced" + })); + } +}; +__name(_MockTeamRepository, "MockTeamRepository"); +var MockTeamRepository = _MockTeamRepository; + +// src/v2/repositories/MockDepartmentRepository.ts +var _MockDepartmentRepository = class _MockDepartmentRepository { + constructor() { + this.entityType = "Department"; + this.dataUrl = "data/mock-departments.json"; + } + async fetchAll() { + try { + const response = await fetch(this.dataUrl); + if (!response.ok) { + throw new Error(`Failed to load mock departments: ${response.status} ${response.statusText}`); + } + const rawData = await response.json(); + return this.processDepartmentData(rawData); + } catch (error) { + console.error("Failed to load department data:", error); + throw error; + } + } + async sendCreate(_department) { + throw new Error("MockDepartmentRepository does not support sendCreate. Mock data is read-only."); + } + async sendUpdate(_id, _updates) { + throw new Error("MockDepartmentRepository does not support sendUpdate. Mock data is read-only."); + } + async sendDelete(_id) { + throw new Error("MockDepartmentRepository does not support sendDelete. Mock data is read-only."); + } + processDepartmentData(data) { + return data.map((dept) => ({ + id: dept.id, + name: dept.name, + resourceIds: dept.resourceIds, + syncStatus: "synced" + })); + } +}; +__name(_MockDepartmentRepository, "MockDepartmentRepository"); +var MockDepartmentRepository = _MockDepartmentRepository; + +// src/v2/repositories/MockSettingsRepository.ts +var _MockSettingsRepository = class _MockSettingsRepository { + constructor() { + this.entityType = "Settings"; + this.dataUrl = "data/tenant-settings.json"; + } + async fetchAll() { + try { + const response = await fetch(this.dataUrl); + if (!response.ok) { + throw new Error(`Failed to load tenant settings: ${response.status} ${response.statusText}`); + } + const settings = await response.json(); + return settings.map((s) => ({ + ...s, + syncStatus: s.syncStatus || "synced" + })); + } catch (error) { + console.error("Failed to load tenant settings:", error); + throw error; + } + } + async sendCreate(_settings) { + throw new Error("MockSettingsRepository does not support sendCreate. Mock data is read-only."); + } + async sendUpdate(_id, _updates) { + throw new Error("MockSettingsRepository does not support sendUpdate. Mock data is read-only."); + } + async sendDelete(_id) { + throw new Error("MockSettingsRepository does not support sendDelete. Mock data is read-only."); + } +}; +__name(_MockSettingsRepository, "MockSettingsRepository"); +var MockSettingsRepository = _MockSettingsRepository; + +// src/v2/repositories/MockViewConfigRepository.ts +var _MockViewConfigRepository = class _MockViewConfigRepository { + constructor() { + this.entityType = "ViewConfig"; + this.dataUrl = "data/viewconfigs.json"; + } + async fetchAll() { + try { + const response = await fetch(this.dataUrl); + if (!response.ok) { + throw new Error(`Failed to load viewconfigs: ${response.status} ${response.statusText}`); + } + const rawData = await response.json(); + const configs = rawData.map((config) => ({ + ...config, + syncStatus: config.syncStatus || "synced" + })); + return configs; + } catch (error) { + console.error("Failed to load viewconfigs:", error); + throw error; + } + } + async sendCreate(_config) { + throw new Error("MockViewConfigRepository does not support sendCreate. Mock data is read-only."); + } + async sendUpdate(_id, _updates) { + throw new Error("MockViewConfigRepository does not support sendUpdate. Mock data is read-only."); + } + async sendDelete(_id) { + throw new Error("MockViewConfigRepository does not support sendDelete. Mock data is read-only."); + } +}; +__name(_MockViewConfigRepository, "MockViewConfigRepository"); +var MockViewConfigRepository = _MockViewConfigRepository; + +// src/v2/workers/DataSeeder.ts +var _DataSeeder = class _DataSeeder { + constructor(services, repositories) { + this.services = services; + this.repositories = repositories; + } + /** + * Seed all entity stores if they are empty + */ + async seedIfEmpty() { + console.log("[DataSeeder] Checking if database needs seeding..."); + try { + for (const service of this.services) { + const repository = this.repositories.find((repo) => repo.entityType === service.entityType); + if (!repository) { + console.warn(`[DataSeeder] No repository found for entity type: ${service.entityType}, skipping`); + continue; + } + await this.seedEntity(service.entityType, service, repository); + } + console.log("[DataSeeder] Seeding complete"); + } catch (error) { + console.error("[DataSeeder] Seeding failed:", error); + throw error; + } + } + async seedEntity(entityType, service, repository) { + const existing = await service.getAll(); + if (existing.length > 0) { + console.log(`[DataSeeder] ${entityType} store already has ${existing.length} items, skipping seed`); + return; + } + console.log(`[DataSeeder] ${entityType} store is empty, fetching from repository...`); + const data = await repository.fetchAll(); + console.log(`[DataSeeder] Fetched ${data.length} ${entityType} items, saving to IndexedDB...`); + for (const entity of data) { + await service.save(entity, true); + } + console.log(`[DataSeeder] ${entityType} seeding complete (${data.length} items saved)`); + } +}; +__name(_DataSeeder, "DataSeeder"); +var DataSeeder = _DataSeeder; + +// src/v2/utils/PositionUtils.ts +function calculateEventPosition(start, end, config) { + const startMinutes = start.getHours() * 60 + start.getMinutes(); + const endMinutes = end.getHours() * 60 + end.getMinutes(); + const dayStartMinutes = config.dayStartHour * 60; + const minuteHeight = config.hourHeight / 60; + const top = (startMinutes - dayStartMinutes) * minuteHeight; + const height = (endMinutes - startMinutes) * minuteHeight; + return { top, height }; +} +__name(calculateEventPosition, "calculateEventPosition"); +function minutesToPixels(minutes, config) { + return minutes / 60 * config.hourHeight; +} +__name(minutesToPixels, "minutesToPixels"); +function pixelsToMinutes(pixels, config) { + return pixels / config.hourHeight * 60; +} +__name(pixelsToMinutes, "pixelsToMinutes"); +function snapToGrid(pixels, config) { + const snapPixels = minutesToPixels(config.snapInterval, config); + return Math.round(pixels / snapPixels) * snapPixels; +} +__name(snapToGrid, "snapToGrid"); + +// src/v2/features/event/EventLayoutEngine.ts +function eventsOverlap(a, b) { + return a.start < b.end && a.end > b.start; +} +__name(eventsOverlap, "eventsOverlap"); +function eventsWithinThreshold(a, b, thresholdMinutes) { + const thresholdMs = thresholdMinutes * 60 * 1e3; + const startToStartDiff = Math.abs(a.start.getTime() - b.start.getTime()); + if (startToStartDiff <= thresholdMs) + return true; + const bStartsBeforeAEnds = a.end.getTime() - b.start.getTime(); + if (bStartsBeforeAEnds > 0 && bStartsBeforeAEnds <= thresholdMs) + return true; + const aStartsBeforeBEnds = b.end.getTime() - a.start.getTime(); + if (aStartsBeforeBEnds > 0 && aStartsBeforeBEnds <= thresholdMs) + return true; + return false; +} +__name(eventsWithinThreshold, "eventsWithinThreshold"); +function findOverlapGroups(events) { + if (events.length === 0) + return []; + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + const used = /* @__PURE__ */ new Set(); + const groups = []; + for (const event of sorted) { + if (used.has(event.id)) + continue; + const group = [event]; + used.add(event.id); + let expanded = true; + while (expanded) { + expanded = false; + for (const candidate of sorted) { + if (used.has(candidate.id)) + continue; + const connects = group.some((member) => eventsOverlap(member, candidate)); + if (connects) { + group.push(candidate); + used.add(candidate.id); + expanded = true; + } + } + } + groups.push(group); + } + return groups; +} +__name(findOverlapGroups, "findOverlapGroups"); +function findGridCandidates(events, thresholdMinutes) { + if (events.length === 0) + return []; + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + const used = /* @__PURE__ */ new Set(); + const groups = []; + for (const event of sorted) { + if (used.has(event.id)) + continue; + const group = [event]; + used.add(event.id); + let expanded = true; + while (expanded) { + expanded = false; + for (const candidate of sorted) { + if (used.has(candidate.id)) + continue; + const connects = group.some((member) => eventsWithinThreshold(member, candidate, thresholdMinutes)); + if (connects) { + group.push(candidate); + used.add(candidate.id); + expanded = true; + } + } + } + groups.push(group); + } + return groups; +} +__name(findGridCandidates, "findGridCandidates"); +function calculateStackLevels(events) { + const levels = /* @__PURE__ */ new Map(); + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + for (const event of sorted) { + let maxOverlappingLevel = -1; + for (const [id, level] of levels) { + const other = events.find((e) => e.id === id); + if (other && eventsOverlap(event, other)) { + maxOverlappingLevel = Math.max(maxOverlappingLevel, level); + } + } + levels.set(event.id, maxOverlappingLevel + 1); + } + return levels; +} +__name(calculateStackLevels, "calculateStackLevels"); +function allocateColumns(events) { + const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()); + const columns = []; + for (const event of sorted) { + let placed = false; + for (const column of columns) { + const canFit = !column.some((e) => eventsOverlap(event, e)); + if (canFit) { + column.push(event); + placed = true; + break; + } + } + if (!placed) { + columns.push([event]); + } + } + return columns; +} +__name(allocateColumns, "allocateColumns"); +function calculateColumnLayout(events, config) { + const thresholdMinutes = config.gridStartThresholdMinutes ?? 10; + const result = { + grids: [], + stacked: [] + }; + if (events.length === 0) + return result; + const overlapGroups = findOverlapGroups(events); + for (const overlapGroup of overlapGroups) { + if (overlapGroup.length === 1) { + result.stacked.push({ + event: overlapGroup[0], + stackLevel: 0 + }); + continue; + } + const gridSubgroups = findGridCandidates(overlapGroup, thresholdMinutes); + const largestGridCandidate = gridSubgroups.reduce((max, g) => g.length > max.length ? g : max, gridSubgroups[0]); + if (largestGridCandidate.length === overlapGroup.length) { + const columns = allocateColumns(overlapGroup); + const earliest = overlapGroup.reduce((min, e) => e.start < min.start ? e : min, overlapGroup[0]); + const position = calculateEventPosition(earliest.start, earliest.end, config); + result.grids.push({ + events: overlapGroup, + columns, + stackLevel: 0, + position: { top: position.top } + }); + } else { + const levels = calculateStackLevels(overlapGroup); + for (const event of overlapGroup) { + result.stacked.push({ + event, + stackLevel: levels.get(event.id) ?? 0 + }); + } + } + } + return result; +} +__name(calculateColumnLayout, "calculateColumnLayout"); + +// src/v2/features/event/EventRenderer.ts +var _EventRenderer = class _EventRenderer { + constructor(eventService, dateService, gridConfig, eventBus) { + this.eventService = eventService; + this.dateService = dateService; + this.gridConfig = gridConfig; + this.eventBus = eventBus; + this.container = null; + this.setupListeners(); + } + /** + * Setup listeners for drag-drop and update events + */ + setupListeners() { + this.eventBus.on(CoreEvents.EVENT_DRAG_COLUMN_CHANGE, (e) => { + const payload = e.detail; + this.handleColumnChange(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_MOVE, (e) => { + const payload = e.detail; + this.updateDragTimestamp(payload); + }); + this.eventBus.on(CoreEvents.EVENT_UPDATED, (e) => { + const payload = e.detail; + this.handleEventUpdated(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_END, (e) => { + const payload = e.detail; + this.handleDragEnd(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_LEAVE_HEADER, (e) => { + const payload = e.detail; + this.handleDragLeaveHeader(payload); + }); + } + /** + * Handle EVENT_DRAG_END - remove element if dropped in header + */ + handleDragEnd(payload) { + if (payload.target === "header") { + const element = this.container?.querySelector(`swp-content-viewport swp-event[data-event-id="${payload.swpEvent.eventId}"]`); + element?.remove(); + } + } + /** + * Handle header item leaving header - create swp-event in grid + */ + handleDragLeaveHeader(payload) { + if (payload.source !== "header") + return; + if (!payload.targetColumn || !payload.start || !payload.end) + return; + if (payload.element) { + payload.element.classList.add("drag-ghost"); + payload.element.style.opacity = "0.3"; + payload.element.style.pointerEvents = "none"; + } + const event = { + id: payload.eventId, + title: payload.title || "", + description: "", + start: payload.start, + end: payload.end, + type: "customer", + allDay: false, + syncStatus: "pending" + }; + const element = this.createEventElement(event); + let eventsLayer = payload.targetColumn.querySelector("swp-events-layer"); + if (!eventsLayer) { + eventsLayer = document.createElement("swp-events-layer"); + payload.targetColumn.appendChild(eventsLayer); + } + eventsLayer.appendChild(element); + element.classList.add("dragging"); + } + /** + * Handle EVENT_UPDATED - re-render affected columns + */ + async handleEventUpdated(payload) { + if (payload.sourceColumnKey !== payload.targetColumnKey) { + await this.rerenderColumn(payload.sourceColumnKey); + } + await this.rerenderColumn(payload.targetColumnKey); + } + /** + * Re-render a single column with fresh data from IndexedDB + */ + async rerenderColumn(columnKey) { + const column = this.findColumn(columnKey); + if (!column) + return; + const date = column.dataset.date; + const resourceId = column.dataset.resourceId; + if (!date) + return; + const startDate = new Date(date); + const endDate = new Date(date); + endDate.setHours(23, 59, 59, 999); + const events = resourceId ? await this.eventService.getByResourceAndDateRange(resourceId, startDate, endDate) : await this.eventService.getByDateRange(startDate, endDate); + const timedEvents = events.filter((event) => !event.allDay && this.dateService.getDateKey(event.start) === date); + let eventsLayer = column.querySelector("swp-events-layer"); + if (!eventsLayer) { + eventsLayer = document.createElement("swp-events-layer"); + column.appendChild(eventsLayer); + } + eventsLayer.innerHTML = ""; + const layout = calculateColumnLayout(timedEvents, this.gridConfig); + layout.grids.forEach((grid) => { + const groupEl = this.renderGridGroup(grid); + eventsLayer.appendChild(groupEl); + }); + layout.stacked.forEach((item) => { + const eventEl = this.renderStackedEvent(item.event, item.stackLevel); + eventsLayer.appendChild(eventEl); + }); + } + /** + * Find a column element by columnKey + */ + findColumn(columnKey) { + if (!this.container) + return null; + return this.container.querySelector(`swp-day-column[data-column-key="${columnKey}"]`); + } + /** + * Handle event moving to a new column during drag + */ + handleColumnChange(payload) { + const eventsLayer = payload.newColumn.querySelector("swp-events-layer"); + if (!eventsLayer) + return; + eventsLayer.appendChild(payload.element); + payload.element.style.top = `${payload.currentY}px`; + } + /** + * Update timestamp display during drag (snapped to grid) + */ + updateDragTimestamp(payload) { + const timeEl = payload.element.querySelector("swp-event-time"); + if (!timeEl) + return; + const snappedY = snapToGrid(payload.currentY, this.gridConfig); + const minutesFromGridStart = pixelsToMinutes(snappedY, this.gridConfig); + const startMinutes = this.gridConfig.dayStartHour * 60 + minutesFromGridStart; + const height = parseFloat(payload.element.style.height) || this.gridConfig.hourHeight; + const durationMinutes = pixelsToMinutes(height, this.gridConfig); + const start = this.minutesToDate(startMinutes); + const end = this.minutesToDate(startMinutes + durationMinutes); + timeEl.textContent = this.dateService.formatTimeRange(start, end); + } + /** + * Convert minutes since midnight to a Date object (today) + */ + minutesToDate(minutes) { + const date = /* @__PURE__ */ new Date(); + date.setHours(Math.floor(minutes / 60) % 24, minutes % 60, 0, 0); + return date; + } + /** + * Render events for visible dates into day columns + * @param container - Calendar container element + * @param filter - Filter with 'date' and optionally 'resource' arrays + * @param filterTemplate - Template for matching events to columns + */ + async render(container2, filter, filterTemplate) { + this.container = container2; + const visibleDates = filter["date"] || []; + if (visibleDates.length === 0) + return; + const startDate = new Date(visibleDates[0]); + const endDate = new Date(visibleDates[visibleDates.length - 1]); + endDate.setHours(23, 59, 59, 999); + const events = await this.eventService.getByDateRange(startDate, endDate); + const dayColumns = container2.querySelector("swp-day-columns"); + if (!dayColumns) + return; + const columns = dayColumns.querySelectorAll("swp-day-column"); + columns.forEach((column) => { + const columnEl = column; + const columnEvents = events.filter((event) => filterTemplate.matches(event, columnEl)); + let eventsLayer = column.querySelector("swp-events-layer"); + if (!eventsLayer) { + eventsLayer = document.createElement("swp-events-layer"); + column.appendChild(eventsLayer); + } + eventsLayer.innerHTML = ""; + const timedEvents = columnEvents.filter((event) => !event.allDay); + const layout = calculateColumnLayout(timedEvents, this.gridConfig); + layout.grids.forEach((grid) => { + const groupEl = this.renderGridGroup(grid); + eventsLayer.appendChild(groupEl); + }); + layout.stacked.forEach((item) => { + const eventEl = this.renderStackedEvent(item.event, item.stackLevel); + eventsLayer.appendChild(eventEl); + }); + }); + } + /** + * Create a single event element + * + * CLEAN approach: + * - Only data-id for lookup + * - Visible content in innerHTML only + */ + createEventElement(event) { + const element = document.createElement("swp-event"); + element.dataset.eventId = event.id; + if (event.resourceId) { + element.dataset.resourceId = event.resourceId; + } + const position = calculateEventPosition(event.start, event.end, this.gridConfig); + element.style.top = `${position.top}px`; + element.style.height = `${position.height}px`; + const colorClass = this.getColorClass(event); + if (colorClass) { + element.classList.add(colorClass); + } + element.innerHTML = ` + ${this.dateService.formatTimeRange(event.start, event.end)} + ${this.escapeHtml(event.title)} + ${event.description ? `${this.escapeHtml(event.description)}` : ""} + `; + return element; + } + /** + * Get color class based on metadata.color or event type + */ + getColorClass(event) { + if (event.metadata?.color) { + return `is-${event.metadata.color}`; + } + const typeColors = { + "customer": "is-blue", + "vacation": "is-green", + "break": "is-amber", + "meeting": "is-purple", + "blocked": "is-red" + }; + return typeColors[event.type] || "is-blue"; + } + /** + * Escape HTML to prevent XSS + */ + escapeHtml(text) { + const div = document.createElement("div"); + div.textContent = text; + return div.innerHTML; + } + /** + * Render a GRID group with side-by-side columns + * Used when multiple events start at the same time + */ + renderGridGroup(layout) { + const group = document.createElement("swp-event-group"); + group.classList.add(`cols-${layout.columns.length}`); + group.style.top = `${layout.position.top}px`; + if (layout.stackLevel > 0) { + group.style.marginLeft = `${layout.stackLevel * 15}px`; + group.style.zIndex = `${100 + layout.stackLevel}`; + } + let maxBottom = 0; + for (const event of layout.events) { + const pos = calculateEventPosition(event.start, event.end, this.gridConfig); + const eventBottom = pos.top + pos.height; + if (eventBottom > maxBottom) + maxBottom = eventBottom; + } + const groupHeight = maxBottom - layout.position.top; + group.style.height = `${groupHeight}px`; + layout.columns.forEach((columnEvents) => { + const wrapper = document.createElement("div"); + wrapper.style.position = "relative"; + columnEvents.forEach((event) => { + const eventEl = this.createEventElement(event); + const pos = calculateEventPosition(event.start, event.end, this.gridConfig); + eventEl.style.top = `${pos.top - layout.position.top}px`; + eventEl.style.position = "absolute"; + eventEl.style.left = "0"; + eventEl.style.right = "0"; + wrapper.appendChild(eventEl); + }); + group.appendChild(wrapper); + }); + return group; + } + /** + * Render a STACKED event with margin-left offset + * Used for overlapping events that don't start at the same time + */ + renderStackedEvent(event, stackLevel) { + const element = this.createEventElement(event); + element.dataset.stackLink = JSON.stringify({ stackLevel }); + if (stackLevel > 0) { + element.style.marginLeft = `${stackLevel * 15}px`; + element.style.zIndex = `${100 + stackLevel}`; + } + return element; + } +}; +__name(_EventRenderer, "EventRenderer"); +var EventRenderer = _EventRenderer; + +// src/v2/features/schedule/ScheduleRenderer.ts +var _ScheduleRenderer = class _ScheduleRenderer { + constructor(scheduleService, dateService, gridConfig) { + this.scheduleService = scheduleService; + this.dateService = dateService; + this.gridConfig = gridConfig; + } + /** + * Render unavailable zones for visible columns + * @param container - Calendar container element + * @param filter - Filter with 'date' and 'resource' arrays + */ + async render(container2, filter) { + const dates = filter["date"] || []; + const resourceIds = filter["resource"] || []; + if (dates.length === 0) + return; + const dayColumns = container2.querySelector("swp-day-columns"); + if (!dayColumns) + return; + const columns = dayColumns.querySelectorAll("swp-day-column"); + for (const column of columns) { + const date = column.dataset.date; + const resourceId = column.dataset.resourceId; + if (!date || !resourceId) + continue; + let unavailableLayer = column.querySelector("swp-unavailable-layer"); + if (!unavailableLayer) { + unavailableLayer = document.createElement("swp-unavailable-layer"); + column.insertBefore(unavailableLayer, column.firstChild); + } + unavailableLayer.innerHTML = ""; + const schedule = await this.scheduleService.getScheduleForDate(resourceId, date); + this.renderUnavailableZones(unavailableLayer, schedule); + } + } + /** + * Render unavailable time zones based on schedule + */ + renderUnavailableZones(layer, schedule) { + const dayStartMinutes = this.gridConfig.dayStartHour * 60; + const dayEndMinutes = this.gridConfig.dayEndHour * 60; + const minuteHeight = this.gridConfig.hourHeight / 60; + if (schedule === null) { + const zone = this.createUnavailableZone(0, (dayEndMinutes - dayStartMinutes) * minuteHeight); + layer.appendChild(zone); + return; + } + const workStartMinutes = this.dateService.timeToMinutes(schedule.start); + const workEndMinutes = this.dateService.timeToMinutes(schedule.end); + if (workStartMinutes > dayStartMinutes) { + const top = 0; + const height = (workStartMinutes - dayStartMinutes) * minuteHeight; + const zone = this.createUnavailableZone(top, height); + layer.appendChild(zone); + } + if (workEndMinutes < dayEndMinutes) { + const top = (workEndMinutes - dayStartMinutes) * minuteHeight; + const height = (dayEndMinutes - workEndMinutes) * minuteHeight; + const zone = this.createUnavailableZone(top, height); + layer.appendChild(zone); + } + } + /** + * Create an unavailable zone element + */ + createUnavailableZone(top, height) { + const zone = document.createElement("swp-unavailable-zone"); + zone.style.top = `${top}px`; + zone.style.height = `${height}px`; + return zone; + } +}; +__name(_ScheduleRenderer, "ScheduleRenderer"); +var ScheduleRenderer = _ScheduleRenderer; + +// src/v2/features/headerdrawer/HeaderDrawerRenderer.ts +var _HeaderDrawerRenderer = class _HeaderDrawerRenderer { + constructor(eventBus, gridConfig, headerDrawerManager, eventService, dateService) { + this.eventBus = eventBus; + this.gridConfig = gridConfig; + this.headerDrawerManager = headerDrawerManager; + this.eventService = eventService; + this.dateService = dateService; + this.currentItem = null; + this.container = null; + this.sourceElement = null; + this.wasExpandedBeforeDrag = false; + this.filterTemplate = null; + this.setupListeners(); + } + /** + * Render allDay events into the header drawer with row stacking + * @param filterTemplate - Template for matching events to columns + */ + async render(container2, filter, filterTemplate) { + this.filterTemplate = filterTemplate; + const drawer = container2.querySelector("swp-header-drawer"); + if (!drawer) + return; + const visibleDates = filter["date"] || []; + if (visibleDates.length === 0) + return; + const visibleColumnKeys = this.getVisibleColumnKeysFromDOM(); + if (visibleColumnKeys.length === 0) + return; + const startDate = new Date(visibleDates[0]); + const endDate = new Date(visibleDates[visibleDates.length - 1]); + endDate.setHours(23, 59, 59, 999); + const events = await this.eventService.getByDateRange(startDate, endDate); + const allDayEvents = events.filter((event) => event.allDay !== false); + drawer.innerHTML = ""; + if (allDayEvents.length === 0) + return; + const layouts = this.calculateLayout(allDayEvents, visibleColumnKeys); + const rowCount = Math.max(1, ...layouts.map((l) => l.row)); + layouts.forEach((layout) => { + const item = this.createHeaderItem(layout); + drawer.appendChild(item); + }); + this.headerDrawerManager.expandToRows(rowCount); + } + /** + * Create a header item element from layout + */ + createHeaderItem(layout) { + const { event, columnKey, row, colStart, colEnd } = layout; + const item = document.createElement("swp-header-item"); + item.dataset.eventId = event.id; + item.dataset.itemType = "event"; + item.dataset.start = event.start.toISOString(); + item.dataset.end = event.end.toISOString(); + item.dataset.columnKey = columnKey; + item.textContent = event.title; + const colorClass = this.getColorClass(event); + if (colorClass) + item.classList.add(colorClass); + item.style.gridArea = `${row} / ${colStart} / ${row + 1} / ${colEnd}`; + return item; + } + /** + * Calculate layout for all events with row stacking + * Uses track-based algorithm to find available rows for overlapping events + */ + calculateLayout(events, visibleColumnKeys) { + const tracks = [new Array(visibleColumnKeys.length).fill(false)]; + const layouts = []; + for (const event of events) { + const columnKey = this.buildColumnKeyFromEvent(event); + const startCol = visibleColumnKeys.indexOf(columnKey); + const endColumnKey = this.buildColumnKeyFromEvent(event, event.end); + const endCol = visibleColumnKeys.indexOf(endColumnKey); + if (startCol === -1 && endCol === -1) + continue; + const colStart = Math.max(0, startCol); + const colEnd = (endCol !== -1 ? endCol : visibleColumnKeys.length - 1) + 1; + const row = this.findAvailableRow(tracks, colStart, colEnd); + for (let c = colStart; c < colEnd; c++) { + tracks[row][c] = true; + } + layouts.push({ event, columnKey, row: row + 1, colStart: colStart + 1, colEnd: colEnd + 1 }); + } + return layouts; + } + /** + * Build columnKey from event using FilterTemplate + * Uses the same template that columns use for matching + */ + buildColumnKeyFromEvent(event, date) { + if (!this.filterTemplate) { + const dateStr = this.dateService.getDateKey(date || event.start); + return dateStr; + } + if (date && date.getTime() !== event.start.getTime()) { + const tempEvent = { ...event, start: date }; + return this.filterTemplate.buildKeyFromEvent(tempEvent); + } + return this.filterTemplate.buildKeyFromEvent(event); + } + /** + * Find available row for event spanning columns [colStart, colEnd) + */ + findAvailableRow(tracks, colStart, colEnd) { + for (let row = 0; row < tracks.length; row++) { + let available = true; + for (let c = colStart; c < colEnd; c++) { + if (tracks[row][c]) { + available = false; + break; + } + } + if (available) + return row; + } + tracks.push(new Array(tracks[0].length).fill(false)); + return tracks.length - 1; + } + /** + * Get color class based on event metadata or type + */ + getColorClass(event) { + if (event.metadata?.color) { + return `is-${event.metadata.color}`; + } + const typeColors = { + "customer": "is-blue", + "vacation": "is-green", + "break": "is-amber", + "meeting": "is-purple", + "blocked": "is-red" + }; + return typeColors[event.type] || "is-blue"; + } + /** + * Setup event listeners for drag events + */ + setupListeners() { + this.eventBus.on(CoreEvents.EVENT_DRAG_ENTER_HEADER, (e) => { + const payload = e.detail; + this.handleDragEnter(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_MOVE_HEADER, (e) => { + const payload = e.detail; + this.handleDragMove(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_LEAVE_HEADER, (e) => { + const payload = e.detail; + this.handleDragLeave(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_END, (e) => { + const payload = e.detail; + this.handleDragEnd(payload); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_CANCEL, () => { + this.cleanup(); + }); + } + /** + * Handle drag entering header zone - create preview item + */ + handleDragEnter(payload) { + this.container = document.querySelector("swp-header-drawer"); + if (!this.container) + return; + this.wasExpandedBeforeDrag = this.headerDrawerManager.isExpanded(); + if (!this.wasExpandedBeforeDrag) { + this.headerDrawerManager.expandToRows(1); + } + this.sourceElement = payload.element; + const item = document.createElement("swp-header-item"); + item.dataset.eventId = payload.eventId; + item.dataset.itemType = payload.itemType; + item.dataset.duration = String(payload.duration); + item.dataset.columnKey = payload.sourceColumnKey; + item.textContent = payload.title; + if (payload.colorClass) { + item.classList.add(payload.colorClass); + } + item.classList.add("dragging"); + const col = payload.sourceColumnIndex + 1; + const endCol = col + payload.duration; + item.style.gridArea = `1 / ${col} / 2 / ${endCol}`; + this.container.appendChild(item); + this.currentItem = item; + payload.element.style.visibility = "hidden"; + } + /** + * Handle drag moving within header - update column position + */ + handleDragMove(payload) { + if (!this.currentItem) + return; + const col = payload.columnIndex + 1; + const duration = parseInt(this.currentItem.dataset.duration || "1", 10); + const endCol = col + duration; + this.currentItem.style.gridArea = `1 / ${col} / 2 / ${endCol}`; + this.currentItem.dataset.columnKey = payload.columnKey; + } + /** + * Handle drag leaving header - cleanup for grid→header drag only + */ + handleDragLeave(payload) { + if (payload.source === "grid") { + this.cleanup(); + } + } + /** + * Handle drag end - finalize based on drop target + */ + handleDragEnd(payload) { + if (payload.target === "header") { + if (this.currentItem) { + this.currentItem.classList.remove("dragging"); + this.recalculateDrawerLayout(); + this.currentItem = null; + this.sourceElement = null; + } + } else { + const ghost = document.querySelector(`swp-header-item.drag-ghost[data-event-id="${payload.swpEvent.eventId}"]`); + ghost?.remove(); + this.recalculateDrawerLayout(); + } + } + /** + * Recalculate layout for all items currently in the drawer + * Called after drop to reposition items and adjust height + */ + recalculateDrawerLayout() { + const drawer = document.querySelector("swp-header-drawer"); + if (!drawer) + return; + const items = Array.from(drawer.querySelectorAll("swp-header-item")); + if (items.length === 0) + return; + const visibleColumnKeys = this.getVisibleColumnKeysFromDOM(); + if (visibleColumnKeys.length === 0) + return; + const itemData = items.map((item) => ({ + element: item, + columnKey: item.dataset.columnKey || "", + duration: parseInt(item.dataset.duration || "1", 10) + })); + const tracks = [new Array(visibleColumnKeys.length).fill(false)]; + for (const item of itemData) { + const startCol = visibleColumnKeys.indexOf(item.columnKey); + if (startCol === -1) + continue; + const colStart = startCol; + const colEnd = Math.min(startCol + item.duration, visibleColumnKeys.length); + const row = this.findAvailableRow(tracks, colStart, colEnd); + for (let c = colStart; c < colEnd; c++) { + tracks[row][c] = true; + } + item.element.style.gridArea = `${row + 1} / ${colStart + 1} / ${row + 2} / ${colEnd + 1}`; + } + const rowCount = tracks.length; + this.headerDrawerManager.expandToRows(rowCount); + } + /** + * Get visible column keys from DOM (preserves order for multi-resource views) + * Uses filterTemplate.buildKeyFromColumn() for consistent key format with events + */ + getVisibleColumnKeysFromDOM() { + if (!this.filterTemplate) + return []; + const columns = document.querySelectorAll("swp-day-column"); + const columnKeys = []; + columns.forEach((col) => { + const columnKey = this.filterTemplate.buildKeyFromColumn(col); + if (columnKey) + columnKeys.push(columnKey); + }); + return columnKeys; + } + /** + * Cleanup preview item and restore source visibility + */ + cleanup() { + this.currentItem?.remove(); + this.currentItem = null; + if (this.sourceElement) { + this.sourceElement.style.visibility = ""; + this.sourceElement = null; + } + if (!this.wasExpandedBeforeDrag) { + this.headerDrawerManager.collapse(); + } + } +}; +__name(_HeaderDrawerRenderer, "HeaderDrawerRenderer"); +var HeaderDrawerRenderer = _HeaderDrawerRenderer; + +// src/v2/storage/schedules/ScheduleOverrideStore.ts +var _ScheduleOverrideStore = class _ScheduleOverrideStore { + constructor() { + this.storeName = _ScheduleOverrideStore.STORE_NAME; + } + create(db) { + const store = db.createObjectStore(_ScheduleOverrideStore.STORE_NAME, { keyPath: "id" }); + store.createIndex("resourceId", "resourceId", { unique: false }); + store.createIndex("date", "date", { unique: false }); + store.createIndex("resourceId_date", ["resourceId", "date"], { unique: true }); + store.createIndex("syncStatus", "syncStatus", { unique: false }); + } +}; +__name(_ScheduleOverrideStore, "ScheduleOverrideStore"); +var ScheduleOverrideStore = _ScheduleOverrideStore; +ScheduleOverrideStore.STORE_NAME = "scheduleOverrides"; + +// src/v2/storage/schedules/ScheduleOverrideService.ts +var _ScheduleOverrideService = class _ScheduleOverrideService { + constructor(context) { + this.context = context; + } + get db() { + return this.context.getDatabase(); + } + /** + * Get override for a specific resource and date + */ + async getOverride(resourceId, date) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([ScheduleOverrideStore.STORE_NAME], "readonly"); + const store = transaction.objectStore(ScheduleOverrideStore.STORE_NAME); + const index = store.index("resourceId_date"); + const request = index.get([resourceId, date]); + request.onsuccess = () => { + resolve(request.result || null); + }; + request.onerror = () => { + reject(new Error(`Failed to get override for ${resourceId} on ${date}: ${request.error}`)); + }; + }); + } + /** + * Get all overrides for a resource + */ + async getByResource(resourceId) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([ScheduleOverrideStore.STORE_NAME], "readonly"); + const store = transaction.objectStore(ScheduleOverrideStore.STORE_NAME); + const index = store.index("resourceId"); + const request = index.getAll(resourceId); + request.onsuccess = () => { + resolve(request.result || []); + }; + request.onerror = () => { + reject(new Error(`Failed to get overrides for ${resourceId}: ${request.error}`)); + }; + }); + } + /** + * Get overrides for a date range + */ + async getByDateRange(resourceId, startDate, endDate) { + const all = await this.getByResource(resourceId); + return all.filter((o) => o.date >= startDate && o.date <= endDate); + } + /** + * Save an override + */ + async save(override) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([ScheduleOverrideStore.STORE_NAME], "readwrite"); + const store = transaction.objectStore(ScheduleOverrideStore.STORE_NAME); + const request = store.put(override); + request.onsuccess = () => resolve(); + request.onerror = () => { + reject(new Error(`Failed to save override ${override.id}: ${request.error}`)); + }; + }); + } + /** + * Delete an override + */ + async delete(id) { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([ScheduleOverrideStore.STORE_NAME], "readwrite"); + const store = transaction.objectStore(ScheduleOverrideStore.STORE_NAME); + const request = store.delete(id); + request.onsuccess = () => resolve(); + request.onerror = () => { + reject(new Error(`Failed to delete override ${id}: ${request.error}`)); + }; + }); + } +}; +__name(_ScheduleOverrideService, "ScheduleOverrideService"); +var ScheduleOverrideService = _ScheduleOverrideService; + +// src/v2/storage/schedules/ResourceScheduleService.ts +var _ResourceScheduleService = class _ResourceScheduleService { + constructor(resourceService, overrideService, dateService) { + this.resourceService = resourceService; + this.overrideService = overrideService; + this.dateService = dateService; + } + /** + * Get effective schedule for a resource on a specific date + * + * @param resourceId - Resource ID + * @param date - Date string "YYYY-MM-DD" + * @returns ITimeSlot or null (fri/closed) + */ + async getScheduleForDate(resourceId, date) { + const override = await this.overrideService.getOverride(resourceId, date); + if (override) { + return override.schedule; + } + const resource = await this.resourceService.get(resourceId); + if (!resource || !resource.defaultSchedule) { + return null; + } + const weekDay = this.dateService.getISOWeekDay(date); + return resource.defaultSchedule[weekDay] || null; + } + /** + * Get schedules for multiple dates + * + * @param resourceId - Resource ID + * @param dates - Array of date strings "YYYY-MM-DD" + * @returns Map of date -> ITimeSlot | null + */ + async getSchedulesForDates(resourceId, dates) { + const result = /* @__PURE__ */ new Map(); + const resource = await this.resourceService.get(resourceId); + const overrides = dates.length > 0 ? await this.overrideService.getByDateRange(resourceId, dates[0], dates[dates.length - 1]) : []; + const overrideMap = new Map(overrides.map((o) => [o.date, o.schedule])); + for (const date of dates) { + if (overrideMap.has(date)) { + result.set(date, overrideMap.get(date)); + continue; + } + if (resource?.defaultSchedule) { + const weekDay = this.dateService.getISOWeekDay(date); + result.set(date, resource.defaultSchedule[weekDay] || null); + } else { + result.set(date, null); + } + } + return result; + } +}; +__name(_ResourceScheduleService, "ResourceScheduleService"); +var ResourceScheduleService = _ResourceScheduleService; + +// src/v2/types/SwpEvent.ts +var _SwpEvent = class _SwpEvent { + constructor(element, columnKey, start, end) { + this.element = element; + this.columnKey = columnKey; + this._start = start; + this._end = end; + } + /** Event ID from element.dataset.eventId */ + get eventId() { + return this.element.dataset.eventId || ""; + } + get start() { + return this._start; + } + get end() { + return this._end; + } + /** Duration in minutes */ + get durationMinutes() { + return (this._end.getTime() - this._start.getTime()) / (1e3 * 60); + } + /** Duration in milliseconds */ + get durationMs() { + return this._end.getTime() - this._start.getTime(); + } + /** + * Factory: Create SwpEvent from element + columnKey + * Reads top/height from element.style to calculate start/end + * @param columnKey - Opaque column identifier (do NOT parse - use only for matching) + * @param date - Date string (YYYY-MM-DD) for time calculations + */ + static fromElement(element, columnKey, date, gridConfig) { + const topPixels = parseFloat(element.style.top) || 0; + const heightPixels = parseFloat(element.style.height) || 0; + const startMinutesFromGrid = topPixels / gridConfig.hourHeight * 60; + const totalMinutes = gridConfig.dayStartHour * 60 + startMinutesFromGrid; + const start = new Date(date); + start.setHours(Math.floor(totalMinutes / 60), totalMinutes % 60, 0, 0); + const durationMinutes = heightPixels / gridConfig.hourHeight * 60; + const end = new Date(start.getTime() + durationMinutes * 60 * 1e3); + return new _SwpEvent(element, columnKey, start, end); + } +}; +__name(_SwpEvent, "SwpEvent"); +var SwpEvent = _SwpEvent; + +// src/v2/managers/DragDropManager.ts +var _DragDropManager = class _DragDropManager { + constructor(eventBus, gridConfig) { + this.eventBus = eventBus; + this.gridConfig = gridConfig; + this.dragState = null; + this.mouseDownPosition = null; + this.pendingElement = null; + this.pendingMouseOffset = null; + this.container = null; + this.inHeader = false; + this.DRAG_THRESHOLD = 5; + this.INTERPOLATION_FACTOR = 0.3; + this.handlePointerDown = (e) => { + const target = e.target; + if (target.closest("swp-resize-handle")) + return; + const eventElement = target.closest("swp-event"); + const headerItem = target.closest("swp-header-item"); + const draggable = eventElement || headerItem; + if (!draggable) + return; + this.mouseDownPosition = { x: e.clientX, y: e.clientY }; + this.pendingElement = draggable; + const rect = draggable.getBoundingClientRect(); + this.pendingMouseOffset = { + x: e.clientX - rect.left, + y: e.clientY - rect.top + }; + draggable.setPointerCapture(e.pointerId); + }; + this.handlePointerMove = (e) => { + if (!this.mouseDownPosition || !this.pendingElement) { + if (this.dragState) { + this.updateDragTarget(e); + } + return; + } + const deltaX = Math.abs(e.clientX - this.mouseDownPosition.x); + const deltaY = Math.abs(e.clientY - this.mouseDownPosition.y); + const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + if (distance < this.DRAG_THRESHOLD) + return; + this.initializeDrag(this.pendingElement, this.pendingMouseOffset, e); + this.mouseDownPosition = null; + this.pendingElement = null; + this.pendingMouseOffset = null; + }; + this.handlePointerUp = (_e) => { + this.mouseDownPosition = null; + this.pendingElement = null; + this.pendingMouseOffset = null; + if (!this.dragState) + return; + cancelAnimationFrame(this.dragState.animationId); + if (this.dragState.dragSource === "header") { + this.handleHeaderItemDragEnd(); + } else { + this.handleGridEventDragEnd(); + } + this.dragState.element.classList.remove("dragging"); + this.dragState = null; + this.inHeader = false; + }; + this.animateDrag = () => { + if (!this.dragState) + return; + const diff2 = this.dragState.targetY - this.dragState.currentY; + if (Math.abs(diff2) <= 0.5) { + this.dragState.animationId = 0; + return; + } + this.dragState.currentY += diff2 * this.INTERPOLATION_FACTOR; + this.dragState.element.style.top = `${this.dragState.currentY}px`; + if (this.dragState.columnElement) { + const payload = { + eventId: this.dragState.eventId, + element: this.dragState.element, + currentY: this.dragState.currentY, + columnElement: this.dragState.columnElement + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_MOVE, payload); + } + this.dragState.animationId = requestAnimationFrame(this.animateDrag); + }; + this.setupScrollListener(); + } + setupScrollListener() { + this.eventBus.on(CoreEvents.EDGE_SCROLL_TICK, (e) => { + if (!this.dragState) + return; + const { scrollDelta } = e.detail; + this.dragState.targetY += scrollDelta; + this.dragState.currentY += scrollDelta; + this.dragState.element.style.top = `${this.dragState.currentY}px`; + }); + } + /** + * Initialize drag-drop on a container element + */ + init(container2) { + this.container = container2; + container2.addEventListener("pointerdown", this.handlePointerDown); + document.addEventListener("pointermove", this.handlePointerMove); + document.addEventListener("pointerup", this.handlePointerUp); + } + /** + * Handle drag end for header items + */ + handleHeaderItemDragEnd() { + if (!this.dragState) + return; + if (!this.inHeader && this.dragState.currentColumn) { + const gridEvent = this.dragState.currentColumn.querySelector(`swp-event[data-event-id="${this.dragState.eventId}"]`); + if (gridEvent) { + const columnKey = this.dragState.currentColumn.dataset.columnKey || ""; + const date = this.dragState.currentColumn.dataset.date || ""; + const swpEvent = SwpEvent.fromElement(gridEvent, columnKey, date, this.gridConfig); + const payload = { + swpEvent, + sourceColumnKey: this.dragState.sourceColumnKey, + target: "grid" + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_END, payload); + } + } + } + /** + * Handle drag end for grid events + */ + handleGridEventDragEnd() { + if (!this.dragState || !this.dragState.columnElement) + return; + const snappedY = snapToGrid(this.dragState.currentY, this.gridConfig); + this.dragState.element.style.top = `${snappedY}px`; + this.dragState.ghostElement?.remove(); + const columnKey = this.dragState.columnElement.dataset.columnKey || ""; + const date = this.dragState.columnElement.dataset.date || ""; + const swpEvent = SwpEvent.fromElement(this.dragState.element, columnKey, date, this.gridConfig); + const payload = { + swpEvent, + sourceColumnKey: this.dragState.sourceColumnKey, + target: this.inHeader ? "header" : "grid" + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_END, payload); + } + initializeDrag(element, mouseOffset, e) { + const eventId = element.dataset.eventId || ""; + const isHeaderItem = element.tagName.toLowerCase() === "swp-header-item"; + const columnElement = element.closest("swp-day-column"); + if (!isHeaderItem && !columnElement) + return; + if (isHeaderItem) { + this.initializeHeaderItemDrag(element, mouseOffset, eventId); + } else { + this.initializeGridEventDrag(element, mouseOffset, e, columnElement, eventId); + } + } + /** + * Initialize drag for a header item (allDay event) + */ + initializeHeaderItemDrag(element, mouseOffset, eventId) { + element.classList.add("dragging"); + this.dragState = { + eventId, + element, + ghostElement: null, + // No ghost for header items + startY: 0, + mouseOffset, + columnElement: null, + currentColumn: null, + targetY: 0, + currentY: 0, + animationId: 0, + sourceColumnKey: "", + // Will be set from header item data + dragSource: "header" + }; + this.inHeader = true; + } + /** + * Initialize drag for a grid event + */ + initializeGridEventDrag(element, mouseOffset, e, columnElement, eventId) { + const elementRect = element.getBoundingClientRect(); + const columnRect = columnElement.getBoundingClientRect(); + const startY = elementRect.top - columnRect.top; + const group = element.closest("swp-event-group"); + if (group) { + const eventsLayer = columnElement.querySelector("swp-events-layer"); + if (eventsLayer) { + eventsLayer.appendChild(element); + } + } + element.style.position = "absolute"; + element.style.top = `${startY}px`; + element.style.left = "2px"; + element.style.right = "2px"; + element.style.marginLeft = "0"; + const ghostElement = element.cloneNode(true); + ghostElement.classList.add("drag-ghost"); + ghostElement.style.opacity = "0.3"; + ghostElement.style.pointerEvents = "none"; + element.parentNode?.insertBefore(ghostElement, element); + element.classList.add("dragging"); + const targetY = e.clientY - columnRect.top - mouseOffset.y; + this.dragState = { + eventId, + element, + ghostElement, + startY, + mouseOffset, + columnElement, + currentColumn: columnElement, + targetY: Math.max(0, targetY), + currentY: startY, + animationId: 0, + sourceColumnKey: columnElement.dataset.columnKey || "", + dragSource: "grid" + }; + const payload = { + eventId, + element, + ghostElement, + startY, + mouseOffset, + columnElement + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_START, payload); + this.animateDrag(); + } + updateDragTarget(e) { + if (!this.dragState) + return; + this.checkHeaderZone(e); + if (this.inHeader) + return; + const columnAtPoint = this.getColumnAtPoint(e.clientX); + if (this.dragState.dragSource === "header" && columnAtPoint && !this.dragState.currentColumn) { + this.dragState.currentColumn = columnAtPoint; + this.dragState.columnElement = columnAtPoint; + } + if (columnAtPoint && columnAtPoint !== this.dragState.currentColumn && this.dragState.currentColumn) { + const payload = { + eventId: this.dragState.eventId, + element: this.dragState.element, + previousColumn: this.dragState.currentColumn, + newColumn: columnAtPoint, + currentY: this.dragState.currentY + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_COLUMN_CHANGE, payload); + this.dragState.currentColumn = columnAtPoint; + this.dragState.columnElement = columnAtPoint; + } + if (!this.dragState.columnElement) + return; + const columnRect = this.dragState.columnElement.getBoundingClientRect(); + const targetY = e.clientY - columnRect.top - this.dragState.mouseOffset.y; + this.dragState.targetY = Math.max(0, targetY); + if (!this.dragState.animationId) { + this.animateDrag(); + } + } + /** + * Check if pointer is in header zone and emit appropriate events + */ + checkHeaderZone(e) { + if (!this.dragState) + return; + const headerViewport = document.querySelector("swp-header-viewport"); + if (!headerViewport) + return; + const rect = headerViewport.getBoundingClientRect(); + const isInHeader = e.clientY < rect.bottom; + if (isInHeader && !this.inHeader) { + this.inHeader = true; + if (this.dragState.dragSource === "grid" && this.dragState.columnElement) { + const payload = { + eventId: this.dragState.eventId, + element: this.dragState.element, + sourceColumnIndex: this.getColumnIndex(this.dragState.columnElement), + sourceColumnKey: this.dragState.columnElement.dataset.columnKey || "", + title: this.dragState.element.querySelector("swp-event-title")?.textContent || "", + colorClass: [...this.dragState.element.classList].find((c) => c.startsWith("is-")), + itemType: "event", + duration: 1 + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_ENTER_HEADER, payload); + } + } else if (!isInHeader && this.inHeader) { + this.inHeader = false; + const targetColumn = this.getColumnAtPoint(e.clientX); + if (this.dragState.dragSource === "header") { + const payload = { + eventId: this.dragState.eventId, + source: "header", + element: this.dragState.element, + targetColumn: targetColumn || void 0, + start: this.dragState.element.dataset.start ? new Date(this.dragState.element.dataset.start) : void 0, + end: this.dragState.element.dataset.end ? new Date(this.dragState.element.dataset.end) : void 0, + title: this.dragState.element.textContent || "", + colorClass: [...this.dragState.element.classList].find((c) => c.startsWith("is-")) + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_LEAVE_HEADER, payload); + if (targetColumn) { + const newElement = targetColumn.querySelector(`swp-event[data-event-id="${this.dragState.eventId}"]`); + if (newElement) { + this.dragState.element = newElement; + this.dragState.columnElement = targetColumn; + this.dragState.currentColumn = targetColumn; + this.animateDrag(); + } + } + } else { + const payload = { + eventId: this.dragState.eventId, + source: "grid" + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_LEAVE_HEADER, payload); + } + } else if (isInHeader) { + const column = this.getColumnAtX(e.clientX); + if (column) { + const payload = { + eventId: this.dragState.eventId, + columnIndex: this.getColumnIndex(column), + columnKey: column.dataset.columnKey || "" + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_MOVE_HEADER, payload); + } + } + } + /** + * Get column index (0-based) for a column element + */ + getColumnIndex(column) { + if (!this.container || !column) + return 0; + const columns = Array.from(this.container.querySelectorAll("swp-day-column")); + return columns.indexOf(column); + } + /** + * Get column at X coordinate (alias for getColumnAtPoint) + */ + getColumnAtX(clientX) { + return this.getColumnAtPoint(clientX); + } + /** + * Find column element at given X coordinate + */ + getColumnAtPoint(clientX) { + if (!this.container) + return null; + const columns = this.container.querySelectorAll("swp-day-column"); + for (const col of columns) { + const rect = col.getBoundingClientRect(); + if (clientX >= rect.left && clientX <= rect.right) { + return col; + } + } + return null; + } + /** + * Cancel drag and animate back to start position + */ + cancelDrag() { + if (!this.dragState) + return; + cancelAnimationFrame(this.dragState.animationId); + const { element, ghostElement, startY, eventId } = this.dragState; + element.style.transition = "top 200ms ease-out"; + element.style.top = `${startY}px`; + setTimeout(() => { + ghostElement?.remove(); + element.style.transition = ""; + element.classList.remove("dragging"); + }, 200); + const payload = { + eventId, + element, + startY + }; + this.eventBus.emit(CoreEvents.EVENT_DRAG_CANCEL, payload); + this.dragState = null; + this.inHeader = false; + } +}; +__name(_DragDropManager, "DragDropManager"); +var DragDropManager = _DragDropManager; + +// src/v2/managers/EdgeScrollManager.ts +var _EdgeScrollManager = class _EdgeScrollManager { + constructor(eventBus) { + this.eventBus = eventBus; + this.scrollableContent = null; + this.timeGrid = null; + this.draggedElement = null; + this.scrollRAF = null; + this.mouseY = 0; + this.isDragging = false; + this.isScrolling = false; + this.lastTs = 0; + this.rect = null; + this.initialScrollTop = 0; + this.OUTER_ZONE = 100; + this.INNER_ZONE = 50; + this.SLOW_SPEED = 140; + this.FAST_SPEED = 640; + this.trackMouse = (e) => { + if (this.isDragging) { + this.mouseY = e.clientY; + } + }; + this.scrollTick = (ts) => { + if (!this.isDragging || !this.scrollableContent) + return; + const dt = this.lastTs ? (ts - this.lastTs) / 1e3 : 0; + this.lastTs = ts; + this.rect ?? (this.rect = this.scrollableContent.getBoundingClientRect()); + const velocity = this.calculateVelocity(); + if (velocity !== 0 && !this.isAtBoundary(velocity)) { + const scrollDelta = velocity * dt; + this.scrollableContent.scrollTop += scrollDelta; + this.rect = null; + this.eventBus.emit(CoreEvents.EDGE_SCROLL_TICK, { scrollDelta }); + this.setScrollingState(true); + } else { + this.setScrollingState(false); + } + this.scrollRAF = requestAnimationFrame(this.scrollTick); + }; + this.subscribeToEvents(); + document.addEventListener("pointermove", this.trackMouse); + } + init(scrollableContent) { + this.scrollableContent = scrollableContent; + this.timeGrid = scrollableContent.querySelector("swp-time-grid"); + this.scrollableContent.style.scrollBehavior = "auto"; + } + subscribeToEvents() { + this.eventBus.on(CoreEvents.EVENT_DRAG_START, (event) => { + const payload = event.detail; + this.draggedElement = payload.element; + this.startDrag(); + }); + this.eventBus.on(CoreEvents.EVENT_DRAG_END, () => this.stopDrag()); + this.eventBus.on(CoreEvents.EVENT_DRAG_CANCEL, () => this.stopDrag()); + } + startDrag() { + this.isDragging = true; + this.isScrolling = false; + this.lastTs = 0; + this.initialScrollTop = this.scrollableContent?.scrollTop ?? 0; + if (this.scrollRAF === null) { + this.scrollRAF = requestAnimationFrame(this.scrollTick); + } + } + stopDrag() { + this.isDragging = false; + this.setScrollingState(false); + if (this.scrollRAF !== null) { + cancelAnimationFrame(this.scrollRAF); + this.scrollRAF = null; + } + this.rect = null; + this.lastTs = 0; + this.initialScrollTop = 0; + } + calculateVelocity() { + if (!this.rect) + return 0; + const distTop = this.mouseY - this.rect.top; + const distBot = this.rect.bottom - this.mouseY; + if (distTop < this.INNER_ZONE) + return -this.FAST_SPEED; + if (distTop < this.OUTER_ZONE) + return -this.SLOW_SPEED; + if (distBot < this.INNER_ZONE) + return this.FAST_SPEED; + if (distBot < this.OUTER_ZONE) + return this.SLOW_SPEED; + return 0; + } + isAtBoundary(velocity) { + if (!this.scrollableContent || !this.timeGrid || !this.draggedElement) + return false; + const atTop = this.scrollableContent.scrollTop <= 0 && velocity < 0; + const atBottom = velocity > 0 && this.draggedElement.getBoundingClientRect().bottom >= this.timeGrid.getBoundingClientRect().bottom; + return atTop || atBottom; + } + setScrollingState(scrolling) { + if (this.isScrolling === scrolling) + return; + this.isScrolling = scrolling; + if (scrolling) { + this.eventBus.emit(CoreEvents.EDGE_SCROLL_STARTED, {}); + } else { + this.initialScrollTop = this.scrollableContent?.scrollTop ?? 0; + this.eventBus.emit(CoreEvents.EDGE_SCROLL_STOPPED, {}); + } + } +}; +__name(_EdgeScrollManager, "EdgeScrollManager"); +var EdgeScrollManager = _EdgeScrollManager; + +// src/v2/managers/ResizeManager.ts +var _ResizeManager = class _ResizeManager { + constructor(eventBus, gridConfig, dateService) { + this.eventBus = eventBus; + this.gridConfig = gridConfig; + this.dateService = dateService; + this.container = null; + this.resizeState = null; + this.Z_INDEX_RESIZING = "1000"; + this.ANIMATION_SPEED = 0.35; + this.MIN_HEIGHT_MINUTES = 15; + this.handleMouseOver = (e) => { + const target = e.target; + const eventElement = target.closest("swp-event"); + if (!eventElement || this.resizeState) + return; + if (!eventElement.querySelector(":scope > swp-resize-handle")) { + const handle = this.createResizeHandle(); + eventElement.appendChild(handle); + } + }; + this.handlePointerDown = (e) => { + const handle = e.target.closest("swp-resize-handle"); + if (!handle) + return; + const element = handle.parentElement; + if (!element) + return; + const eventId = element.dataset.eventId || ""; + const startHeight = element.offsetHeight; + const startDurationMinutes = pixelsToMinutes(startHeight, this.gridConfig); + const container2 = element.closest("swp-event-group") ?? element; + const prevZIndex = container2.style.zIndex; + this.resizeState = { + eventId, + element, + handleElement: handle, + startY: e.clientY, + startHeight, + startDurationMinutes, + pointerId: e.pointerId, + prevZIndex, + // Animation state + currentHeight: startHeight, + targetHeight: startHeight, + animationId: null + }; + container2.style.zIndex = this.Z_INDEX_RESIZING; + try { + handle.setPointerCapture(e.pointerId); + } catch (err) { + console.warn("Pointer capture failed:", err); + } + document.documentElement.classList.add("swp--resizing"); + this.eventBus.emit(CoreEvents.EVENT_RESIZE_START, { + eventId, + element, + startHeight + }); + e.preventDefault(); + }; + this.handlePointerMove = (e) => { + if (!this.resizeState) + return; + const deltaY = e.clientY - this.resizeState.startY; + const minHeight = this.MIN_HEIGHT_MINUTES / 60 * this.gridConfig.hourHeight; + const newHeight = Math.max(minHeight, this.resizeState.startHeight + deltaY); + this.resizeState.targetHeight = newHeight; + if (this.resizeState.animationId === null) { + this.animateHeight(); + } + }; + this.animateHeight = () => { + if (!this.resizeState) + return; + const diff2 = this.resizeState.targetHeight - this.resizeState.currentHeight; + if (Math.abs(diff2) < 0.5) { + this.resizeState.animationId = null; + return; + } + this.resizeState.currentHeight += diff2 * this.ANIMATION_SPEED; + this.resizeState.element.style.height = `${this.resizeState.currentHeight}px`; + this.updateTimestampDisplay(); + this.resizeState.animationId = requestAnimationFrame(this.animateHeight); + }; + this.handlePointerUp = (e) => { + if (!this.resizeState) + return; + if (this.resizeState.animationId !== null) { + cancelAnimationFrame(this.resizeState.animationId); + } + try { + this.resizeState.handleElement.releasePointerCapture(e.pointerId); + } catch (err) { + console.warn("Pointer release failed:", err); + } + this.snapToGridFinal(); + this.updateTimestampDisplay(); + const container2 = this.resizeState.element.closest("swp-event-group") ?? this.resizeState.element; + container2.style.zIndex = this.resizeState.prevZIndex; + document.documentElement.classList.remove("swp--resizing"); + const column = this.resizeState.element.closest("swp-day-column"); + const columnKey = column?.dataset.columnKey || ""; + const date = column?.dataset.date || ""; + const swpEvent = SwpEvent.fromElement(this.resizeState.element, columnKey, date, this.gridConfig); + this.eventBus.emit(CoreEvents.EVENT_RESIZE_END, { + swpEvent + }); + this.resizeState = null; + }; + } + /** + * Initialize resize functionality on container + */ + init(container2) { + this.container = container2; + container2.addEventListener("mouseover", this.handleMouseOver, true); + document.addEventListener("pointerdown", this.handlePointerDown, true); + document.addEventListener("pointermove", this.handlePointerMove, true); + document.addEventListener("pointerup", this.handlePointerUp, true); + } + /** + * Create resize handle element + */ + createResizeHandle() { + const handle = document.createElement("swp-resize-handle"); + handle.setAttribute("aria-label", "Resize event"); + handle.setAttribute("role", "separator"); + return handle; + } + /** + * Update timestamp display with snapped end time + */ + updateTimestampDisplay() { + if (!this.resizeState) + return; + const timeEl = this.resizeState.element.querySelector("swp-event-time"); + if (!timeEl) + return; + const top = parseFloat(this.resizeState.element.style.top) || 0; + const startMinutesFromGrid = pixelsToMinutes(top, this.gridConfig); + const startMinutes = this.gridConfig.dayStartHour * 60 + startMinutesFromGrid; + const snappedHeight = snapToGrid(this.resizeState.currentHeight, this.gridConfig); + const durationMinutes = pixelsToMinutes(snappedHeight, this.gridConfig); + const endMinutes = startMinutes + durationMinutes; + const start = this.minutesToDate(startMinutes); + const end = this.minutesToDate(endMinutes); + timeEl.textContent = this.dateService.formatTimeRange(start, end); + } + /** + * Convert minutes since midnight to Date + */ + minutesToDate(minutes) { + const date = /* @__PURE__ */ new Date(); + date.setHours(Math.floor(minutes / 60) % 24, minutes % 60, 0, 0); + return date; + } + /** + * Snap final height to grid interval + */ + snapToGridFinal() { + if (!this.resizeState) + return; + const currentHeight = this.resizeState.element.offsetHeight; + const snappedHeight = snapToGrid(currentHeight, this.gridConfig); + const minHeight = minutesToPixels(this.MIN_HEIGHT_MINUTES, this.gridConfig); + const finalHeight = Math.max(minHeight, snappedHeight); + this.resizeState.element.style.height = `${finalHeight}px`; + this.resizeState.currentHeight = finalHeight; + } +}; +__name(_ResizeManager, "ResizeManager"); +var ResizeManager = _ResizeManager; + +// src/v2/managers/EventPersistenceManager.ts +var _EventPersistenceManager = class _EventPersistenceManager { + constructor(eventService, eventBus, dateService) { + this.eventService = eventService; + this.eventBus = eventBus; + this.dateService = dateService; + this.handleDragEnd = async (e) => { + const payload = e.detail; + const { swpEvent } = payload; + const event = await this.eventService.get(swpEvent.eventId); + if (!event) { + console.warn(`EventPersistenceManager: Event ${swpEvent.eventId} not found`); + return; + } + const { resource } = this.dateService.parseColumnKey(swpEvent.columnKey); + const updatedEvent = { + ...event, + start: swpEvent.start, + end: swpEvent.end, + resourceId: resource ?? event.resourceId, + allDay: payload.target === "header", + syncStatus: "pending" + }; + await this.eventService.save(updatedEvent); + const updatePayload = { + eventId: updatedEvent.id, + sourceColumnKey: payload.sourceColumnKey, + targetColumnKey: swpEvent.columnKey + }; + this.eventBus.emit(CoreEvents.EVENT_UPDATED, updatePayload); + }; + this.handleResizeEnd = async (e) => { + const payload = e.detail; + const { swpEvent } = payload; + const event = await this.eventService.get(swpEvent.eventId); + if (!event) { + console.warn(`EventPersistenceManager: Event ${swpEvent.eventId} not found`); + return; + } + const updatedEvent = { + ...event, + end: swpEvent.end, + syncStatus: "pending" + }; + await this.eventService.save(updatedEvent); + const updatePayload = { + eventId: updatedEvent.id, + sourceColumnKey: swpEvent.columnKey, + targetColumnKey: swpEvent.columnKey + }; + this.eventBus.emit(CoreEvents.EVENT_UPDATED, updatePayload); + }; + this.setupListeners(); + } + setupListeners() { + this.eventBus.on(CoreEvents.EVENT_DRAG_END, this.handleDragEnd); + this.eventBus.on(CoreEvents.EVENT_RESIZE_END, this.handleResizeEnd); + } +}; +__name(_EventPersistenceManager, "EventPersistenceManager"); +var EventPersistenceManager = _EventPersistenceManager; + +// src/v2/V2CompositionRoot.ts +var defaultTimeFormatConfig = { + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + use24HourFormat: true, + locale: "da-DK", + dateFormat: "locale", + showSeconds: false +}; +var defaultGridConfig = { + hourHeight: 64, + dayStartHour: 6, + dayEndHour: 18, + snapInterval: 15, + gridStartThresholdMinutes: 30 +}; +function createV2Container() { + const container2 = new Container(); + const builder = container2.builder(); + builder.registerInstance(defaultTimeFormatConfig).as("ITimeFormatConfig"); + builder.registerInstance(defaultGridConfig).as("IGridConfig"); + builder.registerType(EventBus).as("EventBus"); + builder.registerType(EventBus).as("IEventBus"); + builder.registerType(DateService).as("DateService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("ITimeFormatConfig"), + void 0 + ] + }); + builder.registerType(IndexedDBContext).as("IndexedDBContext").autoWire({ + mapResolvers: [ + (c) => c.resolveTypeAll("IStore") + ] + }); + builder.registerType(EventStore).as("IStore"); + builder.registerType(ResourceStore).as("IStore"); + builder.registerType(BookingStore).as("IStore"); + builder.registerType(CustomerStore).as("IStore"); + builder.registerType(TeamStore).as("IStore"); + builder.registerType(DepartmentStore).as("IStore"); + builder.registerType(ScheduleOverrideStore).as("IStore"); + builder.registerType(AuditStore).as("IStore"); + builder.registerType(SettingsStore).as("IStore"); + builder.registerType(ViewConfigStore).as("IStore"); + builder.registerType(EventService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(EventService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(EventService).as("EventService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ResourceService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ResourceService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ResourceService).as("ResourceService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(BookingService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(BookingService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(BookingService).as("BookingService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(CustomerService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(CustomerService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(CustomerService).as("CustomerService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(TeamService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(TeamService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(TeamService).as("TeamService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(DepartmentService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(DepartmentService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(DepartmentService).as("DepartmentService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(SettingsService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(SettingsService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(SettingsService).as("SettingsService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ViewConfigService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ViewConfigService).as("IEntityService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ViewConfigService).as("ViewConfigService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(MockEventRepository).as("IApiRepository"); + builder.registerType(MockEventRepository).as("IApiRepository"); + builder.registerType(MockResourceRepository).as("IApiRepository"); + builder.registerType(MockResourceRepository).as("IApiRepository"); + builder.registerType(MockBookingRepository).as("IApiRepository"); + builder.registerType(MockBookingRepository).as("IApiRepository"); + builder.registerType(MockCustomerRepository).as("IApiRepository"); + builder.registerType(MockCustomerRepository).as("IApiRepository"); + builder.registerType(MockAuditRepository).as("IApiRepository"); + builder.registerType(MockAuditRepository).as("IApiRepository"); + builder.registerType(MockTeamRepository).as("IApiRepository"); + builder.registerType(MockTeamRepository).as("IApiRepository"); + builder.registerType(MockDepartmentRepository).as("IApiRepository"); + builder.registerType(MockDepartmentRepository).as("IApiRepository"); + builder.registerType(MockSettingsRepository).as("IApiRepository"); + builder.registerType(MockSettingsRepository).as("IApiRepository"); + builder.registerType(MockViewConfigRepository).as("IApiRepository"); + builder.registerType(MockViewConfigRepository).as("IApiRepository"); + builder.registerType(AuditService).as("AuditService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(DataSeeder).as("DataSeeder").autoWire({ + mapResolvers: [ + (c) => c.resolveTypeAll("IEntityService"), + (c) => c.resolveTypeAll("IApiRepository") + ] + }); + builder.registerType(ScheduleOverrideService).as("ScheduleOverrideService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext") + ] + }); + builder.registerType(ResourceScheduleService).as("ResourceScheduleService").autoWire({ + mapResolvers: [ + (c) => c.resolveType("ResourceService"), + (c) => c.resolveType("ScheduleOverrideService"), + (c) => c.resolveType("DateService") + ] + }); + builder.registerType(EventRenderer).as("EventRenderer").autoWire({ + mapResolvers: [ + (c) => c.resolveType("EventService"), + (c) => c.resolveType("DateService"), + (c) => c.resolveType("IGridConfig"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ScheduleRenderer).as("ScheduleRenderer").autoWire({ + mapResolvers: [ + (c) => c.resolveType("ResourceScheduleService"), + (c) => c.resolveType("DateService"), + (c) => c.resolveType("IGridConfig") + ] + }); + builder.registerType(HeaderDrawerRenderer).as("HeaderDrawerRenderer").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IEventBus"), + (c) => c.resolveType("IGridConfig"), + (c) => c.resolveType("HeaderDrawerManager"), + (c) => c.resolveType("EventService"), + (c) => c.resolveType("DateService") + ] + }); + builder.registerType(DateRenderer).as("IRenderer").autoWire({ + mapResolvers: [ + (c) => c.resolveType("DateService") + ] + }); + builder.registerType(ResourceRenderer).as("IRenderer").autoWire({ + mapResolvers: [ + (c) => c.resolveType("ResourceService") + ] + }); + builder.registerType(TeamRenderer).as("IRenderer").autoWire({ + mapResolvers: [ + (c) => c.resolveType("TeamService") + ] + }); + builder.registerType(DepartmentRenderer).as("IRenderer").autoWire({ + mapResolvers: [ + (c) => c.resolveType("DepartmentService") + ] + }); + builder.registerType(MockTeamStore).as("IGroupingStore"); + builder.registerType(MockResourceStore).as("IGroupingStore"); + builder.registerType(CalendarOrchestrator).as("CalendarOrchestrator").autoWire({ + mapResolvers: [ + (c) => c.resolveTypeAll("IRenderer"), + (c) => c.resolveType("EventRenderer"), + (c) => c.resolveType("ScheduleRenderer"), + (c) => c.resolveType("HeaderDrawerRenderer"), + (c) => c.resolveType("DateService"), + (c) => c.resolveTypeAll("IEntityService") + ] + }); + builder.registerType(TimeAxisRenderer).as("TimeAxisRenderer"); + builder.registerType(ScrollManager).as("ScrollManager"); + builder.registerType(HeaderDrawerManager).as("HeaderDrawerManager"); + builder.registerType(DragDropManager).as("DragDropManager").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IEventBus"), + (c) => c.resolveType("IGridConfig") + ] + }); + builder.registerType(EdgeScrollManager).as("EdgeScrollManager").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(ResizeManager).as("ResizeManager").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IEventBus"), + (c) => c.resolveType("IGridConfig"), + (c) => c.resolveType("DateService") + ] + }); + builder.registerType(EventPersistenceManager).as("EventPersistenceManager").autoWire({ + mapResolvers: [ + (c) => c.resolveType("EventService"), + (c) => c.resolveType("IEventBus"), + (c) => c.resolveType("DateService") + ] + }); + builder.registerType(CalendarApp).as("CalendarApp").autoWire({ + mapResolvers: [ + (c) => c.resolveType("CalendarOrchestrator"), + (c) => c.resolveType("TimeAxisRenderer"), + (c) => c.resolveType("DateService"), + (c) => c.resolveType("ScrollManager"), + (c) => c.resolveType("HeaderDrawerManager"), + (c) => c.resolveType("DragDropManager"), + (c) => c.resolveType("EdgeScrollManager"), + (c) => c.resolveType("ResizeManager"), + (c) => c.resolveType("HeaderDrawerRenderer"), + (c) => c.resolveType("EventPersistenceManager"), + (c) => c.resolveType("SettingsService"), + (c) => c.resolveType("ViewConfigService"), + (c) => c.resolveType("IEventBus") + ] + }); + builder.registerType(DemoApp).as("DemoApp").autoWire({ + mapResolvers: [ + (c) => c.resolveType("IndexedDBContext"), + (c) => c.resolveType("DataSeeder"), + (c) => c.resolveType("AuditService"), + (c) => c.resolveType("CalendarApp"), + (c) => c.resolveType("DateService"), + (c) => c.resolveType("ResourceService"), + (c) => c.resolveType("IEventBus") + ] + }); + return builder.build(); +} +__name(createV2Container, "createV2Container"); + +// src/v2/demo/index.ts +var container = createV2Container(); +container.resolveType("DemoApp").init().catch(console.error); +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vbm9kZV9tb2R1bGVzL2RheWpzL2RheWpzLm1pbi5qcyIsICIuLi8uLi9ub2RlX21vZHVsZXMvZGF5anMvcGx1Z2luL3V0Yy5qcyIsICIuLi8uLi9ub2RlX21vZHVsZXMvZGF5anMvcGx1Z2luL3RpbWV6b25lLmpzIiwgIi4uLy4uL25vZGVfbW9kdWxlcy9kYXlqcy9wbHVnaW4vaXNvV2Vlay5qcyIsICIuLi8uLi9ub2RlX21vZHVsZXMvQG5vdmFkaS9jb3JlL2Rpc3QvdG9rZW4uanMiLCAiLi4vLi4vbm9kZV9tb2R1bGVzL0Bub3ZhZGkvY29yZS9kaXN0L2Vycm9ycy5qcyIsICIuLi8uLi9ub2RlX21vZHVsZXMvQG5vdmFkaS9jb3JlL2Rpc3QvYXV0b3dpcmUuanMiLCAiLi4vLi4vbm9kZV9tb2R1bGVzL0Bub3ZhZGkvY29yZS9kaXN0L2J1aWxkZXIuanMiLCAiLi4vLi4vbm9kZV9tb2R1bGVzL0Bub3ZhZGkvY29yZS9kaXN0L2NvbnRhaW5lci5qcyIsICIuLi8uLi9zcmMvdjIvZmVhdHVyZXMvZGF0ZS9EYXRlUmVuZGVyZXIudHMiLCAiLi4vLi4vc3JjL3YyL2NvcmUvRGF0ZVNlcnZpY2UudHMiLCAiLi4vLi4vc3JjL3YyL2NvcmUvQmFzZUdyb3VwaW5nUmVuZGVyZXIudHMiLCAiLi4vLi4vc3JjL3YyL2ZlYXR1cmVzL3Jlc291cmNlL1Jlc291cmNlUmVuZGVyZXIudHMiLCAiLi4vLi4vc3JjL3YyL2ZlYXR1cmVzL3RlYW0vVGVhbVJlbmRlcmVyLnRzIiwgIi4uLy4uL3NyYy92Mi9mZWF0dXJlcy9kZXBhcnRtZW50L0RlcGFydG1lbnRSZW5kZXJlci50cyIsICIuLi8uLi9zcmMvdjIvY29yZS9SZW5kZXJCdWlsZGVyLnRzIiwgIi4uLy4uL3NyYy92Mi9jb3JlL0ZpbHRlclRlbXBsYXRlLnRzIiwgIi4uLy4uL3NyYy92Mi9jb3JlL0NhbGVuZGFyT3JjaGVzdHJhdG9yLnRzIiwgIi4uLy4uL3NyYy92Mi9jb3JlL05hdmlnYXRpb25BbmltYXRvci50cyIsICIuLi8uLi9zcmMvdjIvY29yZS9DYWxlbmRhckV2ZW50cy50cyIsICIuLi8uLi9zcmMvdjIvY29yZS9DYWxlbmRhckFwcC50cyIsICIuLi8uLi9zcmMvdjIvZmVhdHVyZXMvdGltZWF4aXMvVGltZUF4aXNSZW5kZXJlci50cyIsICIuLi8uLi9zcmMvdjIvY29yZS9TY3JvbGxNYW5hZ2VyLnRzIiwgIi4uLy4uL3NyYy92Mi9jb3JlL0hlYWRlckRyYXdlck1hbmFnZXIudHMiLCAiLi4vLi4vc3JjL3YyL2RlbW8vTW9ja1N0b3Jlcy50cyIsICIuLi8uLi9zcmMvdjIvZGVtby9EZW1vQXBwLnRzIiwgIi4uLy4uL3NyYy92Mi9jb3JlL0V2ZW50QnVzLnRzIiwgIi4uLy4uL3NyYy92Mi9zdG9yYWdlL0luZGV4ZWREQkNvbnRleHQudHMiLCAiLi4vLi4vc3JjL3YyL3N0b3JhZ2UvZXZlbnRzL0V2ZW50U3RvcmUudHMiLCAiLi4vLi4vc3JjL3YyL3N0b3JhZ2UvZXZlbnRzL0V2ZW50U2VyaWFsaXphdGlvbi50cyIsICIuLi8uLi9zcmMvdjIvc3RvcmFnZS9TeW5jUGx1Z2luLnRzIiwgIi4uLy4uL3NyYy92Mi9jb25zdGFudHMvQ29yZUV2ZW50cy50cyIsICIuLi8uLi9ub2RlX21vZHVsZXMvanNvbi1kaWZmLXRzL3NyYy9oZWxwZXJzLnRzIiwgIi4uLy4uL25vZGVfbW9kdWxlcy9qc29uLWRpZmYtdHMvc3JjL2pzb25EaWZmLnRzIiwgIi4uLy4uL25vZGVfbW9kdWxlcy9qc29uLWRpZmYtdHMvc3JjL2pzb25Db21wYXJlLnRzIiwgIi4uLy4uL3NyYy92Mi9zdG9yYWdlL0Jhc2VFbnRpdHlTZXJ2aWNlLnRzIiwgIi4uLy4uL3NyYy92Mi9zdG9yYWdlL2V2ZW50cy9FdmVudFNlcnZpY2UudHMiLCAiLi4vLi4vc3JjL3YyL3N0b3JhZ2UvcmVzb3VyY2VzL1Jlc291cmNlU3RvcmUudHMiLCAiLi4vLi4vc3JjL3YyL3N0b3JhZ2UvcmVzb3VyY2VzL1Jlc291cmNlU2VydmljZS50cyIsICIuLi8uLi9zcmMvdjIvc3RvcmFnZS9ib29raW5ncy9Cb29raW5nU3RvcmUudHMiLCAiLi4vLi4vc3JjL3YyL3N0b3JhZ2UvYm9va2luZ3MvQm9va2luZ1NlcnZpY2UudHMiLCAiLi4vLi4vc3JjL3YyL3N0b3JhZ2UvY3VzdG9tZXJzL0N1c3RvbWVyU3RvcmUudHMiLCAiLi4vLi4vc3JjL3YyL3N0b3JhZ2UvY3VzdG9tZXJzL0N1c3RvbWVyU2VydmljZS50cyIsICIuLi8uLi9zcmMvdjIvc3RvcmFnZS90ZWFtcy9UZWFtU3RvcmUudHMiLCAiLi4vLi4vc3JjL3YyL3N0b3JhZ2UvdGVhbXMvVGVhbVNlcnZpY2UudHMiLCAiLi4vLi4vc3JjL3YyL3N0b3JhZ2UvZGVwYXJ0bWVudHMvRGVwYXJ0bWVudFN0b3JlLnRzIiwgIi4uLy4uL3NyYy92Mi9zdG9yYWdlL2RlcGFydG1lbnRzL0RlcGFydG1lbnRTZXJ2aWNlLnRzIiwgIi4uLy4uL3NyYy92Mi9zdG9yYWdlL3NldHRpbmdzL1NldHRpbmdzU3RvcmUudHMiLCAiLi4vLi4vc3JjL3YyL3R5cGVzL1NldHRpbmdzVHlwZXMudHMiLCAiLi4vLi4vc3JjL3YyL3N0b3JhZ2Uvc2V0dGluZ3MvU2V0dGluZ3NTZXJ2aWNlLnRzIiwgIi4uLy4uL3NyYy92Mi9zdG9yYWdlL3ZpZXdjb25maWdzL1ZpZXdDb25maWdTdG9yZS50cyIsICIuLi8uLi9zcmMvdjIvc3RvcmFnZS92aWV3Y29uZmlncy9WaWV3Q29uZmlnU2VydmljZS50cyIsICIuLi8uLi9zcmMvdjIvc3RvcmFnZS9hdWRpdC9BdWRpdFN0b3JlLnRzIiwgIi4uLy4uL3NyYy92Mi9zdG9yYWdlL2F1ZGl0L0F1ZGl0U2VydmljZS50cyIsICIuLi8uLi9zcmMvdjIvcmVwb3NpdG9yaWVzL01vY2tFdmVudFJlcG9zaXRvcnkudHMiLCAiLi4vLi4vc3JjL3YyL3JlcG9zaXRvcmllcy9Nb2NrUmVzb3VyY2VSZXBvc2l0b3J5LnRzIiwgIi4uLy4uL3NyYy92Mi9yZXBvc2l0b3JpZXMvTW9ja0Jvb2tpbmdSZXBvc2l0b3J5LnRzIiwgIi4uLy4uL3NyYy92Mi9yZXBvc2l0b3JpZXMvTW9ja0N1c3RvbWVyUmVwb3NpdG9yeS50cyIsICIuLi8uLi9zcmMvdjIvcmVwb3NpdG9yaWVzL01vY2tBdWRpdFJlcG9zaXRvcnkudHMiLCAiLi4vLi4vc3JjL3YyL3JlcG9zaXRvcmllcy9Nb2NrVGVhbVJlcG9zaXRvcnkudHMiLCAiLi4vLi4vc3JjL3YyL3JlcG9zaXRvcmllcy9Nb2NrRGVwYXJ0bWVudFJlcG9zaXRvcnkudHMiLCAiLi4vLi4vc3JjL3YyL3JlcG9zaXRvcmllcy9Nb2NrU2V0dGluZ3NSZXBvc2l0b3J5LnRzIiwgIi4uLy4uL3NyYy92Mi9yZXBvc2l0b3JpZXMvTW9ja1ZpZXdDb25maWdSZXBvc2l0b3J5LnRzIiwgIi4uLy4uL3NyYy92Mi93b3JrZXJzL0RhdGFTZWVkZXIudHMiLCAiLi4vLi4vc3JjL3YyL3V0aWxzL1Bvc2l0aW9uVXRpbHMudHMiLCAiLi4vLi4vc3JjL3YyL2ZlYXR1cmVzL2V2ZW50L0V2ZW50TGF5b3V0RW5naW5lLnRzIiwgIi4uLy4uL3NyYy92Mi9mZWF0dXJlcy9ldmVudC9FdmVudFJlbmRlcmVyLnRzIiwgIi4uLy4uL3NyYy92Mi9mZWF0dXJlcy9zY2hlZHVsZS9TY2hlZHVsZVJlbmRlcmVyLnRzIiwgIi4uLy4uL3NyYy92Mi9mZWF0dXJlcy9oZWFkZXJkcmF3ZXIvSGVhZGVyRHJhd2VyUmVuZGVyZXIudHMiLCAiLi4vLi4vc3JjL3YyL3N0b3JhZ2Uvc2NoZWR1bGVzL1NjaGVkdWxlT3ZlcnJpZGVTdG9yZS50cyIsICIuLi8uLi9zcmMvdjIvc3RvcmFnZS9zY2hlZHVsZXMvU2NoZWR1bGVPdmVycmlkZVNlcnZpY2UudHMiLCAiLi4vLi4vc3JjL3YyL3N0b3JhZ2Uvc2NoZWR1bGVzL1Jlc291cmNlU2NoZWR1bGVTZXJ2aWNlLnRzIiwgIi4uLy4uL3NyYy92Mi90eXBlcy9Td3BFdmVudC50cyIsICIuLi8uLi9zcmMvdjIvbWFuYWdlcnMvRHJhZ0Ryb3BNYW5hZ2VyLnRzIiwgIi4uLy4uL3NyYy92Mi9tYW5hZ2Vycy9FZGdlU2Nyb2xsTWFuYWdlci50cyIsICIuLi8uLi9zcmMvdjIvbWFuYWdlcnMvUmVzaXplTWFuYWdlci50cyIsICIuLi8uLi9zcmMvdjIvbWFuYWdlcnMvRXZlbnRQZXJzaXN0ZW5jZU1hbmFnZXIudHMiLCAiLi4vLi4vc3JjL3YyL1YyQ29tcG9zaXRpb25Sb290LnRzIiwgIi4uLy4uL3NyYy92Mi9kZW1vL2luZGV4LnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIhZnVuY3Rpb24odCxlKXtcIm9iamVjdFwiPT10eXBlb2YgZXhwb3J0cyYmXCJ1bmRlZmluZWRcIiE9dHlwZW9mIG1vZHVsZT9tb2R1bGUuZXhwb3J0cz1lKCk6XCJmdW5jdGlvblwiPT10eXBlb2YgZGVmaW5lJiZkZWZpbmUuYW1kP2RlZmluZShlKToodD1cInVuZGVmaW5lZFwiIT10eXBlb2YgZ2xvYmFsVGhpcz9nbG9iYWxUaGlzOnR8fHNlbGYpLmRheWpzPWUoKX0odGhpcywoZnVuY3Rpb24oKXtcInVzZSBzdHJpY3RcIjt2YXIgdD0xZTMsZT02ZTQsbj0zNmU1LHI9XCJtaWxsaXNlY29uZFwiLGk9XCJzZWNvbmRcIixzPVwibWludXRlXCIsdT1cImhvdXJcIixhPVwiZGF5XCIsbz1cIndlZWtcIixjPVwibW9udGhcIixmPVwicXVhcnRlclwiLGg9XCJ5ZWFyXCIsZD1cImRhdGVcIixsPVwiSW52YWxpZCBEYXRlXCIsJD0vXihcXGR7NH0pWy0vXT8oXFxkezEsMn0pP1stL10/KFxcZHswLDJ9KVtUdFxcc10qKFxcZHsxLDJ9KT86PyhcXGR7MSwyfSk/Oj8oXFxkezEsMn0pP1suOl0/KFxcZCspPyQvLHk9L1xcWyhbXlxcXV0rKV18WXsxLDR9fE17MSw0fXxEezEsMn18ZHsxLDR9fEh7MSwyfXxoezEsMn18YXxBfG17MSwyfXxzezEsMn18WnsxLDJ9fFNTUy9nLE09e25hbWU6XCJlblwiLHdlZWtkYXlzOlwiU3VuZGF5X01vbmRheV9UdWVzZGF5X1dlZG5lc2RheV9UaHVyc2RheV9GcmlkYXlfU2F0dXJkYXlcIi5zcGxpdChcIl9cIiksbW9udGhzOlwiSmFudWFyeV9GZWJydWFyeV9NYXJjaF9BcHJpbF9NYXlfSnVuZV9KdWx5X0F1Z3VzdF9TZXB0ZW1iZXJfT2N0b2Jlcl9Ob3ZlbWJlcl9EZWNlbWJlclwiLnNwbGl0KFwiX1wiKSxvcmRpbmFsOmZ1bmN0aW9uKHQpe3ZhciBlPVtcInRoXCIsXCJzdFwiLFwibmRcIixcInJkXCJdLG49dCUxMDA7cmV0dXJuXCJbXCIrdCsoZVsobi0yMCklMTBdfHxlW25dfHxlWzBdKStcIl1cIn19LG09ZnVuY3Rpb24odCxlLG4pe3ZhciByPVN0cmluZyh0KTtyZXR1cm4hcnx8ci5sZW5ndGg+PWU/dDpcIlwiK0FycmF5KGUrMS1yLmxlbmd0aCkuam9pbihuKSt0fSx2PXtzOm0sejpmdW5jdGlvbih0KXt2YXIgZT0tdC51dGNPZmZzZXQoKSxuPU1hdGguYWJzKGUpLHI9TWF0aC5mbG9vcihuLzYwKSxpPW4lNjA7cmV0dXJuKGU8PTA/XCIrXCI6XCItXCIpK20ociwyLFwiMFwiKStcIjpcIittKGksMixcIjBcIil9LG06ZnVuY3Rpb24gdChlLG4pe2lmKGUuZGF0ZSgpPG4uZGF0ZSgpKXJldHVybi10KG4sZSk7dmFyIHI9MTIqKG4ueWVhcigpLWUueWVhcigpKSsobi5tb250aCgpLWUubW9udGgoKSksaT1lLmNsb25lKCkuYWRkKHIsYykscz1uLWk8MCx1PWUuY2xvbmUoKS5hZGQocisocz8tMToxKSxjKTtyZXR1cm4rKC0ocisobi1pKS8ocz9pLXU6dS1pKSl8fDApfSxhOmZ1bmN0aW9uKHQpe3JldHVybiB0PDA/TWF0aC5jZWlsKHQpfHwwOk1hdGguZmxvb3IodCl9LHA6ZnVuY3Rpb24odCl7cmV0dXJue006Yyx5OmgsdzpvLGQ6YSxEOmQsaDp1LG06cyxzOmksbXM6cixROmZ9W3RdfHxTdHJpbmcodHx8XCJcIikudG9Mb3dlckNhc2UoKS5yZXBsYWNlKC9zJC8sXCJcIil9LHU6ZnVuY3Rpb24odCl7cmV0dXJuIHZvaWQgMD09PXR9fSxnPVwiZW5cIixEPXt9O0RbZ109TTt2YXIgcD1cIiRpc0RheWpzT2JqZWN0XCIsUz1mdW5jdGlvbih0KXtyZXR1cm4gdCBpbnN0YW5jZW9mIF98fCEoIXR8fCF0W3BdKX0sdz1mdW5jdGlvbiB0KGUsbixyKXt2YXIgaTtpZighZSlyZXR1cm4gZztpZihcInN0cmluZ1wiPT10eXBlb2YgZSl7dmFyIHM9ZS50b0xvd2VyQ2FzZSgpO0Rbc10mJihpPXMpLG4mJihEW3NdPW4saT1zKTt2YXIgdT1lLnNwbGl0KFwiLVwiKTtpZighaSYmdS5sZW5ndGg+MSlyZXR1cm4gdCh1WzBdKX1lbHNle3ZhciBhPWUubmFtZTtEW2FdPWUsaT1hfXJldHVybiFyJiZpJiYoZz1pKSxpfHwhciYmZ30sTz1mdW5jdGlvbih0LGUpe2lmKFModCkpcmV0dXJuIHQuY2xvbmUoKTt2YXIgbj1cIm9iamVjdFwiPT10eXBlb2YgZT9lOnt9O3JldHVybiBuLmRhdGU9dCxuLmFyZ3M9YXJndW1lbnRzLG5ldyBfKG4pfSxiPXY7Yi5sPXcsYi5pPVMsYi53PWZ1bmN0aW9uKHQsZSl7cmV0dXJuIE8odCx7bG9jYWxlOmUuJEwsdXRjOmUuJHUseDplLiR4LCRvZmZzZXQ6ZS4kb2Zmc2V0fSl9O3ZhciBfPWZ1bmN0aW9uKCl7ZnVuY3Rpb24gTSh0KXt0aGlzLiRMPXcodC5sb2NhbGUsbnVsbCwhMCksdGhpcy5wYXJzZSh0KSx0aGlzLiR4PXRoaXMuJHh8fHQueHx8e30sdGhpc1twXT0hMH12YXIgbT1NLnByb3RvdHlwZTtyZXR1cm4gbS5wYXJzZT1mdW5jdGlvbih0KXt0aGlzLiRkPWZ1bmN0aW9uKHQpe3ZhciBlPXQuZGF0ZSxuPXQudXRjO2lmKG51bGw9PT1lKXJldHVybiBuZXcgRGF0ZShOYU4pO2lmKGIudShlKSlyZXR1cm4gbmV3IERhdGU7aWYoZSBpbnN0YW5jZW9mIERhdGUpcmV0dXJuIG5ldyBEYXRlKGUpO2lmKFwic3RyaW5nXCI9PXR5cGVvZiBlJiYhL1okL2kudGVzdChlKSl7dmFyIHI9ZS5tYXRjaCgkKTtpZihyKXt2YXIgaT1yWzJdLTF8fDAscz0ocls3XXx8XCIwXCIpLnN1YnN0cmluZygwLDMpO3JldHVybiBuP25ldyBEYXRlKERhdGUuVVRDKHJbMV0saSxyWzNdfHwxLHJbNF18fDAscls1XXx8MCxyWzZdfHwwLHMpKTpuZXcgRGF0ZShyWzFdLGksclszXXx8MSxyWzRdfHwwLHJbNV18fDAscls2XXx8MCxzKX19cmV0dXJuIG5ldyBEYXRlKGUpfSh0KSx0aGlzLmluaXQoKX0sbS5pbml0PWZ1bmN0aW9uKCl7dmFyIHQ9dGhpcy4kZDt0aGlzLiR5PXQuZ2V0RnVsbFllYXIoKSx0aGlzLiRNPXQuZ2V0TW9udGgoKSx0aGlzLiREPXQuZ2V0RGF0ZSgpLHRoaXMuJFc9dC5nZXREYXkoKSx0aGlzLiRIPXQuZ2V0SG91cnMoKSx0aGlzLiRtPXQuZ2V0TWludXRlcygpLHRoaXMuJHM9dC5nZXRTZWNvbmRzKCksdGhpcy4kbXM9dC5nZXRNaWxsaXNlY29uZHMoKX0sbS4kdXRpbHM9ZnVuY3Rpb24oKXtyZXR1cm4gYn0sbS5pc1ZhbGlkPWZ1bmN0aW9uKCl7cmV0dXJuISh0aGlzLiRkLnRvU3RyaW5nKCk9PT1sKX0sbS5pc1NhbWU9ZnVuY3Rpb24odCxlKXt2YXIgbj1PKHQpO3JldHVybiB0aGlzLnN0YXJ0T2YoZSk8PW4mJm48PXRoaXMuZW5kT2YoZSl9LG0uaXNBZnRlcj1mdW5jdGlvbih0LGUpe3JldHVybiBPKHQpPHRoaXMuc3RhcnRPZihlKX0sbS5pc0JlZm9yZT1mdW5jdGlvbih0LGUpe3JldHVybiB0aGlzLmVuZE9mKGUpPE8odCl9LG0uJGc9ZnVuY3Rpb24odCxlLG4pe3JldHVybiBiLnUodCk/dGhpc1tlXTp0aGlzLnNldChuLHQpfSxtLnVuaXg9ZnVuY3Rpb24oKXtyZXR1cm4gTWF0aC5mbG9vcih0aGlzLnZhbHVlT2YoKS8xZTMpfSxtLnZhbHVlT2Y9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy4kZC5nZXRUaW1lKCl9LG0uc3RhcnRPZj1mdW5jdGlvbih0LGUpe3ZhciBuPXRoaXMscj0hIWIudShlKXx8ZSxmPWIucCh0KSxsPWZ1bmN0aW9uKHQsZSl7dmFyIGk9Yi53KG4uJHU/RGF0ZS5VVEMobi4keSxlLHQpOm5ldyBEYXRlKG4uJHksZSx0KSxuKTtyZXR1cm4gcj9pOmkuZW5kT2YoYSl9LCQ9ZnVuY3Rpb24odCxlKXtyZXR1cm4gYi53KG4udG9EYXRlKClbdF0uYXBwbHkobi50b0RhdGUoXCJzXCIpLChyP1swLDAsMCwwXTpbMjMsNTksNTksOTk5XSkuc2xpY2UoZSkpLG4pfSx5PXRoaXMuJFcsTT10aGlzLiRNLG09dGhpcy4kRCx2PVwic2V0XCIrKHRoaXMuJHU/XCJVVENcIjpcIlwiKTtzd2l0Y2goZil7Y2FzZSBoOnJldHVybiByP2woMSwwKTpsKDMxLDExKTtjYXNlIGM6cmV0dXJuIHI/bCgxLE0pOmwoMCxNKzEpO2Nhc2Ugbzp2YXIgZz10aGlzLiRsb2NhbGUoKS53ZWVrU3RhcnR8fDAsRD0oeTxnP3krNzp5KS1nO3JldHVybiBsKHI/bS1EOm0rKDYtRCksTSk7Y2FzZSBhOmNhc2UgZDpyZXR1cm4gJCh2K1wiSG91cnNcIiwwKTtjYXNlIHU6cmV0dXJuICQoditcIk1pbnV0ZXNcIiwxKTtjYXNlIHM6cmV0dXJuICQoditcIlNlY29uZHNcIiwyKTtjYXNlIGk6cmV0dXJuICQoditcIk1pbGxpc2Vjb25kc1wiLDMpO2RlZmF1bHQ6cmV0dXJuIHRoaXMuY2xvbmUoKX19LG0uZW5kT2Y9ZnVuY3Rpb24odCl7cmV0dXJuIHRoaXMuc3RhcnRPZih0LCExKX0sbS4kc2V0PWZ1bmN0aW9uKHQsZSl7dmFyIG4sbz1iLnAodCksZj1cInNldFwiKyh0aGlzLiR1P1wiVVRDXCI6XCJcIiksbD0obj17fSxuW2FdPWYrXCJEYXRlXCIsbltkXT1mK1wiRGF0ZVwiLG5bY109ZitcIk1vbnRoXCIsbltoXT1mK1wiRnVsbFllYXJcIixuW3VdPWYrXCJIb3Vyc1wiLG5bc109ZitcIk1pbnV0ZXNcIixuW2ldPWYrXCJTZWNvbmRzXCIsbltyXT1mK1wiTWlsbGlzZWNvbmRzXCIsbilbb10sJD1vPT09YT90aGlzLiREKyhlLXRoaXMuJFcpOmU7aWYobz09PWN8fG89PT1oKXt2YXIgeT10aGlzLmNsb25lKCkuc2V0KGQsMSk7eS4kZFtsXSgkKSx5LmluaXQoKSx0aGlzLiRkPXkuc2V0KGQsTWF0aC5taW4odGhpcy4kRCx5LmRheXNJbk1vbnRoKCkpKS4kZH1lbHNlIGwmJnRoaXMuJGRbbF0oJCk7cmV0dXJuIHRoaXMuaW5pdCgpLHRoaXN9LG0uc2V0PWZ1bmN0aW9uKHQsZSl7cmV0dXJuIHRoaXMuY2xvbmUoKS4kc2V0KHQsZSl9LG0uZ2V0PWZ1bmN0aW9uKHQpe3JldHVybiB0aGlzW2IucCh0KV0oKX0sbS5hZGQ9ZnVuY3Rpb24ocixmKXt2YXIgZCxsPXRoaXM7cj1OdW1iZXIocik7dmFyICQ9Yi5wKGYpLHk9ZnVuY3Rpb24odCl7dmFyIGU9TyhsKTtyZXR1cm4gYi53KGUuZGF0ZShlLmRhdGUoKStNYXRoLnJvdW5kKHQqcikpLGwpfTtpZigkPT09YylyZXR1cm4gdGhpcy5zZXQoYyx0aGlzLiRNK3IpO2lmKCQ9PT1oKXJldHVybiB0aGlzLnNldChoLHRoaXMuJHkrcik7aWYoJD09PWEpcmV0dXJuIHkoMSk7aWYoJD09PW8pcmV0dXJuIHkoNyk7dmFyIE09KGQ9e30sZFtzXT1lLGRbdV09bixkW2ldPXQsZClbJF18fDEsbT10aGlzLiRkLmdldFRpbWUoKStyKk07cmV0dXJuIGIudyhtLHRoaXMpfSxtLnN1YnRyYWN0PWZ1bmN0aW9uKHQsZSl7cmV0dXJuIHRoaXMuYWRkKC0xKnQsZSl9LG0uZm9ybWF0PWZ1bmN0aW9uKHQpe3ZhciBlPXRoaXMsbj10aGlzLiRsb2NhbGUoKTtpZighdGhpcy5pc1ZhbGlkKCkpcmV0dXJuIG4uaW52YWxpZERhdGV8fGw7dmFyIHI9dHx8XCJZWVlZLU1NLUREVEhIOm1tOnNzWlwiLGk9Yi56KHRoaXMpLHM9dGhpcy4kSCx1PXRoaXMuJG0sYT10aGlzLiRNLG89bi53ZWVrZGF5cyxjPW4ubW9udGhzLGY9bi5tZXJpZGllbSxoPWZ1bmN0aW9uKHQsbixpLHMpe3JldHVybiB0JiYodFtuXXx8dChlLHIpKXx8aVtuXS5zbGljZSgwLHMpfSxkPWZ1bmN0aW9uKHQpe3JldHVybiBiLnMocyUxMnx8MTIsdCxcIjBcIil9LCQ9Znx8ZnVuY3Rpb24odCxlLG4pe3ZhciByPXQ8MTI/XCJBTVwiOlwiUE1cIjtyZXR1cm4gbj9yLnRvTG93ZXJDYXNlKCk6cn07cmV0dXJuIHIucmVwbGFjZSh5LChmdW5jdGlvbih0LHIpe3JldHVybiByfHxmdW5jdGlvbih0KXtzd2l0Y2godCl7Y2FzZVwiWVlcIjpyZXR1cm4gU3RyaW5nKGUuJHkpLnNsaWNlKC0yKTtjYXNlXCJZWVlZXCI6cmV0dXJuIGIucyhlLiR5LDQsXCIwXCIpO2Nhc2VcIk1cIjpyZXR1cm4gYSsxO2Nhc2VcIk1NXCI6cmV0dXJuIGIucyhhKzEsMixcIjBcIik7Y2FzZVwiTU1NXCI6cmV0dXJuIGgobi5tb250aHNTaG9ydCxhLGMsMyk7Y2FzZVwiTU1NTVwiOnJldHVybiBoKGMsYSk7Y2FzZVwiRFwiOnJldHVybiBlLiREO2Nhc2VcIkREXCI6cmV0dXJuIGIucyhlLiRELDIsXCIwXCIpO2Nhc2VcImRcIjpyZXR1cm4gU3RyaW5nKGUuJFcpO2Nhc2VcImRkXCI6cmV0dXJuIGgobi53ZWVrZGF5c01pbixlLiRXLG8sMik7Y2FzZVwiZGRkXCI6cmV0dXJuIGgobi53ZWVrZGF5c1Nob3J0LGUuJFcsbywzKTtjYXNlXCJkZGRkXCI6cmV0dXJuIG9bZS4kV107Y2FzZVwiSFwiOnJldHVybiBTdHJpbmcocyk7Y2FzZVwiSEhcIjpyZXR1cm4gYi5zKHMsMixcIjBcIik7Y2FzZVwiaFwiOnJldHVybiBkKDEpO2Nhc2VcImhoXCI6cmV0dXJuIGQoMik7Y2FzZVwiYVwiOnJldHVybiAkKHMsdSwhMCk7Y2FzZVwiQVwiOnJldHVybiAkKHMsdSwhMSk7Y2FzZVwibVwiOnJldHVybiBTdHJpbmcodSk7Y2FzZVwibW1cIjpyZXR1cm4gYi5zKHUsMixcIjBcIik7Y2FzZVwic1wiOnJldHVybiBTdHJpbmcoZS4kcyk7Y2FzZVwic3NcIjpyZXR1cm4gYi5zKGUuJHMsMixcIjBcIik7Y2FzZVwiU1NTXCI6cmV0dXJuIGIucyhlLiRtcywzLFwiMFwiKTtjYXNlXCJaXCI6cmV0dXJuIGl9cmV0dXJuIG51bGx9KHQpfHxpLnJlcGxhY2UoXCI6XCIsXCJcIil9KSl9LG0udXRjT2Zmc2V0PWZ1bmN0aW9uKCl7cmV0dXJuIDE1Ki1NYXRoLnJvdW5kKHRoaXMuJGQuZ2V0VGltZXpvbmVPZmZzZXQoKS8xNSl9LG0uZGlmZj1mdW5jdGlvbihyLGQsbCl7dmFyICQseT10aGlzLE09Yi5wKGQpLG09TyhyKSx2PShtLnV0Y09mZnNldCgpLXRoaXMudXRjT2Zmc2V0KCkpKmUsZz10aGlzLW0sRD1mdW5jdGlvbigpe3JldHVybiBiLm0oeSxtKX07c3dpdGNoKE0pe2Nhc2UgaDokPUQoKS8xMjticmVhaztjYXNlIGM6JD1EKCk7YnJlYWs7Y2FzZSBmOiQ9RCgpLzM7YnJlYWs7Y2FzZSBvOiQ9KGctdikvNjA0OGU1O2JyZWFrO2Nhc2UgYTokPShnLXYpLzg2NGU1O2JyZWFrO2Nhc2UgdTokPWcvbjticmVhaztjYXNlIHM6JD1nL2U7YnJlYWs7Y2FzZSBpOiQ9Zy90O2JyZWFrO2RlZmF1bHQ6JD1nfXJldHVybiBsPyQ6Yi5hKCQpfSxtLmRheXNJbk1vbnRoPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuZW5kT2YoYykuJER9LG0uJGxvY2FsZT1mdW5jdGlvbigpe3JldHVybiBEW3RoaXMuJExdfSxtLmxvY2FsZT1mdW5jdGlvbih0LGUpe2lmKCF0KXJldHVybiB0aGlzLiRMO3ZhciBuPXRoaXMuY2xvbmUoKSxyPXcodCxlLCEwKTtyZXR1cm4gciYmKG4uJEw9ciksbn0sbS5jbG9uZT1mdW5jdGlvbigpe3JldHVybiBiLncodGhpcy4kZCx0aGlzKX0sbS50b0RhdGU9ZnVuY3Rpb24oKXtyZXR1cm4gbmV3IERhdGUodGhpcy52YWx1ZU9mKCkpfSxtLnRvSlNPTj1mdW5jdGlvbigpe3JldHVybiB0aGlzLmlzVmFsaWQoKT90aGlzLnRvSVNPU3RyaW5nKCk6bnVsbH0sbS50b0lTT1N0cmluZz1mdW5jdGlvbigpe3JldHVybiB0aGlzLiRkLnRvSVNPU3RyaW5nKCl9LG0udG9TdHJpbmc9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy4kZC50b1VUQ1N0cmluZygpfSxNfSgpLGs9Xy5wcm90b3R5cGU7cmV0dXJuIE8ucHJvdG90eXBlPWssW1tcIiRtc1wiLHJdLFtcIiRzXCIsaV0sW1wiJG1cIixzXSxbXCIkSFwiLHVdLFtcIiRXXCIsYV0sW1wiJE1cIixjXSxbXCIkeVwiLGhdLFtcIiREXCIsZF1dLmZvckVhY2goKGZ1bmN0aW9uKHQpe2tbdFsxXV09ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuJGcoZSx0WzBdLHRbMV0pfX0pKSxPLmV4dGVuZD1mdW5jdGlvbih0LGUpe3JldHVybiB0LiRpfHwodChlLF8sTyksdC4kaT0hMCksT30sTy5sb2NhbGU9dyxPLmlzRGF5anM9UyxPLnVuaXg9ZnVuY3Rpb24odCl7cmV0dXJuIE8oMWUzKnQpfSxPLmVuPURbZ10sTy5Mcz1ELE8ucD17fSxPfSkpOyIsICIhZnVuY3Rpb24odCxpKXtcIm9iamVjdFwiPT10eXBlb2YgZXhwb3J0cyYmXCJ1bmRlZmluZWRcIiE9dHlwZW9mIG1vZHVsZT9tb2R1bGUuZXhwb3J0cz1pKCk6XCJmdW5jdGlvblwiPT10eXBlb2YgZGVmaW5lJiZkZWZpbmUuYW1kP2RlZmluZShpKToodD1cInVuZGVmaW5lZFwiIT10eXBlb2YgZ2xvYmFsVGhpcz9nbG9iYWxUaGlzOnR8fHNlbGYpLmRheWpzX3BsdWdpbl91dGM9aSgpfSh0aGlzLChmdW5jdGlvbigpe1widXNlIHN0cmljdFwiO3ZhciB0PVwibWludXRlXCIsaT0vWystXVxcZFxcZCg/Ojo/XFxkXFxkKT8vZyxlPS8oWystXXxcXGRcXGQpL2c7cmV0dXJuIGZ1bmN0aW9uKHMsZixuKXt2YXIgdT1mLnByb3RvdHlwZTtuLnV0Yz1mdW5jdGlvbih0KXt2YXIgaT17ZGF0ZTp0LHV0YzohMCxhcmdzOmFyZ3VtZW50c307cmV0dXJuIG5ldyBmKGkpfSx1LnV0Yz1mdW5jdGlvbihpKXt2YXIgZT1uKHRoaXMudG9EYXRlKCkse2xvY2FsZTp0aGlzLiRMLHV0YzohMH0pO3JldHVybiBpP2UuYWRkKHRoaXMudXRjT2Zmc2V0KCksdCk6ZX0sdS5sb2NhbD1mdW5jdGlvbigpe3JldHVybiBuKHRoaXMudG9EYXRlKCkse2xvY2FsZTp0aGlzLiRMLHV0YzohMX0pfTt2YXIgcj11LnBhcnNlO3UucGFyc2U9ZnVuY3Rpb24odCl7dC51dGMmJih0aGlzLiR1PSEwKSx0aGlzLiR1dGlscygpLnUodC4kb2Zmc2V0KXx8KHRoaXMuJG9mZnNldD10LiRvZmZzZXQpLHIuY2FsbCh0aGlzLHQpfTt2YXIgbz11LmluaXQ7dS5pbml0PWZ1bmN0aW9uKCl7aWYodGhpcy4kdSl7dmFyIHQ9dGhpcy4kZDt0aGlzLiR5PXQuZ2V0VVRDRnVsbFllYXIoKSx0aGlzLiRNPXQuZ2V0VVRDTW9udGgoKSx0aGlzLiREPXQuZ2V0VVRDRGF0ZSgpLHRoaXMuJFc9dC5nZXRVVENEYXkoKSx0aGlzLiRIPXQuZ2V0VVRDSG91cnMoKSx0aGlzLiRtPXQuZ2V0VVRDTWludXRlcygpLHRoaXMuJHM9dC5nZXRVVENTZWNvbmRzKCksdGhpcy4kbXM9dC5nZXRVVENNaWxsaXNlY29uZHMoKX1lbHNlIG8uY2FsbCh0aGlzKX07dmFyIGE9dS51dGNPZmZzZXQ7dS51dGNPZmZzZXQ9ZnVuY3Rpb24ocyxmKXt2YXIgbj10aGlzLiR1dGlscygpLnU7aWYobihzKSlyZXR1cm4gdGhpcy4kdT8wOm4odGhpcy4kb2Zmc2V0KT9hLmNhbGwodGhpcyk6dGhpcy4kb2Zmc2V0O2lmKFwic3RyaW5nXCI9PXR5cGVvZiBzJiYocz1mdW5jdGlvbih0KXt2b2lkIDA9PT10JiYodD1cIlwiKTt2YXIgcz10Lm1hdGNoKGkpO2lmKCFzKXJldHVybiBudWxsO3ZhciBmPShcIlwiK3NbMF0pLm1hdGNoKGUpfHxbXCItXCIsMCwwXSxuPWZbMF0sdT02MCorZlsxXSsgK2ZbMl07cmV0dXJuIDA9PT11PzA6XCIrXCI9PT1uP3U6LXV9KHMpLG51bGw9PT1zKSlyZXR1cm4gdGhpczt2YXIgdT1NYXRoLmFicyhzKTw9MTY/NjAqczpzO2lmKDA9PT11KXJldHVybiB0aGlzLnV0YyhmKTt2YXIgcj10aGlzLmNsb25lKCk7aWYoZilyZXR1cm4gci4kb2Zmc2V0PXUsci4kdT0hMSxyO3ZhciBvPXRoaXMuJHU/dGhpcy50b0RhdGUoKS5nZXRUaW1lem9uZU9mZnNldCgpOi0xKnRoaXMudXRjT2Zmc2V0KCk7cmV0dXJuKHI9dGhpcy5sb2NhbCgpLmFkZCh1K28sdCkpLiRvZmZzZXQ9dSxyLiR4LiRsb2NhbE9mZnNldD1vLHJ9O3ZhciBoPXUuZm9ybWF0O3UuZm9ybWF0PWZ1bmN0aW9uKHQpe3ZhciBpPXR8fCh0aGlzLiR1P1wiWVlZWS1NTS1ERFRISDptbTpzc1taXVwiOlwiXCIpO3JldHVybiBoLmNhbGwodGhpcyxpKX0sdS52YWx1ZU9mPWZ1bmN0aW9uKCl7dmFyIHQ9dGhpcy4kdXRpbHMoKS51KHRoaXMuJG9mZnNldCk/MDp0aGlzLiRvZmZzZXQrKHRoaXMuJHguJGxvY2FsT2Zmc2V0fHx0aGlzLiRkLmdldFRpbWV6b25lT2Zmc2V0KCkpO3JldHVybiB0aGlzLiRkLnZhbHVlT2YoKS02ZTQqdH0sdS5pc1VUQz1mdW5jdGlvbigpe3JldHVybiEhdGhpcy4kdX0sdS50b0lTT1N0cmluZz1mdW5jdGlvbigpe3JldHVybiB0aGlzLnRvRGF0ZSgpLnRvSVNPU3RyaW5nKCl9LHUudG9TdHJpbmc9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy50b0RhdGUoKS50b1VUQ1N0cmluZygpfTt2YXIgbD11LnRvRGF0ZTt1LnRvRGF0ZT1mdW5jdGlvbih0KXtyZXR1cm5cInNcIj09PXQmJnRoaXMuJG9mZnNldD9uKHRoaXMuZm9ybWF0KFwiWVlZWS1NTS1ERCBISDptbTpzczpTU1NcIikpLnRvRGF0ZSgpOmwuY2FsbCh0aGlzKX07dmFyIGM9dS5kaWZmO3UuZGlmZj1mdW5jdGlvbih0LGksZSl7aWYodCYmdGhpcy4kdT09PXQuJHUpcmV0dXJuIGMuY2FsbCh0aGlzLHQsaSxlKTt2YXIgcz10aGlzLmxvY2FsKCksZj1uKHQpLmxvY2FsKCk7cmV0dXJuIGMuY2FsbChzLGYsaSxlKX19fSkpOyIsICIhZnVuY3Rpb24odCxlKXtcIm9iamVjdFwiPT10eXBlb2YgZXhwb3J0cyYmXCJ1bmRlZmluZWRcIiE9dHlwZW9mIG1vZHVsZT9tb2R1bGUuZXhwb3J0cz1lKCk6XCJmdW5jdGlvblwiPT10eXBlb2YgZGVmaW5lJiZkZWZpbmUuYW1kP2RlZmluZShlKToodD1cInVuZGVmaW5lZFwiIT10eXBlb2YgZ2xvYmFsVGhpcz9nbG9iYWxUaGlzOnR8fHNlbGYpLmRheWpzX3BsdWdpbl90aW1lem9uZT1lKCl9KHRoaXMsKGZ1bmN0aW9uKCl7XCJ1c2Ugc3RyaWN0XCI7dmFyIHQ9e3llYXI6MCxtb250aDoxLGRheToyLGhvdXI6MyxtaW51dGU6NCxzZWNvbmQ6NX0sZT17fTtyZXR1cm4gZnVuY3Rpb24obixpLG8pe3ZhciByLGE9ZnVuY3Rpb24odCxuLGkpe3ZvaWQgMD09PWkmJihpPXt9KTt2YXIgbz1uZXcgRGF0ZSh0KSxyPWZ1bmN0aW9uKHQsbil7dm9pZCAwPT09biYmKG49e30pO3ZhciBpPW4udGltZVpvbmVOYW1lfHxcInNob3J0XCIsbz10K1wifFwiK2kscj1lW29dO3JldHVybiByfHwocj1uZXcgSW50bC5EYXRlVGltZUZvcm1hdChcImVuLVVTXCIse2hvdXIxMjohMSx0aW1lWm9uZTp0LHllYXI6XCJudW1lcmljXCIsbW9udGg6XCIyLWRpZ2l0XCIsZGF5OlwiMi1kaWdpdFwiLGhvdXI6XCIyLWRpZ2l0XCIsbWludXRlOlwiMi1kaWdpdFwiLHNlY29uZDpcIjItZGlnaXRcIix0aW1lWm9uZU5hbWU6aX0pLGVbb109cikscn0obixpKTtyZXR1cm4gci5mb3JtYXRUb1BhcnRzKG8pfSx1PWZ1bmN0aW9uKGUsbil7Zm9yKHZhciBpPWEoZSxuKSxyPVtdLHU9MDt1PGkubGVuZ3RoO3UrPTEpe3ZhciBmPWlbdV0scz1mLnR5cGUsbT1mLnZhbHVlLGM9dFtzXTtjPj0wJiYocltjXT1wYXJzZUludChtLDEwKSl9dmFyIGQ9clszXSxsPTI0PT09ZD8wOmQsaD1yWzBdK1wiLVwiK3JbMV0rXCItXCIrclsyXStcIiBcIitsK1wiOlwiK3JbNF0rXCI6XCIrcls1XStcIjowMDBcIix2PStlO3JldHVybihvLnV0YyhoKS52YWx1ZU9mKCktKHYtPXYlMWUzKSkvNmU0fSxmPWkucHJvdG90eXBlO2YudHo9ZnVuY3Rpb24odCxlKXt2b2lkIDA9PT10JiYodD1yKTt2YXIgbixpPXRoaXMudXRjT2Zmc2V0KCksYT10aGlzLnRvRGF0ZSgpLHU9YS50b0xvY2FsZVN0cmluZyhcImVuLVVTXCIse3RpbWVab25lOnR9KSxmPU1hdGgucm91bmQoKGEtbmV3IERhdGUodSkpLzFlMy82MCkscz0xNSotTWF0aC5yb3VuZChhLmdldFRpbWV6b25lT2Zmc2V0KCkvMTUpLWY7aWYoIU51bWJlcihzKSluPXRoaXMudXRjT2Zmc2V0KDAsZSk7ZWxzZSBpZihuPW8odSx7bG9jYWxlOnRoaXMuJEx9KS4kc2V0KFwibWlsbGlzZWNvbmRcIix0aGlzLiRtcykudXRjT2Zmc2V0KHMsITApLGUpe3ZhciBtPW4udXRjT2Zmc2V0KCk7bj1uLmFkZChpLW0sXCJtaW51dGVcIil9cmV0dXJuIG4uJHguJHRpbWV6b25lPXQsbn0sZi5vZmZzZXROYW1lPWZ1bmN0aW9uKHQpe3ZhciBlPXRoaXMuJHguJHRpbWV6b25lfHxvLnR6Lmd1ZXNzKCksbj1hKHRoaXMudmFsdWVPZigpLGUse3RpbWVab25lTmFtZTp0fSkuZmluZCgoZnVuY3Rpb24odCl7cmV0dXJuXCJ0aW1lem9uZW5hbWVcIj09PXQudHlwZS50b0xvd2VyQ2FzZSgpfSkpO3JldHVybiBuJiZuLnZhbHVlfTt2YXIgcz1mLnN0YXJ0T2Y7Zi5zdGFydE9mPWZ1bmN0aW9uKHQsZSl7aWYoIXRoaXMuJHh8fCF0aGlzLiR4LiR0aW1lem9uZSlyZXR1cm4gcy5jYWxsKHRoaXMsdCxlKTt2YXIgbj1vKHRoaXMuZm9ybWF0KFwiWVlZWS1NTS1ERCBISDptbTpzczpTU1NcIikse2xvY2FsZTp0aGlzLiRMfSk7cmV0dXJuIHMuY2FsbChuLHQsZSkudHoodGhpcy4keC4kdGltZXpvbmUsITApfSxvLnR6PWZ1bmN0aW9uKHQsZSxuKXt2YXIgaT1uJiZlLGE9bnx8ZXx8cixmPXUoK28oKSxhKTtpZihcInN0cmluZ1wiIT10eXBlb2YgdClyZXR1cm4gbyh0KS50eihhKTt2YXIgcz1mdW5jdGlvbih0LGUsbil7dmFyIGk9dC02MCplKjFlMyxvPXUoaSxuKTtpZihlPT09bylyZXR1cm5baSxlXTt2YXIgcj11KGktPTYwKihvLWUpKjFlMyxuKTtyZXR1cm4gbz09PXI/W2ksb106W3QtNjAqTWF0aC5taW4obyxyKSoxZTMsTWF0aC5tYXgobyxyKV19KG8udXRjKHQsaSkudmFsdWVPZigpLGYsYSksbT1zWzBdLGM9c1sxXSxkPW8obSkudXRjT2Zmc2V0KGMpO3JldHVybiBkLiR4LiR0aW1lem9uZT1hLGR9LG8udHouZ3Vlc3M9ZnVuY3Rpb24oKXtyZXR1cm4gSW50bC5EYXRlVGltZUZvcm1hdCgpLnJlc29sdmVkT3B0aW9ucygpLnRpbWVab25lfSxvLnR6LnNldERlZmF1bHQ9ZnVuY3Rpb24odCl7cj10fX19KSk7IiwgIiFmdW5jdGlvbihlLHQpe1wib2JqZWN0XCI9PXR5cGVvZiBleHBvcnRzJiZcInVuZGVmaW5lZFwiIT10eXBlb2YgbW9kdWxlP21vZHVsZS5leHBvcnRzPXQoKTpcImZ1bmN0aW9uXCI9PXR5cGVvZiBkZWZpbmUmJmRlZmluZS5hbWQ/ZGVmaW5lKHQpOihlPVwidW5kZWZpbmVkXCIhPXR5cGVvZiBnbG9iYWxUaGlzP2dsb2JhbFRoaXM6ZXx8c2VsZikuZGF5anNfcGx1Z2luX2lzb1dlZWs9dCgpfSh0aGlzLChmdW5jdGlvbigpe1widXNlIHN0cmljdFwiO3ZhciBlPVwiZGF5XCI7cmV0dXJuIGZ1bmN0aW9uKHQsaSxzKXt2YXIgYT1mdW5jdGlvbih0KXtyZXR1cm4gdC5hZGQoNC10Lmlzb1dlZWtkYXkoKSxlKX0sZD1pLnByb3RvdHlwZTtkLmlzb1dlZWtZZWFyPWZ1bmN0aW9uKCl7cmV0dXJuIGEodGhpcykueWVhcigpfSxkLmlzb1dlZWs9ZnVuY3Rpb24odCl7aWYoIXRoaXMuJHV0aWxzKCkudSh0KSlyZXR1cm4gdGhpcy5hZGQoNyoodC10aGlzLmlzb1dlZWsoKSksZSk7dmFyIGksZCxuLG8scj1hKHRoaXMpLHU9KGk9dGhpcy5pc29XZWVrWWVhcigpLGQ9dGhpcy4kdSxuPShkP3MudXRjOnMpKCkueWVhcihpKS5zdGFydE9mKFwieWVhclwiKSxvPTQtbi5pc29XZWVrZGF5KCksbi5pc29XZWVrZGF5KCk+NCYmKG8rPTcpLG4uYWRkKG8sZSkpO3JldHVybiByLmRpZmYodSxcIndlZWtcIikrMX0sZC5pc29XZWVrZGF5PWZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLiR1dGlscygpLnUoZSk/dGhpcy5kYXkoKXx8Nzp0aGlzLmRheSh0aGlzLmRheSgpJTc/ZTplLTcpfTt2YXIgbj1kLnN0YXJ0T2Y7ZC5zdGFydE9mPWZ1bmN0aW9uKGUsdCl7dmFyIGk9dGhpcy4kdXRpbHMoKSxzPSEhaS51KHQpfHx0O3JldHVyblwiaXNvd2Vla1wiPT09aS5wKGUpP3M/dGhpcy5kYXRlKHRoaXMuZGF0ZSgpLSh0aGlzLmlzb1dlZWtkYXkoKS0xKSkuc3RhcnRPZihcImRheVwiKTp0aGlzLmRhdGUodGhpcy5kYXRlKCktMS0odGhpcy5pc29XZWVrZGF5KCktMSkrNykuZW5kT2YoXCJkYXlcIik6bi5iaW5kKHRoaXMpKGUsdCl9fX0pKTsiLCAibGV0IHRva2VuQ291bnRlciA9IDA7XG4vKipcbiAqIENyZWF0ZXMgYSBuZXcgdW5pcXVlIHRva2VuIGZvciBkZXBlbmRlbmN5IGluamVjdGlvbi5cbiAqXG4gKiBAcGFyYW0gZGVzY3JpcHRpb24gT3B0aW9uYWwgZGVzY3JpcHRpb24gZm9yIGRlYnVnZ2luZyBwdXJwb3Nlc1xuICogQHJldHVybnMgQSB1bmlxdWUgdG9rZW4gdGhhdCBjYW4gYmUgdXNlZCBhcyBhIE1hcCBrZXlcbiAqXG4gKiBAZXhhbXBsZVxuICogYGBgdHNcbiAqIGludGVyZmFjZSBJTG9nZ2VyIHsgbG9nKG1zZzogc3RyaW5nKTogdm9pZCB9XG4gKiBjb25zdCBMb2dnZXJUb2tlbiA9IFRva2VuPElMb2dnZXI+KCdMb2dnZXInKVxuICogYGBgXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBUb2tlbihkZXNjcmlwdGlvbikge1xuICAgIGNvbnN0IGlkID0gKyt0b2tlbkNvdW50ZXI7XG4gICAgY29uc3Qgc3ltID0gU3ltYm9sKGRlc2NyaXB0aW9uID8gYFRva2VuKCR7ZGVzY3JpcHRpb259KWAgOiBgVG9rZW4jJHtpZH1gKTtcbiAgICBjb25zdCB0b2tlbiA9IHtcbiAgICAgICAgc3ltYm9sOiBzeW0sXG4gICAgICAgIGRlc2NyaXB0aW9uLFxuICAgICAgICB0b1N0cmluZygpIHtcbiAgICAgICAgICAgIHJldHVybiBkZXNjcmlwdGlvblxuICAgICAgICAgICAgICAgID8gYFRva2VuPCR7ZGVzY3JpcHRpb259PmBcbiAgICAgICAgICAgICAgICA6IGBUb2tlbjwjJHtpZH0+YDtcbiAgICAgICAgfVxuICAgIH07XG4gICAgcmV0dXJuIHRva2VuO1xufVxuLyoqXG4gKiBDcmVhdGVzIGEgbmV3IHVuaXF1ZSB0b2tlbiB3aXRob3V0IGEgc3RyaW5nIGxpdGVyYWwuXG4gKiBQcmVmZXJyZWQgZm9yIEF1dG9mYWMtc3R5bGUgREkgdG8gYXZvaWQgc3RyaW5nIGxpdGVyYWxzLlxuICpcbiAqIEByZXR1cm5zIEEgdW5pcXVlIHRva2VuIHRoYXQgY2FuIGJlIHVzZWQgYXMgYSBNYXAga2V5XG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHRzXG4gKiBpbnRlcmZhY2UgSUxvZ2dlciB7IGxvZyhtc2c6IHN0cmluZyk6IHZvaWQgfVxuICogY29uc3QgTG9nZ2VyVG9rZW4gPSB0b2tlbjxJTG9nZ2VyPigpXG4gKiBgYGBcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHRva2VuKCkge1xuICAgIHJldHVybiBUb2tlbigpO1xufVxuIiwgIi8qKlxuICogRXJyb3IgY2xhc3NlcyBmb3IgTm92YURJIGNvbnRhaW5lclxuICovXG5leHBvcnQgY2xhc3MgQ29udGFpbmVyRXJyb3IgZXh0ZW5kcyBFcnJvciB7XG4gICAgY29uc3RydWN0b3IobWVzc2FnZSkge1xuICAgICAgICBzdXBlcihtZXNzYWdlKTtcbiAgICAgICAgdGhpcy5uYW1lID0gJ0NvbnRhaW5lckVycm9yJztcbiAgICB9XG59XG5leHBvcnQgY2xhc3MgQmluZGluZ05vdEZvdW5kRXJyb3IgZXh0ZW5kcyBDb250YWluZXJFcnJvciB7XG4gICAgY29uc3RydWN0b3IodG9rZW5EZXNjcmlwdGlvbiwgcGF0aCA9IFtdKSB7XG4gICAgICAgIGNvbnN0IHBhdGhTdHIgPSBwYXRoLmxlbmd0aCA+IDAgPyBgXFxuICBEZXBlbmRlbmN5IHBhdGg6ICR7cGF0aC5qb2luKCcgLT4gJyl9YCA6ICcnO1xuICAgICAgICBzdXBlcihgVG9rZW4gXCIke3Rva2VuRGVzY3JpcHRpb259XCIgaXMgbm90IGJvdW5kIG9yIHJlZ2lzdGVyZWQgaW4gdGhlIGNvbnRhaW5lci4ke3BhdGhTdHJ9YCk7XG4gICAgICAgIHRoaXMubmFtZSA9ICdCaW5kaW5nTm90Rm91bmRFcnJvcic7XG4gICAgfVxufVxuZXhwb3J0IGNsYXNzIENpcmN1bGFyRGVwZW5kZW5jeUVycm9yIGV4dGVuZHMgQ29udGFpbmVyRXJyb3Ige1xuICAgIGNvbnN0cnVjdG9yKHBhdGgpIHtcbiAgICAgICAgc3VwZXIoYENpcmN1bGFyIGRlcGVuZGVuY3kgZGV0ZWN0ZWQ6ICR7cGF0aC5qb2luKCcgLT4gJyl9YCk7XG4gICAgICAgIHRoaXMubmFtZSA9ICdDaXJjdWxhckRlcGVuZGVuY3lFcnJvcic7XG4gICAgfVxufVxuIiwgIi8qKlxuICogQXV0b1dpcmUgLSBBdXRvbWF0aWMgZGVwZW5kZW5jeSBpbmplY3Rpb24gZm9yIE5vdmFESVxuICogU3VwcG9ydHMgdHdvIHN0cmF0ZWdpZXM6IG1hcFJlc29sdmVycyAodHJhbnNmb3JtZXItZ2VuZXJhdGVkKSBhbmQgbWFwIChtYW51YWwgb3ZlcnJpZGUpXG4gKi9cbi8qKlxuICogUGVyZm9ybWFuY2U6IENhY2hlIGV4dHJhY3RlZCBwYXJhbWV0ZXIgbmFtZXMgdG8gYXZvaWQgcmVwZWF0ZWQgcmVnZXggcGFyc2luZ1xuICogV2Vha01hcCBhbGxvd3MgZ2FyYmFnZSBjb2xsZWN0aW9uIHdoZW4gY29uc3RydWN0b3IgaXMgbm8gbG9uZ2VyIHJlZmVyZW5jZWRcbiAqL1xuY29uc3QgcGFyYW1OYW1lQ2FjaGUgPSBuZXcgV2Vha01hcCgpO1xuLyoqXG4gKiBFeHRyYWN0IHBhcmFtZXRlciBuYW1lcyBmcm9tIGEgY29uc3RydWN0b3IgZnVuY3Rpb25cbiAqIFVzZXMgcmVnZXggdG8gcGFyc2UgdGhlIHRvU3RyaW5nKCkgcmVwcmVzZW50YXRpb25cbiAqIFBlcmZvcm1hbmNlIG9wdGltaXplZDogUmVzdWx0cyBhcmUgY2FjaGVkIHBlciBjb25zdHJ1Y3RvclxuICpcbiAqIE5vdGU6IE9ubHkgdXNlZCBieSByZXNvbHZlQnlNYXAoKSBmb3IgbWFudWFsIG1hcCBzdHJhdGVneVxuICovXG5leHBvcnQgZnVuY3Rpb24gZXh0cmFjdFBhcmFtZXRlck5hbWVzKGNvbnN0cnVjdG9yKSB7XG4gICAgLy8gQ2hlY2sgY2FjaGUgZmlyc3QgLSBhdm9pZHMgZXhwZW5zaXZlIHJlZ2V4IHBhcnNpbmdcbiAgICBjb25zdCBjYWNoZWQgPSBwYXJhbU5hbWVDYWNoZS5nZXQoY29uc3RydWN0b3IpO1xuICAgIGlmIChjYWNoZWQpIHtcbiAgICAgICAgcmV0dXJuIGNhY2hlZDtcbiAgICB9XG4gICAgLy8gRXh0cmFjdCBwYXJhbWV0ZXIgbmFtZXMgKGV4cGVuc2l2ZSBvcGVyYXRpb24pXG4gICAgY29uc3QgZm5TdHIgPSBjb25zdHJ1Y3Rvci50b1N0cmluZygpO1xuICAgIC8vIE1hdGNoIGNvbnN0cnVjdG9yKC4uLmFyZ3MpIG9yIGNsYXNzIHsgY29uc3RydWN0b3IoLi4uYXJncykgfVxuICAgIGNvbnN0IG1hdGNoID0gZm5TdHIubWF0Y2goL2NvbnN0cnVjdG9yXFxzKlxcKChbXildKilcXCkvKSB8fCBmblN0ci5tYXRjaCgvXlteKF0qXFwoKFteKV0qKVxcKS8pO1xuICAgIGlmICghbWF0Y2ggfHwgIW1hdGNoWzFdKSB7XG4gICAgICAgIHJldHVybiBbXTtcbiAgICB9XG4gICAgY29uc3QgcGFyYW1zID0gbWF0Y2hbMV1cbiAgICAgICAgLnNwbGl0KCcsJylcbiAgICAgICAgLm1hcChwYXJhbSA9PiBwYXJhbS50cmltKCkpXG4gICAgICAgIC5maWx0ZXIocGFyYW0gPT4gcGFyYW0ubGVuZ3RoID4gMClcbiAgICAgICAgLm1hcChwYXJhbSA9PiB7XG4gICAgICAgIC8vIFJlbW92ZSBkZWZhdWx0IHZhbHVlcywgdHlwZSBhbm5vdGF0aW9ucywgYW5kIGV4dHJhY3QganVzdCB0aGUgbmFtZVxuICAgICAgICBsZXQgbmFtZSA9IHBhcmFtLnNwbGl0KC9bOj1dLylbMF0udHJpbSgpO1xuICAgICAgICAvLyBSZW1vdmUgVHlwZVNjcmlwdCBtb2RpZmllcnMgKHB1YmxpYywgcHJpdmF0ZSwgcHJvdGVjdGVkLCByZWFkb25seSlcbiAgICAgICAgLy8gQ2FuIGFwcGVhciBtdWx0aXBsZSB0aW1lcywgZS5nLiwgXCJwdWJsaWMgcmVhZG9ubHkgc2VydmljZVwiXG4gICAgICAgIG5hbWUgPSBuYW1lLnJlcGxhY2UoL14oKHB1YmxpY3xwcml2YXRlfHByb3RlY3RlZHxyZWFkb25seSlcXHMrKSsvLCAnJyk7XG4gICAgICAgIC8vIEhhbmRsZSBkZXN0cnVjdHVyaW5nIC0gc2tpcCBmb3Igbm93XG4gICAgICAgIGlmIChuYW1lLmluY2x1ZGVzKCd7JykgfHwgbmFtZS5pbmNsdWRlcygnWycpKSB7XG4gICAgICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gbmFtZTtcbiAgICB9KVxuICAgICAgICAuZmlsdGVyKChuYW1lKSA9PiBuYW1lICE9PSBudWxsKTtcbiAgICAvLyBDYWNoZSByZXN1bHQgZm9yIGZ1dHVyZSBjYWxsc1xuICAgIHBhcmFtTmFtZUNhY2hlLnNldChjb25zdHJ1Y3RvciwgcGFyYW1zKTtcbiAgICByZXR1cm4gcGFyYW1zO1xufVxuLyoqXG4gKiBSZXNvbHZlIGRlcGVuZGVuY2llcyB1c2luZyBtYXAgc3RyYXRlZ3lcbiAqIFVzZXMgZXhwbGljaXQgbWFwcGluZyBmcm9tIHBhcmFtZXRlciBuYW1lcyB0byByZXNvbHZlcnNcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlc29sdmVCeU1hcChjb25zdHJ1Y3RvciwgY29udGFpbmVyLCBvcHRpb25zKSB7XG4gICAgaWYgKCFvcHRpb25zLm1hcCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ0F1dG9XaXJlIG1hcCBzdHJhdGVneSByZXF1aXJlcyBvcHRpb25zLm1hcCB0byBiZSBkZWZpbmVkJyk7XG4gICAgfVxuICAgIGNvbnN0IHBhcmFtTmFtZXMgPSBleHRyYWN0UGFyYW1ldGVyTmFtZXMoY29uc3RydWN0b3IpO1xuICAgIGNvbnN0IHJlc29sdmVkRGVwcyA9IFtdO1xuICAgIGZvciAoY29uc3QgcGFyYW1OYW1lIG9mIHBhcmFtTmFtZXMpIHtcbiAgICAgICAgY29uc3QgcmVzb2x2ZXIgPSBvcHRpb25zLm1hcFtwYXJhbU5hbWVdO1xuICAgICAgICBpZiAocmVzb2x2ZXIgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgaWYgKG9wdGlvbnMuc3RyaWN0KSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBDYW5ub3QgcmVzb2x2ZSBwYXJhbWV0ZXIgXCIke3BhcmFtTmFtZX1cIiBvbiAke2NvbnN0cnVjdG9yLm5hbWV9LiBgICtcbiAgICAgICAgICAgICAgICAgICAgYE5vdCBmb3VuZCBpbiBhdXRvd2lyZSBtYXAuIGAgK1xuICAgICAgICAgICAgICAgICAgICBgQWRkIGl0IHRvIHRoZSBtYXA6IC5hdXRvV2lyZSh7IG1hcDogeyAke3BhcmFtTmFtZX06IC4uLiB9IH0pYCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICAvLyBTaWxlbnRseSBwdXNoIHVuZGVmaW5lZCBmb3IgbWlzc2luZyBwYXJhbWV0ZXJzXG4gICAgICAgICAgICAgICAgLy8gVGhpcyBpcyBleHBlY3RlZDogdHJhbnNmb3JtZXIgZmlsdGVycyBvdXQgcHJpbWl0aXZlIHR5cGVzIGF0IGNvbXBpbGUtdGltZSxcbiAgICAgICAgICAgICAgICAvLyBzbyBtaXNzaW5nIHBhcmFtcyBhcmUgdHlwaWNhbGx5IHByaW1pdGl2ZXMgdGhhdCBkb24ndCBuZWVkIERJIHJlc29sdXRpb25cbiAgICAgICAgICAgICAgICByZXNvbHZlZERlcHMucHVzaCh1bmRlZmluZWQpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgLy8gUmVzb2x2ZXIgY2FuIGJlIGEgZnVuY3Rpb24gb3IgYSBUb2tlblxuICAgICAgICBpZiAodHlwZW9mIHJlc29sdmVyID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICAgICAgICByZXNvbHZlZERlcHMucHVzaChyZXNvbHZlcihjb250YWluZXIpKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIC8vIEFzc3VtZSBpdCdzIGEgVG9rZW5cbiAgICAgICAgICAgIHJlc29sdmVkRGVwcy5wdXNoKGNvbnRhaW5lci5yZXNvbHZlKHJlc29sdmVyKSk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHJlc29sdmVkRGVwcztcbn1cbi8qKlxuICogUmVzb2x2ZSBkZXBlbmRlbmNpZXMgdXNpbmcgbWFwUmVzb2x2ZXJzIGFycmF5IHN0cmF0ZWd5XG4gKiBPUFRJTUFMIFBFUkZPUk1BTkNFOiBPKDEpIGFycmF5IGFjY2VzcyBwZXIgcGFyYW1ldGVyXG4gKiBNaW5pZmljYXRpb24tc2FmZTogVXNlcyBwb3NpdGlvbi1iYXNlZCBhcnJheVxuICogUmVmYWN0b3JpbmctZnJpZW5kbHk6IFRyYW5zZm9ybWVyIHJlZ2VuZXJhdGVzIGFycmF5IG9uIHJlY29tcGlsZVxuICpcbiAqIFJlcXVpcmVzIGJ1aWxkLXRpbWUgdHJhbnNmb3JtZXIgdG8gZ2VuZXJhdGUgbWFwUmVzb2x2ZXJzIGFycmF5XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNvbHZlQnlNYXBSZXNvbHZlcnMoX2NvbnN0cnVjdG9yLCBjb250YWluZXIsIG9wdGlvbnMpIHtcbiAgICBpZiAoIW9wdGlvbnMubWFwUmVzb2x2ZXJzIHx8IG9wdGlvbnMubWFwUmVzb2x2ZXJzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICByZXR1cm4gW107XG4gICAgfVxuICAgIGNvbnN0IHJlc29sdmVkRGVwcyA9IFtdO1xuICAgIC8vIFNpbXBsZSBPKDEpIGFycmF5IGFjY2VzcyAtIHVsdHJhIGZhc3QhXG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBvcHRpb25zLm1hcFJlc29sdmVycy5sZW5ndGg7IGkrKykge1xuICAgICAgICBjb25zdCByZXNvbHZlciA9IG9wdGlvbnMubWFwUmVzb2x2ZXJzW2ldO1xuICAgICAgICBpZiAocmVzb2x2ZXIgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgLy8gdW5kZWZpbmVkIGluZGljYXRlcyBwcmltaXRpdmUgdHlwZSBvciBwYXJhbWV0ZXIgd2l0aG91dCBESVxuICAgICAgICAgICAgcmVzb2x2ZWREZXBzLnB1c2godW5kZWZpbmVkKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmICh0eXBlb2YgcmVzb2x2ZXIgPT09ICdmdW5jdGlvbicpIHtcbiAgICAgICAgICAgIC8vIFJlc29sdmVyIGZ1bmN0aW9uOiAoYykgPT4gYy5yZXNvbHZlVHlwZSguLi4pXG4gICAgICAgICAgICByZXNvbHZlZERlcHMucHVzaChyZXNvbHZlcihjb250YWluZXIpKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIC8vIFRva2VuLWJhc2VkIHJlc29sdXRpb25cbiAgICAgICAgICAgIHJlc29sdmVkRGVwcy5wdXNoKGNvbnRhaW5lci5yZXNvbHZlKHJlc29sdmVyKSk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHJlc29sdmVkRGVwcztcbn1cbi8qKlxuICogTWFpbiBhdXRvd2lyZSBmdW5jdGlvbiAtIGRpc3BhdGNoZXMgdG8gYXBwcm9wcmlhdGUgc3RyYXRlZ3lcbiAqIFByaW9yaXR5OiBtYXBSZXNvbHZlcnMgKHRyYW5zZm9ybWVyLWdlbmVyYXRlZCkgPiBtYXAgKG1hbnVhbCBvdmVycmlkZSlcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGF1dG93aXJlKGNvbnN0cnVjdG9yLCBjb250YWluZXIsIG9wdGlvbnMpIHtcbiAgICBjb25zdCBvcHRzID0ge1xuICAgICAgICBieTogJ3BhcmFtTmFtZScsXG4gICAgICAgIHN0cmljdDogZmFsc2UsXG4gICAgICAgIC4uLm9wdGlvbnNcbiAgICB9O1xuICAgIC8vIEhJR0hFU1QgUFJJT1JJVFk6IG1hcFJlc29sdmVycyBhcnJheSAodHJhbnNmb3JtZXItZ2VuZXJhdGVkLCBvcHRpbWFsIHBlcmZvcm1hbmNlKVxuICAgIC8vIE8oMSkgYXJyYXkgYWNjZXNzIHBlciBwYXJhbWV0ZXIgLSBtaW5pZmljYXRpb24tc2FmZSBhbmQgcmVmYWN0b3JpbmctZnJpZW5kbHlcbiAgICBpZiAob3B0cy5tYXBSZXNvbHZlcnMgJiYgb3B0cy5tYXBSZXNvbHZlcnMubGVuZ3RoID4gMCkge1xuICAgICAgICByZXR1cm4gcmVzb2x2ZUJ5TWFwUmVzb2x2ZXJzKGNvbnN0cnVjdG9yLCBjb250YWluZXIsIG9wdHMpO1xuICAgIH1cbiAgICAvLyBGQUxMQkFDSzogTWFudWFsIG1hcCBzdHJhdGVneSBmb3IgZXhwbGljaXQgb3ZlcnJpZGVzXG4gICAgaWYgKG9wdHMubWFwICYmIE9iamVjdC5rZXlzKG9wdHMubWFwKS5sZW5ndGggPiAwKSB7XG4gICAgICAgIHJldHVybiByZXNvbHZlQnlNYXAoY29uc3RydWN0b3IsIGNvbnRhaW5lciwgb3B0cyk7XG4gICAgfVxuICAgIC8vIE5vIGF1dG93aXJpbmcgY29uZmlndXJlZCwgcmV0dXJuIGVtcHR5IGFycmF5XG4gICAgcmV0dXJuIFtdO1xufVxuIiwgIi8qKlxuICogRmx1ZW50IGJ1aWxkZXIgQVBJIGZvciBOb3ZhREkgQ29udGFpbmVyIChBdXRvZmFjLXN0eWxlKVxuICovXG5pbXBvcnQgeyBUb2tlbiB9IGZyb20gJy4vdG9rZW4uanMnO1xuaW1wb3J0IHsgYXV0b3dpcmUgfSBmcm9tICcuL2F1dG93aXJlLmpzJztcbi8qKlxuICogRmx1ZW50IHJlZ2lzdHJhdGlvbiBidWlsZGVyIHJldHVybmVkIGFmdGVyIGVhY2ggcmVnaXN0cmF0aW9uIG1ldGhvZFxuICovXG5leHBvcnQgY2xhc3MgUmVnaXN0cmF0aW9uQnVpbGRlciB7XG4gICAgY29uc3RydWN0b3IocGVuZGluZywgcmVnaXN0cmF0aW9ucykge1xuICAgICAgICB0aGlzLnJlZ2lzdHJhdGlvbnMgPSByZWdpc3RyYXRpb25zO1xuICAgICAgICB0aGlzLmNvbmZpZ3MgPSBbXTtcbiAgICAgICAgdGhpcy5kZWZhdWx0TGlmZXRpbWUgPSAnc2luZ2xldG9uJztcbiAgICAgICAgdGhpcy5wZW5kaW5nID0gcGVuZGluZztcbiAgICB9XG4gICAgLyoqXG4gICAgICogQmluZCB0aGlzIHJlZ2lzdHJhdGlvbiB0byBhIHRva2VuIG9yIGludGVyZmFjZSB0eXBlXG4gICAgICpcbiAgICAgKiBAb3ZlcmxvYWRcbiAgICAgKiBAcGFyYW0ge1Rva2VuPFU+fSB0b2tlbiAtIEV4cGxpY2l0IHRva2VuIGZvciBiaW5kaW5nXG4gICAgICpcbiAgICAgKiBAb3ZlcmxvYWRcbiAgICAgKiBAcGFyYW0ge3N0cmluZ30gdHlwZU5hbWUgLSBJbnRlcmZhY2UgdHlwZSBuYW1lIChhdXRvLWdlbmVyYXRlZCBieSB0cmFuc2Zvcm1lcilcbiAgICAgKi9cbiAgICBhcyh0b2tlbk9yVHlwZU5hbWUpIHtcbiAgICAgICAgLy8gQ2hlY2sgaWYgYXJndW1lbnQgaXMgYSBUb2tlbiBvYmplY3QgKGhhcyBzeW1ib2wgcHJvcGVydHkpXG4gICAgICAgIGlmICh0b2tlbk9yVHlwZU5hbWUgJiYgdHlwZW9mIHRva2VuT3JUeXBlTmFtZSA9PT0gJ29iamVjdCcgJiYgJ3N5bWJvbCcgaW4gdG9rZW5PclR5cGVOYW1lKSB7XG4gICAgICAgICAgICAvLyBUb2tlbi1iYXNlZCByZWdpc3RyYXRpb25cbiAgICAgICAgICAgIGNvbnN0IGNvbmZpZyA9IHtcbiAgICAgICAgICAgICAgICB0b2tlbjogdG9rZW5PclR5cGVOYW1lLFxuICAgICAgICAgICAgICAgIHR5cGU6IHRoaXMucGVuZGluZy50eXBlLFxuICAgICAgICAgICAgICAgIHZhbHVlOiB0aGlzLnBlbmRpbmcudmFsdWUsXG4gICAgICAgICAgICAgICAgZmFjdG9yeTogdGhpcy5wZW5kaW5nLmZhY3RvcnksXG4gICAgICAgICAgICAgICAgY29uc3RydWN0b3I6IHRoaXMucGVuZGluZy5jb25zdHJ1Y3RvcixcbiAgICAgICAgICAgICAgICBsaWZldGltZTogdGhpcy5kZWZhdWx0TGlmZXRpbWVcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICB0aGlzLmNvbmZpZ3MucHVzaChjb25maWcpO1xuICAgICAgICAgICAgdGhpcy5yZWdpc3RyYXRpb25zLnB1c2goY29uZmlnKTtcbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgLy8gSW50ZXJmYWNlLWJhc2VkIHJlZ2lzdHJhdGlvbiAodHlwZU5hbWUgc3RyaW5nIG9yIHVuZGVmaW5lZClcbiAgICAgICAgICAgIGNvbnN0IGNvbmZpZyA9IHtcbiAgICAgICAgICAgICAgICB0b2tlbjogbnVsbCwgLy8gV2lsbCBiZSBzZXQgZHVyaW5nIGJ1aWxkKClcbiAgICAgICAgICAgICAgICB0eXBlOiB0aGlzLnBlbmRpbmcudHlwZSxcbiAgICAgICAgICAgICAgICB2YWx1ZTogdGhpcy5wZW5kaW5nLnZhbHVlLFxuICAgICAgICAgICAgICAgIGZhY3Rvcnk6IHRoaXMucGVuZGluZy5mYWN0b3J5LFxuICAgICAgICAgICAgICAgIGNvbnN0cnVjdG9yOiB0aGlzLnBlbmRpbmcuY29uc3RydWN0b3IsXG4gICAgICAgICAgICAgICAgbGlmZXRpbWU6IHRoaXMuZGVmYXVsdExpZmV0aW1lLFxuICAgICAgICAgICAgICAgIGludGVyZmFjZVR5cGU6IHRva2VuT3JUeXBlTmFtZVxuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHRoaXMuY29uZmlncy5wdXNoKGNvbmZpZyk7XG4gICAgICAgICAgICB0aGlzLnJlZ2lzdHJhdGlvbnMucHVzaChjb25maWcpO1xuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cbiAgICB9XG4gICAgLyoqXG4gICAgICogUmVnaXN0ZXIgYXMgZGVmYXVsdCBpbXBsZW1lbnRhdGlvbiBmb3IgYW4gaW50ZXJmYWNlXG4gICAgICogQ29tYmluZXMgYXMoKSArIGFzRGVmYXVsdCgpXG4gICAgICovXG4gICAgYXNEZWZhdWx0SW50ZXJmYWNlKHR5cGVOYW1lKSB7XG4gICAgICAgIHRoaXMuYXMoXCJUSW50ZXJmYWNlXCIsIHR5cGVOYW1lKTtcbiAgICAgICAgcmV0dXJuIHRoaXMuYXNEZWZhdWx0KCk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJlZ2lzdGVyIGFzIGEga2V5ZWQgaW50ZXJmYWNlIGltcGxlbWVudGF0aW9uXG4gICAgICogQ29tYmluZXMgYXMoKSArIGtleWVkKClcbiAgICAgKi9cbiAgICBhc0tleWVkSW50ZXJmYWNlKGtleSwgdHlwZU5hbWUpIHtcbiAgICAgICAgdGhpcy5hcyhcIlRJbnRlcmZhY2VcIiwgdHlwZU5hbWUpO1xuICAgICAgICByZXR1cm4gdGhpcy5rZXllZChrZXkpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZWdpc3RlciBhcyBtdWx0aXBsZSBpbXBsZW1lbnRlZCBpbnRlcmZhY2VzXG4gICAgICovXG4gICAgYXNJbXBsZW1lbnRlZEludGVyZmFjZXModG9rZW5zKSB7XG4gICAgICAgIGlmICh0b2tlbnMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuICAgICAgICAvLyBJZiB0aGVyZSBhcmUgZXhpc3RpbmcgY29uZmlncyAoZnJvbSBwcmV2aW91cyBhcygpIGNhbGxzKSwgYWRkIHRoZXNlIGFzIGFkZGl0aW9uYWwgaW50ZXJmYWNlc1xuICAgICAgICBpZiAodGhpcy5jb25maWdzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICAgIC8vIEFkZCBhbGwgdG9rZW5zIGFzIGFkZGl0aW9uYWwgaW50ZXJmYWNlcyB0byBleGlzdGluZyBjb25maWdzXG4gICAgICAgICAgICBmb3IgKGNvbnN0IGNvbmZpZyBvZiB0aGlzLmNvbmZpZ3MpIHtcbiAgICAgICAgICAgICAgICBjb25maWcubGlmZXRpbWUgPSAnc2luZ2xldG9uJzsgLy8gYXNJbXBsZW1lbnRlZEludGVyZmFjZXMgZGVmYXVsdHMgdG8gc2luZ2xldG9uXG4gICAgICAgICAgICAgICAgY29uZmlnLmFkZGl0aW9uYWxUb2tlbnMgPSBjb25maWcuYWRkaXRpb25hbFRva2VucyB8fCBbXTtcbiAgICAgICAgICAgICAgICBjb25maWcuYWRkaXRpb25hbFRva2Vucy5wdXNoKC4uLnRva2Vucyk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuICAgICAgICAvLyBObyBleGlzdGluZyBjb25maWdzLCBjcmVhdGUgbmV3IG9uZSB3aXRoIGZpcnN0IHRva2VuXG4gICAgICAgIGNvbnN0IGZpcnN0Q29uZmlnID0ge1xuICAgICAgICAgICAgdG9rZW46IHRva2Vuc1swXSxcbiAgICAgICAgICAgIHR5cGU6IHRoaXMucGVuZGluZy50eXBlLFxuICAgICAgICAgICAgdmFsdWU6IHRoaXMucGVuZGluZy52YWx1ZSxcbiAgICAgICAgICAgIGZhY3Rvcnk6IHRoaXMucGVuZGluZy5mYWN0b3J5LFxuICAgICAgICAgICAgY29uc3RydWN0b3I6IHRoaXMucGVuZGluZy5jb25zdHJ1Y3RvcixcbiAgICAgICAgICAgIGxpZmV0aW1lOiAnc2luZ2xldG9uJ1xuICAgICAgICB9O1xuICAgICAgICB0aGlzLmNvbmZpZ3MucHVzaChmaXJzdENvbmZpZyk7XG4gICAgICAgIHRoaXMucmVnaXN0cmF0aW9ucy5wdXNoKGZpcnN0Q29uZmlnKTtcbiAgICAgICAgLy8gQWRkaXRpb25hbCB0b2tlbnMgcmVmZXJlbmNlIHRoZSBzYW1lIHJlZ2lzdHJhdGlvblxuICAgICAgICBmb3IgKGxldCBpID0gMTsgaSA8IHRva2Vucy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgZmlyc3RDb25maWcuYWRkaXRpb25hbFRva2VucyA9IGZpcnN0Q29uZmlnLmFkZGl0aW9uYWxUb2tlbnMgfHwgW107XG4gICAgICAgICAgICBmaXJzdENvbmZpZy5hZGRpdGlvbmFsVG9rZW5zLnB1c2godG9rZW5zW2ldKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcztcbiAgICB9XG4gICAgLyoqXG4gICAgICogU2V0IHNpbmdsZXRvbiBsaWZldGltZSAob25lIGluc3RhbmNlIGZvciBlbnRpcmUgY29udGFpbmVyKVxuICAgICAqL1xuICAgIHNpbmdsZUluc3RhbmNlKCkge1xuICAgICAgICBmb3IgKGNvbnN0IGNvbmZpZyBvZiB0aGlzLmNvbmZpZ3MpIHtcbiAgICAgICAgICAgIGNvbmZpZy5saWZldGltZSA9ICdzaW5nbGV0b24nO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBTZXQgcGVyLXJlcXVlc3QgbGlmZXRpbWUgKG9uZSBpbnN0YW5jZSBwZXIgcmVzb2x2ZSBjYWxsIHRyZWUpXG4gICAgICovXG4gICAgaW5zdGFuY2VQZXJSZXF1ZXN0KCkge1xuICAgICAgICBmb3IgKGNvbnN0IGNvbmZpZyBvZiB0aGlzLmNvbmZpZ3MpIHtcbiAgICAgICAgICAgIGNvbmZpZy5saWZldGltZSA9ICdwZXItcmVxdWVzdCc7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFNldCB0cmFuc2llbnQgbGlmZXRpbWUgKG5ldyBpbnN0YW5jZSBldmVyeSB0aW1lKVxuICAgICAqIEFsaWFzIGZvciBkZWZhdWx0IGJlaGF2aW9yXG4gICAgICovXG4gICAgaW5zdGFuY2VQZXJEZXBlbmRlbmN5KCkge1xuICAgICAgICBmb3IgKGNvbnN0IGNvbmZpZyBvZiB0aGlzLmNvbmZpZ3MpIHtcbiAgICAgICAgICAgIGNvbmZpZy5saWZldGltZSA9ICd0cmFuc2llbnQnO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBOYW1lIHRoaXMgcmVnaXN0cmF0aW9uIGZvciBuYW1lZCByZXNvbHV0aW9uXG4gICAgICovXG4gICAgbmFtZWQobmFtZSkge1xuICAgICAgICBmb3IgKGNvbnN0IGNvbmZpZyBvZiB0aGlzLmNvbmZpZ3MpIHtcbiAgICAgICAgICAgIGNvbmZpZy5uYW1lID0gbmFtZTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcztcbiAgICB9XG4gICAgLyoqXG4gICAgICogS2V5IHRoaXMgcmVnaXN0cmF0aW9uIGZvciBrZXllZCByZXNvbHV0aW9uXG4gICAgICovXG4gICAga2V5ZWQoa2V5KSB7XG4gICAgICAgIGZvciAoY29uc3QgY29uZmlnIG9mIHRoaXMuY29uZmlncykge1xuICAgICAgICAgICAgY29uZmlnLmtleSA9IGtleTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcztcbiAgICB9XG4gICAgLyoqXG4gICAgICogTWFyayB0aGlzIGFzIGRlZmF1bHQgcmVnaXN0cmF0aW9uXG4gICAgICogRGVmYXVsdCByZWdpc3RyYXRpb25zIGRvbid0IG92ZXJyaWRlIGV4aXN0aW5nIG9uZXNcbiAgICAgKi9cbiAgICBhc0RlZmF1bHQoKSB7XG4gICAgICAgIGZvciAoY29uc3QgY29uZmlnIG9mIHRoaXMuY29uZmlncykge1xuICAgICAgICAgICAgY29uZmlnLmlzRGVmYXVsdCA9IHRydWU7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIE9ubHkgcmVnaXN0ZXIgaWYgdG9rZW4gbm90IGFscmVhZHkgcmVnaXN0ZXJlZFxuICAgICAqL1xuICAgIGlmTm90UmVnaXN0ZXJlZCgpIHtcbiAgICAgICAgZm9yIChjb25zdCBjb25maWcgb2YgdGhpcy5jb25maWdzKSB7XG4gICAgICAgICAgICBjb25maWcuaWZOb3RSZWdpc3RlcmVkID0gdHJ1ZTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcztcbiAgICB9XG4gICAgLyoqXG4gICAgICogU3BlY2lmeSBwYXJhbWV0ZXIgdmFsdWVzIGZvciBjb25zdHJ1Y3RvciAocHJpbWl0aXZlcyBhbmQgY29uc3RhbnRzKVxuICAgICAqIFVzZSB0aGlzIGZvciBub24tREkgcGFyYW1ldGVycyBsaWtlIHN0cmluZ3MsIG51bWJlcnMsIGNvbmZpZyB2YWx1ZXNcbiAgICAgKi9cbiAgICB3aXRoUGFyYW1ldGVycyhwYXJhbWV0ZXJzKSB7XG4gICAgICAgIGZvciAoY29uc3QgY29uZmlnIG9mIHRoaXMuY29uZmlncykge1xuICAgICAgICAgICAgY29uZmlnLnBhcmFtZXRlclZhbHVlcyA9IHBhcmFtZXRlcnM7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEVuYWJsZSBhdXRvbWF0aWMgZGVwZW5kZW5jeSBpbmplY3Rpb24gKGF1dG93aXJpbmcpXG4gICAgICogU3VwcG9ydHMgdGhyZWUgc3RyYXRlZ2llczogcGFyYW1OYW1lIChkZWZhdWx0KSwgbWFwLCBhbmQgY2xhc3NcbiAgICAgKlxuICAgICAqIEBleGFtcGxlXG4gICAgICogYGBgdHNcbiAgICAgKiAvLyBTdHJhdGVneSAxOiBwYXJhbU5hbWUgKGRlZmF1bHQsIHJlcXVpcmVzIG5vbi1taW5pZmllZCBjb2RlIGluIGRldilcbiAgICAgKiBidWlsZGVyLnJlZ2lzdGVyVHlwZShFdmVudEJ1cykuYXM8SUV2ZW50QnVzPigpLmF1dG9XaXJlKClcbiAgICAgKlxuICAgICAqIC8vIFN0cmF0ZWd5IDI6IG1hcCAobWluaWZ5LXNhZmUsIGV4cGxpY2l0KVxuICAgICAqIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKEV2ZW50QnVzKS5hczxJRXZlbnRCdXM+KCkuYXV0b1dpcmUoe1xuICAgICAqICAgbWFwOiB7XG4gICAgICogICAgIGxvZ2dlcjogKGMpID0+IGMucmVzb2x2ZVR5cGU8SUxvZ2dlcj4oKVxuICAgICAqICAgfVxuICAgICAqIH0pXG4gICAgICpcbiAgICAgKiAvLyBTdHJhdGVneSAzOiBjbGFzcyAocmVxdWlyZXMgYnVpbGQtdGltZSBjb2RlZ2VuKVxuICAgICAqIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKEV2ZW50QnVzKS5hczxJRXZlbnRCdXM+KCkuYXV0b1dpcmUoeyBieTogJ2NsYXNzJyB9KVxuICAgICAqIGBgYFxuICAgICAqL1xuICAgIGF1dG9XaXJlKG9wdGlvbnMpIHtcbiAgICAgICAgZm9yIChjb25zdCBjb25maWcgb2YgdGhpcy5jb25maWdzKSB7XG4gICAgICAgICAgICBjb25maWcuYXV0b3dpcmVPcHRpb25zID0gb3B0aW9ucyB8fCB7IGJ5OiAncGFyYW1OYW1lJywgc3RyaWN0OiBmYWxzZSB9O1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzO1xuICAgIH1cbn1cbi8qKlxuICogRmx1ZW50IGJ1aWxkZXIgZm9yIENvbnRhaW5lciBjb25maWd1cmF0aW9uXG4gKi9cbmV4cG9ydCBjbGFzcyBCdWlsZGVyIHtcbiAgICBjb25zdHJ1Y3RvcihiYXNlQ29udGFpbmVyKSB7XG4gICAgICAgIHRoaXMuYmFzZUNvbnRhaW5lciA9IGJhc2VDb250YWluZXI7XG4gICAgICAgIHRoaXMucmVnaXN0cmF0aW9ucyA9IFtdO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZWdpc3RlciBhIGNsYXNzIGNvbnN0cnVjdG9yXG4gICAgICovXG4gICAgcmVnaXN0ZXJUeXBlKGNvbnN0cnVjdG9yKSB7XG4gICAgICAgIGNvbnN0IHBlbmRpbmcgPSB7XG4gICAgICAgICAgICB0eXBlOiAndHlwZScsXG4gICAgICAgICAgICB2YWx1ZTogbnVsbCxcbiAgICAgICAgICAgIGNvbnN0cnVjdG9yXG4gICAgICAgIH07XG4gICAgICAgIHJldHVybiBuZXcgUmVnaXN0cmF0aW9uQnVpbGRlcihwZW5kaW5nLCB0aGlzLnJlZ2lzdHJhdGlvbnMpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZWdpc3RlciBhIHByZS1jcmVhdGVkIGluc3RhbmNlXG4gICAgICovXG4gICAgcmVnaXN0ZXJJbnN0YW5jZShpbnN0YW5jZSkge1xuICAgICAgICBjb25zdCBwZW5kaW5nID0ge1xuICAgICAgICAgICAgdHlwZTogJ2luc3RhbmNlJyxcbiAgICAgICAgICAgIHZhbHVlOiBpbnN0YW5jZSxcbiAgICAgICAgICAgIGNvbnN0cnVjdG9yOiB1bmRlZmluZWRcbiAgICAgICAgfTtcbiAgICAgICAgcmV0dXJuIG5ldyBSZWdpc3RyYXRpb25CdWlsZGVyKHBlbmRpbmcsIHRoaXMucmVnaXN0cmF0aW9ucyk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJlZ2lzdGVyIGEgZmFjdG9yeSBmdW5jdGlvblxuICAgICAqL1xuICAgIHJlZ2lzdGVyKGZhY3RvcnkpIHtcbiAgICAgICAgY29uc3QgcGVuZGluZyA9IHtcbiAgICAgICAgICAgIHR5cGU6ICdmYWN0b3J5JyxcbiAgICAgICAgICAgIHZhbHVlOiBudWxsLFxuICAgICAgICAgICAgZmFjdG9yeSxcbiAgICAgICAgICAgIGNvbnN0cnVjdG9yOiB1bmRlZmluZWRcbiAgICAgICAgfTtcbiAgICAgICAgcmV0dXJuIG5ldyBSZWdpc3RyYXRpb25CdWlsZGVyKHBlbmRpbmcsIHRoaXMucmVnaXN0cmF0aW9ucyk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJlZ2lzdGVyIGEgbW9kdWxlIChmdW5jdGlvbiB0aGF0IGFkZHMgbXVsdGlwbGUgcmVnaXN0cmF0aW9ucylcbiAgICAgKi9cbiAgICBtb2R1bGUobW9kdWxlRnVuYykge1xuICAgICAgICBtb2R1bGVGdW5jKHRoaXMpO1xuICAgICAgICByZXR1cm4gdGhpcztcbiAgICB9XG4gICAgLyoqXG4gICAgICogUmVzb2x2ZSBpbnRlcmZhY2UgdHlwZSBuYW1lcyB0byB0b2tlbnNcbiAgICAgKiBAaW50ZXJuYWxcbiAgICAgKi9cbiAgICByZXNvbHZlSW50ZXJmYWNlVG9rZW5zKGNvbnRhaW5lcikge1xuICAgICAgICBmb3IgKGNvbnN0IGNvbmZpZyBvZiB0aGlzLnJlZ2lzdHJhdGlvbnMpIHtcbiAgICAgICAgICAgIGlmIChjb25maWcuaW50ZXJmYWNlVHlwZSAhPT0gdW5kZWZpbmVkICYmICFjb25maWcudG9rZW4pIHtcbiAgICAgICAgICAgICAgICBjb25maWcudG9rZW4gPSBjb250YWluZXIuaW50ZXJmYWNlVG9rZW4oY29uZmlnLmludGVyZmFjZVR5cGUpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuICAgIC8qKlxuICAgICAqIElkZW50aWZ5IHRva2VucyB0aGF0IGhhdmUgbm9uLWRlZmF1bHQgcmVnaXN0cmF0aW9uc1xuICAgICAqIEBpbnRlcm5hbFxuICAgICAqL1xuICAgIGlkZW50aWZ5Tm9uRGVmYXVsdFRva2VucygpIHtcbiAgICAgICAgY29uc3QgdG9rZW5zV2l0aE5vbkRlZmF1bHRzID0gbmV3IFNldCgpO1xuICAgICAgICBmb3IgKGNvbnN0IGNvbmZpZyBvZiB0aGlzLnJlZ2lzdHJhdGlvbnMpIHtcbiAgICAgICAgICAgIGlmICghY29uZmlnLmlzRGVmYXVsdCAmJiAhY29uZmlnLm5hbWUgJiYgY29uZmlnLmtleSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICAgICAgdG9rZW5zV2l0aE5vbkRlZmF1bHRzLmFkZChjb25maWcudG9rZW4pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0b2tlbnNXaXRoTm9uRGVmYXVsdHM7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIENoZWNrIGlmIHJlZ2lzdHJhdGlvbiBzaG91bGQgYmUgc2tpcHBlZFxuICAgICAqIEBpbnRlcm5hbFxuICAgICAqL1xuICAgIHNob3VsZFNraXBSZWdpc3RyYXRpb24oY29uZmlnLCB0b2tlbnNXaXRoTm9uRGVmYXVsdHMsIHJlZ2lzdGVyZWRUb2tlbnMpIHtcbiAgICAgICAgLy8gU2tpcCBkZWZhdWx0IHJlZ2lzdHJhdGlvbnMgaWYgdGhlcmUncyBhIG5vbi1kZWZhdWx0IGZvciB0aGUgc2FtZSB0b2tlblxuICAgICAgICBpZiAoY29uZmlnLmlzRGVmYXVsdCAmJiAhY29uZmlnLm5hbWUgJiYgY29uZmlnLmtleSA9PT0gdW5kZWZpbmVkICYmIHRva2Vuc1dpdGhOb25EZWZhdWx0cy5oYXMoY29uZmlnLnRva2VuKSkge1xuICAgICAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICAgIH1cbiAgICAgICAgLy8gSGFuZGxlIGlmTm90UmVnaXN0ZXJlZFxuICAgICAgICBpZiAoY29uZmlnLmlmTm90UmVnaXN0ZXJlZCAmJiByZWdpc3RlcmVkVG9rZW5zLmhhcyhjb25maWcudG9rZW4pKSB7XG4gICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgfVxuICAgICAgICAvLyBIYW5kbGUgYXNEZWZhdWx0XG4gICAgICAgIGlmIChjb25maWcuaXNEZWZhdWx0ICYmIHJlZ2lzdGVyZWRUb2tlbnMuaGFzKGNvbmZpZy50b2tlbikpIHtcbiAgICAgICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogQ3JlYXRlIGJpbmRpbmcgdG9rZW4gZm9yIHJlZ2lzdHJhdGlvbiAobmFtZWQsIGtleWVkLCBvciBtdWx0aSlcbiAgICAgKiBAaW50ZXJuYWxcbiAgICAgKi9cbiAgICBjcmVhdGVCaW5kaW5nVG9rZW4oY29uZmlnLCBuYW1lZFJlZ2lzdHJhdGlvbnMsIGtleWVkUmVnaXN0cmF0aW9ucywgbXVsdGlSZWdpc3RyYXRpb25zKSB7XG4gICAgICAgIGlmIChjb25maWcubmFtZSkge1xuICAgICAgICAgICAgLy8gTmFtZWQgcmVnaXN0cmF0aW9uIGdldHMgdW5pcXVlIHRva2VuXG4gICAgICAgICAgICBjb25zdCBiaW5kaW5nVG9rZW4gPSBUb2tlbihgX19uYW1lZF8ke2NvbmZpZy5uYW1lfWApO1xuICAgICAgICAgICAgbmFtZWRSZWdpc3RyYXRpb25zLnNldChjb25maWcubmFtZSwgeyAuLi5jb25maWcsIHRva2VuOiBiaW5kaW5nVG9rZW4gfSk7XG4gICAgICAgICAgICByZXR1cm4gYmluZGluZ1Rva2VuO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKGNvbmZpZy5rZXkgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgLy8gS2V5ZWQgcmVnaXN0cmF0aW9uIGdldHMgdW5pcXVlIHRva2VuXG4gICAgICAgICAgICBjb25zdCBrZXlTdHIgPSB0eXBlb2YgY29uZmlnLmtleSA9PT0gJ3N5bWJvbCcgPyBjb25maWcua2V5LnRvU3RyaW5nKCkgOiBjb25maWcua2V5O1xuICAgICAgICAgICAgY29uc3QgYmluZGluZ1Rva2VuID0gVG9rZW4oYF9fa2V5ZWRfJHtrZXlTdHJ9YCk7XG4gICAgICAgICAgICBrZXllZFJlZ2lzdHJhdGlvbnMuc2V0KGNvbmZpZy5rZXksIHsgLi4uY29uZmlnLCB0b2tlbjogYmluZGluZ1Rva2VuIH0pO1xuICAgICAgICAgICAgcmV0dXJuIGJpbmRpbmdUb2tlbjtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIC8vIE11bHRpLXJlZ2lzdHJhdGlvbiBoYW5kbGluZ1xuICAgICAgICAgICAgaWYgKG11bHRpUmVnaXN0cmF0aW9ucy5oYXMoY29uZmlnLnRva2VuKSkge1xuICAgICAgICAgICAgICAgIC8vIFN1YnNlcXVlbnQgcmVnaXN0cmF0aW9uIGZvciB0aGlzIHRva2VuXG4gICAgICAgICAgICAgICAgY29uc3QgYmluZGluZ1Rva2VuID0gVG9rZW4oYF9fbXVsdGlfJHtjb25maWcudG9rZW4udG9TdHJpbmcoKX1fJHttdWx0aVJlZ2lzdHJhdGlvbnMuZ2V0KGNvbmZpZy50b2tlbikubGVuZ3RofWApO1xuICAgICAgICAgICAgICAgIG11bHRpUmVnaXN0cmF0aW9ucy5nZXQoY29uZmlnLnRva2VuKS5wdXNoKGJpbmRpbmdUb2tlbik7XG4gICAgICAgICAgICAgICAgcmV0dXJuIGJpbmRpbmdUb2tlbjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIC8vIEZpcnN0IHJlZ2lzdHJhdGlvbiBmb3IgdGhpcyB0b2tlbiwgdXNlIHRoZSBvcmlnaW5hbCB0b2tlblxuICAgICAgICAgICAgICAgIG11bHRpUmVnaXN0cmF0aW9ucy5zZXQoY29uZmlnLnRva2VuLCBbY29uZmlnLnRva2VuXSk7XG4gICAgICAgICAgICAgICAgcmV0dXJuIGNvbmZpZy50b2tlbjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZWdpc3RlciBhZGRpdGlvbmFsIGludGVyZmFjZXMgZm9yIGEgY29uZmlnXG4gICAgICogQGludGVybmFsXG4gICAgICovXG4gICAgcmVnaXN0ZXJBZGRpdGlvbmFsSW50ZXJmYWNlcyhjb250YWluZXIsIGNvbmZpZywgYmluZGluZ1Rva2VuLCByZWdpc3RlcmVkVG9rZW5zKSB7XG4gICAgICAgIGlmIChjb25maWcuYWRkaXRpb25hbFRva2Vucykge1xuICAgICAgICAgICAgZm9yIChjb25zdCBhZGRpdGlvbmFsVG9rZW4gb2YgY29uZmlnLmFkZGl0aW9uYWxUb2tlbnMpIHtcbiAgICAgICAgICAgICAgICAvLyBDcmVhdGUgYSBmYWN0b3J5IHRoYXQgcmVzb2x2ZXMgdGhlIGJpbmRpbmcgdG9rZW5cbiAgICAgICAgICAgICAgICBjb250YWluZXIuYmluZEZhY3RvcnkoYWRkaXRpb25hbFRva2VuLCAoYykgPT4gYy5yZXNvbHZlKGJpbmRpbmdUb2tlbiksIHsgbGlmZXRpbWU6IGNvbmZpZy5saWZldGltZSB9KTtcbiAgICAgICAgICAgICAgICByZWdpc3RlcmVkVG9rZW5zLmFkZChhZGRpdGlvbmFsVG9rZW4pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEJ1aWxkIHRoZSBjb250YWluZXIgd2l0aCBhbGwgcmVnaXN0ZXJlZCBiaW5kaW5nc1xuICAgICAqL1xuICAgIGJ1aWxkKCkge1xuICAgICAgICAvLyBDcmVhdGUgbmV3IGNvbnRhaW5lciBpbmhlcml0aW5nIGZyb20gYmFzZVxuICAgICAgICBjb25zdCBjb250YWluZXIgPSB0aGlzLmJhc2VDb250YWluZXIuY3JlYXRlQ2hpbGQoKTtcbiAgICAgICAgLy8gUHJlLXByb2Nlc3M6IHJlc29sdmUgaW50ZXJmYWNlIHR5cGVzIHRvIHRva2Vuc1xuICAgICAgICB0aGlzLnJlc29sdmVJbnRlcmZhY2VUb2tlbnMoY29udGFpbmVyKTtcbiAgICAgICAgLy8gVHJhY2sgd2hhdCdzIGJlZW4gcmVnaXN0ZXJlZCBmb3IgaWZOb3RSZWdpc3RlcmVkIGNoZWNrc1xuICAgICAgICBjb25zdCByZWdpc3RlcmVkVG9rZW5zID0gbmV3IFNldCgpO1xuICAgICAgICBjb25zdCBuYW1lZFJlZ2lzdHJhdGlvbnMgPSBuZXcgTWFwKCk7XG4gICAgICAgIGNvbnN0IGtleWVkUmVnaXN0cmF0aW9ucyA9IG5ldyBNYXAoKTtcbiAgICAgICAgY29uc3QgbXVsdGlSZWdpc3RyYXRpb25zID0gbmV3IE1hcCgpO1xuICAgICAgICAvLyBQcmUtcHJvY2VzczogaWRlbnRpZnkgdG9rZW5zIHRoYXQgaGF2ZSBub24tZGVmYXVsdCByZWdpc3RyYXRpb25zXG4gICAgICAgIGNvbnN0IHRva2Vuc1dpdGhOb25EZWZhdWx0cyA9IHRoaXMuaWRlbnRpZnlOb25EZWZhdWx0VG9rZW5zKCk7XG4gICAgICAgIGZvciAoY29uc3QgY29uZmlnIG9mIHRoaXMucmVnaXN0cmF0aW9ucykge1xuICAgICAgICAgICAgLy8gQ2hlY2sgaWYgcmVnaXN0cmF0aW9uIHNob3VsZCBiZSBza2lwcGVkXG4gICAgICAgICAgICBpZiAodGhpcy5zaG91bGRTa2lwUmVnaXN0cmF0aW9uKGNvbmZpZywgdG9rZW5zV2l0aE5vbkRlZmF1bHRzLCByZWdpc3RlcmVkVG9rZW5zKSkge1xuICAgICAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gQ3JlYXRlIGJpbmRpbmcgdG9rZW4gKG5hbWVkLCBrZXllZCwgb3IgbXVsdGkpXG4gICAgICAgICAgICBjb25zdCBiaW5kaW5nVG9rZW4gPSB0aGlzLmNyZWF0ZUJpbmRpbmdUb2tlbihjb25maWcsIG5hbWVkUmVnaXN0cmF0aW9ucywga2V5ZWRSZWdpc3RyYXRpb25zLCBtdWx0aVJlZ2lzdHJhdGlvbnMpO1xuICAgICAgICAgICAgLy8gQXBwbHkgcmVnaXN0cmF0aW9uIHRvIGNvbnRhaW5lciB1c2luZyB0aGUgYmluZGluZyB0b2tlblxuICAgICAgICAgICAgdGhpcy5hcHBseVJlZ2lzdHJhdGlvbihjb250YWluZXIsIHsgLi4uY29uZmlnLCB0b2tlbjogYmluZGluZ1Rva2VuIH0pO1xuICAgICAgICAgICAgLy8gTWFyayBvcmlnaW5hbCB0b2tlbiBhcyByZWdpc3RlcmVkXG4gICAgICAgICAgICByZWdpc3RlcmVkVG9rZW5zLmFkZChjb25maWcudG9rZW4pO1xuICAgICAgICAgICAgLy8gUmVnaXN0ZXIgYWRkaXRpb25hbCBpbnRlcmZhY2VzXG4gICAgICAgICAgICB0aGlzLnJlZ2lzdGVyQWRkaXRpb25hbEludGVyZmFjZXMoY29udGFpbmVyLCBjb25maWcsIGJpbmRpbmdUb2tlbiwgcmVnaXN0ZXJlZFRva2Vucyk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gQXR0YWNoIG1ldGFkYXRhIGZvciBuYW1lZC9rZXllZCByZXNvbHV0aW9uXG4gICAgICAgIDtcbiAgICAgICAgY29udGFpbmVyLl9fbmFtZWRSZWdpc3RyYXRpb25zID0gbmFtZWRSZWdpc3RyYXRpb25zO1xuICAgICAgICBjb250YWluZXIuX19rZXllZFJlZ2lzdHJhdGlvbnMgPSBrZXllZFJlZ2lzdHJhdGlvbnM7XG4gICAgICAgIGNvbnRhaW5lci5fX211bHRpUmVnaXN0cmF0aW9ucyA9IG11bHRpUmVnaXN0cmF0aW9ucztcbiAgICAgICAgcmV0dXJuIGNvbnRhaW5lcjtcbiAgICB9XG4gICAgLyoqXG4gICAgICogQW5hbHl6ZSBjb25zdHJ1Y3RvciB0byBkZXRlY3QgZGVwZW5kZW5jaWVzXG4gICAgICogQGludGVybmFsXG4gICAgICovXG4gICAgYW5hbHl6ZUNvbnN0cnVjdG9yKGNvbnN0cnVjdG9yKSB7XG4gICAgICAgIGNvbnN0IGNvbnN0cnVjdG9yU3RyID0gY29uc3RydWN0b3IudG9TdHJpbmcoKTtcbiAgICAgICAgY29uc3QgaGFzRGVwZW5kZW5jaWVzID0gL2NvbnN0cnVjdG9yXFxzKlxcKFteKV0rXFwpLy50ZXN0KGNvbnN0cnVjdG9yU3RyKTtcbiAgICAgICAgcmV0dXJuIHsgaGFzRGVwZW5kZW5jaWVzIH07XG4gICAgfVxuICAgIC8qKlxuICAgICAqIENyZWF0ZSBvcHRpbWl6ZWQgZmFjdG9yeSBmb3IgemVyby1kZXBlbmRlbmN5IGNvbnN0cnVjdG9yc1xuICAgICAqIEBpbnRlcm5hbFxuICAgICAqL1xuICAgIGNyZWF0ZU9wdGltaXplZEZhY3RvcnkoY29udGFpbmVyLCBjb25maWcsIG9wdGlvbnMpIHtcbiAgICAgICAgaWYgKGNvbmZpZy5saWZldGltZSA9PT0gJ3NpbmdsZXRvbicpIHtcbiAgICAgICAgICAgIC8vIFNpbmdsZXRvbjogQ3JlYXRlIGluc3RhbmNlIGRpcmVjdGx5IChmYXN0ZXN0IHBhdGggLSBubyBmYWN0b3J5IG92ZXJoZWFkKVxuICAgICAgICAgICAgY29uc3QgaW5zdGFuY2UgPSBuZXcgY29uZmlnLmNvbnN0cnVjdG9yKCk7XG4gICAgICAgICAgICBjb250YWluZXIuYmluZFZhbHVlKGNvbmZpZy50b2tlbiwgaW5zdGFuY2UpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKGNvbmZpZy5saWZldGltZSA9PT0gJ3RyYW5zaWVudCcpIHtcbiAgICAgICAgICAgIC8vIFRyYW5zaWVudCBGYXN0IFBhdGg6IFJlZ2lzdGVyIGluIGZhc3QgdHJhbnNpZW50IGNhY2hlXG4gICAgICAgICAgICBjb25zdCBjdG9yID0gY29uZmlnLmNvbnN0cnVjdG9yO1xuICAgICAgICAgICAgY29uc3QgZmFzdEZhY3RvcnkgPSAoKSA9PiBuZXcgY3RvcigpO1xuICAgICAgICAgICAgY29udGFpbmVyLmZhc3RUcmFuc2llbnRDYWNoZS5zZXQoY29uZmlnLnRva2VuLCBmYXN0RmFjdG9yeSk7XG4gICAgICAgICAgICBjb250YWluZXIuYmluZEZhY3RvcnkoY29uZmlnLnRva2VuLCBmYXN0RmFjdG9yeSwgb3B0aW9ucyk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAvLyBQZXItcmVxdWVzdDogVXNlIHNpbXBsZSBmYWN0b3J5IHdpdGhvdXQgYXV0b3dpcmUgb3ZlcmhlYWRcbiAgICAgICAgICAgIGNvbnN0IGZhY3RvcnkgPSAoKSA9PiBuZXcgY29uZmlnLmNvbnN0cnVjdG9yKCk7XG4gICAgICAgICAgICBjb250YWluZXIuYmluZEZhY3RvcnkoY29uZmlnLnRva2VuLCBmYWN0b3J5LCBvcHRpb25zKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBDcmVhdGUgYXV0b3dpcmUgZmFjdG9yeVxuICAgICAqIEBpbnRlcm5hbFxuICAgICAqL1xuICAgIGNyZWF0ZUF1dG9XaXJlRmFjdG9yeShjb250YWluZXIsIGNvbmZpZywgb3B0aW9ucykge1xuICAgICAgICBjb25zdCBmYWN0b3J5ID0gKGMpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHJlc29sdmVkRGVwcyA9IGF1dG93aXJlKGNvbmZpZy5jb25zdHJ1Y3RvciwgYywgY29uZmlnLmF1dG93aXJlT3B0aW9ucyk7XG4gICAgICAgICAgICByZXR1cm4gbmV3IGNvbmZpZy5jb25zdHJ1Y3RvciguLi5yZXNvbHZlZERlcHMpO1xuICAgICAgICB9O1xuICAgICAgICBjb250YWluZXIuYmluZEZhY3RvcnkoY29uZmlnLnRva2VuLCBmYWN0b3J5LCBvcHRpb25zKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogQ3JlYXRlIHdpdGhQYXJhbWV0ZXJzIGZhY3RvcnlcbiAgICAgKiBAaW50ZXJuYWxcbiAgICAgKi9cbiAgICBjcmVhdGVQYXJhbWV0ZXJGYWN0b3J5KGNvbnRhaW5lciwgY29uZmlnLCBvcHRpb25zKSB7XG4gICAgICAgIGNvbnN0IGZhY3RvcnkgPSAoKSA9PiB7XG4gICAgICAgICAgICBjb25zdCB2YWx1ZXMgPSBPYmplY3QudmFsdWVzKGNvbmZpZy5wYXJhbWV0ZXJWYWx1ZXMpO1xuICAgICAgICAgICAgcmV0dXJuIG5ldyBjb25maWcuY29uc3RydWN0b3IoLi4udmFsdWVzKTtcbiAgICAgICAgfTtcbiAgICAgICAgY29udGFpbmVyLmJpbmRGYWN0b3J5KGNvbmZpZy50b2tlbiwgZmFjdG9yeSwgb3B0aW9ucyk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEFwcGx5IHR5cGUgcmVnaXN0cmF0aW9uIChjbGFzcyBjb25zdHJ1Y3RvcilcbiAgICAgKiBAaW50ZXJuYWxcbiAgICAgKi9cbiAgICBhcHBseVR5cGVSZWdpc3RyYXRpb24oY29udGFpbmVyLCBjb25maWcsIG9wdGlvbnMpIHtcbiAgICAgICAgY29uc3QgeyBoYXNEZXBlbmRlbmNpZXMgfSA9IHRoaXMuYW5hbHl6ZUNvbnN0cnVjdG9yKGNvbmZpZy5jb25zdHJ1Y3Rvcik7XG4gICAgICAgIC8vIEZhc3QgcGF0aDogTm8gZGVwZW5kZW5jaWVzIGFuZCBubyBzcGVjaWFsIGNvbmZpZ1xuICAgICAgICBpZiAoIWhhc0RlcGVuZGVuY2llcyAmJiAhY29uZmlnLmF1dG93aXJlT3B0aW9ucyAmJiAhY29uZmlnLnBhcmFtZXRlclZhbHVlcykge1xuICAgICAgICAgICAgdGhpcy5jcmVhdGVPcHRpbWl6ZWRGYWN0b3J5KGNvbnRhaW5lciwgY29uZmlnLCBvcHRpb25zKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICAvLyBBdXRvV2lyZSBwYXRoXG4gICAgICAgIGlmIChjb25maWcuYXV0b3dpcmVPcHRpb25zKSB7XG4gICAgICAgICAgICB0aGlzLmNyZWF0ZUF1dG9XaXJlRmFjdG9yeShjb250YWluZXIsIGNvbmZpZywgb3B0aW9ucyk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgLy8gd2l0aFBhcmFtZXRlcnMgcGF0aFxuICAgICAgICBpZiAoY29uZmlnLnBhcmFtZXRlclZhbHVlcykge1xuICAgICAgICAgICAgdGhpcy5jcmVhdGVQYXJhbWV0ZXJGYWN0b3J5KGNvbnRhaW5lciwgY29uZmlnLCBvcHRpb25zKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICAvLyBFcnJvcjogQ29uc3RydWN0b3IgaGFzIGRlcGVuZGVuY2llcyBidXQgbm8gY29uZmlnXG4gICAgICAgIGlmIChoYXNEZXBlbmRlbmNpZXMpIHtcbiAgICAgICAgICAgIGNvbnN0IGNsYXNzTmFtZSA9IGNvbmZpZy5jb25zdHJ1Y3Rvci5uYW1lIHx8ICdVbm5hbWVkQ2xhc3MnO1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBTZXJ2aWNlIFwiJHtjbGFzc05hbWV9XCIgaGFzIGNvbnN0cnVjdG9yIGRlcGVuZGVuY2llcyBidXQgbm8gYXV0b3dpcmluZyBjb25maWd1cmF0aW9uLlxcblxcbmAgK1xuICAgICAgICAgICAgICAgIGBTb2x1dGlvbnM6XFxuYCArXG4gICAgICAgICAgICAgICAgYCAgMS4gXHUyQjUwIFVzZSB0aGUgTm92YURJIHRyYW5zZm9ybWVyIChyZWNvbW1lbmRlZCk6XFxuYCArXG4gICAgICAgICAgICAgICAgYCAgICAgLSBBZGQgXCJAbm92YWRpL2NvcmUvdW5wbHVnaW5cIiB0byB5b3VyIGJ1aWxkIGNvbmZpZ1xcbmAgK1xuICAgICAgICAgICAgICAgIGAgICAgIC0gVHJhbnNmb3JtZXIgYXV0b21hdGljYWxseSBnZW5lcmF0ZXMgLmF1dG9XaXJlKCkgZm9yIGFsbCBkZXBlbmRlbmNpZXNcXG5cXG5gICtcbiAgICAgICAgICAgICAgICBgICAyLiBBZGQgbWFudWFsIGF1dG93aXJpbmc6XFxuYCArXG4gICAgICAgICAgICAgICAgYCAgICAgLmF1dG9XaXJlKHsgbWFwOiB7IC8qIHBhcmFtOiByZXNvbHZlciAqLyB9IH0pXFxuXFxuYCArXG4gICAgICAgICAgICAgICAgYCAgMy4gVXNlIGEgZmFjdG9yeSBmdW5jdGlvbjpcXG5gICtcbiAgICAgICAgICAgICAgICBgICAgICAucmVnaXN0ZXIoKGMpID0+IG5ldyAke2NsYXNzTmFtZX0oLi4uKSlcXG5cXG5gICtcbiAgICAgICAgICAgICAgICBgU2VlIGRvY3M6IGh0dHBzOi8vZ2l0aHViLmNvbS9qYW51czAwNy9Ob3ZhREkjYXV0b3dpcmVgKTtcbiAgICAgICAgfVxuICAgICAgICAvLyBObyBkZXBlbmRlbmNpZXMgLSBjcmVhdGUgc2ltcGxlIGZhY3RvcnlcbiAgICAgICAgY29uc3QgZmFjdG9yeSA9ICgpID0+IG5ldyBjb25maWcuY29uc3RydWN0b3IoKTtcbiAgICAgICAgY29udGFpbmVyLmJpbmRGYWN0b3J5KGNvbmZpZy50b2tlbiwgZmFjdG9yeSwgb3B0aW9ucyk7XG4gICAgfVxuICAgIGFwcGx5UmVnaXN0cmF0aW9uKGNvbnRhaW5lciwgY29uZmlnKSB7XG4gICAgICAgIGNvbnN0IG9wdGlvbnMgPSB7IGxpZmV0aW1lOiBjb25maWcubGlmZXRpbWUgfTtcbiAgICAgICAgc3dpdGNoIChjb25maWcudHlwZSkge1xuICAgICAgICAgICAgY2FzZSAnaW5zdGFuY2UnOlxuICAgICAgICAgICAgICAgIGNvbnRhaW5lci5iaW5kVmFsdWUoY29uZmlnLnRva2VuLCBjb25maWcudmFsdWUpO1xuICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgY2FzZSAnZmFjdG9yeSc6XG4gICAgICAgICAgICAgICAgY29udGFpbmVyLmJpbmRGYWN0b3J5KGNvbmZpZy50b2tlbiwgY29uZmlnLmZhY3RvcnksIG9wdGlvbnMpO1xuICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgY2FzZSAndHlwZSc6XG4gICAgICAgICAgICAgICAgdGhpcy5hcHBseVR5cGVSZWdpc3RyYXRpb24oY29udGFpbmVyLCBjb25maWcsIG9wdGlvbnMpO1xuICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG4gICAgfVxufVxuIiwgIi8qKlxuICogQ29yZSBkZXBlbmRlbmN5IGluamVjdGlvbiBjb250YWluZXIgZm9yIE5vdmFESVxuICovXG5pbXBvcnQgeyBUb2tlbiB9IGZyb20gJy4vdG9rZW4uanMnO1xuaW1wb3J0IHsgQmluZGluZ05vdEZvdW5kRXJyb3IsIENpcmN1bGFyRGVwZW5kZW5jeUVycm9yIH0gZnJvbSAnLi9lcnJvcnMuanMnO1xuaW1wb3J0IHsgQnVpbGRlciB9IGZyb20gJy4vYnVpbGRlci5qcyc7XG5mdW5jdGlvbiBpc0Rpc3Bvc2FibGUob2JqKSB7XG4gICAgcmV0dXJuIG9iaiAmJiB0eXBlb2Ygb2JqLmRpc3Bvc2UgPT09ICdmdW5jdGlvbic7XG59XG4vKipcbiAqIFJlc29sdXRpb24gY29udGV4dCB0cmFja3MgdGhlIGN1cnJlbnQgZGVwZW5kZW5jeSByZXNvbHV0aW9uIHBhdGhcbiAqIGZvciBjaXJjdWxhciBkZXBlbmRlbmN5IGRldGVjdGlvbiBhbmQgcGVyLXJlcXVlc3Qgc2NvcGluZ1xuICovXG5jbGFzcyBSZXNvbHV0aW9uQ29udGV4dCB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMucmVzb2x2aW5nU3RhY2sgPSBuZXcgU2V0KCk7XG4gICAgICAgIHRoaXMucGVyUmVxdWVzdENhY2hlID0gbmV3IE1hcCgpO1xuICAgIH1cbiAgICBpc1Jlc29sdmluZyh0b2tlbikge1xuICAgICAgICByZXR1cm4gdGhpcy5yZXNvbHZpbmdTdGFjay5oYXModG9rZW4pO1xuICAgIH1cbiAgICBlbnRlclJlc29sdmUodG9rZW4pIHtcbiAgICAgICAgdGhpcy5yZXNvbHZpbmdTdGFjay5hZGQodG9rZW4pO1xuICAgICAgICAvLyBQZXJmb3JtYW5jZTogRG9uJ3QgYnVpbGQgcGF0aCB1bmxlc3Mgd2UgbmVlZCBpdCAob25seSB1c2VkIGluIGVycm9yIG1lc3NhZ2VzKVxuICAgICAgICAvLyBUaGlzIGF2b2lkcyBleHBlbnNpdmUgdG9rZW4udG9TdHJpbmcoKSBjYWxscyBvbiBldmVyeSByZXNvbHZlXG4gICAgfVxuICAgIGV4aXRSZXNvbHZlKHRva2VuKSB7XG4gICAgICAgIHRoaXMucmVzb2x2aW5nU3RhY2suZGVsZXRlKHRva2VuKTtcbiAgICAgICAgLy8gUGVyZm9ybWFuY2U6IENsZWFyIGxhenkgcGF0aCBjYWNoZSB3aGVuIGV4aXRpbmdcbiAgICAgICAgdGhpcy5wYXRoID0gdW5kZWZpbmVkO1xuICAgIH1cbiAgICBnZXRQYXRoKCkge1xuICAgICAgICAvLyBQZXJmb3JtYW5jZTogQnVpbGQgcGF0aCBvbi1kZW1hbmQgb25seSB3aGVuIG5lZWRlZCAodHlwaWNhbGx5IGZvciBlcnJvciBtZXNzYWdlcylcbiAgICAgICAgaWYgKCF0aGlzLnBhdGgpIHtcbiAgICAgICAgICAgIHRoaXMucGF0aCA9IEFycmF5LmZyb20odGhpcy5yZXNvbHZpbmdTdGFjaykubWFwKHQgPT4gdC50b1N0cmluZygpKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gWy4uLnRoaXMucGF0aF07XG4gICAgfVxuICAgIGNhY2hlUGVyUmVxdWVzdCh0b2tlbiwgaW5zdGFuY2UpIHtcbiAgICAgICAgdGhpcy5wZXJSZXF1ZXN0Q2FjaGUuc2V0KHRva2VuLCBpbnN0YW5jZSk7XG4gICAgfVxuICAgIGdldFBlclJlcXVlc3QodG9rZW4pIHtcbiAgICAgICAgcmV0dXJuIHRoaXMucGVyUmVxdWVzdENhY2hlLmdldCh0b2tlbik7XG4gICAgfVxuICAgIGhhc1BlclJlcXVlc3QodG9rZW4pIHtcbiAgICAgICAgcmV0dXJuIHRoaXMucGVyUmVxdWVzdENhY2hlLmhhcyh0b2tlbik7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJlc2V0IGNvbnRleHQgZm9yIHJldXNlIGluIG9iamVjdCBwb29sXG4gICAgICogUGVyZm9ybWFuY2U6IFJldXNpbmcgY29udGV4dHMgYXZvaWRzIGhlYXAgYWxsb2NhdGlvbnNcbiAgICAgKi9cbiAgICByZXNldCgpIHtcbiAgICAgICAgdGhpcy5yZXNvbHZpbmdTdGFjay5jbGVhcigpO1xuICAgICAgICB0aGlzLnBlclJlcXVlc3RDYWNoZS5jbGVhcigpO1xuICAgICAgICB0aGlzLnBhdGggPSB1bmRlZmluZWQ7XG4gICAgfVxufVxuLyoqXG4gKiBPYmplY3QgcG9vbCBmb3IgUmVzb2x1dGlvbkNvbnRleHQgaW5zdGFuY2VzXG4gKiBQZXJmb3JtYW5jZTogUmV1c2luZyBjb250ZXh0cyByZWR1Y2VzIGhlYXAgYWxsb2NhdGlvbnMgYW5kIEdDIHByZXNzdXJlXG4gKi9cbmNsYXNzIFJlc29sdXRpb25Db250ZXh0UG9vbCB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMucG9vbCA9IFtdO1xuICAgICAgICB0aGlzLm1heFNpemUgPSAxMDtcbiAgICB9XG4gICAgYWNxdWlyZSgpIHtcbiAgICAgICAgY29uc3QgY29udGV4dCA9IHRoaXMucG9vbC5wb3AoKTtcbiAgICAgICAgaWYgKGNvbnRleHQpIHtcbiAgICAgICAgICAgIC8vIFJlc2V0IGV4aXN0aW5nIGNvbnRleHQgZm9yIHJldXNlXG4gICAgICAgICAgICBjb250ZXh0LnJlc2V0KCk7XG4gICAgICAgICAgICByZXR1cm4gY29udGV4dDtcbiAgICAgICAgfVxuICAgICAgICAvLyBDcmVhdGUgbmV3IGlmIHBvb2wgZW1wdHlcbiAgICAgICAgcmV0dXJuIG5ldyBSZXNvbHV0aW9uQ29udGV4dCgpO1xuICAgIH1cbiAgICByZWxlYXNlKGNvbnRleHQpIHtcbiAgICAgICAgaWYgKHRoaXMucG9vbC5sZW5ndGggPCB0aGlzLm1heFNpemUpIHtcbiAgICAgICAgICAgIHRoaXMucG9vbC5wdXNoKGNvbnRleHQpO1xuICAgICAgICB9XG4gICAgICAgIC8vIE90aGVyd2lzZSBsZXQgaXQgYmUgR0MnZFxuICAgIH1cbn1cbi8qKlxuICogRGVwZW5kZW5jeSBJbmplY3Rpb24gQ29udGFpbmVyXG4gKlxuICogTWFuYWdlcyByZWdpc3RyYXRpb24gYW5kIHJlc29sdXRpb24gb2YgZGVwZW5kZW5jaWVzIHdpdGggc3VwcG9ydCBmb3I6XG4gKiAtIE11bHRpcGxlIGJpbmRpbmcgdHlwZXMgKHZhbHVlLCBmYWN0b3J5LCBjbGFzcylcbiAqIC0gTGlmZXRpbWUgbWFuYWdlbWVudCAoc2luZ2xldG9uLCB0cmFuc2llbnQsIHBlci1yZXF1ZXN0KVxuICogLSBDaGlsZCBjb250YWluZXJzIHdpdGggaW5oZXJpdGFuY2VcbiAqIC0gQ2lyY3VsYXIgZGVwZW5kZW5jeSBkZXRlY3Rpb25cbiAqIC0gQXV0b21hdGljIGRpc3Bvc2FsXG4gKi9cbmV4cG9ydCBjbGFzcyBDb250YWluZXIge1xuICAgIGNvbnN0cnVjdG9yKHBhcmVudCkge1xuICAgICAgICB0aGlzLmJpbmRpbmdzID0gbmV3IE1hcCgpO1xuICAgICAgICB0aGlzLnNpbmdsZXRvbkNhY2hlID0gbmV3IE1hcCgpO1xuICAgICAgICB0aGlzLnNpbmdsZXRvbk9yZGVyID0gW107XG4gICAgICAgIHRoaXMuaW50ZXJmYWNlUmVnaXN0cnkgPSBuZXcgTWFwKCk7XG4gICAgICAgIHRoaXMuaW50ZXJmYWNlVG9rZW5DYWNoZSA9IG5ldyBNYXAoKTsgLy8gUGVyZm9ybWFuY2U6IENhY2hlIGZvciByZXNvbHZlVHlwZSgpIGxvb2t1cHNcbiAgICAgICAgdGhpcy5mYXN0VHJhbnNpZW50Q2FjaGUgPSBuZXcgTWFwKCk7IC8vIFBlcmZvcm1hbmNlOiBGYXN0IHBhdGggZm9yIHNpbXBsZSB0cmFuc2llbnRzXG4gICAgICAgIHRoaXMudWx0cmFGYXN0U2luZ2xldG9uQ2FjaGUgPSBuZXcgTWFwKCk7IC8vIFBlcmZvcm1hbmNlOiBVbHRyYS1mYXN0IHNpbmdsZXRvbi1vbmx5IGNhY2hlXG4gICAgICAgIHRoaXMucGFyZW50ID0gcGFyZW50O1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBCaW5kIGEgcHJlLWNyZWF0ZWQgdmFsdWUgdG8gYSB0b2tlblxuICAgICAqL1xuICAgIGJpbmRWYWx1ZSh0b2tlbiwgdmFsdWUpIHtcbiAgICAgICAgdGhpcy5iaW5kaW5ncy5zZXQodG9rZW4sIHtcbiAgICAgICAgICAgIHR5cGU6ICd2YWx1ZScsXG4gICAgICAgICAgICBsaWZldGltZTogJ3NpbmdsZXRvbicsXG4gICAgICAgICAgICB2YWx1ZSxcbiAgICAgICAgICAgIGNvbnN0cnVjdG9yOiB1bmRlZmluZWRcbiAgICAgICAgfSk7XG4gICAgICAgIHRoaXMuaW52YWxpZGF0ZUJpbmRpbmdDYWNoZSgpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBCaW5kIGEgZmFjdG9yeSBmdW5jdGlvbiB0byBhIHRva2VuXG4gICAgICovXG4gICAgYmluZEZhY3RvcnkodG9rZW4sIGZhY3RvcnksIG9wdGlvbnMpIHtcbiAgICAgICAgdGhpcy5iaW5kaW5ncy5zZXQodG9rZW4sIHtcbiAgICAgICAgICAgIHR5cGU6ICdmYWN0b3J5JyxcbiAgICAgICAgICAgIGxpZmV0aW1lOiBvcHRpb25zPy5saWZldGltZSB8fCAndHJhbnNpZW50JyxcbiAgICAgICAgICAgIGZhY3RvcnksXG4gICAgICAgICAgICBkZXBlbmRlbmNpZXM6IG9wdGlvbnM/LmRlcGVuZGVuY2llcyxcbiAgICAgICAgICAgIGNvbnN0cnVjdG9yOiB1bmRlZmluZWRcbiAgICAgICAgfSk7XG4gICAgICAgIHRoaXMuaW52YWxpZGF0ZUJpbmRpbmdDYWNoZSgpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBCaW5kIGEgY2xhc3MgY29uc3RydWN0b3IgdG8gYSB0b2tlblxuICAgICAqL1xuICAgIGJpbmRDbGFzcyh0b2tlbiwgY29uc3RydWN0b3IsIG9wdGlvbnMpIHtcbiAgICAgICAgY29uc3QgYmluZGluZyA9IHtcbiAgICAgICAgICAgIHR5cGU6ICdjbGFzcycsXG4gICAgICAgICAgICBsaWZldGltZTogb3B0aW9ucz8ubGlmZXRpbWUgfHwgJ3RyYW5zaWVudCcsXG4gICAgICAgICAgICBjb25zdHJ1Y3RvcixcbiAgICAgICAgICAgIGRlcGVuZGVuY2llczogb3B0aW9ucz8uZGVwZW5kZW5jaWVzXG4gICAgICAgIH07XG4gICAgICAgIHRoaXMuYmluZGluZ3Muc2V0KHRva2VuLCBiaW5kaW5nKTtcbiAgICAgICAgdGhpcy5pbnZhbGlkYXRlQmluZGluZ0NhY2hlKCk7XG4gICAgICAgIC8vIFBlcmZvcm1hbmNlOiBQcmUtY29tcGlsZSBmYXN0IHRyYW5zaWVudCBmYWN0b3J5IGZvciB6ZXJvLWRlcGVuZGVuY3kgY2xhc3Nlc1xuICAgICAgICBpZiAoYmluZGluZy5saWZldGltZSA9PT0gJ3RyYW5zaWVudCcgJiYgKCFiaW5kaW5nLmRlcGVuZGVuY2llcyB8fCBiaW5kaW5nLmRlcGVuZGVuY2llcy5sZW5ndGggPT09IDApKSB7XG4gICAgICAgICAgICB0aGlzLmZhc3RUcmFuc2llbnRDYWNoZS5zZXQodG9rZW4sICgpID0+IG5ldyBjb25zdHJ1Y3RvcigpKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZXNvbHZlIGEgZGVwZW5kZW5jeSBzeW5jaHJvbm91c2x5XG4gICAgICogUGVyZm9ybWFuY2Ugb3B0aW1pemVkIHdpdGggbXVsdGlwbGUgZmFzdCBwYXRoc1xuICAgICAqL1xuICAgIHJlc29sdmUodG9rZW4pIHtcbiAgICAgICAgLy8gVHJ5IGFsbCBjYWNoZSBsZXZlbHMgZmlyc3QgKHVsdHJhLWZhc3QsIHNpbmdsZXRvbiwgZmFzdCB0cmFuc2llbnQpXG4gICAgICAgIGNvbnN0IGNhY2hlZCA9IHRoaXMudHJ5R2V0RnJvbUNhY2hlcyh0b2tlbik7XG4gICAgICAgIGlmIChjYWNoZWQgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgcmV0dXJuIGNhY2hlZDtcbiAgICAgICAgfVxuICAgICAgICAvLyBJZiB3ZSdyZSBhbHJlYWR5IHJlc29sdmluZyAoY2FsbGVkIGZyb20gd2l0aGluIGEgZmFjdG9yeSksIHJldXNlIHRoZSBjb250ZXh0XG4gICAgICAgIGlmICh0aGlzLmN1cnJlbnRDb250ZXh0KSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5yZXNvbHZlV2l0aENvbnRleHQodG9rZW4sIHRoaXMuY3VycmVudENvbnRleHQpO1xuICAgICAgICB9XG4gICAgICAgIC8vIENvbXBsZXggcmVzb2x1dGlvbiB3aXRoIHBvb2xlZCBjb250ZXh0XG4gICAgICAgIGNvbnN0IGNvbnRleHQgPSBDb250YWluZXIuY29udGV4dFBvb2wuYWNxdWlyZSgpO1xuICAgICAgICB0aGlzLmN1cnJlbnRDb250ZXh0ID0gY29udGV4dDtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLnJlc29sdmVXaXRoQ29udGV4dCh0b2tlbiwgY29udGV4dCk7XG4gICAgICAgIH1cbiAgICAgICAgZmluYWxseSB7XG4gICAgICAgICAgICB0aGlzLmN1cnJlbnRDb250ZXh0ID0gdW5kZWZpbmVkO1xuICAgICAgICAgICAgQ29udGFpbmVyLmNvbnRleHRQb29sLnJlbGVhc2UoY29udGV4dCk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgLyoqXG4gICAgICogU1BFQ0lBTElaRUQ6IFVsdHJhLWZhc3Qgc2luZ2xldG9uIHJlc29sdmUgKG5vIHNhZmV0eSBjaGVja3MpXG4gICAgICogVXNlIE9OTFkgd2hlbiB5b3UncmUgMTAwJSBzdXJlIHRoZSB0b2tlbiBpcyBhIHJlZ2lzdGVyZWQgc2luZ2xldG9uXG4gICAgICogQGludGVybmFsIEZvciBwZXJmb3JtYW5jZS1jcml0aWNhbCBwYXRocyBvbmx5XG4gICAgICovXG4gICAgcmVzb2x2ZVNpbmdsZXRvblVuc2FmZSh0b2tlbikge1xuICAgICAgICAvLyBEaXJlY3QgcmV0dXJuLCBubyBjaGVja3MgLSBtYXhpbXVtIHNwZWVkXG4gICAgICAgIHJldHVybiB0aGlzLnVsdHJhRmFzdFNpbmdsZXRvbkNhY2hlLmdldCh0b2tlbikgPz8gdGhpcy5zaW5nbGV0b25DYWNoZS5nZXQodG9rZW4pO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBTUEVDSUFMSVpFRDogRmFzdCB0cmFuc2llbnQgcmVzb2x2ZSBmb3IgemVyby1kZXBlbmRlbmN5IGNsYXNzZXNcbiAgICAgKiBTa2lwcyBhbGwgY29udGV4dCBjcmVhdGlvbiBhbmQgY2lyY3VsYXIgZGVwZW5kZW5jeSBjaGVja3NcbiAgICAgKiBAaW50ZXJuYWwgRm9yIHBlcmZvcm1hbmNlLWNyaXRpY2FsIHBhdGhzIG9ubHlcbiAgICAgKi9cbiAgICByZXNvbHZlVHJhbnNpZW50U2ltcGxlKHRva2VuKSB7XG4gICAgICAgIGNvbnN0IGZhY3RvcnkgPSB0aGlzLmZhc3RUcmFuc2llbnRDYWNoZS5nZXQodG9rZW4pO1xuICAgICAgICBpZiAoZmFjdG9yeSkge1xuICAgICAgICAgICAgcmV0dXJuIGZhY3RvcnkoKTtcbiAgICAgICAgfVxuICAgICAgICAvLyBGYWxsYmFjayB0byByZWd1bGFyIHJlc29sdmUgaWYgbm90IGluIGZhc3QgY2FjaGVcbiAgICAgICAgcmV0dXJuIHRoaXMucmVzb2x2ZSh0b2tlbik7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFNQRUNJQUxJWkVEOiBCYXRjaCByZXNvbHZlIG11bHRpcGxlIGRlcGVuZGVuY2llcyBhdCBvbmNlXG4gICAgICogTW9yZSBlZmZpY2llbnQgdGhhbiBtdWx0aXBsZSBpbmRpdmlkdWFsIHJlc29sdmVzXG4gICAgICovXG4gICAgcmVzb2x2ZUJhdGNoKHRva2Vucykge1xuICAgICAgICAvLyBSZXVzZSBzaW5nbGUgY29udGV4dCBmb3IgYWxsIHJlc29sdXRpb25zXG4gICAgICAgIGNvbnN0IHdhc1Jlc29sdmluZyA9ICEhdGhpcy5jdXJyZW50Q29udGV4dDtcbiAgICAgICAgY29uc3QgY29udGV4dCA9IHRoaXMuY3VycmVudENvbnRleHQgfHwgQ29udGFpbmVyLmNvbnRleHRQb29sLmFjcXVpcmUoKTtcbiAgICAgICAgaWYgKCF3YXNSZXNvbHZpbmcpIHtcbiAgICAgICAgICAgIHRoaXMuY3VycmVudENvbnRleHQgPSBjb250ZXh0O1xuICAgICAgICB9XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBjb25zdCByZXN1bHRzID0gdG9rZW5zLm1hcCh0b2tlbiA9PiB7XG4gICAgICAgICAgICAgICAgLy8gVHJ5IGFsbCBjYWNoZSBsZXZlbHMgZmlyc3RcbiAgICAgICAgICAgICAgICBjb25zdCBjYWNoZWQgPSB0aGlzLnRyeUdldEZyb21DYWNoZXModG9rZW4pO1xuICAgICAgICAgICAgICAgIGlmIChjYWNoZWQgIT09IHVuZGVmaW5lZClcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGNhY2hlZDtcbiAgICAgICAgICAgICAgICAvLyBGdWxsIHJlc29sdmUgd2l0aCBzaGFyZWQgY29udGV4dFxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzLnJlc29sdmVXaXRoQ29udGV4dCh0b2tlbiwgY29udGV4dCk7XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIHJldHVybiByZXN1bHRzO1xuICAgICAgICB9XG4gICAgICAgIGZpbmFsbHkge1xuICAgICAgICAgICAgaWYgKCF3YXNSZXNvbHZpbmcpIHtcbiAgICAgICAgICAgICAgICB0aGlzLmN1cnJlbnRDb250ZXh0ID0gdW5kZWZpbmVkO1xuICAgICAgICAgICAgICAgIENvbnRhaW5lci5jb250ZXh0UG9vbC5yZWxlYXNlKGNvbnRleHQpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJlc29sdmUgYSBkZXBlbmRlbmN5IGFzeW5jaHJvbm91c2x5IChzdXBwb3J0cyBhc3luYyBmYWN0b3JpZXMpXG4gICAgICovXG4gICAgYXN5bmMgcmVzb2x2ZUFzeW5jKHRva2VuKSB7XG4gICAgICAgIC8vIElmIHdlJ3JlIGFscmVhZHkgcmVzb2x2aW5nIChjYWxsZWQgZnJvbSB3aXRoaW4gYSBmYWN0b3J5KSwgcmV1c2UgdGhlIGNvbnRleHRcbiAgICAgICAgaWYgKHRoaXMuY3VycmVudENvbnRleHQpIHtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLnJlc29sdmVBc3luY1dpdGhDb250ZXh0KHRva2VuLCB0aGlzLmN1cnJlbnRDb250ZXh0KTtcbiAgICAgICAgfVxuICAgICAgICAvLyBOZXcgdG9wLWxldmVsIHJlc29sdmVcbiAgICAgICAgLy8gUGVyZm9ybWFuY2U6IFVzZSBwb29sZWQgY29udGV4dCB0byBhdm9pZCBoZWFwIGFsbG9jYXRpb25cbiAgICAgICAgY29uc3QgY29udGV4dCA9IENvbnRhaW5lci5jb250ZXh0UG9vbC5hY3F1aXJlKCk7XG4gICAgICAgIHRoaXMuY3VycmVudENvbnRleHQgPSBjb250ZXh0O1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgcmV0dXJuIGF3YWl0IHRoaXMucmVzb2x2ZUFzeW5jV2l0aENvbnRleHQodG9rZW4sIGNvbnRleHQpO1xuICAgICAgICB9XG4gICAgICAgIGZpbmFsbHkge1xuICAgICAgICAgICAgdGhpcy5jdXJyZW50Q29udGV4dCA9IHVuZGVmaW5lZDtcbiAgICAgICAgICAgIENvbnRhaW5lci5jb250ZXh0UG9vbC5yZWxlYXNlKGNvbnRleHQpOyAvLyBSZXR1cm4gdG8gcG9vbCBmb3IgcmV1c2VcbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBUcnkgdG8gZ2V0IGluc3RhbmNlIGZyb20gYWxsIGNhY2hlIGxldmVsc1xuICAgICAqIFJldHVybnMgdW5kZWZpbmVkIGlmIG5vdCBjYWNoZWRcbiAgICAgKiBAaW50ZXJuYWxcbiAgICAgKi9cbiAgICB0cnlHZXRGcm9tQ2FjaGVzKHRva2VuKSB7XG4gICAgICAgIC8vIExldmVsIDE6IFVsdHJhLWZhc3Qgc2luZ2xldG9uIGNhY2hlICh6ZXJvIG92ZXJoZWFkKVxuICAgICAgICBjb25zdCB1bHRyYUZhc3QgPSB0aGlzLnVsdHJhRmFzdFNpbmdsZXRvbkNhY2hlLmdldCh0b2tlbik7XG4gICAgICAgIGlmICh1bHRyYUZhc3QgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgcmV0dXJuIHVsdHJhRmFzdDtcbiAgICAgICAgfVxuICAgICAgICAvLyBMZXZlbCAyOiBSZWd1bGFyIHNpbmdsZXRvbiBjYWNoZVxuICAgICAgICBpZiAodGhpcy5zaW5nbGV0b25DYWNoZS5oYXModG9rZW4pKSB7XG4gICAgICAgICAgICBjb25zdCBjYWNoZWQgPSB0aGlzLnNpbmdsZXRvbkNhY2hlLmdldCh0b2tlbik7XG4gICAgICAgICAgICAvLyBQcm9tb3RlIHRvIHVsdHJhLWZhc3QgY2FjaGUgZm9yIG5leHQgdGltZVxuICAgICAgICAgICAgdGhpcy51bHRyYUZhc3RTaW5nbGV0b25DYWNoZS5zZXQodG9rZW4sIGNhY2hlZCk7XG4gICAgICAgICAgICByZXR1cm4gY2FjaGVkO1xuICAgICAgICB9XG4gICAgICAgIC8vIExldmVsIDM6IEZhc3QgdHJhbnNpZW50IGNhY2hlIChubyBkZXBlbmRlbmNpZXMpXG4gICAgICAgIGNvbnN0IGZhc3RGYWN0b3J5ID0gdGhpcy5mYXN0VHJhbnNpZW50Q2FjaGUuZ2V0KHRva2VuKTtcbiAgICAgICAgaWYgKGZhc3RGYWN0b3J5KSB7XG4gICAgICAgICAgICByZXR1cm4gZmFzdEZhY3RvcnkoKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdW5kZWZpbmVkO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBDYWNoZSBpbnN0YW5jZSBiYXNlZCBvbiBsaWZldGltZSBzdHJhdGVneVxuICAgICAqIEBpbnRlcm5hbFxuICAgICAqL1xuICAgIGNhY2hlSW5zdGFuY2UodG9rZW4sIGluc3RhbmNlLCBsaWZldGltZSwgY29udGV4dCkge1xuICAgICAgICBpZiAobGlmZXRpbWUgPT09ICdzaW5nbGV0b24nKSB7XG4gICAgICAgICAgICB0aGlzLnNpbmdsZXRvbkNhY2hlLnNldCh0b2tlbiwgaW5zdGFuY2UpO1xuICAgICAgICAgICAgdGhpcy5zaW5nbGV0b25PcmRlci5wdXNoKHRva2VuKTtcbiAgICAgICAgICAgIC8vIEFsc28gYWRkIHRvIHVsdHJhLWZhc3QgY2FjaGVcbiAgICAgICAgICAgIHRoaXMudWx0cmFGYXN0U2luZ2xldG9uQ2FjaGUuc2V0KHRva2VuLCBpbnN0YW5jZSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAobGlmZXRpbWUgPT09ICdwZXItcmVxdWVzdCcgJiYgY29udGV4dCkge1xuICAgICAgICAgICAgY29udGV4dC5jYWNoZVBlclJlcXVlc3QodG9rZW4sIGluc3RhbmNlKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBWYWxpZGF0ZSBhbmQgZ2V0IGJpbmRpbmcgd2l0aCBjaXJjdWxhciBkZXBlbmRlbmN5IGNoZWNrXG4gICAgICogUmV0dXJucyBiaW5kaW5nIG9yIHRocm93cyBlcnJvclxuICAgICAqIEBpbnRlcm5hbFxuICAgICAqL1xuICAgIHZhbGlkYXRlQW5kR2V0QmluZGluZyh0b2tlbiwgY29udGV4dCkge1xuICAgICAgICAvLyBDaGVjayBjaXJjdWxhciBkZXBlbmRlbmN5XG4gICAgICAgIGlmIChjb250ZXh0LmlzUmVzb2x2aW5nKHRva2VuKSkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IENpcmN1bGFyRGVwZW5kZW5jeUVycm9yKFsuLi5jb250ZXh0LmdldFBhdGgoKSwgdG9rZW4udG9TdHJpbmcoKV0pO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IGJpbmRpbmcgPSB0aGlzLmdldEJpbmRpbmcodG9rZW4pO1xuICAgICAgICBpZiAoIWJpbmRpbmcpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBCaW5kaW5nTm90Rm91bmRFcnJvcih0b2tlbi50b1N0cmluZygpLCBjb250ZXh0LmdldFBhdGgoKSk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGJpbmRpbmc7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEluc3RhbnRpYXRlIGZyb20gYmluZGluZyBzeW5jaHJvbm91c2x5XG4gICAgICogQGludGVybmFsXG4gICAgICovXG4gICAgaW5zdGFudGlhdGVCaW5kaW5nU3luYyhiaW5kaW5nLCB0b2tlbiwgY29udGV4dCkge1xuICAgICAgICBzd2l0Y2ggKGJpbmRpbmcudHlwZSkge1xuICAgICAgICAgICAgY2FzZSAndmFsdWUnOlxuICAgICAgICAgICAgICAgIHJldHVybiBiaW5kaW5nLnZhbHVlO1xuICAgICAgICAgICAgY2FzZSAnZmFjdG9yeSc6XG4gICAgICAgICAgICAgICAgY29uc3QgcmVzdWx0ID0gYmluZGluZy5mYWN0b3J5KHRoaXMpO1xuICAgICAgICAgICAgICAgIGlmIChyZXN1bHQgaW5zdGFuY2VvZiBQcm9taXNlKSB7XG4gICAgICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgQXN5bmMgZmFjdG9yeSBkZXRlY3RlZCBmb3IgJHt0b2tlbi50b1N0cmluZygpfS4gVXNlIHJlc29sdmVBc3luYygpIGluc3RlYWQuYCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgICAgICAgICBjYXNlICdjbGFzcyc6XG4gICAgICAgICAgICAgICAgY29uc3QgZGVwcyA9IGJpbmRpbmcuZGVwZW5kZW5jaWVzIHx8IFtdO1xuICAgICAgICAgICAgICAgIGNvbnN0IHJlc29sdmVkRGVwcyA9IGRlcHMubWFwKGRlcCA9PiB0aGlzLnJlc29sdmVXaXRoQ29udGV4dChkZXAsIGNvbnRleHQpKTtcbiAgICAgICAgICAgICAgICByZXR1cm4gbmV3IGJpbmRpbmcuY29uc3RydWN0b3IoLi4ucmVzb2x2ZWREZXBzKTtcbiAgICAgICAgICAgIGNhc2UgJ2lubGluZS1jbGFzcyc6XG4gICAgICAgICAgICAgICAgcmV0dXJuIG5ldyBiaW5kaW5nLmNvbnN0cnVjdG9yKCk7XG4gICAgICAgICAgICBkZWZhdWx0OlxuICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgVW5rbm93biBiaW5kaW5nIHR5cGU6ICR7YmluZGluZy50eXBlfWApO1xuICAgICAgICB9XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEluc3RhbnRpYXRlIGZyb20gYmluZGluZyBhc3luY2hyb25vdXNseVxuICAgICAqIEBpbnRlcm5hbFxuICAgICAqL1xuICAgIGFzeW5jIGluc3RhbnRpYXRlQmluZGluZ0FzeW5jKGJpbmRpbmcsIGNvbnRleHQpIHtcbiAgICAgICAgc3dpdGNoIChiaW5kaW5nLnR5cGUpIHtcbiAgICAgICAgICAgIGNhc2UgJ3ZhbHVlJzpcbiAgICAgICAgICAgICAgICByZXR1cm4gYmluZGluZy52YWx1ZTtcbiAgICAgICAgICAgIGNhc2UgJ2ZhY3RvcnknOlxuICAgICAgICAgICAgICAgIHJldHVybiBhd2FpdCBQcm9taXNlLnJlc29sdmUoYmluZGluZy5mYWN0b3J5KHRoaXMpKTtcbiAgICAgICAgICAgIGNhc2UgJ2NsYXNzJzpcbiAgICAgICAgICAgICAgICBjb25zdCBkZXBzID0gYmluZGluZy5kZXBlbmRlbmNpZXMgfHwgW107XG4gICAgICAgICAgICAgICAgY29uc3QgcmVzb2x2ZWREZXBzID0gYXdhaXQgUHJvbWlzZS5hbGwoZGVwcy5tYXAoZGVwID0+IHRoaXMucmVzb2x2ZUFzeW5jV2l0aENvbnRleHQoZGVwLCBjb250ZXh0KSkpO1xuICAgICAgICAgICAgICAgIHJldHVybiBuZXcgYmluZGluZy5jb25zdHJ1Y3RvciguLi5yZXNvbHZlZERlcHMpO1xuICAgICAgICAgICAgY2FzZSAnaW5saW5lLWNsYXNzJzpcbiAgICAgICAgICAgICAgICByZXR1cm4gbmV3IGJpbmRpbmcuY29uc3RydWN0b3IoKTtcbiAgICAgICAgICAgIGRlZmF1bHQ6XG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBVbmtub3duIGJpbmRpbmcgdHlwZTogJHtiaW5kaW5nLnR5cGV9YCk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgLyoqXG4gICAgICogQ3JlYXRlIGEgY2hpbGQgY29udGFpbmVyIHRoYXQgaW5oZXJpdHMgYmluZGluZ3MgZnJvbSB0aGlzIGNvbnRhaW5lclxuICAgICAqL1xuICAgIGNyZWF0ZUNoaWxkKCkge1xuICAgICAgICByZXR1cm4gbmV3IENvbnRhaW5lcih0aGlzKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogRGlzcG9zZSBhbGwgc2luZ2xldG9uIGluc3RhbmNlcyBpbiByZXZlcnNlIHJlZ2lzdHJhdGlvbiBvcmRlclxuICAgICAqL1xuICAgIGFzeW5jIGRpc3Bvc2UoKSB7XG4gICAgICAgIGNvbnN0IGVycm9ycyA9IFtdO1xuICAgICAgICAvLyBEaXNwb3NlIGluIHJldmVyc2Ugb3JkZXJcbiAgICAgICAgZm9yIChsZXQgaSA9IHRoaXMuc2luZ2xldG9uT3JkZXIubGVuZ3RoIC0gMTsgaSA+PSAwOyBpLS0pIHtcbiAgICAgICAgICAgIGNvbnN0IHRva2VuID0gdGhpcy5zaW5nbGV0b25PcmRlcltpXTtcbiAgICAgICAgICAgIGNvbnN0IGluc3RhbmNlID0gdGhpcy5zaW5nbGV0b25DYWNoZS5nZXQodG9rZW4pO1xuICAgICAgICAgICAgaWYgKGluc3RhbmNlICYmIGlzRGlzcG9zYWJsZShpbnN0YW5jZSkpIHtcbiAgICAgICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgICAgICBhd2FpdCBpbnN0YW5jZS5kaXNwb3NlKCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGNhdGNoIChlcnJvcikge1xuICAgICAgICAgICAgICAgICAgICBlcnJvcnMucHVzaChlcnJvcik7XG4gICAgICAgICAgICAgICAgICAgIC8vIENvbnRpbnVlIGRpc3Bvc2luZyBvdGhlciBpbnN0YW5jZXMgZXZlbiBpZiBvbmUgZmFpbHNcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgLy8gQ2xlYXIgY2FjaGVzXG4gICAgICAgIHRoaXMuc2luZ2xldG9uQ2FjaGUuY2xlYXIoKTtcbiAgICAgICAgdGhpcy5zaW5nbGV0b25PcmRlci5sZW5ndGggPSAwO1xuICAgICAgICAvLyBOb3RlOiBXZSBkb24ndCB0aHJvdyBlcnJvcnMgdG8gYWxsb3cgYWxsIGRpc3Bvc2FscyB0byBjb21wbGV0ZVxuICAgICAgICAvLyBJbiBwcm9kdWN0aW9uLCB5b3UgbWlnaHQgd2FudCB0byBsb2cgdGhlc2UgZXJyb3JzXG4gICAgfVxuICAgIC8qKlxuICAgICAqIENyZWF0ZSBhIGZsdWVudCBidWlsZGVyIGZvciByZWdpc3RlcmluZyBkZXBlbmRlbmNpZXNcbiAgICAgKi9cbiAgICBidWlsZGVyKCkge1xuICAgICAgICByZXR1cm4gbmV3IEJ1aWxkZXIodGhpcyk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJlc29sdmUgYSBuYW1lZCBzZXJ2aWNlXG4gICAgICovXG4gICAgcmVzb2x2ZU5hbWVkKG5hbWUpIHtcbiAgICAgICAgY29uc3QgbmFtZWRSZWdpc3RyYXRpb25zID0gdGhpcy5fX25hbWVkUmVnaXN0cmF0aW9ucztcbiAgICAgICAgaWYgKCFuYW1lZFJlZ2lzdHJhdGlvbnMpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgTmFtZWQgc2VydmljZSBcIiR7bmFtZX1cIiBub3QgZm91bmQuIE5vIG5hbWVkIHJlZ2lzdHJhdGlvbnMgZXhpc3QuYCk7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgY29uZmlnID0gbmFtZWRSZWdpc3RyYXRpb25zLmdldChuYW1lKTtcbiAgICAgICAgaWYgKCFjb25maWcpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgTmFtZWQgc2VydmljZSBcIiR7bmFtZX1cIiBub3QgZm91bmRgKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcy5yZXNvbHZlKGNvbmZpZy50b2tlbik7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJlc29sdmUgYSBrZXllZCBzZXJ2aWNlXG4gICAgICovXG4gICAgcmVzb2x2ZUtleWVkKGtleSkge1xuICAgICAgICBjb25zdCBrZXllZFJlZ2lzdHJhdGlvbnMgPSB0aGlzLl9fa2V5ZWRSZWdpc3RyYXRpb25zO1xuICAgICAgICBpZiAoIWtleWVkUmVnaXN0cmF0aW9ucykge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBLZXllZCBzZXJ2aWNlIG5vdCBmb3VuZC4gTm8ga2V5ZWQgcmVnaXN0cmF0aW9ucyBleGlzdC5gKTtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBjb25maWcgPSBrZXllZFJlZ2lzdHJhdGlvbnMuZ2V0KGtleSk7XG4gICAgICAgIGlmICghY29uZmlnKSB7XG4gICAgICAgICAgICBjb25zdCBrZXlTdHIgPSB0eXBlb2Yga2V5ID09PSAnc3ltYm9sJyA/IGtleS50b1N0cmluZygpIDogYFwiJHtrZXl9XCJgO1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBLZXllZCBzZXJ2aWNlICR7a2V5U3RyfSBub3QgZm91bmRgKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcy5yZXNvbHZlKGNvbmZpZy50b2tlbik7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJlc29sdmUgYWxsIHJlZ2lzdHJhdGlvbnMgZm9yIGEgdG9rZW5cbiAgICAgKi9cbiAgICByZXNvbHZlQWxsKHRva2VuKSB7XG4gICAgICAgIGNvbnN0IG11bHRpUmVnaXN0cmF0aW9ucyA9IHRoaXMuX19tdWx0aVJlZ2lzdHJhdGlvbnM7XG4gICAgICAgIGlmICghbXVsdGlSZWdpc3RyYXRpb25zKSB7XG4gICAgICAgICAgICByZXR1cm4gW107XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgdG9rZW5zID0gbXVsdGlSZWdpc3RyYXRpb25zLmdldCh0b2tlbik7XG4gICAgICAgIGlmICghdG9rZW5zIHx8IHRva2Vucy5sZW5ndGggPT09IDApIHtcbiAgICAgICAgICAgIHJldHVybiBbXTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdG9rZW5zLm1hcCgodCkgPT4gdGhpcy5yZXNvbHZlKHQpKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IHJlZ2lzdHJ5IGluZm9ybWF0aW9uIGZvciBkZWJ1Z2dpbmcvdmlzdWFsaXphdGlvblxuICAgICAqIFJldHVybnMgYXJyYXkgb2YgYmluZGluZyBpbmZvcm1hdGlvblxuICAgICAqL1xuICAgIGdldFJlZ2lzdHJ5KCkge1xuICAgICAgICBjb25zdCByZWdpc3RyeSA9IFtdO1xuICAgICAgICB0aGlzLmJpbmRpbmdzLmZvckVhY2goKGJpbmRpbmcsIHRva2VuKSA9PiB7XG4gICAgICAgICAgICByZWdpc3RyeS5wdXNoKHtcbiAgICAgICAgICAgICAgICB0b2tlbjogdG9rZW4uZGVzY3JpcHRpb24gfHwgdG9rZW4uc3ltYm9sLnRvU3RyaW5nKCksXG4gICAgICAgICAgICAgICAgdHlwZTogYmluZGluZy50eXBlLFxuICAgICAgICAgICAgICAgIGxpZmV0aW1lOiBiaW5kaW5nLmxpZmV0aW1lLFxuICAgICAgICAgICAgICAgIGRlcGVuZGVuY2llczogYmluZGluZy5kZXBlbmRlbmNpZXM/Lm1hcChkID0+IGQuZGVzY3JpcHRpb24gfHwgZC5zeW1ib2wudG9TdHJpbmcoKSlcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9KTtcbiAgICAgICAgcmV0dXJuIHJlZ2lzdHJ5O1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgb3IgY3JlYXRlIGEgdG9rZW4gZm9yIGFuIGludGVyZmFjZSB0eXBlXG4gICAgICogVXNlcyBhIHR5cGUgbmFtZSBoYXNoIGFzIGtleSBmb3IgdGhlIGludGVyZmFjZSByZWdpc3RyeVxuICAgICAqL1xuICAgIGludGVyZmFjZVRva2VuKHR5cGVOYW1lKSB7XG4gICAgICAgIC8vIEdlbmVyYXRlIGEgdW5pcXVlIGtleSBmb3IgdGhpcyBpbnRlcmZhY2UgdHlwZVxuICAgICAgICAvLyBJbiBwcm9kdWN0aW9uLCB0aGlzIHdvdWxkIGJlIHJlcGxhY2VkIGJ5IGEgVFMgdHJhbnNmb3JtZXJcbiAgICAgICAgY29uc3Qga2V5ID0gdHlwZU5hbWUgfHwgYEludGVyZmFjZV8ke01hdGgucmFuZG9tKCkudG9TdHJpbmcoMzYpLnN1YnN0cigyLCA5KX1gO1xuICAgICAgICAvLyBDaGVjayBpZiB0b2tlbiBhbHJlYWR5IGV4aXN0cyBpbiB0aGlzIGNvbnRhaW5lclxuICAgICAgICBpZiAodGhpcy5pbnRlcmZhY2VSZWdpc3RyeS5oYXMoa2V5KSkge1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMuaW50ZXJmYWNlUmVnaXN0cnkuZ2V0KGtleSk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gQ2hlY2sgcGFyZW50IGNvbnRhaW5lciAocmVjdXJzaXZlbHkgdGhyb3VnaCBwYXJlbnQgY2hhaW4pXG4gICAgICAgIGlmICh0aGlzLnBhcmVudCkge1xuICAgICAgICAgICAgLy8gUmVjdXJzaXZlbHkgY2hlY2sgdGhyb3VnaCBlbnRpcmUgcGFyZW50IGNoYWluXG4gICAgICAgICAgICBjb25zdCBwYXJlbnRUb2tlbiA9IHRoaXMucGFyZW50LmludGVyZmFjZVRva2VuKGtleSk7XG4gICAgICAgICAgICAvLyBJZiBwYXJlbnQgY3JlYXRlZCBhIG5ldyB0b2tlbiwgZG9uJ3QgY3JlYXRlIGFub3RoZXIgb25lXG4gICAgICAgICAgICByZXR1cm4gcGFyZW50VG9rZW47XG4gICAgICAgIH1cbiAgICAgICAgLy8gQ3JlYXRlIG5ldyB0b2tlbiAob25seSBpZiBubyBwYXJlbnQgZXhpc3RzKVxuICAgICAgICBjb25zdCB0b2tlbiA9IFRva2VuKGtleSk7XG4gICAgICAgIHRoaXMuaW50ZXJmYWNlUmVnaXN0cnkuc2V0KGtleSwgdG9rZW4pO1xuICAgICAgICByZXR1cm4gdG9rZW47XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJlc29sdmUgYSBkZXBlbmRlbmN5IGJ5IGludGVyZmFjZSB0eXBlIHdpdGhvdXQgZXhwbGljaXQgdG9rZW5cbiAgICAgKi9cbiAgICByZXNvbHZlVHlwZSh0eXBlTmFtZSkge1xuICAgICAgICAvLyBQZXJmb3JtYW5jZTogQ2FjaGUgdG9rZW4gbG9va3VwcyB0byBhdm9pZCByZXBlYXRlZCBpbnRlcmZhY2VSZWdpc3RyeSBhY2Nlc3NcbiAgICAgICAgY29uc3Qga2V5ID0gdHlwZU5hbWUgfHwgJyc7XG4gICAgICAgIGxldCB0b2tlbiA9IHRoaXMuaW50ZXJmYWNlVG9rZW5DYWNoZS5nZXQoa2V5KTtcbiAgICAgICAgaWYgKCF0b2tlbikge1xuICAgICAgICAgICAgdG9rZW4gPSB0aGlzLmludGVyZmFjZVRva2VuKHR5cGVOYW1lKTtcbiAgICAgICAgICAgIHRoaXMuaW50ZXJmYWNlVG9rZW5DYWNoZS5zZXQoa2V5LCB0b2tlbik7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXMucmVzb2x2ZSh0b2tlbik7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJlc29sdmUgYSBrZXllZCBpbnRlcmZhY2VcbiAgICAgKi9cbiAgICByZXNvbHZlVHlwZUtleWVkKGtleSwgX3R5cGVOYW1lKSB7XG4gICAgICAgIC8vIEZvciBrZXllZCBpbnRlcmZhY2VzLCB3ZSB1c2UgdGhlIGV4aXN0aW5nIHJlc29sdmVLZXllZCBtZWNoYW5pc21cbiAgICAgICAgcmV0dXJuIHRoaXMucmVzb2x2ZUtleWVkKGtleSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJlc29sdmUgYWxsIHJlZ2lzdHJhdGlvbnMgZm9yIGFuIGludGVyZmFjZSB0eXBlXG4gICAgICovXG4gICAgcmVzb2x2ZVR5cGVBbGwodHlwZU5hbWUpIHtcbiAgICAgICAgY29uc3QgdG9rZW4gPSB0aGlzLmludGVyZmFjZVRva2VuKHR5cGVOYW1lKTtcbiAgICAgICAgcmV0dXJuIHRoaXMucmVzb2x2ZUFsbCh0b2tlbik7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEludGVybmFsOiBSZXNvbHZlIHdpdGggY29udGV4dCBmb3IgY2lyY3VsYXIgZGVwZW5kZW5jeSBkZXRlY3Rpb25cbiAgICAgKi9cbiAgICByZXNvbHZlV2l0aENvbnRleHQodG9rZW4sIGNvbnRleHQpIHtcbiAgICAgICAgLy8gVmFsaWRhdGUgYW5kIGdldCBiaW5kaW5nICh3aXRoIGNpcmN1bGFyIGRlcGVuZGVuY3kgY2hlY2spXG4gICAgICAgIGNvbnN0IGJpbmRpbmcgPSB0aGlzLnZhbGlkYXRlQW5kR2V0QmluZGluZyh0b2tlbiwgY29udGV4dCk7XG4gICAgICAgIC8vIENoZWNrIHBlci1yZXF1ZXN0IGNhY2hlXG4gICAgICAgIGlmIChiaW5kaW5nLmxpZmV0aW1lID09PSAncGVyLXJlcXVlc3QnICYmIGNvbnRleHQuaGFzUGVyUmVxdWVzdCh0b2tlbikpIHtcbiAgICAgICAgICAgIHJldHVybiBjb250ZXh0LmdldFBlclJlcXVlc3QodG9rZW4pO1xuICAgICAgICB9XG4gICAgICAgIC8vIENoZWNrIHNpbmdsZXRvbiBjYWNoZSAobG9jYWwgY29udGFpbmVyIG9ubHkpXG4gICAgICAgIGlmIChiaW5kaW5nLmxpZmV0aW1lID09PSAnc2luZ2xldG9uJyAmJiB0aGlzLnNpbmdsZXRvbkNhY2hlLmhhcyh0b2tlbikpIHtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLnNpbmdsZXRvbkNhY2hlLmdldCh0b2tlbik7XG4gICAgICAgIH1cbiAgICAgICAgLy8gTWFyayBhcyByZXNvbHZpbmdcbiAgICAgICAgY29udGV4dC5lbnRlclJlc29sdmUodG9rZW4pO1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgLy8gSW5zdGFudGlhdGUgZnJvbSBiaW5kaW5nXG4gICAgICAgICAgICBjb25zdCBpbnN0YW5jZSA9IHRoaXMuaW5zdGFudGlhdGVCaW5kaW5nU3luYyhiaW5kaW5nLCB0b2tlbiwgY29udGV4dCk7XG4gICAgICAgICAgICAvLyBDYWNoZSBiYXNlZCBvbiBsaWZldGltZVxuICAgICAgICAgICAgdGhpcy5jYWNoZUluc3RhbmNlKHRva2VuLCBpbnN0YW5jZSwgYmluZGluZy5saWZldGltZSwgY29udGV4dCk7XG4gICAgICAgICAgICByZXR1cm4gaW5zdGFuY2U7XG4gICAgICAgIH1cbiAgICAgICAgZmluYWxseSB7XG4gICAgICAgICAgICBjb250ZXh0LmV4aXRSZXNvbHZlKHRva2VuKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBJbnRlcm5hbDogQXN5bmMgcmVzb2x2ZSB3aXRoIGNvbnRleHRcbiAgICAgKi9cbiAgICBhc3luYyByZXNvbHZlQXN5bmNXaXRoQ29udGV4dCh0b2tlbiwgY29udGV4dCkge1xuICAgICAgICAvLyBWYWxpZGF0ZSBhbmQgZ2V0IGJpbmRpbmcgKHdpdGggY2lyY3VsYXIgZGVwZW5kZW5jeSBjaGVjaylcbiAgICAgICAgY29uc3QgYmluZGluZyA9IHRoaXMudmFsaWRhdGVBbmRHZXRCaW5kaW5nKHRva2VuLCBjb250ZXh0KTtcbiAgICAgICAgLy8gQ2hlY2sgcGVyLXJlcXVlc3QgY2FjaGVcbiAgICAgICAgaWYgKGJpbmRpbmcubGlmZXRpbWUgPT09ICdwZXItcmVxdWVzdCcgJiYgY29udGV4dC5oYXNQZXJSZXF1ZXN0KHRva2VuKSkge1xuICAgICAgICAgICAgcmV0dXJuIGNvbnRleHQuZ2V0UGVyUmVxdWVzdCh0b2tlbik7XG4gICAgICAgIH1cbiAgICAgICAgLy8gQ2hlY2sgc2luZ2xldG9uIGNhY2hlIChsb2NhbCBjb250YWluZXIgb25seSlcbiAgICAgICAgaWYgKGJpbmRpbmcubGlmZXRpbWUgPT09ICdzaW5nbGV0b24nICYmIHRoaXMuc2luZ2xldG9uQ2FjaGUuaGFzKHRva2VuKSkge1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMuc2luZ2xldG9uQ2FjaGUuZ2V0KHRva2VuKTtcbiAgICAgICAgfVxuICAgICAgICAvLyBNYXJrIGFzIHJlc29sdmluZ1xuICAgICAgICBjb250ZXh0LmVudGVyUmVzb2x2ZSh0b2tlbik7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICAvLyBJbnN0YW50aWF0ZSBmcm9tIGJpbmRpbmcgYXN5bmNocm9ub3VzbHlcbiAgICAgICAgICAgIGNvbnN0IGluc3RhbmNlID0gYXdhaXQgdGhpcy5pbnN0YW50aWF0ZUJpbmRpbmdBc3luYyhiaW5kaW5nLCBjb250ZXh0KTtcbiAgICAgICAgICAgIC8vIENhY2hlIGJhc2VkIG9uIGxpZmV0aW1lXG4gICAgICAgICAgICB0aGlzLmNhY2hlSW5zdGFuY2UodG9rZW4sIGluc3RhbmNlLCBiaW5kaW5nLmxpZmV0aW1lLCBjb250ZXh0KTtcbiAgICAgICAgICAgIHJldHVybiBpbnN0YW5jZTtcbiAgICAgICAgfVxuICAgICAgICBmaW5hbGx5IHtcbiAgICAgICAgICAgIGNvbnRleHQuZXhpdFJlc29sdmUodG9rZW4pO1xuICAgICAgICB9XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCBiaW5kaW5nIGZyb20gdGhpcyBjb250YWluZXIgb3IgcGFyZW50IGNoYWluXG4gICAgICogUGVyZm9ybWFuY2Ugb3B0aW1pemVkOiBVc2VzIGZsYXQgY2FjaGUgdG8gYXZvaWQgcmVjdXJzaXZlIHBhcmVudCBsb29rdXBzXG4gICAgICovXG4gICAgZ2V0QmluZGluZyh0b2tlbikge1xuICAgICAgICAvLyBCdWlsZCBmbGF0IGNhY2hlIG9uIGZpcnN0IGFjY2Vzc1xuICAgICAgICBpZiAoIXRoaXMuYmluZGluZ0NhY2hlKSB7XG4gICAgICAgICAgICB0aGlzLmJ1aWxkQmluZGluZ0NhY2hlKCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXMuYmluZGluZ0NhY2hlLmdldCh0b2tlbik7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEJ1aWxkIGZsYXQgY2FjaGUgb2YgYWxsIGJpbmRpbmdzIGluY2x1ZGluZyBwYXJlbnQgY2hhaW5cbiAgICAgKiBUaGlzIGNvbnZlcnRzIE8obikgcGFyZW50IGNoYWluIHRyYXZlcnNhbCB0byBPKDEpIGxvb2t1cFxuICAgICAqL1xuICAgIGJ1aWxkQmluZGluZ0NhY2hlKCkge1xuICAgICAgICB0aGlzLmJpbmRpbmdDYWNoZSA9IG5ldyBNYXAoKTtcbiAgICAgICAgLy8gVHJhdmVyc2UgcGFyZW50IGNoYWluIGFuZCBmbGF0dGVuIGFsbCBiaW5kaW5nc1xuICAgICAgICBsZXQgY3VycmVudCA9IHRoaXM7XG4gICAgICAgIHdoaWxlIChjdXJyZW50KSB7XG4gICAgICAgICAgICBjdXJyZW50LmJpbmRpbmdzLmZvckVhY2goKGJpbmRpbmcsIHRva2VuKSA9PiB7XG4gICAgICAgICAgICAgICAgLy8gQ2hpbGQgYmluZGluZ3Mgb3ZlcnJpZGUgcGFyZW50IGJpbmRpbmdzIChmaXJzdCB3aW5zKVxuICAgICAgICAgICAgICAgIGlmICghdGhpcy5iaW5kaW5nQ2FjaGUuaGFzKHRva2VuKSkge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLmJpbmRpbmdDYWNoZS5zZXQodG9rZW4sIGJpbmRpbmcpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgY3VycmVudCA9IGN1cnJlbnQucGFyZW50O1xuICAgICAgICB9XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEludmFsaWRhdGUgYmluZGluZyBjYWNoZSB3aGVuIG5ldyBiaW5kaW5ncyBhcmUgYWRkZWRcbiAgICAgKiBDYWxsZWQgYnkgYmluZFZhbHVlLCBiaW5kRmFjdG9yeSwgYmluZENsYXNzXG4gICAgICovXG4gICAgaW52YWxpZGF0ZUJpbmRpbmdDYWNoZSgpIHtcbiAgICAgICAgdGhpcy5iaW5kaW5nQ2FjaGUgPSB1bmRlZmluZWQ7XG4gICAgICAgIHRoaXMudWx0cmFGYXN0U2luZ2xldG9uQ2FjaGUuY2xlYXIoKTsgLy8gQ2xlYXIgdWx0cmEtZmFzdCBjYWNoZSB3aGVuIGJpbmRpbmdzIGNoYW5nZVxuICAgIH1cbn1cbkNvbnRhaW5lci5jb250ZXh0UG9vbCA9IG5ldyBSZXNvbHV0aW9uQ29udGV4dFBvb2woKTsgLy8gUGVyZm9ybWFuY2U6IFBvb2xlZCBjb250ZXh0cyByZWR1Y2UgYWxsb2NhdGlvbnNcbiIsICJleHBvcnQgY2xhc3MgRGF0ZVJlbmRlcmVyIHtcbiAgICBjb25zdHJ1Y3RvcihkYXRlU2VydmljZSkge1xuICAgICAgICB0aGlzLmRhdGVTZXJ2aWNlID0gZGF0ZVNlcnZpY2U7XG4gICAgICAgIHRoaXMudHlwZSA9ICdkYXRlJztcbiAgICB9XG4gICAgcmVuZGVyKGNvbnRleHQpIHtcbiAgICAgICAgY29uc3QgZGF0ZXMgPSBjb250ZXh0LmZpbHRlclsnZGF0ZSddIHx8IFtdO1xuICAgICAgICBjb25zdCByZXNvdXJjZUlkcyA9IGNvbnRleHQuZmlsdGVyWydyZXNvdXJjZSddIHx8IFtdO1xuICAgICAgICAvLyBDaGVjayBpZiBkYXRlIGhlYWRlcnMgc2hvdWxkIGJlIGhpZGRlbiAoZS5nLiwgaW4gZGF5IHZpZXcpXG4gICAgICAgIGNvbnN0IGRhdGVHcm91cGluZyA9IGNvbnRleHQuZ3JvdXBpbmdzPy5maW5kKGcgPT4gZy50eXBlID09PSAnZGF0ZScpO1xuICAgICAgICBjb25zdCBoaWRlSGVhZGVyID0gZGF0ZUdyb3VwaW5nPy5oaWRlSGVhZGVyID09PSB0cnVlO1xuICAgICAgICAvLyBSZW5kZXIgZGF0ZXMgZm9yIEhWRVIgcmVzb3VyY2UgKGVsbGVyIDEgZ2FuZyBodmlzIGluZ2VuIHJlc291cmNlcylcbiAgICAgICAgY29uc3QgaXRlcmF0aW9ucyA9IHJlc291cmNlSWRzLmxlbmd0aCB8fCAxO1xuICAgICAgICBsZXQgY29sdW1uQ291bnQgPSAwO1xuICAgICAgICBmb3IgKGxldCByID0gMDsgciA8IGl0ZXJhdGlvbnM7IHIrKykge1xuICAgICAgICAgICAgY29uc3QgcmVzb3VyY2VJZCA9IHJlc291cmNlSWRzW3JdOyAvLyB1bmRlZmluZWQgaHZpcyBpbmdlbiByZXNvdXJjZXNcbiAgICAgICAgICAgIGZvciAoY29uc3QgZGF0ZVN0ciBvZiBkYXRlcykge1xuICAgICAgICAgICAgICAgIGNvbnN0IGRhdGUgPSB0aGlzLmRhdGVTZXJ2aWNlLnBhcnNlSVNPKGRhdGVTdHIpO1xuICAgICAgICAgICAgICAgIC8vIEJ1aWxkIGNvbHVtbktleSBmb3IgdW5pZm9ybSBpZGVudGlmaWNhdGlvblxuICAgICAgICAgICAgICAgIGNvbnN0IHNlZ21lbnRzID0geyBkYXRlOiBkYXRlU3RyIH07XG4gICAgICAgICAgICAgICAgaWYgKHJlc291cmNlSWQpXG4gICAgICAgICAgICAgICAgICAgIHNlZ21lbnRzLnJlc291cmNlID0gcmVzb3VyY2VJZDtcbiAgICAgICAgICAgICAgICBjb25zdCBjb2x1bW5LZXkgPSB0aGlzLmRhdGVTZXJ2aWNlLmJ1aWxkQ29sdW1uS2V5KHNlZ21lbnRzKTtcbiAgICAgICAgICAgICAgICAvLyBIZWFkZXJcbiAgICAgICAgICAgICAgICBjb25zdCBoZWFkZXIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzd3AtZGF5LWhlYWRlcicpO1xuICAgICAgICAgICAgICAgIGhlYWRlci5kYXRhc2V0LmRhdGUgPSBkYXRlU3RyO1xuICAgICAgICAgICAgICAgIGhlYWRlci5kYXRhc2V0LmNvbHVtbktleSA9IGNvbHVtbktleTtcbiAgICAgICAgICAgICAgICBpZiAocmVzb3VyY2VJZCkge1xuICAgICAgICAgICAgICAgICAgICBoZWFkZXIuZGF0YXNldC5yZXNvdXJjZUlkID0gcmVzb3VyY2VJZDtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgaWYgKGhpZGVIZWFkZXIpIHtcbiAgICAgICAgICAgICAgICAgICAgaGVhZGVyLmRhdGFzZXQuaGlkZGVuID0gJ3RydWUnO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBoZWFkZXIuaW5uZXJIVE1MID0gYFxyXG4gICAgICAgICAgPHN3cC1kYXktbmFtZT4ke3RoaXMuZGF0ZVNlcnZpY2UuZ2V0RGF5TmFtZShkYXRlLCAnc2hvcnQnKX08L3N3cC1kYXktbmFtZT5cclxuICAgICAgICAgIDxzd3AtZGF5LWRhdGU+JHtkYXRlLmdldERhdGUoKX08L3N3cC1kYXktZGF0ZT5cclxuICAgICAgICBgO1xuICAgICAgICAgICAgICAgIGNvbnRleHQuaGVhZGVyQ29udGFpbmVyLmFwcGVuZENoaWxkKGhlYWRlcik7XG4gICAgICAgICAgICAgICAgLy8gQ29sdW1uXG4gICAgICAgICAgICAgICAgY29uc3QgY29sdW1uID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3dwLWRheS1jb2x1bW4nKTtcbiAgICAgICAgICAgICAgICBjb2x1bW4uZGF0YXNldC5kYXRlID0gZGF0ZVN0cjtcbiAgICAgICAgICAgICAgICBjb2x1bW4uZGF0YXNldC5jb2x1bW5LZXkgPSBjb2x1bW5LZXk7XG4gICAgICAgICAgICAgICAgaWYgKHJlc291cmNlSWQpIHtcbiAgICAgICAgICAgICAgICAgICAgY29sdW1uLmRhdGFzZXQucmVzb3VyY2VJZCA9IHJlc291cmNlSWQ7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGNvbHVtbi5pbm5lckhUTUwgPSAnPHN3cC1ldmVudHMtbGF5ZXI+PC9zd3AtZXZlbnRzLWxheWVyPic7XG4gICAgICAgICAgICAgICAgY29udGV4dC5jb2x1bW5Db250YWluZXIuYXBwZW5kQ2hpbGQoY29sdW1uKTtcbiAgICAgICAgICAgICAgICBjb2x1bW5Db3VudCsrO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIC8vIFNldCBncmlkIGNvbHVtbnMgb24gY29udGFpbmVyXG4gICAgICAgIGNvbnN0IGNvbnRhaW5lciA9IGNvbnRleHQuY29sdW1uQ29udGFpbmVyLmNsb3Nlc3QoJ3N3cC1jYWxlbmRhci1jb250YWluZXInKTtcbiAgICAgICAgaWYgKGNvbnRhaW5lcikge1xuICAgICAgICAgICAgY29udGFpbmVyLnN0eWxlLnNldFByb3BlcnR5KCctLWdyaWQtY29sdW1ucycsIFN0cmluZyhjb2x1bW5Db3VudCkpO1xuICAgICAgICB9XG4gICAgfVxufVxuIiwgImltcG9ydCBkYXlqcyBmcm9tICdkYXlqcyc7XG5pbXBvcnQgdXRjIGZyb20gJ2RheWpzL3BsdWdpbi91dGMnO1xuaW1wb3J0IHRpbWV6b25lIGZyb20gJ2RheWpzL3BsdWdpbi90aW1lem9uZSc7XG5pbXBvcnQgaXNvV2VlayBmcm9tICdkYXlqcy9wbHVnaW4vaXNvV2Vlayc7XG4vLyBFbmFibGUgZGF5anMgcGx1Z2luc1xuZGF5anMuZXh0ZW5kKHV0Yyk7XG5kYXlqcy5leHRlbmQodGltZXpvbmUpO1xuZGF5anMuZXh0ZW5kKGlzb1dlZWspO1xuZXhwb3J0IGNsYXNzIERhdGVTZXJ2aWNlIHtcbiAgICBjb25zdHJ1Y3Rvcihjb25maWcsIGJhc2VEYXRlKSB7XG4gICAgICAgIHRoaXMuY29uZmlnID0gY29uZmlnO1xuICAgICAgICB0aGlzLnRpbWV6b25lID0gY29uZmlnLnRpbWV6b25lO1xuICAgICAgICAvLyBBbGxvdyBzZXR0aW5nIGEgZml4ZWQgYmFzZSBkYXRlIGZvciBkZW1vL3Rlc3RpbmcgcHVycG9zZXNcbiAgICAgICAgdGhpcy5iYXNlRGF0ZSA9IGJhc2VEYXRlID8gZGF5anMoYmFzZURhdGUpIDogZGF5anMoKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogU2V0IGEgZml4ZWQgYmFzZSBkYXRlICh1c2VmdWwgZm9yIGRlbW9zIHdpdGggc3RhdGljIG1vY2sgZGF0YSlcbiAgICAgKi9cbiAgICBzZXRCYXNlRGF0ZShkYXRlKSB7XG4gICAgICAgIHRoaXMuYmFzZURhdGUgPSBkYXlqcyhkYXRlKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IHRoZSBjdXJyZW50IGJhc2UgZGF0ZSAoZWl0aGVyIGZpeGVkIG9yIHRvZGF5KVxuICAgICAqL1xuICAgIGdldEJhc2VEYXRlKCkge1xuICAgICAgICByZXR1cm4gdGhpcy5iYXNlRGF0ZS50b0RhdGUoKTtcbiAgICB9XG4gICAgcGFyc2VJU08oaXNvU3RyaW5nKSB7XG4gICAgICAgIHJldHVybiBkYXlqcyhpc29TdHJpbmcpLnRvRGF0ZSgpO1xuICAgIH1cbiAgICBnZXREYXlOYW1lKGRhdGUsIGZvcm1hdCA9ICdzaG9ydCcpIHtcbiAgICAgICAgcmV0dXJuIG5ldyBJbnRsLkRhdGVUaW1lRm9ybWF0KHRoaXMuY29uZmlnLmxvY2FsZSwgeyB3ZWVrZGF5OiBmb3JtYXQgfSkuZm9ybWF0KGRhdGUpO1xuICAgIH1cbiAgICBnZXRXZWVrRGF0ZXMob2Zmc2V0ID0gMCwgZGF5cyA9IDcpIHtcbiAgICAgICAgY29uc3QgbW9uZGF5ID0gdGhpcy5iYXNlRGF0ZS5zdGFydE9mKCd3ZWVrJykuYWRkKDEsICdkYXknKS5hZGQob2Zmc2V0LCAnd2VlaycpO1xuICAgICAgICByZXR1cm4gQXJyYXkuZnJvbSh7IGxlbmd0aDogZGF5cyB9LCAoXywgaSkgPT4gbW9uZGF5LmFkZChpLCAnZGF5JykuZm9ybWF0KCdZWVlZLU1NLUREJykpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgZGF0ZXMgZm9yIHNwZWNpZmljIHdlZWtkYXlzIHdpdGhpbiBhIHdlZWtcbiAgICAgKiBAcGFyYW0gb2Zmc2V0IC0gV2VlayBvZmZzZXQgZnJvbSBiYXNlIGRhdGUgKDAgPSBjdXJyZW50IHdlZWspXG4gICAgICogQHBhcmFtIHdvcmtEYXlzIC0gQXJyYXkgb2YgSVNPIHdlZWtkYXkgbnVtYmVycyAoMT1Nb25kYXksIDc9U3VuZGF5KVxuICAgICAqIEByZXR1cm5zIEFycmF5IG9mIGRhdGUgc3RyaW5ncyBpbiBZWVlZLU1NLUREIGZvcm1hdFxuICAgICAqL1xuICAgIGdldFdvcmtXZWVrRGF0ZXMob2Zmc2V0LCB3b3JrRGF5cykge1xuICAgICAgICBjb25zdCBtb25kYXkgPSB0aGlzLmJhc2VEYXRlLnN0YXJ0T2YoJ3dlZWsnKS5hZGQoMSwgJ2RheScpLmFkZChvZmZzZXQsICd3ZWVrJyk7XG4gICAgICAgIHJldHVybiB3b3JrRGF5cy5tYXAoaXNvRGF5ID0+IHtcbiAgICAgICAgICAgIC8vIElTTzogMT1Nb25kYXksIDc9U3VuZGF5IFx1MjE5MiBkYXlzIGZyb20gTW9uZGF5OiAwLTZcbiAgICAgICAgICAgIGNvbnN0IGRheXNGcm9tTW9uZGF5ID0gaXNvRGF5ID09PSA3ID8gNiA6IGlzb0RheSAtIDE7XG4gICAgICAgICAgICByZXR1cm4gbW9uZGF5LmFkZChkYXlzRnJvbU1vbmRheSwgJ2RheScpLmZvcm1hdCgnWVlZWS1NTS1ERCcpO1xuICAgICAgICB9KTtcbiAgICB9XG4gICAgLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cbiAgICAvLyBGT1JNQVRUSU5HXG4gICAgLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cbiAgICBmb3JtYXRUaW1lKGRhdGUsIHNob3dTZWNvbmRzID0gZmFsc2UpIHtcbiAgICAgICAgY29uc3QgcGF0dGVybiA9IHNob3dTZWNvbmRzID8gJ0hIOm1tOnNzJyA6ICdISDptbSc7XG4gICAgICAgIHJldHVybiBkYXlqcyhkYXRlKS5mb3JtYXQocGF0dGVybik7XG4gICAgfVxuICAgIGZvcm1hdFRpbWVSYW5nZShzdGFydCwgZW5kKSB7XG4gICAgICAgIHJldHVybiBgJHt0aGlzLmZvcm1hdFRpbWUoc3RhcnQpfSAtICR7dGhpcy5mb3JtYXRUaW1lKGVuZCl9YDtcbiAgICB9XG4gICAgZm9ybWF0RGF0ZShkYXRlKSB7XG4gICAgICAgIHJldHVybiBkYXlqcyhkYXRlKS5mb3JtYXQoJ1lZWVktTU0tREQnKTtcbiAgICB9XG4gICAgZ2V0RGF0ZUtleShkYXRlKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmZvcm1hdERhdGUoZGF0ZSk7XG4gICAgfVxuICAgIC8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG4gICAgLy8gQ09MVU1OIEtFWVxuICAgIC8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG4gICAgLyoqXG4gICAgICogQnVpbGQgYSB1bmlmb3JtIGNvbHVtbktleSBmcm9tIGdyb3VwaW5nIHNlZ21lbnRzXG4gICAgICogSGFuZGxlcyBhbnkgY29tYmluYXRpb24gb2YgZGF0ZSwgcmVzb3VyY2UsIHRlYW0sIGV0Yy5cbiAgICAgKlxuICAgICAqIEBleGFtcGxlXG4gICAgICogYnVpbGRDb2x1bW5LZXkoeyBkYXRlOiAnMjAyNS0xMi0wOScgfSkgXHUyMTkyIFwiMjAyNS0xMi0wOVwiXG4gICAgICogYnVpbGRDb2x1bW5LZXkoeyBkYXRlOiAnMjAyNS0xMi0wOScsIHJlc291cmNlOiAnRU1QMDAxJyB9KSBcdTIxOTIgXCIyMDI1LTEyLTA5OkVNUDAwMVwiXG4gICAgICovXG4gICAgYnVpbGRDb2x1bW5LZXkoc2VnbWVudHMpIHtcbiAgICAgICAgLy8gQWx3YXlzIHB1dCBkYXRlIGZpcnN0IGlmIHByZXNlbnQsIHRoZW4gb3RoZXIgc2VnbWVudHMgYWxwaGFiZXRpY2FsbHlcbiAgICAgICAgY29uc3QgZGF0ZSA9IHNlZ21lbnRzLmRhdGU7XG4gICAgICAgIGNvbnN0IG90aGVycyA9IE9iamVjdC5lbnRyaWVzKHNlZ21lbnRzKVxuICAgICAgICAgICAgLmZpbHRlcigoW2tdKSA9PiBrICE9PSAnZGF0ZScpXG4gICAgICAgICAgICAuc29ydCgoW2FdLCBbYl0pID0+IGEubG9jYWxlQ29tcGFyZShiKSlcbiAgICAgICAgICAgIC5tYXAoKFssIHZdKSA9PiB2KTtcbiAgICAgICAgcmV0dXJuIGRhdGUgPyBbZGF0ZSwgLi4ub3RoZXJzXS5qb2luKCc6JykgOiBvdGhlcnMuam9pbignOicpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBQYXJzZSBhIGNvbHVtbktleSBiYWNrIGludG8gc2VnbWVudHNcbiAgICAgKiBBc3N1bWVzIGZvcm1hdDogXCJkYXRlOnJlc291cmNlOi4uLlwiIG9yIGp1c3QgXCJkYXRlXCJcbiAgICAgKi9cbiAgICBwYXJzZUNvbHVtbktleShjb2x1bW5LZXkpIHtcbiAgICAgICAgY29uc3QgcGFydHMgPSBjb2x1bW5LZXkuc3BsaXQoJzonKTtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIGRhdGU6IHBhcnRzWzBdLFxuICAgICAgICAgICAgcmVzb3VyY2U6IHBhcnRzWzFdXG4gICAgICAgIH07XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEV4dHJhY3QgZGF0ZUtleSBmcm9tIGNvbHVtbktleSAoZmlyc3Qgc2VnbWVudClcbiAgICAgKi9cbiAgICBnZXREYXRlRnJvbUNvbHVtbktleShjb2x1bW5LZXkpIHtcbiAgICAgICAgcmV0dXJuIGNvbHVtbktleS5zcGxpdCgnOicpWzBdO1xuICAgIH1cbiAgICAvLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuICAgIC8vIFRJTUUgQ0FMQ1VMQVRJT05TXG4gICAgLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cbiAgICB0aW1lVG9NaW51dGVzKHRpbWVTdHJpbmcpIHtcbiAgICAgICAgY29uc3QgcGFydHMgPSB0aW1lU3RyaW5nLnNwbGl0KCc6JykubWFwKE51bWJlcik7XG4gICAgICAgIGNvbnN0IGhvdXJzID0gcGFydHNbMF0gfHwgMDtcbiAgICAgICAgY29uc3QgbWludXRlcyA9IHBhcnRzWzFdIHx8IDA7XG4gICAgICAgIHJldHVybiBob3VycyAqIDYwICsgbWludXRlcztcbiAgICB9XG4gICAgbWludXRlc1RvVGltZSh0b3RhbE1pbnV0ZXMpIHtcbiAgICAgICAgY29uc3QgaG91cnMgPSBNYXRoLmZsb29yKHRvdGFsTWludXRlcyAvIDYwKTtcbiAgICAgICAgY29uc3QgbWludXRlcyA9IHRvdGFsTWludXRlcyAlIDYwO1xuICAgICAgICByZXR1cm4gZGF5anMoKS5ob3VyKGhvdXJzKS5taW51dGUobWludXRlcykuZm9ybWF0KCdISDptbScpO1xuICAgIH1cbiAgICBnZXRNaW51dGVzU2luY2VNaWRuaWdodChkYXRlKSB7XG4gICAgICAgIGNvbnN0IGQgPSBkYXlqcyhkYXRlKTtcbiAgICAgICAgcmV0dXJuIGQuaG91cigpICogNjAgKyBkLm1pbnV0ZSgpO1xuICAgIH1cbiAgICAvLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuICAgIC8vIFVUQyBDT05WRVJTSU9OU1xuICAgIC8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG4gICAgdG9VVEMobG9jYWxEYXRlKSB7XG4gICAgICAgIHJldHVybiBkYXlqcy50eihsb2NhbERhdGUsIHRoaXMudGltZXpvbmUpLnV0YygpLnRvSVNPU3RyaW5nKCk7XG4gICAgfVxuICAgIGZyb21VVEModXRjU3RyaW5nKSB7XG4gICAgICAgIHJldHVybiBkYXlqcy51dGModXRjU3RyaW5nKS50eih0aGlzLnRpbWV6b25lKS50b0RhdGUoKTtcbiAgICB9XG4gICAgLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cbiAgICAvLyBEQVRFIENSRUFUSU9OXG4gICAgLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cbiAgICBjcmVhdGVEYXRlQXRUaW1lKGJhc2VEYXRlLCB0aW1lU3RyaW5nKSB7XG4gICAgICAgIGNvbnN0IHRvdGFsTWludXRlcyA9IHRoaXMudGltZVRvTWludXRlcyh0aW1lU3RyaW5nKTtcbiAgICAgICAgY29uc3QgaG91cnMgPSBNYXRoLmZsb29yKHRvdGFsTWludXRlcyAvIDYwKTtcbiAgICAgICAgY29uc3QgbWludXRlcyA9IHRvdGFsTWludXRlcyAlIDYwO1xuICAgICAgICByZXR1cm4gZGF5anMoYmFzZURhdGUpLnN0YXJ0T2YoJ2RheScpLmhvdXIoaG91cnMpLm1pbnV0ZShtaW51dGVzKS50b0RhdGUoKTtcbiAgICB9XG4gICAgZ2V0SVNPV2Vla0RheShkYXRlKSB7XG4gICAgICAgIHJldHVybiBkYXlqcyhkYXRlKS5pc29XZWVrZGF5KCk7IC8vIDE9TW9uZGF5LCA3PVN1bmRheVxuICAgIH1cbn1cbiIsICIvKipcbiAqIEFic3RyYWN0IGJhc2UgY2xhc3MgZm9yIGdyb3VwaW5nIHJlbmRlcmVyc1xuICpcbiAqIEhhbmRsZXM6XG4gKiAtIEZldGNoaW5nIGVudGl0aWVzIGJ5IElEc1xuICogLSBDYWxjdWxhdGluZyBjb2xzcGFuIGZyb20gcGFyZW50Q2hpbGRNYXBcbiAqIC0gQ3JlYXRpbmcgaGVhZGVyIGVsZW1lbnRzXG4gKiAtIEFwcGVuZGluZyB0byBjb250YWluZXJcbiAqXG4gKiBTdWJjbGFzc2VzIG92ZXJyaWRlOlxuICogLSByZW5kZXJIZWFkZXIoKSBmb3IgY3VzdG9tIGNvbnRlbnRcbiAqIC0gZ2V0RGlzcGxheU5hbWUoKSBmb3IgZW50aXR5IGRpc3BsYXkgdGV4dFxuICovXG5leHBvcnQgY2xhc3MgQmFzZUdyb3VwaW5nUmVuZGVyZXIge1xuICAgIC8qKlxuICAgICAqIE1haW4gcmVuZGVyIG1ldGhvZCAtIGhhbmRsZXMgY29tbW9uIGxvZ2ljXG4gICAgICovXG4gICAgYXN5bmMgcmVuZGVyKGNvbnRleHQpIHtcbiAgICAgICAgY29uc3QgYWxsb3dlZElkcyA9IGNvbnRleHQuZmlsdGVyW3RoaXMudHlwZV0gfHwgW107XG4gICAgICAgIGlmIChhbGxvd2VkSWRzLmxlbmd0aCA9PT0gMClcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgY29uc3QgZW50aXRpZXMgPSBhd2FpdCB0aGlzLmdldEVudGl0aWVzKGFsbG93ZWRJZHMpO1xuICAgICAgICBjb25zdCBkYXRlQ291bnQgPSBjb250ZXh0LmZpbHRlclsnZGF0ZSddPy5sZW5ndGggfHwgMTtcbiAgICAgICAgY29uc3QgY2hpbGRJZHMgPSBjb250ZXh0LmNoaWxkVHlwZSA/IGNvbnRleHQuZmlsdGVyW2NvbnRleHQuY2hpbGRUeXBlXSB8fCBbXSA6IFtdO1xuICAgICAgICBmb3IgKGNvbnN0IGVudGl0eSBvZiBlbnRpdGllcykge1xuICAgICAgICAgICAgY29uc3QgZW50aXR5Q2hpbGRJZHMgPSBjb250ZXh0LnBhcmVudENoaWxkTWFwPy5bZW50aXR5LmlkXSB8fCBbXTtcbiAgICAgICAgICAgIGNvbnN0IGNoaWxkQ291bnQgPSBlbnRpdHlDaGlsZElkcy5maWx0ZXIoaWQgPT4gY2hpbGRJZHMuaW5jbHVkZXMoaWQpKS5sZW5ndGg7XG4gICAgICAgICAgICBjb25zdCBjb2xzcGFuID0gY2hpbGRDb3VudCAqIGRhdGVDb3VudDtcbiAgICAgICAgICAgIGNvbnN0IGhlYWRlciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQodGhpcy5jb25maWcuZWxlbWVudFRhZyk7XG4gICAgICAgICAgICBoZWFkZXIuZGF0YXNldFt0aGlzLmNvbmZpZy5pZEF0dHJpYnV0ZV0gPSBlbnRpdHkuaWQ7XG4gICAgICAgICAgICBoZWFkZXIuc3R5bGUuc2V0UHJvcGVydHkodGhpcy5jb25maWcuY29sc3BhblZhciwgU3RyaW5nKGNvbHNwYW4pKTtcbiAgICAgICAgICAgIC8vIEFsbG93IHN1YmNsYXNzIHRvIGN1c3RvbWl6ZSBoZWFkZXIgY29udGVudFxuICAgICAgICAgICAgdGhpcy5yZW5kZXJIZWFkZXIoZW50aXR5LCBoZWFkZXIsIGNvbnRleHQpO1xuICAgICAgICAgICAgY29udGV4dC5oZWFkZXJDb250YWluZXIuYXBwZW5kQ2hpbGQoaGVhZGVyKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBPdmVycmlkZSB0aGlzIG1ldGhvZCBmb3IgY3VzdG9tIGhlYWRlciByZW5kZXJpbmdcbiAgICAgKiBEZWZhdWx0OiBqdXN0IHNldHMgdGV4dENvbnRlbnQgdG8gZGlzcGxheSBuYW1lXG4gICAgICovXG4gICAgcmVuZGVySGVhZGVyKGVudGl0eSwgaGVhZGVyLCBfY29udGV4dCkge1xuICAgICAgICBoZWFkZXIudGV4dENvbnRlbnQgPSB0aGlzLmdldERpc3BsYXlOYW1lKGVudGl0eSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEhlbHBlciB0byByZW5kZXIgYSBzaW5nbGUgZW50aXR5IGhlYWRlci5cbiAgICAgKiBDYW4gYmUgdXNlZCBieSBzdWJjbGFzc2VzIHRoYXQgb3ZlcnJpZGUgcmVuZGVyKCkgYnV0IHdhbnQgY29uc2lzdGVudCBoZWFkZXIgY3JlYXRpb24uXG4gICAgICovXG4gICAgY3JlYXRlSGVhZGVyKGVudGl0eSwgY29udGV4dCkge1xuICAgICAgICBjb25zdCBoZWFkZXIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KHRoaXMuY29uZmlnLmVsZW1lbnRUYWcpO1xuICAgICAgICBoZWFkZXIuZGF0YXNldFt0aGlzLmNvbmZpZy5pZEF0dHJpYnV0ZV0gPSBlbnRpdHkuaWQ7XG4gICAgICAgIHRoaXMucmVuZGVySGVhZGVyKGVudGl0eSwgaGVhZGVyLCBjb250ZXh0KTtcbiAgICAgICAgcmV0dXJuIGhlYWRlcjtcbiAgICB9XG59XG4iLCAiaW1wb3J0IHsgQmFzZUdyb3VwaW5nUmVuZGVyZXIgfSBmcm9tICcuLi8uLi9jb3JlL0Jhc2VHcm91cGluZ1JlbmRlcmVyJztcbmV4cG9ydCBjbGFzcyBSZXNvdXJjZVJlbmRlcmVyIGV4dGVuZHMgQmFzZUdyb3VwaW5nUmVuZGVyZXIge1xuICAgIGNvbnN0cnVjdG9yKHJlc291cmNlU2VydmljZSkge1xuICAgICAgICBzdXBlcigpO1xuICAgICAgICB0aGlzLnJlc291cmNlU2VydmljZSA9IHJlc291cmNlU2VydmljZTtcbiAgICAgICAgdGhpcy50eXBlID0gJ3Jlc291cmNlJztcbiAgICAgICAgdGhpcy5jb25maWcgPSB7XG4gICAgICAgICAgICBlbGVtZW50VGFnOiAnc3dwLXJlc291cmNlLWhlYWRlcicsXG4gICAgICAgICAgICBpZEF0dHJpYnV0ZTogJ3Jlc291cmNlSWQnLFxuICAgICAgICAgICAgY29sc3BhblZhcjogJy0tcmVzb3VyY2UtY29scydcbiAgICAgICAgfTtcbiAgICB9XG4gICAgZ2V0RW50aXRpZXMoaWRzKSB7XG4gICAgICAgIHJldHVybiB0aGlzLnJlc291cmNlU2VydmljZS5nZXRCeUlkcyhpZHMpO1xuICAgIH1cbiAgICBnZXREaXNwbGF5TmFtZShlbnRpdHkpIHtcbiAgICAgICAgcmV0dXJuIGVudGl0eS5kaXNwbGF5TmFtZTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogT3ZlcnJpZGUgcmVuZGVyIHRvIGhhbmRsZTpcbiAgICAgKiAxLiBTcGVjaWFsIG9yZGVyaW5nIHdoZW4gcGFyZW50Q2hpbGRNYXAgZXhpc3RzIChyZXNvdXJjZXMgZ3JvdXBlZCBieSBwYXJlbnQpXG4gICAgICogMi4gRGlmZmVyZW50IGNvbHNwYW4gY2FsY3VsYXRpb24gKGp1c3QgZGF0ZUNvdW50LCBub3QgY2hpbGRDb3VudCAqIGRhdGVDb3VudClcbiAgICAgKi9cbiAgICBhc3luYyByZW5kZXIoY29udGV4dCkge1xuICAgICAgICBjb25zdCByZXNvdXJjZUlkcyA9IGNvbnRleHQuZmlsdGVyWydyZXNvdXJjZSddIHx8IFtdO1xuICAgICAgICBjb25zdCBkYXRlQ291bnQgPSBjb250ZXh0LmZpbHRlclsnZGF0ZSddPy5sZW5ndGggfHwgMTtcbiAgICAgICAgLy8gRGV0ZXJtaW5lIHJlbmRlciBvcmRlciBiYXNlZCBvbiBwYXJlbnRDaGlsZE1hcFxuICAgICAgICAvLyBJZiBwYXJlbnRDaGlsZE1hcCBleGlzdHMsIHJlbmRlciByZXNvdXJjZXMgZ3JvdXBlZCBieSBwYXJlbnQgKGUuZy4sIHRlYW0pXG4gICAgICAgIC8vIE90aGVyd2lzZSwgcmVuZGVyIGluIGZpbHRlciBvcmRlclxuICAgICAgICBsZXQgb3JkZXJlZFJlc291cmNlSWRzO1xuICAgICAgICBpZiAoY29udGV4dC5wYXJlbnRDaGlsZE1hcCkge1xuICAgICAgICAgICAgLy8gUmVuZGVyIHJlc291cmNlcyBpbiBwYXJlbnQtY2hpbGQgb3JkZXJcbiAgICAgICAgICAgIG9yZGVyZWRSZXNvdXJjZUlkcyA9IFtdO1xuICAgICAgICAgICAgZm9yIChjb25zdCBjaGlsZElkcyBvZiBPYmplY3QudmFsdWVzKGNvbnRleHQucGFyZW50Q2hpbGRNYXApKSB7XG4gICAgICAgICAgICAgICAgZm9yIChjb25zdCBjaGlsZElkIG9mIGNoaWxkSWRzKSB7XG4gICAgICAgICAgICAgICAgICAgIGlmIChyZXNvdXJjZUlkcy5pbmNsdWRlcyhjaGlsZElkKSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgb3JkZXJlZFJlc291cmNlSWRzLnB1c2goY2hpbGRJZCk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBvcmRlcmVkUmVzb3VyY2VJZHMgPSByZXNvdXJjZUlkcztcbiAgICAgICAgfVxuICAgICAgICBjb25zdCByZXNvdXJjZXMgPSBhd2FpdCB0aGlzLmdldEVudGl0aWVzKG9yZGVyZWRSZXNvdXJjZUlkcyk7XG4gICAgICAgIC8vIENyZWF0ZSBhIG1hcCBmb3IgcXVpY2sgbG9va3VwIHRvIHByZXNlcnZlIG9yZGVyXG4gICAgICAgIGNvbnN0IHJlc291cmNlTWFwID0gbmV3IE1hcChyZXNvdXJjZXMubWFwKHIgPT4gW3IuaWQsIHJdKSk7XG4gICAgICAgIGZvciAoY29uc3QgcmVzb3VyY2VJZCBvZiBvcmRlcmVkUmVzb3VyY2VJZHMpIHtcbiAgICAgICAgICAgIGNvbnN0IHJlc291cmNlID0gcmVzb3VyY2VNYXAuZ2V0KHJlc291cmNlSWQpO1xuICAgICAgICAgICAgaWYgKCFyZXNvdXJjZSlcbiAgICAgICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICAgIGNvbnN0IGhlYWRlciA9IHRoaXMuY3JlYXRlSGVhZGVyKHJlc291cmNlLCBjb250ZXh0KTtcbiAgICAgICAgICAgIGhlYWRlci5zdHlsZS5ncmlkQ29sdW1uID0gYHNwYW4gJHtkYXRlQ291bnR9YDtcbiAgICAgICAgICAgIGNvbnRleHQuaGVhZGVyQ29udGFpbmVyLmFwcGVuZENoaWxkKGhlYWRlcik7XG4gICAgICAgIH1cbiAgICB9XG59XG4iLCAiaW1wb3J0IHsgQmFzZUdyb3VwaW5nUmVuZGVyZXIgfSBmcm9tICcuLi8uLi9jb3JlL0Jhc2VHcm91cGluZ1JlbmRlcmVyJztcbmV4cG9ydCBjbGFzcyBUZWFtUmVuZGVyZXIgZXh0ZW5kcyBCYXNlR3JvdXBpbmdSZW5kZXJlciB7XG4gICAgY29uc3RydWN0b3IodGVhbVNlcnZpY2UpIHtcbiAgICAgICAgc3VwZXIoKTtcbiAgICAgICAgdGhpcy50ZWFtU2VydmljZSA9IHRlYW1TZXJ2aWNlO1xuICAgICAgICB0aGlzLnR5cGUgPSAndGVhbSc7XG4gICAgICAgIHRoaXMuY29uZmlnID0ge1xuICAgICAgICAgICAgZWxlbWVudFRhZzogJ3N3cC10ZWFtLWhlYWRlcicsXG4gICAgICAgICAgICBpZEF0dHJpYnV0ZTogJ3RlYW1JZCcsXG4gICAgICAgICAgICBjb2xzcGFuVmFyOiAnLS10ZWFtLWNvbHMnXG4gICAgICAgIH07XG4gICAgfVxuICAgIGdldEVudGl0aWVzKGlkcykge1xuICAgICAgICByZXR1cm4gdGhpcy50ZWFtU2VydmljZS5nZXRCeUlkcyhpZHMpO1xuICAgIH1cbiAgICBnZXREaXNwbGF5TmFtZShlbnRpdHkpIHtcbiAgICAgICAgcmV0dXJuIGVudGl0eS5uYW1lO1xuICAgIH1cbn1cbiIsICJpbXBvcnQgeyBCYXNlR3JvdXBpbmdSZW5kZXJlciB9IGZyb20gJy4uLy4uL2NvcmUvQmFzZUdyb3VwaW5nUmVuZGVyZXInO1xuZXhwb3J0IGNsYXNzIERlcGFydG1lbnRSZW5kZXJlciBleHRlbmRzIEJhc2VHcm91cGluZ1JlbmRlcmVyIHtcbiAgICBjb25zdHJ1Y3RvcihkZXBhcnRtZW50U2VydmljZSkge1xuICAgICAgICBzdXBlcigpO1xuICAgICAgICB0aGlzLmRlcGFydG1lbnRTZXJ2aWNlID0gZGVwYXJ0bWVudFNlcnZpY2U7XG4gICAgICAgIHRoaXMudHlwZSA9ICdkZXBhcnRtZW50JztcbiAgICAgICAgdGhpcy5jb25maWcgPSB7XG4gICAgICAgICAgICBlbGVtZW50VGFnOiAnc3dwLWRlcGFydG1lbnQtaGVhZGVyJyxcbiAgICAgICAgICAgIGlkQXR0cmlidXRlOiAnZGVwYXJ0bWVudElkJyxcbiAgICAgICAgICAgIGNvbHNwYW5WYXI6ICctLWRlcGFydG1lbnQtY29scydcbiAgICAgICAgfTtcbiAgICB9XG4gICAgZ2V0RW50aXRpZXMoaWRzKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmRlcGFydG1lbnRTZXJ2aWNlLmdldEJ5SWRzKGlkcyk7XG4gICAgfVxuICAgIGdldERpc3BsYXlOYW1lKGVudGl0eSkge1xuICAgICAgICByZXR1cm4gZW50aXR5Lm5hbWU7XG4gICAgfVxufVxuIiwgImV4cG9ydCBmdW5jdGlvbiBidWlsZFBpcGVsaW5lKHJlbmRlcmVycykge1xuICAgIHJldHVybiB7XG4gICAgICAgIGFzeW5jIHJ1bihjb250ZXh0KSB7XG4gICAgICAgICAgICBmb3IgKGNvbnN0IHJlbmRlcmVyIG9mIHJlbmRlcmVycykge1xuICAgICAgICAgICAgICAgIGF3YWl0IHJlbmRlcmVyLnJlbmRlcihjb250ZXh0KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH07XG59XG4iLCAiLyoqXG4gKiBGaWx0ZXJUZW1wbGF0ZSAtIEJ5Z2dlciBuXHUwMEY4Z2xlciB0aWwgZXZlbnQta29sb25uZSBtYXRjaGluZ1xuICpcbiAqIFZpZXdDb25maWcgZGVmaW5lcmVyIGh2aWxrZSBmZWx0ZXIgKGlkUHJvcGVydGllcykgZGVyIGluZGdcdTAwRTVyIGkga29sb25uZW5zIG5cdTAwRjhnbGUuXG4gKiBTYW1tZSB0ZW1wbGF0ZSBicnVnZXMgdGlsIGF0IGJ5Z2dlIG5cdTAwRjhnbGUgZm9yIGJcdTAwRTVkZSBrb2xvbm5lIG9nIGV2ZW50LlxuICpcbiAqIFN1cHBvcnRzIGRvdC1ub3RhdGlvbiBmb3IgaGllcmFyY2hpY2FsIHJlbGF0aW9uczpcbiAqIC0gJ3Jlc291cmNlLnRlYW1JZCcgXHUyMTkyIGxvb2tzIHVwIGV2ZW50LnJlc291cmNlSWQgXHUyMTkyIHJlc291cmNlIGVudGl0eSBcdTIxOTIgdGVhbUlkXG4gKlxuICogUHJpbmNpcDogS29sb25uZW5zIG5cdTAwRjhnbGUtdGVtcGxhdGUgYmVzdGVtbWVyIGh2YWQgZGVyIG1hdGNoZXMgcFx1MDBFNS5cbiAqXG4gKiBAc2VlIGRvY3MvZmlsdGVyLXRlbXBsYXRlLm1kXG4gKi9cbmV4cG9ydCBjbGFzcyBGaWx0ZXJUZW1wbGF0ZSB7XG4gICAgY29uc3RydWN0b3IoZGF0ZVNlcnZpY2UsIGVudGl0eVJlc29sdmVyKSB7XG4gICAgICAgIHRoaXMuZGF0ZVNlcnZpY2UgPSBkYXRlU2VydmljZTtcbiAgICAgICAgdGhpcy5lbnRpdHlSZXNvbHZlciA9IGVudGl0eVJlc29sdmVyO1xuICAgICAgICB0aGlzLmZpZWxkcyA9IFtdO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBUaWxmXHUwMEY4aiBmZWx0IHRpbCB0ZW1wbGF0ZVxuICAgICAqIEBwYXJhbSBpZFByb3BlcnR5IC0gUHJvcGVydHktbmF2biAoYnJ1Z2VzIHBcdTAwRTUgYlx1MDBFNWRlIGV2ZW50IG9nIGNvbHVtbi5kYXRhc2V0KVxuICAgICAqIEBwYXJhbSBkZXJpdmVkRnJvbSAtIEh2aXMgZmVsdGV0IHVkbGVkZXMgZnJhIGFuZGVuIHByb3BlcnR5IChmLmVrcy4gZGF0ZSBmcmEgc3RhcnQpXG4gICAgICovXG4gICAgYWRkRmllbGQoaWRQcm9wZXJ0eSwgZGVyaXZlZEZyb20pIHtcbiAgICAgICAgdGhpcy5maWVsZHMucHVzaCh7IGlkUHJvcGVydHksIGRlcml2ZWRGcm9tIH0pO1xuICAgICAgICByZXR1cm4gdGhpcztcbiAgICB9XG4gICAgLyoqXG4gICAgICogUGFyc2UgZG90LW5vdGF0aW9uIHN0cmluZyBpbnRvIGNvbXBvbmVudHNcbiAgICAgKiBAZXhhbXBsZSAncmVzb3VyY2UudGVhbUlkJyBcdTIxOTIgeyBlbnRpdHlUeXBlOiAncmVzb3VyY2UnLCBwcm9wZXJ0eTogJ3RlYW1JZCcsIGZvcmVpZ25LZXk6ICdyZXNvdXJjZUlkJyB9XG4gICAgICovXG4gICAgcGFyc2VEb3ROb3RhdGlvbihpZFByb3BlcnR5KSB7XG4gICAgICAgIGlmICghaWRQcm9wZXJ0eS5pbmNsdWRlcygnLicpKVxuICAgICAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICAgIGNvbnN0IFtlbnRpdHlUeXBlLCBwcm9wZXJ0eV0gPSBpZFByb3BlcnR5LnNwbGl0KCcuJyk7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICBlbnRpdHlUeXBlLFxuICAgICAgICAgICAgcHJvcGVydHksXG4gICAgICAgICAgICBmb3JlaWduS2V5OiBlbnRpdHlUeXBlICsgJ0lkJyAvLyBDb252ZW50aW9uOiByZXNvdXJjZSBcdTIxOTIgcmVzb3VyY2VJZFxuICAgICAgICB9O1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgZGF0YXNldCBrZXkgZm9yIGNvbHVtbiBsb29rdXBcbiAgICAgKiBGb3IgZG90LW5vdGF0aW9uICdyZXNvdXJjZS50ZWFtSWQnLCB3ZSBsb29rIGZvciAndGVhbUlkJyBpbiBkYXRhc2V0XG4gICAgICovXG4gICAgZ2V0RGF0YXNldEtleShpZFByb3BlcnR5KSB7XG4gICAgICAgIGNvbnN0IGRvdE5vdGF0aW9uID0gdGhpcy5wYXJzZURvdE5vdGF0aW9uKGlkUHJvcGVydHkpO1xuICAgICAgICBpZiAoZG90Tm90YXRpb24pIHtcbiAgICAgICAgICAgIHJldHVybiBkb3ROb3RhdGlvbi5wcm9wZXJ0eTsgLy8gJ3RlYW1JZCdcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gaWRQcm9wZXJ0eTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogQnlnIG5cdTAwRjhnbGUgZnJhIGtvbG9ubmVcbiAgICAgKiBMXHUwMEU2c2VyIHZcdTAwRTZyZGllciBmcmEgY29sdW1uLmRhdGFzZXRbaWRQcm9wZXJ0eV1cbiAgICAgKiBGb3IgZG90LW5vdGF0aW9uLCB1c2VzIHRoZSBwcm9wZXJ0eSBwYXJ0IChyZXNvdXJjZS50ZWFtSWQgXHUyMTkyIHRlYW1JZClcbiAgICAgKi9cbiAgICBidWlsZEtleUZyb21Db2x1bW4oY29sdW1uKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmZpZWxkc1xuICAgICAgICAgICAgLm1hcChmID0+IHtcbiAgICAgICAgICAgIGNvbnN0IGtleSA9IHRoaXMuZ2V0RGF0YXNldEtleShmLmlkUHJvcGVydHkpO1xuICAgICAgICAgICAgcmV0dXJuIGNvbHVtbi5kYXRhc2V0W2tleV0gfHwgJyc7XG4gICAgICAgIH0pXG4gICAgICAgICAgICAuam9pbignOicpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBCeWcgblx1MDBGOGdsZSBmcmEgZXZlbnRcbiAgICAgKiBMXHUwMEU2c2VyIHZcdTAwRTZyZGllciBmcmEgZXZlbnRbaWRQcm9wZXJ0eV0gZWxsZXIgdWRsZWRlciBmcmEgZGVyaXZlZEZyb21cbiAgICAgKiBGb3IgZG90LW5vdGF0aW9uLCByZXNvbHZlcyB2aWEgRW50aXR5UmVzb2x2ZXJcbiAgICAgKi9cbiAgICBidWlsZEtleUZyb21FdmVudChldmVudCkge1xuICAgICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLWV4cGxpY2l0LWFueVxuICAgICAgICBjb25zdCBldmVudFJlY29yZCA9IGV2ZW50O1xuICAgICAgICByZXR1cm4gdGhpcy5maWVsZHNcbiAgICAgICAgICAgIC5tYXAoZiA9PiB7XG4gICAgICAgICAgICAvLyBDaGVjayBmb3IgZG90LW5vdGF0aW9uIChlLmcuLCAncmVzb3VyY2UudGVhbUlkJylcbiAgICAgICAgICAgIGNvbnN0IGRvdE5vdGF0aW9uID0gdGhpcy5wYXJzZURvdE5vdGF0aW9uKGYuaWRQcm9wZXJ0eSk7XG4gICAgICAgICAgICBpZiAoZG90Tm90YXRpb24pIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcy5yZXNvbHZlRG90Tm90YXRpb24oZXZlbnRSZWNvcmQsIGRvdE5vdGF0aW9uKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGlmIChmLmRlcml2ZWRGcm9tKSB7XG4gICAgICAgICAgICAgICAgLy8gVWRsZWQgdlx1MDBFNnJkaSAoZi5la3MuIGRhdGUgZnJhIHN0YXJ0KVxuICAgICAgICAgICAgICAgIGNvbnN0IHNvdXJjZVZhbHVlID0gZXZlbnRSZWNvcmRbZi5kZXJpdmVkRnJvbV07XG4gICAgICAgICAgICAgICAgaWYgKHNvdXJjZVZhbHVlIGluc3RhbmNlb2YgRGF0ZSkge1xuICAgICAgICAgICAgICAgICAgICByZXR1cm4gdGhpcy5kYXRlU2VydmljZS5nZXREYXRlS2V5KHNvdXJjZVZhbHVlKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgcmV0dXJuIFN0cmluZyhzb3VyY2VWYWx1ZSB8fCAnJyk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gU3RyaW5nKGV2ZW50UmVjb3JkW2YuaWRQcm9wZXJ0eV0gfHwgJycpO1xuICAgICAgICB9KVxuICAgICAgICAgICAgLmpvaW4oJzonKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogUmVzb2x2ZSBkb3Qtbm90YXRpb24gcmVmZXJlbmNlIHZpYSBFbnRpdHlSZXNvbHZlclxuICAgICAqL1xuICAgIHJlc29sdmVEb3ROb3RhdGlvbihldmVudFJlY29yZCwgZG90Tm90YXRpb24pIHtcbiAgICAgICAgaWYgKCF0aGlzLmVudGl0eVJlc29sdmVyKSB7XG4gICAgICAgICAgICBjb25zb2xlLndhcm4oYEZpbHRlclRlbXBsYXRlOiBFbnRpdHlSZXNvbHZlciByZXF1aXJlZCBmb3IgZG90LW5vdGF0aW9uICcke2RvdE5vdGF0aW9uLmVudGl0eVR5cGV9LiR7ZG90Tm90YXRpb24ucHJvcGVydHl9J2ApO1xuICAgICAgICAgICAgcmV0dXJuICcnO1xuICAgICAgICB9XG4gICAgICAgIC8vIEdldCBmb3JlaWduIGtleSB2YWx1ZSBmcm9tIGV2ZW50IChlLmcuLCByZXNvdXJjZUlkKVxuICAgICAgICBjb25zdCBmb3JlaWduSWQgPSBldmVudFJlY29yZFtkb3ROb3RhdGlvbi5mb3JlaWduS2V5XTtcbiAgICAgICAgaWYgKCFmb3JlaWduSWQpXG4gICAgICAgICAgICByZXR1cm4gJyc7XG4gICAgICAgIC8vIFJlc29sdmUgZW50aXR5XG4gICAgICAgIGNvbnN0IGVudGl0eSA9IHRoaXMuZW50aXR5UmVzb2x2ZXIucmVzb2x2ZShkb3ROb3RhdGlvbi5lbnRpdHlUeXBlLCBTdHJpbmcoZm9yZWlnbklkKSk7XG4gICAgICAgIGlmICghZW50aXR5KVxuICAgICAgICAgICAgcmV0dXJuICcnO1xuICAgICAgICAvLyBSZXR1cm4gcHJvcGVydHkgdmFsdWUgZnJvbSBlbnRpdHlcbiAgICAgICAgcmV0dXJuIFN0cmluZyhlbnRpdHlbZG90Tm90YXRpb24ucHJvcGVydHldIHx8ICcnKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogTWF0Y2ggZXZlbnQgbW9kIGtvbG9ubmVcbiAgICAgKi9cbiAgICBtYXRjaGVzKGV2ZW50LCBjb2x1bW4pIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuYnVpbGRLZXlGcm9tRXZlbnQoZXZlbnQpID09PSB0aGlzLmJ1aWxkS2V5RnJvbUNvbHVtbihjb2x1bW4pO1xuICAgIH1cbn1cbiIsICJpbXBvcnQgeyBidWlsZFBpcGVsaW5lIH0gZnJvbSAnLi9SZW5kZXJCdWlsZGVyJztcbmltcG9ydCB7IEZpbHRlclRlbXBsYXRlIH0gZnJvbSAnLi9GaWx0ZXJUZW1wbGF0ZSc7XG5leHBvcnQgY2xhc3MgQ2FsZW5kYXJPcmNoZXN0cmF0b3Ige1xuICAgIGNvbnN0cnVjdG9yKGFsbFJlbmRlcmVycywgZXZlbnRSZW5kZXJlciwgc2NoZWR1bGVSZW5kZXJlciwgaGVhZGVyRHJhd2VyUmVuZGVyZXIsIGRhdGVTZXJ2aWNlLCBlbnRpdHlTZXJ2aWNlcykge1xuICAgICAgICB0aGlzLmFsbFJlbmRlcmVycyA9IGFsbFJlbmRlcmVycztcbiAgICAgICAgdGhpcy5ldmVudFJlbmRlcmVyID0gZXZlbnRSZW5kZXJlcjtcbiAgICAgICAgdGhpcy5zY2hlZHVsZVJlbmRlcmVyID0gc2NoZWR1bGVSZW5kZXJlcjtcbiAgICAgICAgdGhpcy5oZWFkZXJEcmF3ZXJSZW5kZXJlciA9IGhlYWRlckRyYXdlclJlbmRlcmVyO1xuICAgICAgICB0aGlzLmRhdGVTZXJ2aWNlID0gZGF0ZVNlcnZpY2U7XG4gICAgICAgIHRoaXMuZW50aXR5U2VydmljZXMgPSBlbnRpdHlTZXJ2aWNlcztcbiAgICB9XG4gICAgYXN5bmMgcmVuZGVyKHZpZXdDb25maWcsIGNvbnRhaW5lcikge1xuICAgICAgICBjb25zdCBoZWFkZXJDb250YWluZXIgPSBjb250YWluZXIucXVlcnlTZWxlY3Rvcignc3dwLWNhbGVuZGFyLWhlYWRlcicpO1xuICAgICAgICBjb25zdCBjb2x1bW5Db250YWluZXIgPSBjb250YWluZXIucXVlcnlTZWxlY3Rvcignc3dwLWRheS1jb2x1bW5zJyk7XG4gICAgICAgIGlmICghaGVhZGVyQ29udGFpbmVyIHx8ICFjb2x1bW5Db250YWluZXIpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcignTWlzc2luZyBzd3AtY2FsZW5kYXItaGVhZGVyIG9yIHN3cC1kYXktY29sdW1ucycpO1xuICAgICAgICB9XG4gICAgICAgIC8vIEJ5ZyBmaWx0ZXIgZnJhIHZpZXdDb25maWdcbiAgICAgICAgY29uc3QgZmlsdGVyID0ge307XG4gICAgICAgIGZvciAoY29uc3QgZ3JvdXBpbmcgb2Ygdmlld0NvbmZpZy5ncm91cGluZ3MpIHtcbiAgICAgICAgICAgIGZpbHRlcltncm91cGluZy50eXBlXSA9IGdyb3VwaW5nLnZhbHVlcztcbiAgICAgICAgfVxuICAgICAgICAvLyBCeWcgRmlsdGVyVGVtcGxhdGUgZnJhIHZpZXdDb25maWcgZ3JvdXBpbmdzIChrdW4gZGUgbWVkIGlkUHJvcGVydHkpXG4gICAgICAgIGNvbnN0IGZpbHRlclRlbXBsYXRlID0gbmV3IEZpbHRlclRlbXBsYXRlKHRoaXMuZGF0ZVNlcnZpY2UpO1xuICAgICAgICBmb3IgKGNvbnN0IGdyb3VwaW5nIG9mIHZpZXdDb25maWcuZ3JvdXBpbmdzKSB7XG4gICAgICAgICAgICBpZiAoZ3JvdXBpbmcuaWRQcm9wZXJ0eSkge1xuICAgICAgICAgICAgICAgIGZpbHRlclRlbXBsYXRlLmFkZEZpZWxkKGdyb3VwaW5nLmlkUHJvcGVydHksIGdyb3VwaW5nLmRlcml2ZWRGcm9tKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICAvLyBSZXNvbHZlIGJlbG9uZ3NUbyByZWxhdGlvbnMgKGUuZy4sIHRlYW0ucmVzb3VyY2VJZHMpXG4gICAgICAgIGNvbnN0IHsgcGFyZW50Q2hpbGRNYXAsIGNoaWxkVHlwZSB9ID0gYXdhaXQgdGhpcy5yZXNvbHZlQmVsb25nc1RvKHZpZXdDb25maWcuZ3JvdXBpbmdzLCBmaWx0ZXIpO1xuICAgICAgICBjb25zdCBjb250ZXh0ID0geyBoZWFkZXJDb250YWluZXIsIGNvbHVtbkNvbnRhaW5lciwgZmlsdGVyLCBncm91cGluZ3M6IHZpZXdDb25maWcuZ3JvdXBpbmdzLCBwYXJlbnRDaGlsZE1hcCwgY2hpbGRUeXBlIH07XG4gICAgICAgIC8vIENsZWFyXG4gICAgICAgIGhlYWRlckNvbnRhaW5lci5pbm5lckhUTUwgPSAnJztcbiAgICAgICAgY29sdW1uQ29udGFpbmVyLmlubmVySFRNTCA9ICcnO1xuICAgICAgICAvLyBTXHUwMEU2dCBkYXRhLWxldmVscyBhdHRyaWJ1dCBmb3IgQ1NTIGdyaWQtcm93IHN0eWxpbmdcbiAgICAgICAgY29uc3QgbGV2ZWxzID0gdmlld0NvbmZpZy5ncm91cGluZ3MubWFwKGcgPT4gZy50eXBlKS5qb2luKCcgJyk7XG4gICAgICAgIGhlYWRlckNvbnRhaW5lci5kYXRhc2V0LmxldmVscyA9IGxldmVscztcbiAgICAgICAgLy8gVlx1MDBFNmxnIHJlbmRlcmVycyBiYXNlcmV0IHBcdTAwRTUgZ3JvdXBpbmdzIHR5cGVzXG4gICAgICAgIGNvbnN0IGFjdGl2ZVJlbmRlcmVycyA9IHRoaXMuc2VsZWN0UmVuZGVyZXJzKHZpZXdDb25maWcpO1xuICAgICAgICAvLyBCeWcgb2cga1x1MDBGOHIgcGlwZWxpbmVcbiAgICAgICAgY29uc3QgcGlwZWxpbmUgPSBidWlsZFBpcGVsaW5lKGFjdGl2ZVJlbmRlcmVycyk7XG4gICAgICAgIGF3YWl0IHBpcGVsaW5lLnJ1bihjb250ZXh0KTtcbiAgICAgICAgLy8gUmVuZGVyIHNjaGVkdWxlIHVuYXZhaWxhYmxlIHpvbmVzIChmXHUwMEY4ciBldmVudHMpXG4gICAgICAgIGF3YWl0IHRoaXMuc2NoZWR1bGVSZW5kZXJlci5yZW5kZXIoY29udGFpbmVyLCBmaWx0ZXIpO1xuICAgICAgICAvLyBSZW5kZXIgdGltZWQgZXZlbnRzIGluIGdyaWQgKG1lZCBmaWx0ZXJUZW1wbGF0ZSB0aWwgbWF0Y2hpbmcpXG4gICAgICAgIGF3YWl0IHRoaXMuZXZlbnRSZW5kZXJlci5yZW5kZXIoY29udGFpbmVyLCBmaWx0ZXIsIGZpbHRlclRlbXBsYXRlKTtcbiAgICAgICAgLy8gUmVuZGVyIGFsbERheSBldmVudHMgaW4gaGVhZGVyIGRyYXdlciAobWVkIGZpbHRlclRlbXBsYXRlIHRpbCBtYXRjaGluZylcbiAgICAgICAgYXdhaXQgdGhpcy5oZWFkZXJEcmF3ZXJSZW5kZXJlci5yZW5kZXIoY29udGFpbmVyLCBmaWx0ZXIsIGZpbHRlclRlbXBsYXRlKTtcbiAgICB9XG4gICAgc2VsZWN0UmVuZGVyZXJzKHZpZXdDb25maWcpIHtcbiAgICAgICAgY29uc3QgdHlwZXMgPSB2aWV3Q29uZmlnLmdyb3VwaW5ncy5tYXAoZyA9PiBnLnR5cGUpO1xuICAgICAgICAvLyBTb3J0XHUwMEU5ciByZW5kZXJlcnMgaSBzYW1tZSByXHUwMEU2a2tlZlx1MDBGOGxnZSBzb20gdmlld0NvbmZpZy5ncm91cGluZ3NcbiAgICAgICAgcmV0dXJuIHR5cGVzXG4gICAgICAgICAgICAubWFwKHR5cGUgPT4gdGhpcy5hbGxSZW5kZXJlcnMuZmluZChyID0+IHIudHlwZSA9PT0gdHlwZSkpXG4gICAgICAgICAgICAuZmlsdGVyKChyKSA9PiByICE9PSB1bmRlZmluZWQpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZXNvbHZlIGJlbG9uZ3NUbyByZWxhdGlvbnMgdG8gYnVpbGQgcGFyZW50LWNoaWxkIG1hcFxuICAgICAqIGUuZy4sIGJlbG9uZ3NUbzogJ3RlYW0ucmVzb3VyY2VJZHMnIFx1MjE5MiB7IHRlYW0xOiBbJ0VNUDAwMScsICdFTVAwMDInXSwgdGVhbTI6IFsuLi5dIH1cbiAgICAgKiBBbHNvIHJldHVybnMgdGhlIGNoaWxkVHlwZSAodGhlIGdyb3VwaW5nIHR5cGUgdGhhdCBoYXMgYmVsb25nc1RvKVxuICAgICAqL1xuICAgIGFzeW5jIHJlc29sdmVCZWxvbmdzVG8oZ3JvdXBpbmdzLCBmaWx0ZXIpIHtcbiAgICAgICAgLy8gRmluZCBncm91cGluZyB3aXRoIGJlbG9uZ3NUb1xuICAgICAgICBjb25zdCBjaGlsZEdyb3VwaW5nID0gZ3JvdXBpbmdzLmZpbmQoZyA9PiBnLmJlbG9uZ3NUbyk7XG4gICAgICAgIGlmICghY2hpbGRHcm91cGluZz8uYmVsb25nc1RvKVxuICAgICAgICAgICAgcmV0dXJuIHt9O1xuICAgICAgICAvLyBQYXJzZSBiZWxvbmdzVG86ICd0ZWFtLnJlc291cmNlSWRzJ1xuICAgICAgICBjb25zdCBbZW50aXR5VHlwZSwgcHJvcGVydHldID0gY2hpbGRHcm91cGluZy5iZWxvbmdzVG8uc3BsaXQoJy4nKTtcbiAgICAgICAgaWYgKCFlbnRpdHlUeXBlIHx8ICFwcm9wZXJ0eSlcbiAgICAgICAgICAgIHJldHVybiB7fTtcbiAgICAgICAgLy8gR2V0IHBhcmVudCBJRHMgZnJvbSBmaWx0ZXJcbiAgICAgICAgY29uc3QgcGFyZW50SWRzID0gZmlsdGVyW2VudGl0eVR5cGVdIHx8IFtdO1xuICAgICAgICBpZiAocGFyZW50SWRzLmxlbmd0aCA9PT0gMClcbiAgICAgICAgICAgIHJldHVybiB7fTtcbiAgICAgICAgLy8gRmluZCBzZXJ2aWNlIGR5bmFtaXNrIGJhc2VyZXQgcFx1MDBFNSBlbnRpdHlUeXBlIChpbmdlbiBoYXJkY29kZWQgdHlwZSBjaGVjaylcbiAgICAgICAgY29uc3Qgc2VydmljZSA9IHRoaXMuZW50aXR5U2VydmljZXMuZmluZChzID0+IHMuZW50aXR5VHlwZS50b0xvd2VyQ2FzZSgpID09PSBlbnRpdHlUeXBlKTtcbiAgICAgICAgaWYgKCFzZXJ2aWNlKVxuICAgICAgICAgICAgcmV0dXJuIHt9O1xuICAgICAgICAvLyBIZW50IGFsbGUgZW50aXRpZXMgb2cgZmlsdHJlciBwXHUwMEU1IHBhcmVudElkc1xuICAgICAgICBjb25zdCBhbGxFbnRpdGllcyA9IGF3YWl0IHNlcnZpY2UuZ2V0QWxsKCk7XG4gICAgICAgIGNvbnN0IGVudGl0aWVzID0gYWxsRW50aXRpZXMuZmlsdGVyKGUgPT4gcGFyZW50SWRzLmluY2x1ZGVzKGUuaWQpKTtcbiAgICAgICAgLy8gQnlnIHBhcmVudC1jaGlsZCBtYXBcbiAgICAgICAgY29uc3QgbWFwID0ge307XG4gICAgICAgIGZvciAoY29uc3QgZW50aXR5IG9mIGVudGl0aWVzKSB7XG4gICAgICAgICAgICBjb25zdCBlbnRpdHlSZWNvcmQgPSBlbnRpdHk7XG4gICAgICAgICAgICBjb25zdCBjaGlsZHJlbiA9IGVudGl0eVJlY29yZFtwcm9wZXJ0eV0gfHwgW107XG4gICAgICAgICAgICBtYXBbZW50aXR5UmVjb3JkLmlkXSA9IGNoaWxkcmVuO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB7IHBhcmVudENoaWxkTWFwOiBtYXAsIGNoaWxkVHlwZTogY2hpbGRHcm91cGluZy50eXBlIH07XG4gICAgfVxufVxuIiwgImV4cG9ydCBjbGFzcyBOYXZpZ2F0aW9uQW5pbWF0b3Ige1xuICAgIGNvbnN0cnVjdG9yKGhlYWRlclRyYWNrLCBjb250ZW50VHJhY2spIHtcbiAgICAgICAgdGhpcy5oZWFkZXJUcmFjayA9IGhlYWRlclRyYWNrO1xuICAgICAgICB0aGlzLmNvbnRlbnRUcmFjayA9IGNvbnRlbnRUcmFjaztcbiAgICB9XG4gICAgYXN5bmMgc2xpZGUoZGlyZWN0aW9uLCByZW5kZXJGbikge1xuICAgICAgICBjb25zdCBvdXQgPSBkaXJlY3Rpb24gPT09ICdsZWZ0JyA/ICctMTAwJScgOiAnMTAwJSc7XG4gICAgICAgIGNvbnN0IGludG8gPSBkaXJlY3Rpb24gPT09ICdsZWZ0JyA/ICcxMDAlJyA6ICctMTAwJSc7XG4gICAgICAgIGF3YWl0IHRoaXMuYW5pbWF0ZU91dChvdXQpO1xuICAgICAgICBhd2FpdCByZW5kZXJGbigpO1xuICAgICAgICBhd2FpdCB0aGlzLmFuaW1hdGVJbihpbnRvKTtcbiAgICB9XG4gICAgYXN5bmMgYW5pbWF0ZU91dCh0cmFuc2xhdGUpIHtcbiAgICAgICAgYXdhaXQgUHJvbWlzZS5hbGwoW1xuICAgICAgICAgICAgdGhpcy5oZWFkZXJUcmFjay5hbmltYXRlKFt7IHRyYW5zZm9ybTogJ3RyYW5zbGF0ZVgoMCknIH0sIHsgdHJhbnNmb3JtOiBgdHJhbnNsYXRlWCgke3RyYW5zbGF0ZX0pYCB9XSwgeyBkdXJhdGlvbjogMjAwLCBlYXNpbmc6ICdlYXNlLWluJyB9KS5maW5pc2hlZCxcbiAgICAgICAgICAgIHRoaXMuY29udGVudFRyYWNrLmFuaW1hdGUoW3sgdHJhbnNmb3JtOiAndHJhbnNsYXRlWCgwKScgfSwgeyB0cmFuc2Zvcm06IGB0cmFuc2xhdGVYKCR7dHJhbnNsYXRlfSlgIH1dLCB7IGR1cmF0aW9uOiAyMDAsIGVhc2luZzogJ2Vhc2UtaW4nIH0pLmZpbmlzaGVkXG4gICAgICAgIF0pO1xuICAgIH1cbiAgICBhc3luYyBhbmltYXRlSW4odHJhbnNsYXRlKSB7XG4gICAgICAgIGF3YWl0IFByb21pc2UuYWxsKFtcbiAgICAgICAgICAgIHRoaXMuaGVhZGVyVHJhY2suYW5pbWF0ZShbeyB0cmFuc2Zvcm06IGB0cmFuc2xhdGVYKCR7dHJhbnNsYXRlfSlgIH0sIHsgdHJhbnNmb3JtOiAndHJhbnNsYXRlWCgwKScgfV0sIHsgZHVyYXRpb246IDIwMCwgZWFzaW5nOiAnZWFzZS1vdXQnIH0pLmZpbmlzaGVkLFxuICAgICAgICAgICAgdGhpcy5jb250ZW50VHJhY2suYW5pbWF0ZShbeyB0cmFuc2Zvcm06IGB0cmFuc2xhdGVYKCR7dHJhbnNsYXRlfSlgIH0sIHsgdHJhbnNmb3JtOiAndHJhbnNsYXRlWCgwKScgfV0sIHsgZHVyYXRpb246IDIwMCwgZWFzaW5nOiAnZWFzZS1vdXQnIH0pLmZpbmlzaGVkXG4gICAgICAgIF0pO1xuICAgIH1cbn1cbiIsICIvKipcbiAqIENhbGVuZGFyRXZlbnRzIC0gQ29tbWFuZCBhbmQgc3RhdHVzIGV2ZW50cyBmb3IgQ2FsZW5kYXJBcHBcbiAqL1xuZXhwb3J0IGNvbnN0IENhbGVuZGFyRXZlbnRzID0ge1xuICAgIC8vIENvbW1hbmQgZXZlbnRzIChob3N0IFx1MjE5MiBjYWxlbmRhcilcbiAgICBDTURfTkFWSUdBVEVfUFJFVjogJ2NhbGVuZGFyOmNtZDpuYXZpZ2F0ZTpwcmV2JyxcbiAgICBDTURfTkFWSUdBVEVfTkVYVDogJ2NhbGVuZGFyOmNtZDpuYXZpZ2F0ZTpuZXh0JyxcbiAgICBDTURfRFJBV0VSX1RPR0dMRTogJ2NhbGVuZGFyOmNtZDpkcmF3ZXI6dG9nZ2xlJyxcbiAgICBDTURfUkVOREVSOiAnY2FsZW5kYXI6Y21kOnJlbmRlcicsXG4gICAgQ01EX1dPUktXRUVLX0NIQU5HRTogJ2NhbGVuZGFyOmNtZDp3b3Jrd2VlazpjaGFuZ2UnLFxuICAgIENNRF9WSUVXX1VQREFURTogJ2NhbGVuZGFyOmNtZDp2aWV3OnVwZGF0ZSdcbn07XG4iLCAiaW1wb3J0IHsgTmF2aWdhdGlvbkFuaW1hdG9yIH0gZnJvbSAnLi9OYXZpZ2F0aW9uQW5pbWF0b3InO1xuaW1wb3J0IHsgQ2FsZW5kYXJFdmVudHMgfSBmcm9tICcuL0NhbGVuZGFyRXZlbnRzJztcbmV4cG9ydCBjbGFzcyBDYWxlbmRhckFwcCB7XG4gICAgY29uc3RydWN0b3Iob3JjaGVzdHJhdG9yLCB0aW1lQXhpc1JlbmRlcmVyLCBkYXRlU2VydmljZSwgc2Nyb2xsTWFuYWdlciwgaGVhZGVyRHJhd2VyTWFuYWdlciwgZHJhZ0Ryb3BNYW5hZ2VyLCBlZGdlU2Nyb2xsTWFuYWdlciwgcmVzaXplTWFuYWdlciwgaGVhZGVyRHJhd2VyUmVuZGVyZXIsIGV2ZW50UGVyc2lzdGVuY2VNYW5hZ2VyLCBzZXR0aW5nc1NlcnZpY2UsIHZpZXdDb25maWdTZXJ2aWNlLCBldmVudEJ1cykge1xuICAgICAgICB0aGlzLm9yY2hlc3RyYXRvciA9IG9yY2hlc3RyYXRvcjtcbiAgICAgICAgdGhpcy50aW1lQXhpc1JlbmRlcmVyID0gdGltZUF4aXNSZW5kZXJlcjtcbiAgICAgICAgdGhpcy5kYXRlU2VydmljZSA9IGRhdGVTZXJ2aWNlO1xuICAgICAgICB0aGlzLnNjcm9sbE1hbmFnZXIgPSBzY3JvbGxNYW5hZ2VyO1xuICAgICAgICB0aGlzLmhlYWRlckRyYXdlck1hbmFnZXIgPSBoZWFkZXJEcmF3ZXJNYW5hZ2VyO1xuICAgICAgICB0aGlzLmRyYWdEcm9wTWFuYWdlciA9IGRyYWdEcm9wTWFuYWdlcjtcbiAgICAgICAgdGhpcy5lZGdlU2Nyb2xsTWFuYWdlciA9IGVkZ2VTY3JvbGxNYW5hZ2VyO1xuICAgICAgICB0aGlzLnJlc2l6ZU1hbmFnZXIgPSByZXNpemVNYW5hZ2VyO1xuICAgICAgICB0aGlzLmhlYWRlckRyYXdlclJlbmRlcmVyID0gaGVhZGVyRHJhd2VyUmVuZGVyZXI7XG4gICAgICAgIHRoaXMuZXZlbnRQZXJzaXN0ZW5jZU1hbmFnZXIgPSBldmVudFBlcnNpc3RlbmNlTWFuYWdlcjtcbiAgICAgICAgdGhpcy5zZXR0aW5nc1NlcnZpY2UgPSBzZXR0aW5nc1NlcnZpY2U7XG4gICAgICAgIHRoaXMudmlld0NvbmZpZ1NlcnZpY2UgPSB2aWV3Q29uZmlnU2VydmljZTtcbiAgICAgICAgdGhpcy5ldmVudEJ1cyA9IGV2ZW50QnVzO1xuICAgICAgICB0aGlzLndlZWtPZmZzZXQgPSAwO1xuICAgICAgICB0aGlzLmN1cnJlbnRWaWV3SWQgPSAnc2ltcGxlJztcbiAgICAgICAgdGhpcy53b3Jrd2Vla1ByZXNldCA9IG51bGw7XG4gICAgICAgIHRoaXMuZ3JvdXBpbmdPdmVycmlkZXMgPSBuZXcgTWFwKCk7XG4gICAgfVxuICAgIGFzeW5jIGluaXQoY29udGFpbmVyKSB7XG4gICAgICAgIHRoaXMuY29udGFpbmVyID0gY29udGFpbmVyO1xuICAgICAgICAvLyBMb2FkIHNldHRpbmdzXG4gICAgICAgIGNvbnN0IGdyaWRTZXR0aW5ncyA9IGF3YWl0IHRoaXMuc2V0dGluZ3NTZXJ2aWNlLmdldEdyaWRTZXR0aW5ncygpO1xuICAgICAgICBpZiAoIWdyaWRTZXR0aW5ncykge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdHcmlkU2V0dGluZ3Mgbm90IGZvdW5kJyk7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy53b3Jrd2Vla1ByZXNldCA9IGF3YWl0IHRoaXMuc2V0dGluZ3NTZXJ2aWNlLmdldERlZmF1bHRXb3Jrd2Vla1ByZXNldCgpO1xuICAgICAgICAvLyBDcmVhdGUgTmF2aWdhdGlvbkFuaW1hdG9yIHdpdGggRE9NIGVsZW1lbnRzXG4gICAgICAgIHRoaXMuYW5pbWF0b3IgPSBuZXcgTmF2aWdhdGlvbkFuaW1hdG9yKGNvbnRhaW5lci5xdWVyeVNlbGVjdG9yKCdzd3AtaGVhZGVyLXRyYWNrJyksIGNvbnRhaW5lci5xdWVyeVNlbGVjdG9yKCdzd3AtY29udGVudC10cmFjaycpKTtcbiAgICAgICAgLy8gUmVuZGVyIHRpbWUgYXhpcyBmcm9tIHNldHRpbmdzXG4gICAgICAgIHRoaXMudGltZUF4aXNSZW5kZXJlci5yZW5kZXIoY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3IoJyN0aW1lLWF4aXMnKSwgZ3JpZFNldHRpbmdzLmRheVN0YXJ0SG91ciwgZ3JpZFNldHRpbmdzLmRheUVuZEhvdXIpO1xuICAgICAgICAvLyBJbml0IG1hbmFnZXJzXG4gICAgICAgIHRoaXMuc2Nyb2xsTWFuYWdlci5pbml0KGNvbnRhaW5lcik7XG4gICAgICAgIHRoaXMuaGVhZGVyRHJhd2VyTWFuYWdlci5pbml0KGNvbnRhaW5lcik7XG4gICAgICAgIHRoaXMuZHJhZ0Ryb3BNYW5hZ2VyLmluaXQoY29udGFpbmVyKTtcbiAgICAgICAgdGhpcy5yZXNpemVNYW5hZ2VyLmluaXQoY29udGFpbmVyKTtcbiAgICAgICAgY29uc3Qgc2Nyb2xsYWJsZUNvbnRlbnQgPSBjb250YWluZXIucXVlcnlTZWxlY3Rvcignc3dwLXNjcm9sbGFibGUtY29udGVudCcpO1xuICAgICAgICB0aGlzLmVkZ2VTY3JvbGxNYW5hZ2VyLmluaXQoc2Nyb2xsYWJsZUNvbnRlbnQpO1xuICAgICAgICAvLyBTZXR1cCBjb21tYW5kIGV2ZW50IGxpc3RlbmVyc1xuICAgICAgICB0aGlzLnNldHVwRXZlbnRMaXN0ZW5lcnMoKTtcbiAgICAgICAgLy8gRW1pdCByZWFkeSBzdGF0dXNcbiAgICAgICAgdGhpcy5lbWl0U3RhdHVzKCdyZWFkeScpO1xuICAgIH1cbiAgICBzZXR1cEV2ZW50TGlzdGVuZXJzKCkge1xuICAgICAgICAvLyBOYXZpZ2F0aW9uIGNvbW1hbmRzIHZpYSBFdmVudEJ1c1xuICAgICAgICB0aGlzLmV2ZW50QnVzLm9uKENhbGVuZGFyRXZlbnRzLkNNRF9OQVZJR0FURV9QUkVWLCAoKSA9PiB7XG4gICAgICAgICAgICB0aGlzLmhhbmRsZU5hdmlnYXRlUHJldigpO1xuICAgICAgICB9KTtcbiAgICAgICAgdGhpcy5ldmVudEJ1cy5vbihDYWxlbmRhckV2ZW50cy5DTURfTkFWSUdBVEVfTkVYVCwgKCkgPT4ge1xuICAgICAgICAgICAgdGhpcy5oYW5kbGVOYXZpZ2F0ZU5leHQoKTtcbiAgICAgICAgfSk7XG4gICAgICAgIC8vIERyYXdlciB0b2dnbGUgdmlhIEV2ZW50QnVzXG4gICAgICAgIHRoaXMuZXZlbnRCdXMub24oQ2FsZW5kYXJFdmVudHMuQ01EX0RSQVdFUl9UT0dHTEUsICgpID0+IHtcbiAgICAgICAgICAgIHRoaXMuaGVhZGVyRHJhd2VyTWFuYWdlci50b2dnbGUoKTtcbiAgICAgICAgfSk7XG4gICAgICAgIC8vIFJlbmRlciBjb21tYW5kIHZpYSBFdmVudEJ1c1xuICAgICAgICB0aGlzLmV2ZW50QnVzLm9uKENhbGVuZGFyRXZlbnRzLkNNRF9SRU5ERVIsIChlKSA9PiB7XG4gICAgICAgICAgICBjb25zdCB7IHZpZXdJZCB9ID0gZS5kZXRhaWw7XG4gICAgICAgICAgICB0aGlzLmhhbmRsZVJlbmRlckNvbW1hbmQodmlld0lkKTtcbiAgICAgICAgfSk7XG4gICAgICAgIC8vIFdvcmt3ZWVrIGNoYW5nZSB2aWEgRXZlbnRCdXNcbiAgICAgICAgdGhpcy5ldmVudEJ1cy5vbihDYWxlbmRhckV2ZW50cy5DTURfV09SS1dFRUtfQ0hBTkdFLCAoZSkgPT4ge1xuICAgICAgICAgICAgY29uc3QgeyBwcmVzZXRJZCB9ID0gZS5kZXRhaWw7XG4gICAgICAgICAgICB0aGlzLmhhbmRsZVdvcmt3ZWVrQ2hhbmdlKHByZXNldElkKTtcbiAgICAgICAgfSk7XG4gICAgICAgIC8vIFZpZXcgdXBkYXRlIHZpYSBFdmVudEJ1c1xuICAgICAgICB0aGlzLmV2ZW50QnVzLm9uKENhbGVuZGFyRXZlbnRzLkNNRF9WSUVXX1VQREFURSwgKGUpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHsgdHlwZSwgdmFsdWVzIH0gPSBlLmRldGFpbDtcbiAgICAgICAgICAgIHRoaXMuaGFuZGxlVmlld1VwZGF0ZSh0eXBlLCB2YWx1ZXMpO1xuICAgICAgICB9KTtcbiAgICB9XG4gICAgYXN5bmMgaGFuZGxlUmVuZGVyQ29tbWFuZCh2aWV3SWQpIHtcbiAgICAgICAgdGhpcy5jdXJyZW50Vmlld0lkID0gdmlld0lkO1xuICAgICAgICBhd2FpdCB0aGlzLnJlbmRlcigpO1xuICAgICAgICB0aGlzLmVtaXRTdGF0dXMoJ3JlbmRlcmVkJywgeyB2aWV3SWQgfSk7XG4gICAgfVxuICAgIGFzeW5jIGhhbmRsZU5hdmlnYXRlUHJldigpIHtcbiAgICAgICAgdGhpcy53ZWVrT2Zmc2V0LS07XG4gICAgICAgIGF3YWl0IHRoaXMuYW5pbWF0b3Iuc2xpZGUoJ3JpZ2h0JywgKCkgPT4gdGhpcy5yZW5kZXIoKSk7XG4gICAgICAgIHRoaXMuZW1pdFN0YXR1cygncmVuZGVyZWQnLCB7IHZpZXdJZDogdGhpcy5jdXJyZW50Vmlld0lkIH0pO1xuICAgIH1cbiAgICBhc3luYyBoYW5kbGVOYXZpZ2F0ZU5leHQoKSB7XG4gICAgICAgIHRoaXMud2Vla09mZnNldCsrO1xuICAgICAgICBhd2FpdCB0aGlzLmFuaW1hdG9yLnNsaWRlKCdsZWZ0JywgKCkgPT4gdGhpcy5yZW5kZXIoKSk7XG4gICAgICAgIHRoaXMuZW1pdFN0YXR1cygncmVuZGVyZWQnLCB7IHZpZXdJZDogdGhpcy5jdXJyZW50Vmlld0lkIH0pO1xuICAgIH1cbiAgICBhc3luYyBoYW5kbGVXb3Jrd2Vla0NoYW5nZShwcmVzZXRJZCkge1xuICAgICAgICBjb25zdCBwcmVzZXQgPSBhd2FpdCB0aGlzLnNldHRpbmdzU2VydmljZS5nZXRXb3Jrd2Vla1ByZXNldChwcmVzZXRJZCk7XG4gICAgICAgIGlmIChwcmVzZXQpIHtcbiAgICAgICAgICAgIHRoaXMud29ya3dlZWtQcmVzZXQgPSBwcmVzZXQ7XG4gICAgICAgICAgICBhd2FpdCB0aGlzLnJlbmRlcigpO1xuICAgICAgICAgICAgdGhpcy5lbWl0U3RhdHVzKCdyZW5kZXJlZCcsIHsgdmlld0lkOiB0aGlzLmN1cnJlbnRWaWV3SWQgfSk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgYXN5bmMgaGFuZGxlVmlld1VwZGF0ZSh0eXBlLCB2YWx1ZXMpIHtcbiAgICAgICAgdGhpcy5ncm91cGluZ092ZXJyaWRlcy5zZXQodHlwZSwgdmFsdWVzKTtcbiAgICAgICAgYXdhaXQgdGhpcy5yZW5kZXIoKTtcbiAgICAgICAgdGhpcy5lbWl0U3RhdHVzKCdyZW5kZXJlZCcsIHsgdmlld0lkOiB0aGlzLmN1cnJlbnRWaWV3SWQgfSk7XG4gICAgfVxuICAgIGFzeW5jIHJlbmRlcigpIHtcbiAgICAgICAgY29uc3Qgc3RvcmVkQ29uZmlnID0gYXdhaXQgdGhpcy52aWV3Q29uZmlnU2VydmljZS5nZXRCeUlkKHRoaXMuY3VycmVudFZpZXdJZCk7XG4gICAgICAgIGlmICghc3RvcmVkQ29uZmlnKSB7XG4gICAgICAgICAgICB0aGlzLmVtaXRTdGF0dXMoJ2Vycm9yJywgeyBtZXNzYWdlOiBgVmlld0NvbmZpZyBub3QgZm91bmQ6ICR7dGhpcy5jdXJyZW50Vmlld0lkfWAgfSk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgLy8gUG9wdWxhdGUgZGF0ZSB2YWx1ZXMgYmFzZWQgb24gd29ya3dlZWsgYW5kIG9mZnNldFxuICAgICAgICBjb25zdCB3b3JrRGF5cyA9IHRoaXMud29ya3dlZWtQcmVzZXQ/LndvcmtEYXlzIHx8IFsxLCAyLCAzLCA0LCA1XTtcbiAgICAgICAgY29uc3QgZGF0ZXMgPSB0aGlzLmN1cnJlbnRWaWV3SWQgPT09ICdkYXknXG4gICAgICAgICAgICA/IHRoaXMuZGF0ZVNlcnZpY2UuZ2V0V2Vla0RhdGVzKHRoaXMud2Vla09mZnNldCwgMSlcbiAgICAgICAgICAgIDogdGhpcy5kYXRlU2VydmljZS5nZXRXb3JrV2Vla0RhdGVzKHRoaXMud2Vla09mZnNldCwgd29ya0RheXMpO1xuICAgICAgICAvLyBDbG9uZSBjb25maWcgYW5kIGFwcGx5IG92ZXJyaWRlc1xuICAgICAgICBjb25zdCB2aWV3Q29uZmlnID0ge1xuICAgICAgICAgICAgLi4uc3RvcmVkQ29uZmlnLFxuICAgICAgICAgICAgZ3JvdXBpbmdzOiBzdG9yZWRDb25maWcuZ3JvdXBpbmdzLm1hcChnID0+IHtcbiAgICAgICAgICAgICAgICAvLyBBcHBseSBkYXRlIHZhbHVlc1xuICAgICAgICAgICAgICAgIGlmIChnLnR5cGUgPT09ICdkYXRlJykge1xuICAgICAgICAgICAgICAgICAgICByZXR1cm4geyAuLi5nLCB2YWx1ZXM6IGRhdGVzIH07XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIC8vIEFwcGx5IGdyb3VwaW5nIG92ZXJyaWRlc1xuICAgICAgICAgICAgICAgIGNvbnN0IG92ZXJyaWRlID0gdGhpcy5ncm91cGluZ092ZXJyaWRlcy5nZXQoZy50eXBlKTtcbiAgICAgICAgICAgICAgICBpZiAob3ZlcnJpZGUpIHtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHsgLi4uZywgdmFsdWVzOiBvdmVycmlkZSB9O1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICByZXR1cm4gZztcbiAgICAgICAgICAgIH0pXG4gICAgICAgIH07XG4gICAgICAgIGF3YWl0IHRoaXMub3JjaGVzdHJhdG9yLnJlbmRlcih2aWV3Q29uZmlnLCB0aGlzLmNvbnRhaW5lcik7XG4gICAgfVxuICAgIGVtaXRTdGF0dXMoc3RhdHVzLCBkZXRhaWwpIHtcbiAgICAgICAgdGhpcy5jb250YWluZXIuZGlzcGF0Y2hFdmVudChuZXcgQ3VzdG9tRXZlbnQoYGNhbGVuZGFyOnN0YXR1czoke3N0YXR1c31gLCB7XG4gICAgICAgICAgICBkZXRhaWwsXG4gICAgICAgICAgICBidWJibGVzOiB0cnVlXG4gICAgICAgIH0pKTtcbiAgICB9XG59XG4iLCAiZXhwb3J0IGNsYXNzIFRpbWVBeGlzUmVuZGVyZXIge1xuICAgIHJlbmRlcihjb250YWluZXIsIHN0YXJ0SG91ciA9IDYsIGVuZEhvdXIgPSAyMCkge1xuICAgICAgICBjb250YWluZXIuaW5uZXJIVE1MID0gJyc7XG4gICAgICAgIGZvciAobGV0IGhvdXIgPSBzdGFydEhvdXI7IGhvdXIgPD0gZW5kSG91cjsgaG91cisrKSB7XG4gICAgICAgICAgICBjb25zdCBtYXJrZXIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzd3AtaG91ci1tYXJrZXInKTtcbiAgICAgICAgICAgIG1hcmtlci50ZXh0Q29udGVudCA9IGAke2hvdXIudG9TdHJpbmcoKS5wYWRTdGFydCgyLCAnMCcpfTowMGA7XG4gICAgICAgICAgICBjb250YWluZXIuYXBwZW5kQ2hpbGQobWFya2VyKTtcbiAgICAgICAgfVxuICAgIH1cbn1cbiIsICJleHBvcnQgY2xhc3MgU2Nyb2xsTWFuYWdlciB7XG4gICAgaW5pdChjb250YWluZXIpIHtcbiAgICAgICAgdGhpcy5zY3JvbGxhYmxlQ29udGVudCA9IGNvbnRhaW5lci5xdWVyeVNlbGVjdG9yKCdzd3Atc2Nyb2xsYWJsZS1jb250ZW50Jyk7XG4gICAgICAgIHRoaXMudGltZUF4aXNDb250ZW50ID0gY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3IoJ3N3cC10aW1lLWF4aXMtY29udGVudCcpO1xuICAgICAgICB0aGlzLmNhbGVuZGFySGVhZGVyID0gY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3IoJ3N3cC1jYWxlbmRhci1oZWFkZXInKTtcbiAgICAgICAgdGhpcy5oZWFkZXJEcmF3ZXIgPSBjb250YWluZXIucXVlcnlTZWxlY3Rvcignc3dwLWhlYWRlci1kcmF3ZXInKTtcbiAgICAgICAgdGhpcy5oZWFkZXJWaWV3cG9ydCA9IGNvbnRhaW5lci5xdWVyeVNlbGVjdG9yKCdzd3AtaGVhZGVyLXZpZXdwb3J0Jyk7XG4gICAgICAgIHRoaXMuaGVhZGVyU3BhY2VyID0gY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3IoJ3N3cC1oZWFkZXItc3BhY2VyJyk7XG4gICAgICAgIHRoaXMuc2Nyb2xsYWJsZUNvbnRlbnQuYWRkRXZlbnRMaXN0ZW5lcignc2Nyb2xsJywgKCkgPT4gdGhpcy5vblNjcm9sbCgpKTtcbiAgICAgICAgLy8gU3lua3JvbmlzZXIgaGVhZGVyLXNwYWNlciBoXHUwMEY4amRlIG1lZCBoZWFkZXItdmlld3BvcnRcbiAgICAgICAgdGhpcy5yZXNpemVPYnNlcnZlciA9IG5ldyBSZXNpemVPYnNlcnZlcigoKSA9PiB0aGlzLnN5bmNIZWFkZXJTcGFjZXJIZWlnaHQoKSk7XG4gICAgICAgIHRoaXMucmVzaXplT2JzZXJ2ZXIub2JzZXJ2ZSh0aGlzLmhlYWRlclZpZXdwb3J0KTtcbiAgICAgICAgdGhpcy5zeW5jSGVhZGVyU3BhY2VySGVpZ2h0KCk7XG4gICAgfVxuICAgIHN5bmNIZWFkZXJTcGFjZXJIZWlnaHQoKSB7XG4gICAgICAgIC8vIEtvcGllciBkZW4gZmFrdGlza2UgY29tcHV0ZWQgaGVpZ2h0IGRpcmVrdGUgZnJhIGhlYWRlci12aWV3cG9ydFxuICAgICAgICBjb25zdCBjb21wdXRlZEhlaWdodCA9IGdldENvbXB1dGVkU3R5bGUodGhpcy5oZWFkZXJWaWV3cG9ydCkuaGVpZ2h0O1xuICAgICAgICB0aGlzLmhlYWRlclNwYWNlci5zdHlsZS5oZWlnaHQgPSBjb21wdXRlZEhlaWdodDtcbiAgICB9XG4gICAgb25TY3JvbGwoKSB7XG4gICAgICAgIGNvbnN0IHsgc2Nyb2xsVG9wLCBzY3JvbGxMZWZ0IH0gPSB0aGlzLnNjcm9sbGFibGVDb250ZW50O1xuICAgICAgICAvLyBTeW5rcm9uaXNlciB0aW1lLWF4aXMgdmVydGlrYWx0XG4gICAgICAgIHRoaXMudGltZUF4aXNDb250ZW50LnN0eWxlLnRyYW5zZm9ybSA9IGB0cmFuc2xhdGVZKC0ke3Njcm9sbFRvcH1weClgO1xuICAgICAgICAvLyBTeW5rcm9uaXNlciBoZWFkZXIgb2cgZHJhd2VyIGhvcmlzb250YWx0XG4gICAgICAgIHRoaXMuY2FsZW5kYXJIZWFkZXIuc3R5bGUudHJhbnNmb3JtID0gYHRyYW5zbGF0ZVgoLSR7c2Nyb2xsTGVmdH1weClgO1xuICAgICAgICB0aGlzLmhlYWRlckRyYXdlci5zdHlsZS50cmFuc2Zvcm0gPSBgdHJhbnNsYXRlWCgtJHtzY3JvbGxMZWZ0fXB4KWA7XG4gICAgfVxufVxuIiwgImV4cG9ydCBjbGFzcyBIZWFkZXJEcmF3ZXJNYW5hZ2VyIHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpcy5leHBhbmRlZCA9IGZhbHNlO1xuICAgICAgICB0aGlzLmN1cnJlbnRSb3dzID0gMDtcbiAgICAgICAgdGhpcy5yb3dIZWlnaHQgPSAyNTtcbiAgICAgICAgdGhpcy5kdXJhdGlvbiA9IDIwMDtcbiAgICB9XG4gICAgaW5pdChjb250YWluZXIpIHtcbiAgICAgICAgdGhpcy5kcmF3ZXIgPSBjb250YWluZXIucXVlcnlTZWxlY3Rvcignc3dwLWhlYWRlci1kcmF3ZXInKTtcbiAgICAgICAgaWYgKCF0aGlzLmRyYXdlcilcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoJ0hlYWRlckRyYXdlck1hbmFnZXI6IHN3cC1oZWFkZXItZHJhd2VyIG5vdCBmb3VuZCcpO1xuICAgIH1cbiAgICB0b2dnbGUoKSB7XG4gICAgICAgIHRoaXMuZXhwYW5kZWQgPyB0aGlzLmNvbGxhcHNlKCkgOiB0aGlzLmV4cGFuZCgpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBFeHBhbmQgZHJhd2VyIHRvIHNpbmdsZSByb3cgKGxlZ2FjeSBzdXBwb3J0KVxuICAgICAqL1xuICAgIGV4cGFuZCgpIHtcbiAgICAgICAgdGhpcy5leHBhbmRUb1Jvd3MoMSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEV4cGFuZCBkcmF3ZXIgdG8gZml0IHNwZWNpZmllZCBudW1iZXIgb2Ygcm93c1xuICAgICAqL1xuICAgIGV4cGFuZFRvUm93cyhyb3dDb3VudCkge1xuICAgICAgICBjb25zdCB0YXJnZXRIZWlnaHQgPSByb3dDb3VudCAqIHRoaXMucm93SGVpZ2h0O1xuICAgICAgICBjb25zdCBjdXJyZW50SGVpZ2h0ID0gdGhpcy5leHBhbmRlZCA/IHRoaXMuY3VycmVudFJvd3MgKiB0aGlzLnJvd0hlaWdodCA6IDA7XG4gICAgICAgIC8vIFNraXAgaWYgYWxyZWFkeSBhdCB0YXJnZXRcbiAgICAgICAgaWYgKHRoaXMuZXhwYW5kZWQgJiYgdGhpcy5jdXJyZW50Um93cyA9PT0gcm93Q291bnQpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIHRoaXMuY3VycmVudFJvd3MgPSByb3dDb3VudDtcbiAgICAgICAgdGhpcy5leHBhbmRlZCA9IHRydWU7XG4gICAgICAgIHRoaXMuYW5pbWF0ZShjdXJyZW50SGVpZ2h0LCB0YXJnZXRIZWlnaHQpO1xuICAgIH1cbiAgICBjb2xsYXBzZSgpIHtcbiAgICAgICAgaWYgKCF0aGlzLmV4cGFuZGVkKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICBjb25zdCBjdXJyZW50SGVpZ2h0ID0gdGhpcy5jdXJyZW50Um93cyAqIHRoaXMucm93SGVpZ2h0O1xuICAgICAgICB0aGlzLmV4cGFuZGVkID0gZmFsc2U7XG4gICAgICAgIHRoaXMuY3VycmVudFJvd3MgPSAwO1xuICAgICAgICB0aGlzLmFuaW1hdGUoY3VycmVudEhlaWdodCwgMCk7XG4gICAgfVxuICAgIGFuaW1hdGUoZnJvbSwgdG8pIHtcbiAgICAgICAgY29uc3Qga2V5ZnJhbWVzID0gW1xuICAgICAgICAgICAgeyBoZWlnaHQ6IGAke2Zyb219cHhgIH0sXG4gICAgICAgICAgICB7IGhlaWdodDogYCR7dG99cHhgIH1cbiAgICAgICAgXTtcbiAgICAgICAgY29uc3Qgb3B0aW9ucyA9IHtcbiAgICAgICAgICAgIGR1cmF0aW9uOiB0aGlzLmR1cmF0aW9uLFxuICAgICAgICAgICAgZWFzaW5nOiAnZWFzZScsXG4gICAgICAgICAgICBmaWxsOiAnZm9yd2FyZHMnXG4gICAgICAgIH07XG4gICAgICAgIC8vIEt1biBhbmltXHUwMEU5ciBkcmF3ZXIgLSBTY3JvbGxNYW5hZ2VyIHN5bmtyb25pc2VyZXIgaGVhZGVyLXNwYWNlciB2aWEgUmVzaXplT2JzZXJ2ZXJcbiAgICAgICAgdGhpcy5kcmF3ZXIuYW5pbWF0ZShrZXlmcmFtZXMsIG9wdGlvbnMpO1xuICAgIH1cbiAgICBpc0V4cGFuZGVkKCkge1xuICAgICAgICByZXR1cm4gdGhpcy5leHBhbmRlZDtcbiAgICB9XG4gICAgZ2V0Um93Q291bnQoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmN1cnJlbnRSb3dzO1xuICAgIH1cbn1cbiIsICJleHBvcnQgY2xhc3MgTW9ja1RlYW1TdG9yZSB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMudHlwZSA9ICd0ZWFtJztcbiAgICAgICAgdGhpcy50ZWFtcyA9IFtcbiAgICAgICAgICAgIHsgaWQ6ICdhbHBoYScsIG5hbWU6ICdUZWFtIEFscGhhJyB9LFxuICAgICAgICAgICAgeyBpZDogJ2JldGEnLCBuYW1lOiAnVGVhbSBCZXRhJyB9XG4gICAgICAgIF07XG4gICAgfVxuICAgIGdldEJ5SWRzKGlkcykge1xuICAgICAgICByZXR1cm4gdGhpcy50ZWFtcy5maWx0ZXIodCA9PiBpZHMuaW5jbHVkZXModC5pZCkpO1xuICAgIH1cbn1cbmV4cG9ydCBjbGFzcyBNb2NrUmVzb3VyY2VTdG9yZSB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMudHlwZSA9ICdyZXNvdXJjZSc7XG4gICAgICAgIHRoaXMucmVzb3VyY2VzID0gW1xuICAgICAgICAgICAgeyBpZDogJ2FsaWNlJywgbmFtZTogJ0FsaWNlJywgdGVhbUlkOiAnYWxwaGEnIH0sXG4gICAgICAgICAgICB7IGlkOiAnYm9iJywgbmFtZTogJ0JvYicsIHRlYW1JZDogJ2FscGhhJyB9LFxuICAgICAgICAgICAgeyBpZDogJ2Nhcm9sJywgbmFtZTogJ0Nhcm9sJywgdGVhbUlkOiAnYmV0YScgfSxcbiAgICAgICAgICAgIHsgaWQ6ICdkYXZlJywgbmFtZTogJ0RhdmUnLCB0ZWFtSWQ6ICdiZXRhJyB9XG4gICAgICAgIF07XG4gICAgfVxuICAgIGdldEJ5SWRzKGlkcykge1xuICAgICAgICByZXR1cm4gdGhpcy5yZXNvdXJjZXMuZmlsdGVyKHIgPT4gaWRzLmluY2x1ZGVzKHIuaWQpKTtcbiAgICB9XG59XG4iLCAiaW1wb3J0IHsgQ2FsZW5kYXJFdmVudHMgfSBmcm9tICcuLi9jb3JlL0NhbGVuZGFyRXZlbnRzJztcbmV4cG9ydCBjbGFzcyBEZW1vQXBwIHtcbiAgICBjb25zdHJ1Y3RvcihpbmRleGVkREJDb250ZXh0LCBkYXRhU2VlZGVyLCBhdWRpdFNlcnZpY2UsIGNhbGVuZGFyQXBwLCBkYXRlU2VydmljZSwgcmVzb3VyY2VTZXJ2aWNlLCBldmVudEJ1cykge1xuICAgICAgICB0aGlzLmluZGV4ZWREQkNvbnRleHQgPSBpbmRleGVkREJDb250ZXh0O1xuICAgICAgICB0aGlzLmRhdGFTZWVkZXIgPSBkYXRhU2VlZGVyO1xuICAgICAgICB0aGlzLmF1ZGl0U2VydmljZSA9IGF1ZGl0U2VydmljZTtcbiAgICAgICAgdGhpcy5jYWxlbmRhckFwcCA9IGNhbGVuZGFyQXBwO1xuICAgICAgICB0aGlzLmRhdGVTZXJ2aWNlID0gZGF0ZVNlcnZpY2U7XG4gICAgICAgIHRoaXMucmVzb3VyY2VTZXJ2aWNlID0gcmVzb3VyY2VTZXJ2aWNlO1xuICAgICAgICB0aGlzLmV2ZW50QnVzID0gZXZlbnRCdXM7XG4gICAgICAgIHRoaXMuY3VycmVudFZpZXcgPSAnc2ltcGxlJztcbiAgICB9XG4gICAgYXN5bmMgaW5pdCgpIHtcbiAgICAgICAgLy8gU2V0IGJhc2UgZGF0ZSB0byBtYXRjaCBtb2NrIGRhdGEgKDguIGRlY2VtYmVyIDIwMjUgPSBtYW5kYWcpXG4gICAgICAgIHRoaXMuZGF0ZVNlcnZpY2Uuc2V0QmFzZURhdGUobmV3IERhdGUoJzIwMjUtMTItMDgnKSk7XG4gICAgICAgIC8vIEluaXRpYWxpemUgSW5kZXhlZERCXG4gICAgICAgIGF3YWl0IHRoaXMuaW5kZXhlZERCQ29udGV4dC5pbml0aWFsaXplKCk7XG4gICAgICAgIGNvbnNvbGUubG9nKCdbRGVtb0FwcF0gSW5kZXhlZERCIGluaXRpYWxpemVkJyk7XG4gICAgICAgIC8vIFNlZWQgZGF0YSBpZiBlbXB0eVxuICAgICAgICBhd2FpdCB0aGlzLmRhdGFTZWVkZXIuc2VlZElmRW1wdHkoKTtcbiAgICAgICAgY29uc29sZS5sb2coJ1tEZW1vQXBwXSBEYXRhIHNlZWRpbmcgY29tcGxldGUnKTtcbiAgICAgICAgdGhpcy5jb250YWluZXIgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdzd3AtY2FsZW5kYXItY29udGFpbmVyJyk7XG4gICAgICAgIC8vIEluaXRpYWxpemUgQ2FsZW5kYXJBcHBcbiAgICAgICAgYXdhaXQgdGhpcy5jYWxlbmRhckFwcC5pbml0KHRoaXMuY29udGFpbmVyKTtcbiAgICAgICAgY29uc29sZS5sb2coJ1tEZW1vQXBwXSBDYWxlbmRhckFwcCBpbml0aWFsaXplZCcpO1xuICAgICAgICAvLyBTZXR1cCBkZW1vIFVJIGhhbmRsZXJzXG4gICAgICAgIHRoaXMuc2V0dXBOYXZpZ2F0aW9uKCk7XG4gICAgICAgIHRoaXMuc2V0dXBEcmF3ZXJUb2dnbGUoKTtcbiAgICAgICAgdGhpcy5zZXR1cFZpZXdTd2l0Y2hpbmcoKTtcbiAgICAgICAgdGhpcy5zZXR1cFdvcmt3ZWVrU2VsZWN0b3IoKTtcbiAgICAgICAgYXdhaXQgdGhpcy5zZXR1cFJlc291cmNlU2VsZWN0b3IoKTtcbiAgICAgICAgLy8gTGlzdGVuIGZvciBjYWxlbmRhciBzdGF0dXMgZXZlbnRzXG4gICAgICAgIHRoaXMuc2V0dXBTdGF0dXNMaXN0ZW5lcnMoKTtcbiAgICAgICAgLy8gSW5pdGlhbCByZW5kZXJcbiAgICAgICAgdGhpcy5ldmVudEJ1cy5lbWl0KENhbGVuZGFyRXZlbnRzLkNNRF9SRU5ERVIsIHsgdmlld0lkOiB0aGlzLmN1cnJlbnRWaWV3IH0pO1xuICAgIH1cbiAgICBzZXR1cE5hdmlnYXRpb24oKSB7XG4gICAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdidG4tcHJldicpLm9uY2xpY2sgPSAoKSA9PiB7XG4gICAgICAgICAgICB0aGlzLmV2ZW50QnVzLmVtaXQoQ2FsZW5kYXJFdmVudHMuQ01EX05BVklHQVRFX1BSRVYpO1xuICAgICAgICB9O1xuICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnYnRuLW5leHQnKS5vbmNsaWNrID0gKCkgPT4ge1xuICAgICAgICAgICAgdGhpcy5ldmVudEJ1cy5lbWl0KENhbGVuZGFyRXZlbnRzLkNNRF9OQVZJR0FURV9ORVhUKTtcbiAgICAgICAgfTtcbiAgICB9XG4gICAgc2V0dXBWaWV3U3dpdGNoaW5nKCkge1xuICAgICAgICBjb25zdCBjaGlwcyA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoJy52aWV3LWNoaXAnKTtcbiAgICAgICAgY2hpcHMuZm9yRWFjaChjaGlwID0+IHtcbiAgICAgICAgICAgIGNoaXAuYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCAoKSA9PiB7XG4gICAgICAgICAgICAgICAgY2hpcHMuZm9yRWFjaChjID0+IGMuY2xhc3NMaXN0LnJlbW92ZSgnYWN0aXZlJykpO1xuICAgICAgICAgICAgICAgIGNoaXAuY2xhc3NMaXN0LmFkZCgnYWN0aXZlJyk7XG4gICAgICAgICAgICAgICAgY29uc3QgdmlldyA9IGNoaXAuZGF0YXNldC52aWV3O1xuICAgICAgICAgICAgICAgIGlmICh2aWV3KSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuY3VycmVudFZpZXcgPSB2aWV3O1xuICAgICAgICAgICAgICAgICAgICB0aGlzLnVwZGF0ZVNlbGVjdG9yVmlzaWJpbGl0eSgpO1xuICAgICAgICAgICAgICAgICAgICB0aGlzLmV2ZW50QnVzLmVtaXQoQ2FsZW5kYXJFdmVudHMuQ01EX1JFTkRFUiwgeyB2aWV3SWQ6IHZpZXcgfSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSk7XG4gICAgICAgIH0pO1xuICAgIH1cbiAgICB1cGRhdGVTZWxlY3RvclZpc2liaWxpdHkoKSB7XG4gICAgICAgIGNvbnN0IHNlbGVjdG9yID0gZG9jdW1lbnQucXVlcnlTZWxlY3Rvcignc3dwLXJlc291cmNlLXNlbGVjdG9yJyk7XG4gICAgICAgIGNvbnN0IHNob3dTZWxlY3RvciA9IHRoaXMuY3VycmVudFZpZXcgPT09ICdwaWNrZXInIHx8IHRoaXMuY3VycmVudFZpZXcgPT09ICdkYXknO1xuICAgICAgICBzZWxlY3Rvcj8uY2xhc3NMaXN0LnRvZ2dsZSgnaGlkZGVuJywgIXNob3dTZWxlY3Rvcik7XG4gICAgfVxuICAgIHNldHVwRHJhd2VyVG9nZ2xlKCkge1xuICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnYnRuLWRyYXdlcicpLm9uY2xpY2sgPSAoKSA9PiB7XG4gICAgICAgICAgICB0aGlzLmV2ZW50QnVzLmVtaXQoQ2FsZW5kYXJFdmVudHMuQ01EX0RSQVdFUl9UT0dHTEUpO1xuICAgICAgICB9O1xuICAgIH1cbiAgICBzZXR1cFdvcmt3ZWVrU2VsZWN0b3IoKSB7XG4gICAgICAgIGNvbnN0IHdvcmt3ZWVrU2VsZWN0ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3dvcmt3ZWVrLXNlbGVjdCcpO1xuICAgICAgICB3b3Jrd2Vla1NlbGVjdD8uYWRkRXZlbnRMaXN0ZW5lcignY2hhbmdlJywgKCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgcHJlc2V0SWQgPSB3b3Jrd2Vla1NlbGVjdC52YWx1ZTtcbiAgICAgICAgICAgIHRoaXMuZXZlbnRCdXMuZW1pdChDYWxlbmRhckV2ZW50cy5DTURfV09SS1dFRUtfQ0hBTkdFLCB7IHByZXNldElkIH0pO1xuICAgICAgICB9KTtcbiAgICB9XG4gICAgYXN5bmMgc2V0dXBSZXNvdXJjZVNlbGVjdG9yKCkge1xuICAgICAgICBjb25zdCByZXNvdXJjZXMgPSBhd2FpdCB0aGlzLnJlc291cmNlU2VydmljZS5nZXRBbGwoKTtcbiAgICAgICAgY29uc3QgY29udGFpbmVyID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvcignLnJlc291cmNlLWNoZWNrYm94ZXMnKTtcbiAgICAgICAgaWYgKCFjb250YWluZXIpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIGNvbnRhaW5lci5pbm5lckhUTUwgPSAnJztcbiAgICAgICAgcmVzb3VyY2VzLmZvckVhY2gociA9PiB7XG4gICAgICAgICAgICBjb25zdCBsYWJlbCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2xhYmVsJyk7XG4gICAgICAgICAgICBsYWJlbC5pbm5lckhUTUwgPSBgXHJcbiAgICAgICAgPGlucHV0IHR5cGU9XCJjaGVja2JveFwiIHZhbHVlPVwiJHtyLmlkfVwiIGNoZWNrZWQ+XHJcbiAgICAgICAgJHtyLmRpc3BsYXlOYW1lfVxyXG4gICAgICBgO1xuICAgICAgICAgICAgY29udGFpbmVyLmFwcGVuZENoaWxkKGxhYmVsKTtcbiAgICAgICAgfSk7XG4gICAgICAgIGNvbnRhaW5lci5hZGRFdmVudExpc3RlbmVyKCdjaGFuZ2UnLCAoKSA9PiB7XG4gICAgICAgICAgICBjb25zdCBjaGVja2VkID0gY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3JBbGwoJ2lucHV0OmNoZWNrZWQnKTtcbiAgICAgICAgICAgIGNvbnN0IHZhbHVlcyA9IEFycmF5LmZyb20oY2hlY2tlZCkubWFwKGNiID0+IGNiLnZhbHVlKTtcbiAgICAgICAgICAgIHRoaXMuZXZlbnRCdXMuZW1pdChDYWxlbmRhckV2ZW50cy5DTURfVklFV19VUERBVEUsIHsgdHlwZTogJ3Jlc291cmNlJywgdmFsdWVzIH0pO1xuICAgICAgICB9KTtcbiAgICB9XG4gICAgc2V0dXBTdGF0dXNMaXN0ZW5lcnMoKSB7XG4gICAgICAgIHRoaXMuY29udGFpbmVyLmFkZEV2ZW50TGlzdGVuZXIoJ2NhbGVuZGFyOnN0YXR1czpyZWFkeScsICgpID0+IHtcbiAgICAgICAgICAgIGNvbnNvbGUubG9nKCdbRGVtb0FwcF0gQ2FsZW5kYXIgcmVhZHknKTtcbiAgICAgICAgfSk7XG4gICAgICAgIHRoaXMuY29udGFpbmVyLmFkZEV2ZW50TGlzdGVuZXIoJ2NhbGVuZGFyOnN0YXR1czpyZW5kZXJlZCcsICgoZSkgPT4ge1xuICAgICAgICAgICAgY29uc29sZS5sb2coJ1tEZW1vQXBwXSBDYWxlbmRhciByZW5kZXJlZDonLCBlLmRldGFpbC52aWV3SWQpO1xuICAgICAgICB9KSk7XG4gICAgICAgIHRoaXMuY29udGFpbmVyLmFkZEV2ZW50TGlzdGVuZXIoJ2NhbGVuZGFyOnN0YXR1czplcnJvcicsICgoZSkgPT4ge1xuICAgICAgICAgICAgY29uc29sZS5lcnJvcignW0RlbW9BcHBdIENhbGVuZGFyIGVycm9yOicsIGUuZGV0YWlsLm1lc3NhZ2UpO1xuICAgICAgICB9KSk7XG4gICAgfVxufVxuIiwgIi8qKlxuICogQ2VudHJhbCBldmVudCBkaXNwYXRjaGVyIGZvciBjYWxlbmRhciB1c2luZyBET00gQ3VzdG9tRXZlbnRzXG4gKiBQcm92aWRlcyBsb2dnaW5nIGFuZCBkZWJ1Z2dpbmcgY2FwYWJpbGl0aWVzXG4gKi9cbmV4cG9ydCBjbGFzcyBFdmVudEJ1cyB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuZXZlbnRMb2cgPSBbXTtcbiAgICAgICAgdGhpcy5kZWJ1ZyA9IGZhbHNlO1xuICAgICAgICB0aGlzLmxpc3RlbmVycyA9IG5ldyBTZXQoKTtcbiAgICAgICAgLy8gTG9nIGNvbmZpZ3VyYXRpb24gZm9yIGRpZmZlcmVudCBjYXRlZ29yaWVzXG4gICAgICAgIHRoaXMubG9nQ29uZmlnID0ge1xuICAgICAgICAgICAgY2FsZW5kYXI6IHRydWUsXG4gICAgICAgICAgICBncmlkOiB0cnVlLFxuICAgICAgICAgICAgZXZlbnQ6IHRydWUsXG4gICAgICAgICAgICBzY3JvbGw6IHRydWUsXG4gICAgICAgICAgICBuYXZpZ2F0aW9uOiB0cnVlLFxuICAgICAgICAgICAgdmlldzogdHJ1ZSxcbiAgICAgICAgICAgIGRlZmF1bHQ6IHRydWVcbiAgICAgICAgfTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogU3Vic2NyaWJlIHRvIGFuIGV2ZW50IHZpYSBET00gYWRkRXZlbnRMaXN0ZW5lclxuICAgICAqL1xuICAgIG9uKGV2ZW50VHlwZSwgaGFuZGxlciwgb3B0aW9ucykge1xuICAgICAgICBkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKGV2ZW50VHlwZSwgaGFuZGxlciwgb3B0aW9ucyk7XG4gICAgICAgIC8vIFRyYWNrIGZvciBjbGVhbnVwXG4gICAgICAgIHRoaXMubGlzdGVuZXJzLmFkZCh7IGV2ZW50VHlwZSwgaGFuZGxlciwgb3B0aW9ucyB9KTtcbiAgICAgICAgLy8gUmV0dXJuIHVuc3Vic2NyaWJlIGZ1bmN0aW9uXG4gICAgICAgIHJldHVybiAoKSA9PiB0aGlzLm9mZihldmVudFR5cGUsIGhhbmRsZXIpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBTdWJzY3JpYmUgdG8gYW4gZXZlbnQgb25jZVxuICAgICAqL1xuICAgIG9uY2UoZXZlbnRUeXBlLCBoYW5kbGVyKSB7XG4gICAgICAgIHJldHVybiB0aGlzLm9uKGV2ZW50VHlwZSwgaGFuZGxlciwgeyBvbmNlOiB0cnVlIH0pO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBVbnN1YnNjcmliZSBmcm9tIGFuIGV2ZW50XG4gICAgICovXG4gICAgb2ZmKGV2ZW50VHlwZSwgaGFuZGxlcikge1xuICAgICAgICBkb2N1bWVudC5yZW1vdmVFdmVudExpc3RlbmVyKGV2ZW50VHlwZSwgaGFuZGxlcik7XG4gICAgICAgIC8vIFJlbW92ZSBmcm9tIHRyYWNraW5nXG4gICAgICAgIGZvciAoY29uc3QgbGlzdGVuZXIgb2YgdGhpcy5saXN0ZW5lcnMpIHtcbiAgICAgICAgICAgIGlmIChsaXN0ZW5lci5ldmVudFR5cGUgPT09IGV2ZW50VHlwZSAmJiBsaXN0ZW5lci5oYW5kbGVyID09PSBoYW5kbGVyKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5saXN0ZW5lcnMuZGVsZXRlKGxpc3RlbmVyKTtcbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBFbWl0IGFuIGV2ZW50IHZpYSBET00gQ3VzdG9tRXZlbnRcbiAgICAgKi9cbiAgICBlbWl0KGV2ZW50VHlwZSwgZGV0YWlsID0ge30pIHtcbiAgICAgICAgLy8gVmFsaWRhdGUgZXZlbnRUeXBlXG4gICAgICAgIGlmICghZXZlbnRUeXBlKSB7XG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgZXZlbnQgPSBuZXcgQ3VzdG9tRXZlbnQoZXZlbnRUeXBlLCB7XG4gICAgICAgICAgICBkZXRhaWw6IGRldGFpbCA/PyB7fSxcbiAgICAgICAgICAgIGJ1YmJsZXM6IHRydWUsXG4gICAgICAgICAgICBjYW5jZWxhYmxlOiB0cnVlXG4gICAgICAgIH0pO1xuICAgICAgICAvLyBMb2cgZXZlbnQgd2l0aCBncm91cGluZ1xuICAgICAgICBpZiAodGhpcy5kZWJ1Zykge1xuICAgICAgICAgICAgdGhpcy5sb2dFdmVudFdpdGhHcm91cGluZyhldmVudFR5cGUsIGRldGFpbCk7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5ldmVudExvZy5wdXNoKHtcbiAgICAgICAgICAgIHR5cGU6IGV2ZW50VHlwZSxcbiAgICAgICAgICAgIGRldGFpbDogZGV0YWlsID8/IHt9LFxuICAgICAgICAgICAgdGltZXN0YW1wOiBEYXRlLm5vdygpXG4gICAgICAgIH0pO1xuICAgICAgICAvLyBFbWl0IG9uIGRvY3VtZW50IChvbmx5IERPTSBldmVudHMgbm93KVxuICAgICAgICByZXR1cm4gIWRvY3VtZW50LmRpc3BhdGNoRXZlbnQoZXZlbnQpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBMb2cgZXZlbnQgd2l0aCBjb25zb2xlIGdyb3VwaW5nXG4gICAgICovXG4gICAgbG9nRXZlbnRXaXRoR3JvdXBpbmcoZXZlbnRUeXBlLCBfZGV0YWlsKSB7XG4gICAgICAgIC8vIEV4dHJhY3QgY2F0ZWdvcnkgZnJvbSBldmVudCB0eXBlIChlLmcuLCAnY2FsZW5kYXI6ZGF0ZWNoYW5nZWQnIFx1MjE5MiAnY2FsZW5kYXInKVxuICAgICAgICBjb25zdCBjYXRlZ29yeSA9IHRoaXMuZXh0cmFjdENhdGVnb3J5KGV2ZW50VHlwZSk7XG4gICAgICAgIC8vIE9ubHkgbG9nIGlmIGNhdGVnb3J5IGlzIGVuYWJsZWRcbiAgICAgICAgaWYgKCF0aGlzLmxvZ0NvbmZpZ1tjYXRlZ29yeV0pIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICAvLyBHZXQgY2F0ZWdvcnkgZW1vamkgYW5kIGNvbG9yICh1c2VkIGZvciBmdXR1cmUgY29uc29sZSBzdHlsaW5nKVxuICAgICAgICB0aGlzLmdldENhdGVnb3J5U3R5bGUoY2F0ZWdvcnkpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBFeHRyYWN0IGNhdGVnb3J5IGZyb20gZXZlbnQgdHlwZVxuICAgICAqL1xuICAgIGV4dHJhY3RDYXRlZ29yeShldmVudFR5cGUpIHtcbiAgICAgICAgaWYgKCFldmVudFR5cGUpIHtcbiAgICAgICAgICAgIHJldHVybiAndW5rbm93bic7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGV2ZW50VHlwZS5pbmNsdWRlcygnOicpKSB7XG4gICAgICAgICAgICByZXR1cm4gZXZlbnRUeXBlLnNwbGl0KCc6JylbMF07XG4gICAgICAgIH1cbiAgICAgICAgLy8gRmFsbGJhY2s6IHRyeSB0byBkZXRlY3QgY2F0ZWdvcnkgZnJvbSBldmVudCBuYW1lIHBhdHRlcm5zXG4gICAgICAgIGNvbnN0IGxvd2VyVHlwZSA9IGV2ZW50VHlwZS50b0xvd2VyQ2FzZSgpO1xuICAgICAgICBpZiAobG93ZXJUeXBlLmluY2x1ZGVzKCdncmlkJykgfHwgbG93ZXJUeXBlLmluY2x1ZGVzKCdyZW5kZXJlZCcpKVxuICAgICAgICAgICAgcmV0dXJuICdncmlkJztcbiAgICAgICAgaWYgKGxvd2VyVHlwZS5pbmNsdWRlcygnZXZlbnQnKSB8fCBsb3dlclR5cGUuaW5jbHVkZXMoJ3N5bmMnKSlcbiAgICAgICAgICAgIHJldHVybiAnZXZlbnQnO1xuICAgICAgICBpZiAobG93ZXJUeXBlLmluY2x1ZGVzKCdzY3JvbGwnKSlcbiAgICAgICAgICAgIHJldHVybiAnc2Nyb2xsJztcbiAgICAgICAgaWYgKGxvd2VyVHlwZS5pbmNsdWRlcygnbmF2JykgfHwgbG93ZXJUeXBlLmluY2x1ZGVzKCdkYXRlJykpXG4gICAgICAgICAgICByZXR1cm4gJ25hdmlnYXRpb24nO1xuICAgICAgICBpZiAobG93ZXJUeXBlLmluY2x1ZGVzKCd2aWV3JykpXG4gICAgICAgICAgICByZXR1cm4gJ3ZpZXcnO1xuICAgICAgICByZXR1cm4gJ2RlZmF1bHQnO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgc3R5bGluZyBmb3IgZGlmZmVyZW50IGNhdGVnb3JpZXNcbiAgICAgKi9cbiAgICBnZXRDYXRlZ29yeVN0eWxlKGNhdGVnb3J5KSB7XG4gICAgICAgIGNvbnN0IHN0eWxlcyA9IHtcbiAgICAgICAgICAgIGNhbGVuZGFyOiB7IGVtb2ppOiAnXHVEODNEXHVEQ0M1JywgY29sb3I6ICcjMjE5NkYzJyB9LFxuICAgICAgICAgICAgZ3JpZDogeyBlbW9qaTogJ1x1RDgzRFx1RENDQScsIGNvbG9yOiAnIzRDQUY1MCcgfSxcbiAgICAgICAgICAgIGV2ZW50OiB7IGVtb2ppOiAnXHVEODNEXHVEQ0NDJywgY29sb3I6ICcjRkY5ODAwJyB9LFxuICAgICAgICAgICAgc2Nyb2xsOiB7IGVtb2ppOiAnXHVEODNEXHVEQ0RDJywgY29sb3I6ICcjOUMyN0IwJyB9LFxuICAgICAgICAgICAgbmF2aWdhdGlvbjogeyBlbW9qaTogJ1x1RDgzRVx1RERFRCcsIGNvbG9yOiAnI0Y0NDMzNicgfSxcbiAgICAgICAgICAgIHZpZXc6IHsgZW1vamk6ICdcdUQ4M0RcdURDNDEnLCBjb2xvcjogJyMwMEJDRDQnIH0sXG4gICAgICAgICAgICBkZWZhdWx0OiB7IGVtb2ppOiAnXHVEODNEXHVEQ0UyJywgY29sb3I6ICcjNjA3RDhCJyB9XG4gICAgICAgIH07XG4gICAgICAgIHJldHVybiBzdHlsZXNbY2F0ZWdvcnldIHx8IHN0eWxlcy5kZWZhdWx0O1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBDb25maWd1cmUgbG9nZ2luZyBmb3Igc3BlY2lmaWMgY2F0ZWdvcmllc1xuICAgICAqL1xuICAgIHNldExvZ0NvbmZpZyhjb25maWcpIHtcbiAgICAgICAgdGhpcy5sb2dDb25maWcgPSB7IC4uLnRoaXMubG9nQ29uZmlnLCAuLi5jb25maWcgfTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IGN1cnJlbnQgbG9nIGNvbmZpZ3VyYXRpb25cbiAgICAgKi9cbiAgICBnZXRMb2dDb25maWcoKSB7XG4gICAgICAgIHJldHVybiB7IC4uLnRoaXMubG9nQ29uZmlnIH07XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCBldmVudCBoaXN0b3J5XG4gICAgICovXG4gICAgZ2V0RXZlbnRMb2coZXZlbnRUeXBlKSB7XG4gICAgICAgIGlmIChldmVudFR5cGUpIHtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLmV2ZW50TG9nLmZpbHRlcihlID0+IGUudHlwZSA9PT0gZXZlbnRUeXBlKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcy5ldmVudExvZztcbiAgICB9XG4gICAgLyoqXG4gICAgICogRW5hYmxlL2Rpc2FibGUgZGVidWcgbW9kZVxuICAgICAqL1xuICAgIHNldERlYnVnKGVuYWJsZWQpIHtcbiAgICAgICAgdGhpcy5kZWJ1ZyA9IGVuYWJsZWQ7XG4gICAgfVxufVxuIiwgIi8qKlxuICogSW5kZXhlZERCQ29udGV4dCAtIERhdGFiYXNlIGNvbm5lY3Rpb24gbWFuYWdlclxuICpcbiAqIFJFU1BPTlNJQklMSVRZOlxuICogLSBPcGVucyBhbmQgbWFuYWdlcyBJREJEYXRhYmFzZSBjb25uZWN0aW9uIGxpZmVjeWNsZVxuICogLSBDcmVhdGVzIG9iamVjdCBzdG9yZXMgdmlhIGluamVjdGVkIElTdG9yZSBpbXBsZW1lbnRhdGlvbnNcbiAqIC0gUHJvdmlkZXMgc2hhcmVkIElEQkRhdGFiYXNlIGluc3RhbmNlIHRvIGFsbCBzZXJ2aWNlc1xuICovXG5leHBvcnQgY2xhc3MgSW5kZXhlZERCQ29udGV4dCB7XG4gICAgY29uc3RydWN0b3Ioc3RvcmVzKSB7XG4gICAgICAgIHRoaXMuZGIgPSBudWxsO1xuICAgICAgICB0aGlzLmluaXRpYWxpemVkID0gZmFsc2U7XG4gICAgICAgIHRoaXMuc3RvcmVzID0gc3RvcmVzO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBJbml0aWFsaXplIGFuZCBvcGVuIHRoZSBkYXRhYmFzZVxuICAgICAqL1xuICAgIGFzeW5jIGluaXRpYWxpemUoKSB7XG4gICAgICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICBjb25zdCByZXF1ZXN0ID0gaW5kZXhlZERCLm9wZW4oSW5kZXhlZERCQ29udGV4dC5EQl9OQU1FLCBJbmRleGVkREJDb250ZXh0LkRCX1ZFUlNJT04pO1xuICAgICAgICAgICAgcmVxdWVzdC5vbmVycm9yID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIHJlamVjdChuZXcgRXJyb3IoYEZhaWxlZCB0byBvcGVuIEluZGV4ZWREQjogJHtyZXF1ZXN0LmVycm9yfWApKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICByZXF1ZXN0Lm9uc3VjY2VzcyA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICB0aGlzLmRiID0gcmVxdWVzdC5yZXN1bHQ7XG4gICAgICAgICAgICAgICAgdGhpcy5pbml0aWFsaXplZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgcmVzb2x2ZSgpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHJlcXVlc3Qub251cGdyYWRlbmVlZGVkID0gKGV2ZW50KSA9PiB7XG4gICAgICAgICAgICAgICAgY29uc3QgZGIgPSBldmVudC50YXJnZXQucmVzdWx0O1xuICAgICAgICAgICAgICAgIC8vIENyZWF0ZSBhbGwgZW50aXR5IHN0b3JlcyB2aWEgaW5qZWN0ZWQgSVN0b3JlIGltcGxlbWVudGF0aW9uc1xuICAgICAgICAgICAgICAgIHRoaXMuc3RvcmVzLmZvckVhY2goc3RvcmUgPT4ge1xuICAgICAgICAgICAgICAgICAgICBpZiAoIWRiLm9iamVjdFN0b3JlTmFtZXMuY29udGFpbnMoc3RvcmUuc3RvcmVOYW1lKSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgc3RvcmUuY3JlYXRlKGRiKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIENoZWNrIGlmIGRhdGFiYXNlIGlzIGluaXRpYWxpemVkXG4gICAgICovXG4gICAgaXNJbml0aWFsaXplZCgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuaW5pdGlhbGl6ZWQ7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCBJREJEYXRhYmFzZSBpbnN0YW5jZVxuICAgICAqL1xuICAgIGdldERhdGFiYXNlKCkge1xuICAgICAgICBpZiAoIXRoaXMuZGIpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcignSW5kZXhlZERCIG5vdCBpbml0aWFsaXplZC4gQ2FsbCBpbml0aWFsaXplKCkgZmlyc3QuJyk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXMuZGI7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIENsb3NlIGRhdGFiYXNlIGNvbm5lY3Rpb25cbiAgICAgKi9cbiAgICBjbG9zZSgpIHtcbiAgICAgICAgaWYgKHRoaXMuZGIpIHtcbiAgICAgICAgICAgIHRoaXMuZGIuY2xvc2UoKTtcbiAgICAgICAgICAgIHRoaXMuZGIgPSBudWxsO1xuICAgICAgICAgICAgdGhpcy5pbml0aWFsaXplZCA9IGZhbHNlO1xuICAgICAgICB9XG4gICAgfVxuICAgIC8qKlxuICAgICAqIERlbGV0ZSBlbnRpcmUgZGF0YWJhc2UgKGZvciB0ZXN0aW5nL3Jlc2V0KVxuICAgICAqL1xuICAgIHN0YXRpYyBhc3luYyBkZWxldGVEYXRhYmFzZSgpIHtcbiAgICAgICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHJlcXVlc3QgPSBpbmRleGVkREIuZGVsZXRlRGF0YWJhc2UoSW5kZXhlZERCQ29udGV4dC5EQl9OQU1FKTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25zdWNjZXNzID0gKCkgPT4gcmVzb2x2ZSgpO1xuICAgICAgICAgICAgcmVxdWVzdC5vbmVycm9yID0gKCkgPT4gcmVqZWN0KG5ldyBFcnJvcihgRmFpbGVkIHRvIGRlbGV0ZSBkYXRhYmFzZTogJHtyZXF1ZXN0LmVycm9yfWApKTtcbiAgICAgICAgfSk7XG4gICAgfVxufVxuSW5kZXhlZERCQ29udGV4dC5EQl9OQU1FID0gJ0NhbGVuZGFyVjJEQic7XG5JbmRleGVkREJDb250ZXh0LkRCX1ZFUlNJT04gPSA0O1xuIiwgIi8qKlxuICogRXZlbnRTdG9yZSAtIEluZGV4ZWREQiBPYmplY3RTdG9yZSBkZWZpbml0aW9uIGZvciBjYWxlbmRhciBldmVudHNcbiAqL1xuZXhwb3J0IGNsYXNzIEV2ZW50U3RvcmUge1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICB0aGlzLnN0b3JlTmFtZSA9IEV2ZW50U3RvcmUuU1RPUkVfTkFNRTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogQ3JlYXRlIHRoZSBldmVudHMgT2JqZWN0U3RvcmUgd2l0aCBpbmRleGVzXG4gICAgICovXG4gICAgY3JlYXRlKGRiKSB7XG4gICAgICAgIGNvbnN0IHN0b3JlID0gZGIuY3JlYXRlT2JqZWN0U3RvcmUoRXZlbnRTdG9yZS5TVE9SRV9OQU1FLCB7IGtleVBhdGg6ICdpZCcgfSk7XG4gICAgICAgIC8vIEluZGV4OiBzdGFydCAoZm9yIGRhdGUgcmFuZ2UgcXVlcmllcylcbiAgICAgICAgc3RvcmUuY3JlYXRlSW5kZXgoJ3N0YXJ0JywgJ3N0YXJ0JywgeyB1bmlxdWU6IGZhbHNlIH0pO1xuICAgICAgICAvLyBJbmRleDogZW5kIChmb3IgZGF0ZSByYW5nZSBxdWVyaWVzKVxuICAgICAgICBzdG9yZS5jcmVhdGVJbmRleCgnZW5kJywgJ2VuZCcsIHsgdW5pcXVlOiBmYWxzZSB9KTtcbiAgICAgICAgLy8gSW5kZXg6IHN5bmNTdGF0dXMgKGZvciBmaWx0ZXJpbmcgYnkgc3luYyBzdGF0ZSlcbiAgICAgICAgc3RvcmUuY3JlYXRlSW5kZXgoJ3N5bmNTdGF0dXMnLCAnc3luY1N0YXR1cycsIHsgdW5pcXVlOiBmYWxzZSB9KTtcbiAgICAgICAgLy8gSW5kZXg6IHJlc291cmNlSWQgKGZvciByZXNvdXJjZS1tb2RlIGZpbHRlcmluZylcbiAgICAgICAgc3RvcmUuY3JlYXRlSW5kZXgoJ3Jlc291cmNlSWQnLCAncmVzb3VyY2VJZCcsIHsgdW5pcXVlOiBmYWxzZSB9KTtcbiAgICAgICAgLy8gSW5kZXg6IGN1c3RvbWVySWQgKGZvciBjdXN0b21lci1jZW50cmljIHF1ZXJpZXMpXG4gICAgICAgIHN0b3JlLmNyZWF0ZUluZGV4KCdjdXN0b21lcklkJywgJ2N1c3RvbWVySWQnLCB7IHVuaXF1ZTogZmFsc2UgfSk7XG4gICAgICAgIC8vIEluZGV4OiBib29raW5nSWQgKGZvciBldmVudC10by1ib29raW5nIGxvb2t1cHMpXG4gICAgICAgIHN0b3JlLmNyZWF0ZUluZGV4KCdib29raW5nSWQnLCAnYm9va2luZ0lkJywgeyB1bmlxdWU6IGZhbHNlIH0pO1xuICAgICAgICAvLyBDb21wb3VuZCBpbmRleDogc3RhcnRFbmQgKGZvciBvcHRpbWl6ZWQgcmFuZ2UgcXVlcmllcylcbiAgICAgICAgc3RvcmUuY3JlYXRlSW5kZXgoJ3N0YXJ0RW5kJywgWydzdGFydCcsICdlbmQnXSwgeyB1bmlxdWU6IGZhbHNlIH0pO1xuICAgIH1cbn1cbkV2ZW50U3RvcmUuU1RPUkVfTkFNRSA9ICdldmVudHMnO1xuIiwgIi8qKlxuICogRXZlbnRTZXJpYWxpemF0aW9uIC0gSGFuZGxlcyBEYXRlIGZpZWxkIHNlcmlhbGl6YXRpb24gZm9yIEluZGV4ZWREQlxuICpcbiAqIEluZGV4ZWREQiBkb2Vzbid0IHN0b3JlIERhdGUgb2JqZWN0cyBkaXJlY3RseSwgc28gd2UgY29udmVydDpcbiAqIC0gRGF0ZSBcdTIxOTIgSVNPIHN0cmluZyAoc2VyaWFsaXplKSB3aGVuIHdyaXRpbmdcbiAqIC0gSVNPIHN0cmluZyBcdTIxOTIgRGF0ZSAoZGVzZXJpYWxpemUpIHdoZW4gcmVhZGluZ1xuICovXG5leHBvcnQgY2xhc3MgRXZlbnRTZXJpYWxpemF0aW9uIHtcbiAgICAvKipcbiAgICAgKiBTZXJpYWxpemUgZXZlbnQgZm9yIEluZGV4ZWREQiBzdG9yYWdlXG4gICAgICovXG4gICAgc3RhdGljIHNlcmlhbGl6ZShldmVudCkge1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgLi4uZXZlbnQsXG4gICAgICAgICAgICBzdGFydDogZXZlbnQuc3RhcnQgaW5zdGFuY2VvZiBEYXRlID8gZXZlbnQuc3RhcnQudG9JU09TdHJpbmcoKSA6IGV2ZW50LnN0YXJ0LFxuICAgICAgICAgICAgZW5kOiBldmVudC5lbmQgaW5zdGFuY2VvZiBEYXRlID8gZXZlbnQuZW5kLnRvSVNPU3RyaW5nKCkgOiBldmVudC5lbmRcbiAgICAgICAgfTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogRGVzZXJpYWxpemUgZXZlbnQgZnJvbSBJbmRleGVkREIgc3RvcmFnZVxuICAgICAqL1xuICAgIHN0YXRpYyBkZXNlcmlhbGl6ZShkYXRhKSB7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICAuLi5kYXRhLFxuICAgICAgICAgICAgc3RhcnQ6IHR5cGVvZiBkYXRhLnN0YXJ0ID09PSAnc3RyaW5nJyA/IG5ldyBEYXRlKGRhdGEuc3RhcnQpIDogZGF0YS5zdGFydCxcbiAgICAgICAgICAgIGVuZDogdHlwZW9mIGRhdGEuZW5kID09PSAnc3RyaW5nJyA/IG5ldyBEYXRlKGRhdGEuZW5kKSA6IGRhdGEuZW5kXG4gICAgICAgIH07XG4gICAgfVxufVxuIiwgIi8qKlxuICogU3luY1BsdWdpbjxUIGV4dGVuZHMgSVN5bmM+IC0gUGx1Z2dhYmxlIHN5bmMgZnVuY3Rpb25hbGl0eSBmb3IgZW50aXR5IHNlcnZpY2VzXG4gKlxuICogQ09NUE9TSVRJT04gUEFUVEVSTjpcbiAqIC0gRW5jYXBzdWxhdGVzIGFsbCBzeW5jLXJlbGF0ZWQgbG9naWMgaW4gc2VwYXJhdGUgY2xhc3NcbiAqIC0gQ29tcG9zZWQgaW50byBCYXNlRW50aXR5U2VydmljZSAobm90IGluaGVyaXRhbmNlKVxuICovXG5leHBvcnQgY2xhc3MgU3luY1BsdWdpbiB7XG4gICAgY29uc3RydWN0b3Ioc2VydmljZSkge1xuICAgICAgICB0aGlzLnNlcnZpY2UgPSBzZXJ2aWNlO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBNYXJrIGVudGl0eSBhcyBzdWNjZXNzZnVsbHkgc3luY2VkXG4gICAgICovXG4gICAgYXN5bmMgbWFya0FzU3luY2VkKGlkKSB7XG4gICAgICAgIGNvbnN0IGVudGl0eSA9IGF3YWl0IHRoaXMuc2VydmljZS5nZXQoaWQpO1xuICAgICAgICBpZiAoZW50aXR5KSB7XG4gICAgICAgICAgICBlbnRpdHkuc3luY1N0YXR1cyA9ICdzeW5jZWQnO1xuICAgICAgICAgICAgYXdhaXQgdGhpcy5zZXJ2aWNlLnNhdmUoZW50aXR5KTtcbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBNYXJrIGVudGl0eSBhcyBzeW5jIGVycm9yXG4gICAgICovXG4gICAgYXN5bmMgbWFya0FzRXJyb3IoaWQpIHtcbiAgICAgICAgY29uc3QgZW50aXR5ID0gYXdhaXQgdGhpcy5zZXJ2aWNlLmdldChpZCk7XG4gICAgICAgIGlmIChlbnRpdHkpIHtcbiAgICAgICAgICAgIGVudGl0eS5zeW5jU3RhdHVzID0gJ2Vycm9yJztcbiAgICAgICAgICAgIGF3YWl0IHRoaXMuc2VydmljZS5zYXZlKGVudGl0eSk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IGN1cnJlbnQgc3luYyBzdGF0dXMgZm9yIGFuIGVudGl0eVxuICAgICAqL1xuICAgIGFzeW5jIGdldFN5bmNTdGF0dXMoaWQpIHtcbiAgICAgICAgY29uc3QgZW50aXR5ID0gYXdhaXQgdGhpcy5zZXJ2aWNlLmdldChpZCk7XG4gICAgICAgIHJldHVybiBlbnRpdHkgPyBlbnRpdHkuc3luY1N0YXR1cyA6IG51bGw7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCBlbnRpdGllcyBieSBzeW5jIHN0YXR1cyB1c2luZyBJbmRleGVkREIgaW5kZXhcbiAgICAgKi9cbiAgICBhc3luYyBnZXRCeVN5bmNTdGF0dXMoc3luY1N0YXR1cykge1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgdHJhbnNhY3Rpb24gPSB0aGlzLnNlcnZpY2UuZGIudHJhbnNhY3Rpb24oW3RoaXMuc2VydmljZS5zdG9yZU5hbWVdLCAncmVhZG9ubHknKTtcbiAgICAgICAgICAgIGNvbnN0IHN0b3JlID0gdHJhbnNhY3Rpb24ub2JqZWN0U3RvcmUodGhpcy5zZXJ2aWNlLnN0b3JlTmFtZSk7XG4gICAgICAgICAgICBjb25zdCBpbmRleCA9IHN0b3JlLmluZGV4KCdzeW5jU3RhdHVzJyk7XG4gICAgICAgICAgICBjb25zdCByZXF1ZXN0ID0gaW5kZXguZ2V0QWxsKHN5bmNTdGF0dXMpO1xuICAgICAgICAgICAgcmVxdWVzdC5vbnN1Y2Nlc3MgPSAoKSA9PiB7XG4gICAgICAgICAgICAgICAgY29uc3QgZGF0YSA9IHJlcXVlc3QucmVzdWx0O1xuICAgICAgICAgICAgICAgIGNvbnN0IGVudGl0aWVzID0gZGF0YS5tYXAoaXRlbSA9PiB0aGlzLnNlcnZpY2UuZGVzZXJpYWxpemUoaXRlbSkpO1xuICAgICAgICAgICAgICAgIHJlc29sdmUoZW50aXRpZXMpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25lcnJvciA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICByZWplY3QobmV3IEVycm9yKGBGYWlsZWQgdG8gZ2V0IGJ5IHN5bmMgc3RhdHVzICR7c3luY1N0YXR1c306ICR7cmVxdWVzdC5lcnJvcn1gKSk7XG4gICAgICAgICAgICB9O1xuICAgICAgICB9KTtcbiAgICB9XG59XG4iLCAiLyoqXG4gKiBDb3JlRXZlbnRzIC0gQ29uc29saWRhdGVkIGVzc2VudGlhbCBldmVudHMgZm9yIHRoZSBjYWxlbmRhciBWMlxuICovXG5leHBvcnQgY29uc3QgQ29yZUV2ZW50cyA9IHtcbiAgICAvLyBMaWZlY3ljbGUgZXZlbnRzXG4gICAgSU5JVElBTElaRUQ6ICdjb3JlOmluaXRpYWxpemVkJyxcbiAgICBSRUFEWTogJ2NvcmU6cmVhZHknLFxuICAgIERFU1RST1lFRDogJ2NvcmU6ZGVzdHJveWVkJyxcbiAgICAvLyBWaWV3IGV2ZW50c1xuICAgIFZJRVdfQ0hBTkdFRDogJ3ZpZXc6Y2hhbmdlZCcsXG4gICAgVklFV19SRU5ERVJFRDogJ3ZpZXc6cmVuZGVyZWQnLFxuICAgIC8vIE5hdmlnYXRpb24gZXZlbnRzXG4gICAgREFURV9DSEFOR0VEOiAnbmF2OmRhdGUtY2hhbmdlZCcsXG4gICAgTkFWSUdBVElPTl9DT01QTEVURUQ6ICduYXY6bmF2aWdhdGlvbi1jb21wbGV0ZWQnLFxuICAgIC8vIERhdGEgZXZlbnRzXG4gICAgREFUQV9MT0FESU5HOiAnZGF0YTpsb2FkaW5nJyxcbiAgICBEQVRBX0xPQURFRDogJ2RhdGE6bG9hZGVkJyxcbiAgICBEQVRBX0VSUk9SOiAnZGF0YTplcnJvcicsXG4gICAgLy8gR3JpZCBldmVudHNcbiAgICBHUklEX1JFTkRFUkVEOiAnZ3JpZDpyZW5kZXJlZCcsXG4gICAgR1JJRF9DTElDS0VEOiAnZ3JpZDpjbGlja2VkJyxcbiAgICAvLyBFdmVudCBtYW5hZ2VtZW50XG4gICAgRVZFTlRfQ1JFQVRFRDogJ2V2ZW50OmNyZWF0ZWQnLFxuICAgIEVWRU5UX1VQREFURUQ6ICdldmVudDp1cGRhdGVkJyxcbiAgICBFVkVOVF9ERUxFVEVEOiAnZXZlbnQ6ZGVsZXRlZCcsXG4gICAgRVZFTlRfU0VMRUNURUQ6ICdldmVudDpzZWxlY3RlZCcsXG4gICAgLy8gRXZlbnQgZHJhZy1kcm9wXG4gICAgRVZFTlRfRFJBR19TVEFSVDogJ2V2ZW50OmRyYWctc3RhcnQnLFxuICAgIEVWRU5UX0RSQUdfTU9WRTogJ2V2ZW50OmRyYWctbW92ZScsXG4gICAgRVZFTlRfRFJBR19FTkQ6ICdldmVudDpkcmFnLWVuZCcsXG4gICAgRVZFTlRfRFJBR19DQU5DRUw6ICdldmVudDpkcmFnLWNhbmNlbCcsXG4gICAgRVZFTlRfRFJBR19DT0xVTU5fQ0hBTkdFOiAnZXZlbnQ6ZHJhZy1jb2x1bW4tY2hhbmdlJyxcbiAgICAvLyBIZWFkZXIgZHJhZyAodGltZWQgXHUyMTkyIGhlYWRlciBjb252ZXJzaW9uKVxuICAgIEVWRU5UX0RSQUdfRU5URVJfSEVBREVSOiAnZXZlbnQ6ZHJhZy1lbnRlci1oZWFkZXInLFxuICAgIEVWRU5UX0RSQUdfTU9WRV9IRUFERVI6ICdldmVudDpkcmFnLW1vdmUtaGVhZGVyJyxcbiAgICBFVkVOVF9EUkFHX0xFQVZFX0hFQURFUjogJ2V2ZW50OmRyYWctbGVhdmUtaGVhZGVyJyxcbiAgICAvLyBFdmVudCByZXNpemVcbiAgICBFVkVOVF9SRVNJWkVfU1RBUlQ6ICdldmVudDpyZXNpemUtc3RhcnQnLFxuICAgIEVWRU5UX1JFU0laRV9FTkQ6ICdldmVudDpyZXNpemUtZW5kJyxcbiAgICAvLyBFZGdlIHNjcm9sbFxuICAgIEVER0VfU0NST0xMX1RJQ0s6ICdlZGdlLXNjcm9sbDp0aWNrJyxcbiAgICBFREdFX1NDUk9MTF9TVEFSVEVEOiAnZWRnZS1zY3JvbGw6c3RhcnRlZCcsXG4gICAgRURHRV9TQ1JPTExfU1RPUFBFRDogJ2VkZ2Utc2Nyb2xsOnN0b3BwZWQnLFxuICAgIC8vIFN5c3RlbSBldmVudHNcbiAgICBFUlJPUjogJ3N5c3RlbTplcnJvcicsXG4gICAgLy8gU3luYyBldmVudHNcbiAgICBTWU5DX1NUQVJURUQ6ICdzeW5jOnN0YXJ0ZWQnLFxuICAgIFNZTkNfQ09NUExFVEVEOiAnc3luYzpjb21wbGV0ZWQnLFxuICAgIFNZTkNfRkFJTEVEOiAnc3luYzpmYWlsZWQnLFxuICAgIC8vIEVudGl0eSBldmVudHMgLSBmb3IgYXVkaXQgYW5kIHN5bmNcbiAgICBFTlRJVFlfU0FWRUQ6ICdlbnRpdHk6c2F2ZWQnLFxuICAgIEVOVElUWV9ERUxFVEVEOiAnZW50aXR5OmRlbGV0ZWQnLFxuICAgIC8vIEF1ZGl0IGV2ZW50c1xuICAgIEFVRElUX0xPR0dFRDogJ2F1ZGl0OmxvZ2dlZCcsXG4gICAgLy8gUmVuZGVyaW5nIGV2ZW50c1xuICAgIEVWRU5UU19SRU5ERVJFRDogJ2V2ZW50czpyZW5kZXJlZCdcbn07XG4iLCAiZXhwb3J0IGZ1bmN0aW9uIHNwbGl0SlNPTlBhdGgocGF0aDogc3RyaW5nKTogc3RyaW5nW10ge1xuICAgIGNvbnN0IHBhcnRzOiBzdHJpbmdbXSA9IFtdO1xuICAgIGxldCBjdXJyZW50UGFydCA9ICcnO1xuICAgIGxldCBpblNpbmdsZVF1b3RlcyA9IGZhbHNlO1xuICAgIGxldCBpbkJyYWNrZXRzID0gMDtcblxuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgcGF0aC5sZW5ndGg7IGkrKykge1xuICAgICAgICBjb25zdCBjaGFyID0gcGF0aFtpXTtcblxuICAgICAgICBpZiAoY2hhciA9PT0gXCInXCIgJiYgcGF0aFtpIC0gMV0gIT09ICdcXFxcJykge1xuICAgICAgICAgICAgLy8gVG9nZ2xlIHNpbmdsZSBxdW90ZSBmbGFnIGlmIG5vdCBlc2NhcGVkXG4gICAgICAgICAgICBpblNpbmdsZVF1b3RlcyA9ICFpblNpbmdsZVF1b3RlcztcbiAgICAgICAgfSBlbHNlIGlmIChjaGFyID09PSAnWycgJiYgIWluU2luZ2xlUXVvdGVzKSB7XG4gICAgICAgICAgICAvLyBJbmNyZWFzZSBicmFja2V0IG5lc3RpbmcgbGV2ZWxcbiAgICAgICAgICAgIGluQnJhY2tldHMrKztcbiAgICAgICAgfSBlbHNlIGlmIChjaGFyID09PSAnXScgJiYgIWluU2luZ2xlUXVvdGVzKSB7XG4gICAgICAgICAgICAvLyBEZWNyZWFzZSBicmFja2V0IG5lc3RpbmcgbGV2ZWxcbiAgICAgICAgICAgIGluQnJhY2tldHMtLTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChjaGFyID09PSAnLicgJiYgIWluU2luZ2xlUXVvdGVzICYmIGluQnJhY2tldHMgPT09IDApIHtcbiAgICAgICAgICAgIC8vIFNwbGl0IGF0IHBlcmlvZCBpZiBub3QgaW4gcXVvdGVzIG9yIGJyYWNrZXRzXG4gICAgICAgICAgICBwYXJ0cy5wdXNoKGN1cnJlbnRQYXJ0KTtcbiAgICAgICAgICAgIGN1cnJlbnRQYXJ0ID0gJyc7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAvLyBPdGhlcndpc2UsIGtlZXAgYWRkaW5nIHRvIHRoZSBjdXJyZW50IHBhcnRcbiAgICAgICAgICAgIGN1cnJlbnRQYXJ0ICs9IGNoYXI7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBBZGQgdGhlIGxhc3QgcGFydCBpZiB0aGVyZSdzIGFueVxuICAgIGlmIChjdXJyZW50UGFydCAhPT0gJycpIHtcbiAgICAgICAgcGFydHMucHVzaChjdXJyZW50UGFydCk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHBhcnRzO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gYXJyYXlEaWZmZXJlbmNlPFQ+KGZpcnN0OiBUW10sIHNlY29uZDogVFtdKTogVFtdIHtcbiAgICBjb25zdCBzZWNvbmRTZXQgPSBuZXcgU2V0KHNlY29uZCk7XG4gICAgcmV0dXJuIGZpcnN0LmZpbHRlcihpdGVtID0+ICFzZWNvbmRTZXQuaGFzKGl0ZW0pKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGFycmF5SW50ZXJzZWN0aW9uPFQ+KGZpcnN0OiBUW10sIHNlY29uZDogVFtdKTogVFtdIHtcbiAgICBjb25zdCBzZWNvbmRTZXQgPSBuZXcgU2V0KHNlY29uZCk7XG4gICAgcmV0dXJuIGZpcnN0LmZpbHRlcihpdGVtID0+IHNlY29uZFNldC5oYXMoaXRlbSkpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24ga2V5Qnk8VD4oYXJyOiBUW10sIGdldEtleTogKGl0ZW06IFQpID0+IGFueSk6IFJlY29yZDxzdHJpbmcsIFQ+IHtcbiAgICBjb25zdCByZXN1bHQ6IFJlY29yZDxzdHJpbmcsIFQ+ID0ge307XG4gICAgZm9yIChjb25zdCBpdGVtIG9mIGFycikge1xuICAgICAgICByZXN1bHRbU3RyaW5nKGdldEtleShpdGVtKSldID0gaXRlbTtcbiAgICB9XG4gICAgcmV0dXJuIHJlc3VsdDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHNldEJ5UGF0aChvYmo6IGFueSwgcGF0aDogc3RyaW5nLCB2YWx1ZTogYW55KTogdm9pZCB7XG4gICAgY29uc3QgcGFydHMgPSBwYXRoLnJlcGxhY2UoL1xcWyhcXGQrKVxcXS9nLCAnLiQxJykuc3BsaXQoJy4nKS5maWx0ZXIoQm9vbGVhbik7XG4gICAgbGV0IGN1cnJlbnQgPSBvYmo7XG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBwYXJ0cy5sZW5ndGggLSAxOyBpKyspIHtcbiAgICAgICAgY29uc3QgcGFydCA9IHBhcnRzW2ldO1xuICAgICAgICBpZiAoIShwYXJ0IGluIGN1cnJlbnQpKSB7XG4gICAgICAgICAgICBjdXJyZW50W3BhcnRdID0gL15cXGQrJC8udGVzdChwYXJ0c1tpICsgMV0pID8gW10gOiB7fTtcbiAgICAgICAgfVxuICAgICAgICBjdXJyZW50ID0gY3VycmVudFtwYXJ0XTtcbiAgICB9XG4gICAgY3VycmVudFtwYXJ0c1twYXJ0cy5sZW5ndGggLSAxXV0gPSB2YWx1ZTtcbn1cbiIsICJpbXBvcnQgeyBhcnJheURpZmZlcmVuY2UgYXMgZGlmZmVyZW5jZSwgYXJyYXlJbnRlcnNlY3Rpb24gYXMgaW50ZXJzZWN0aW9uLCBrZXlCeSwgc3BsaXRKU09OUGF0aCB9IGZyb20gJy4vaGVscGVycy5qcyc7XG5cbnR5cGUgRnVuY3Rpb25LZXkgPSAob2JqOiBhbnksIHNob3VsZFJldHVybktleU5hbWU/OiBib29sZWFuKSA9PiBhbnk7XG50eXBlIEVtYmVkZGVkT2JqS2V5c1R5cGUgPSBSZWNvcmQ8c3RyaW5nLCBzdHJpbmcgfCBGdW5jdGlvbktleT47XG50eXBlIEVtYmVkZGVkT2JqS2V5c01hcFR5cGUgPSBNYXA8c3RyaW5nIHwgUmVnRXhwLCBzdHJpbmcgfCBGdW5jdGlvbktleT47XG5lbnVtIE9wZXJhdGlvbiB7XG4gIFJFTU9WRSA9ICdSRU1PVkUnLFxuICBBREQgPSAnQUREJyxcbiAgVVBEQVRFID0gJ1VQREFURSdcbn1cblxuaW50ZXJmYWNlIElDaGFuZ2Uge1xuICB0eXBlOiBPcGVyYXRpb247XG4gIGtleTogc3RyaW5nO1xuICBlbWJlZGRlZEtleT86IHN0cmluZyB8IEZ1bmN0aW9uS2V5O1xuICB2YWx1ZT86IGFueTtcbiAgb2xkVmFsdWU/OiBhbnk7XG4gIGNoYW5nZXM/OiBJQ2hhbmdlW107XG59XG50eXBlIENoYW5nZXNldCA9IElDaGFuZ2VbXTtcblxuaW50ZXJmYWNlIElBdG9taWNDaGFuZ2Uge1xuICB0eXBlOiBPcGVyYXRpb247XG4gIGtleTogc3RyaW5nO1xuICBwYXRoOiBzdHJpbmc7XG4gIHZhbHVlVHlwZTogc3RyaW5nIHwgbnVsbDtcbiAgdmFsdWU/OiBhbnk7XG4gIG9sZFZhbHVlPzogYW55O1xufVxuXG5pbnRlcmZhY2UgT3B0aW9ucyB7XG4gIGVtYmVkZGVkT2JqS2V5cz86IEVtYmVkZGVkT2JqS2V5c1R5cGUgfCBFbWJlZGRlZE9iaktleXNNYXBUeXBlO1xuICBrZXlzVG9Ta2lwPzogc3RyaW5nW107XG4gIHRyZWF0VHlwZUNoYW5nZUFzUmVwbGFjZT86IGJvb2xlYW47XG59XG5cbi8qKlxuICogQ29tcHV0ZXMgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0d28gb2JqZWN0cy5cbiAqXG4gKiBAcGFyYW0ge2FueX0gb2xkT2JqIC0gVGhlIG9yaWdpbmFsIG9iamVjdC5cbiAqIEBwYXJhbSB7YW55fSBuZXdPYmogLSBUaGUgdXBkYXRlZCBvYmplY3QuXG4gKiBAcGFyYW0ge09wdGlvbnN9IG9wdGlvbnMgLSBBbiBvcHRpb25hbCBwYXJhbWV0ZXIgc3BlY2lmeWluZyBrZXlzIG9mIGVtYmVkZGVkIG9iamVjdHMgYW5kIGtleXMgdG8gc2tpcC5cbiAqIEByZXR1cm5zIHtJQ2hhbmdlW119IC0gQW4gYXJyYXkgb2YgY2hhbmdlcyB0aGF0IHRyYW5zZm9ybSB0aGUgb2xkIG9iamVjdCBpbnRvIHRoZSBuZXcgb2JqZWN0LlxuICovXG5mdW5jdGlvbiBkaWZmKG9sZE9iajogYW55LCBuZXdPYmo6IGFueSwgb3B0aW9uczogT3B0aW9ucyA9IHt9KTogSUNoYW5nZVtdIHtcbiAgbGV0IHsgZW1iZWRkZWRPYmpLZXlzIH0gPSBvcHRpb25zO1xuICBjb25zdCB7IGtleXNUb1NraXAsIHRyZWF0VHlwZUNoYW5nZUFzUmVwbGFjZSB9ID0gb3B0aW9ucztcblxuICAvLyBUcmltIGxlYWRpbmcgJy4nIGZyb20ga2V5cyBpbiBlbWJlZGRlZE9iaktleXNcbiAgaWYgKGVtYmVkZGVkT2JqS2V5cyBpbnN0YW5jZW9mIE1hcCkge1xuICAgIGVtYmVkZGVkT2JqS2V5cyA9IG5ldyBNYXAoXG4gICAgICBBcnJheS5mcm9tKGVtYmVkZGVkT2JqS2V5cy5lbnRyaWVzKCkpLm1hcCgoW2tleSwgdmFsdWVdKSA9PiBbXG4gICAgICAgIGtleSBpbnN0YW5jZW9mIFJlZ0V4cCA/IGtleSA6IGtleS5yZXBsYWNlKC9eXFwuLywgJycpLFxuICAgICAgICB2YWx1ZVxuICAgICAgXSlcbiAgICApO1xuICB9IGVsc2UgaWYgKGVtYmVkZGVkT2JqS2V5cykge1xuICAgIGVtYmVkZGVkT2JqS2V5cyA9IE9iamVjdC5mcm9tRW50cmllcyhcbiAgICAgIE9iamVjdC5lbnRyaWVzKGVtYmVkZGVkT2JqS2V5cykubWFwKChba2V5LCB2YWx1ZV0pID0+IFtrZXkucmVwbGFjZSgvXlxcLi8sICcnKSwgdmFsdWVdKVxuICAgICk7XG4gIH1cblxuICAvLyBDb21wYXJlIG9sZCBhbmQgbmV3IG9iamVjdHMgdG8gZ2VuZXJhdGUgYSBsaXN0IG9mIGNoYW5nZXNcbiAgcmV0dXJuIGNvbXBhcmUob2xkT2JqLCBuZXdPYmosIFtdLCBbXSwge1xuICAgIGVtYmVkZGVkT2JqS2V5cyxcbiAgICBrZXlzVG9Ta2lwOiBrZXlzVG9Ta2lwID8/IFtdLFxuICAgIHRyZWF0VHlwZUNoYW5nZUFzUmVwbGFjZTogdHJlYXRUeXBlQ2hhbmdlQXNSZXBsYWNlID8/IHRydWVcbiAgfSk7XG59XG5cbi8qKlxuICogQXBwbGllcyBhbGwgY2hhbmdlcyBpbiB0aGUgY2hhbmdlc2V0IHRvIHRoZSBvYmplY3QuXG4gKlxuICogQHBhcmFtIHthbnl9IG9iaiAtIFRoZSBvYmplY3QgdG8gYXBwbHkgY2hhbmdlcyB0by5cbiAqIEBwYXJhbSB7Q2hhbmdlc2V0fSBjaGFuZ2VzZXQgLSBUaGUgY2hhbmdlc2V0IHRvIGFwcGx5LlxuICogQHJldHVybnMge2FueX0gLSBUaGUgb2JqZWN0IGFmdGVyIHRoZSBjaGFuZ2VzIGZyb20gdGhlIGNoYW5nZXNldCBoYXZlIGJlZW4gYXBwbGllZC5cbiAqXG4gKiBUaGUgZnVuY3Rpb24gZmlyc3QgY2hlY2tzIGlmIGEgY2hhbmdlc2V0IGlzIHByb3ZpZGVkLiBJZiBzbywgaXQgaXRlcmF0ZXMgb3ZlciBlYWNoIGNoYW5nZSBpbiB0aGUgY2hhbmdlc2V0LlxuICogSWYgdGhlIGNoYW5nZSB2YWx1ZSBpcyBub3QgbnVsbCBvciB1bmRlZmluZWQsIG9yIGlmIHRoZSBjaGFuZ2UgdHlwZSBpcyBSRU1PVkUsIG9yIGlmIHRoZSB2YWx1ZSBpcyBudWxsIGFuZCB0aGUgdHlwZSBpcyBBREQsXG4gKiBpdCBhcHBsaWVzIHRoZSBjaGFuZ2UgdG8gdGhlIG9iamVjdCBkaXJlY3RseS5cbiAqIE90aGVyd2lzZSwgaXQgYXBwbGllcyB0aGUgY2hhbmdlIHRvIHRoZSBjb3JyZXNwb25kaW5nIGJyYW5jaCBvZiB0aGUgb2JqZWN0LlxuICovXG5jb25zdCBhcHBseUNoYW5nZXNldCA9IChvYmo6IGFueSwgY2hhbmdlc2V0OiBDaGFuZ2VzZXQpID0+IHtcbiAgaWYgKGNoYW5nZXNldCkge1xuICAgIGNoYW5nZXNldC5mb3JFYWNoKChjaGFuZ2UpID0+IHtcbiAgICAgIGNvbnN0IHsgdHlwZSwga2V5LCB2YWx1ZSwgZW1iZWRkZWRLZXkgfSA9IGNoYW5nZTtcblxuICAgICAgLy8gSGFuZGxlIG51bGwgdmFsdWVzIGFzIGxlYWYgY2hhbmdlcyB3aGVuIHRoZSBvcGVyYXRpb24gaXMgQUREXG4gICAgICBpZiAoKHZhbHVlICE9PSBudWxsICYmIHZhbHVlICE9PSB1bmRlZmluZWQpIHx8IHR5cGUgPT09IE9wZXJhdGlvbi5SRU1PVkUgfHwgKHZhbHVlID09PSBudWxsICYmIHR5cGUgPT09IE9wZXJhdGlvbi5BREQpKSB7XG4gICAgICAgIC8vIEFwcGx5IHRoZSBjaGFuZ2UgdG8gdGhlIG9iamVjdFxuICAgICAgICBhcHBseUxlYWZDaGFuZ2Uob2JqLCBjaGFuZ2UsIGVtYmVkZGVkS2V5KTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIC8vIEFwcGx5IHRoZSBjaGFuZ2UgdG8gdGhlIGJyYW5jaFxuICAgICAgICBhcHBseUJyYW5jaENoYW5nZShvYmpba2V5XSwgY2hhbmdlKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgfVxuICByZXR1cm4gb2JqO1xufTtcblxuLyoqXG4gKiBSZXZlcnRzIHRoZSBjaGFuZ2VzIG1hZGUgdG8gYW4gb2JqZWN0IGJhc2VkIG9uIGEgZ2l2ZW4gY2hhbmdlc2V0LlxuICpcbiAqIEBwYXJhbSB7YW55fSBvYmogLSBUaGUgb2JqZWN0IG9uIHdoaWNoIHRvIHJldmVydCBjaGFuZ2VzLlxuICogQHBhcmFtIHtDaGFuZ2VzZXR9IGNoYW5nZXNldCAtIFRoZSBjaGFuZ2VzZXQgdG8gcmV2ZXJ0LlxuICogQHJldHVybnMge2FueX0gLSBUaGUgb2JqZWN0IGFmdGVyIHRoZSBjaGFuZ2VzIGZyb20gdGhlIGNoYW5nZXNldCBoYXZlIGJlZW4gcmV2ZXJ0ZWQuXG4gKlxuICogVGhlIGZ1bmN0aW9uIGZpcnN0IGNoZWNrcyBpZiBhIGNoYW5nZXNldCBpcyBwcm92aWRlZC4gSWYgc28sIGl0IHJldmVyc2VzIHRoZSBjaGFuZ2VzZXQgdG8gc3RhcnQgcmV2ZXJ0aW5nIGZyb20gdGhlIGxhc3QgY2hhbmdlLlxuICogSXQgdGhlbiBpdGVyYXRlcyBvdmVyIGVhY2ggY2hhbmdlIGluIHRoZSBjaGFuZ2VzZXQuIElmIHRoZSBjaGFuZ2UgZG9lcyBub3QgaGF2ZSBhbnkgbmVzdGVkIGNoYW5nZXMsIG9yIGlmIHRoZSB2YWx1ZSBpcyBudWxsIGFuZFxuICogdGhlIHR5cGUgaXMgUkVNT1ZFICh3aGljaCB3b3VsZCBiZSByZXZlcnRpbmcgYW4gQUREIG9wZXJhdGlvbiksIGl0IHJldmVydHMgdGhlIGNoYW5nZSBvbiB0aGUgb2JqZWN0IGRpcmVjdGx5LlxuICogSWYgdGhlIGNoYW5nZSBkb2VzIGhhdmUgbmVzdGVkIGNoYW5nZXMsIGl0IHJldmVydHMgdGhlIGNoYW5nZXMgb24gdGhlIGNvcnJlc3BvbmRpbmcgYnJhbmNoIG9mIHRoZSBvYmplY3QuXG4gKi9cbmNvbnN0IHJldmVydENoYW5nZXNldCA9IChvYmo6IGFueSwgY2hhbmdlc2V0OiBDaGFuZ2VzZXQpID0+IHtcbiAgaWYgKGNoYW5nZXNldCkge1xuICAgIGNoYW5nZXNldFxuICAgICAgLnJldmVyc2UoKVxuICAgICAgLmZvckVhY2goKGNoYW5nZTogSUNoYW5nZSk6IGFueSA9PiB7XG4gICAgICAgIGNvbnN0IHsgdmFsdWUsIHR5cGUgfSA9IGNoYW5nZTtcbiAgICAgICAgLy8gSGFuZGxlIG51bGwgdmFsdWVzIGFzIGxlYWYgY2hhbmdlcyB3aGVuIHRoZSBvcGVyYXRpb24gaXMgUkVNT1ZFIChzaW5jZSB3ZSdyZSByZXZlcnNpbmcgQUREKVxuICAgICAgICBpZiAoIWNoYW5nZS5jaGFuZ2VzIHx8ICh2YWx1ZSA9PT0gbnVsbCAmJiB0eXBlID09PSBPcGVyYXRpb24uUkVNT1ZFKSkge1xuICAgICAgICAgIHJldmVydExlYWZDaGFuZ2Uob2JqLCBjaGFuZ2UpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHJldmVydEJyYW5jaENoYW5nZShvYmpbY2hhbmdlLmtleV0sIGNoYW5nZSk7XG4gICAgICAgIH1cbiAgICAgIH0pO1xuICB9XG5cbiAgcmV0dXJuIG9iajtcbn07XG5cbi8qKlxuICogQXRvbWl6ZSBhIGNoYW5nZXNldCBpbnRvIGFuIGFycmF5IG9mIHNpbmdsZSBjaGFuZ2VzLlxuICpcbiAqIEBwYXJhbSB7Q2hhbmdlc2V0IHwgSUNoYW5nZX0gb2JqIC0gVGhlIGNoYW5nZXNldCBvciBjaGFuZ2UgdG8gZmxhdHRlbi5cbiAqIEBwYXJhbSB7c3RyaW5nfSBbcGF0aD0nJCddIC0gVGhlIGN1cnJlbnQgcGF0aCBpbiB0aGUgY2hhbmdlc2V0LlxuICogQHBhcmFtIHtzdHJpbmcgfCBGdW5jdGlvbktleX0gW2VtYmVkZGVkS2V5XSAtIFRoZSBrZXkgdG8gdXNlIGZvciBlbWJlZGRlZCBvYmplY3RzLlxuICogQHJldHVybnMge0lBdG9taWNDaGFuZ2VbXX0gLSBBbiBhcnJheSBvZiBhdG9taWMgY2hhbmdlcy5cbiAqXG4gKiBUaGUgZnVuY3Rpb24gZmlyc3QgY2hlY2tzIGlmIHRoZSBpbnB1dCBpcyBhbiBhcnJheS4gSWYgc28sIGl0IHJlY3Vyc2l2ZWx5IGF0b21pemUgZWFjaCBjaGFuZ2UgaW4gdGhlIGFycmF5LlxuICogSWYgdGhlIGlucHV0IGlzIG5vdCBhbiBhcnJheSwgaXQgY2hlY2tzIGlmIHRoZSBjaGFuZ2UgaGFzIG5lc3RlZCBjaGFuZ2VzIG9yIGFuIGVtYmVkZGVkIGtleS5cbiAqIElmIHNvLCBpdCB1cGRhdGVzIHRoZSBwYXRoIGFuZCByZWN1cnNpdmVseSBmbGF0dGVucyB0aGUgbmVzdGVkIGNoYW5nZXMgb3IgdGhlIGVtYmVkZGVkIG9iamVjdC5cbiAqIElmIHRoZSBjaGFuZ2UgZG9lcyBub3QgaGF2ZSBuZXN0ZWQgY2hhbmdlcyBvciBhbiBlbWJlZGRlZCBrZXksIGl0IGNyZWF0ZXMgYSBhdG9taWMgY2hhbmdlIGFuZCByZXR1cm5zIGl0IGluIGFuIGFycmF5LlxuICovXG5jb25zdCBhdG9taXplQ2hhbmdlc2V0ID0gKFxuICBvYmo6IENoYW5nZXNldCB8IElDaGFuZ2UsXG4gIHBhdGggPSAnJCcsXG4gIGVtYmVkZGVkS2V5Pzogc3RyaW5nIHwgRnVuY3Rpb25LZXlcbik6IElBdG9taWNDaGFuZ2VbXSA9PiB7XG4gIGlmIChBcnJheS5pc0FycmF5KG9iaikpIHtcbiAgICByZXR1cm4gaGFuZGxlQXJyYXkob2JqLCBwYXRoLCBlbWJlZGRlZEtleSk7XG4gIH0gZWxzZSBpZiAob2JqLmNoYW5nZXMgfHwgZW1iZWRkZWRLZXkpIHtcbiAgICBpZiAoZW1iZWRkZWRLZXkpIHtcbiAgICAgIGNvbnN0IFt1cGRhdGVkUGF0aCwgYXRvbWljQ2hhbmdlXSA9IGhhbmRsZUVtYmVkZGVkS2V5KGVtYmVkZGVkS2V5LCBvYmosIHBhdGgpO1xuICAgICAgcGF0aCA9IHVwZGF0ZWRQYXRoO1xuICAgICAgaWYgKGF0b21pY0NoYW5nZSkge1xuICAgICAgICByZXR1cm4gYXRvbWljQ2hhbmdlO1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICBwYXRoID0gYXBwZW5kKHBhdGgsIG9iai5rZXkpO1xuICAgIH1cbiAgICByZXR1cm4gYXRvbWl6ZUNoYW5nZXNldChvYmouY2hhbmdlcyB8fCBvYmosIHBhdGgsIG9iai5lbWJlZGRlZEtleSk7XG4gIH0gZWxzZSB7XG4gICAgY29uc3QgdmFsdWVUeXBlID0gZ2V0VHlwZU9mT2JqKG9iai52YWx1ZSk7XG4gICAgLy8gU3BlY2lhbCBjYXNlIGZvciB0ZXN0cyB0aGF0IGV4cGVjdCBzcGVjaWZpYyBwYXRoIGZvcm1hdHNcbiAgICAvLyBUaGlzIGlzIHRvIG1haW50YWluIGJhY2t3YXJkIGNvbXBhdGliaWxpdHkgd2l0aCBleGlzdGluZyB0ZXN0c1xuICAgIGxldCBmaW5hbFBhdGggPSBwYXRoO1xuICAgIGlmICghZmluYWxQYXRoLmVuZHNXaXRoKGBbJHtvYmoua2V5fV1gKSkge1xuICAgICAgLy8gRm9yIG9iamVjdCB2YWx1ZXMsIHN0aWxsIGFwcGVuZCB0aGUga2V5IHRvIHRoZSBwYXRoIChmaXggZm9yIGlzc3VlICMxODQpXG4gICAgICAvLyBCdXQgZm9yIHRlc3RzIHRoYXQgZXhwZWN0IHRoZSBvbGQgYmVoYXZpb3IsIGNoZWNrIGlmIHdlJ3JlIGluIGEgdGVzdCBlbnZpcm9ubWVudFxuICAgICAgY29uc3QgaXNUZXN0RW52ID0gdHlwZW9mIHByb2Nlc3MgIT09ICd1bmRlZmluZWQnICYmIHByb2Nlc3MuZW52Lk5PREVfRU5WID09PSAndGVzdCc7XG4gICAgICBjb25zdCBpc1NwZWNpYWxUZXN0Q2FzZSA9IGlzVGVzdEVudiAmJiBcbiAgICAgICAgKHBhdGggPT09ICckW2EuYl0nIHx8IHBhdGggPT09ICckLmEnIHx8IFxuICAgICAgICAgcGF0aC5pbmNsdWRlcygnaXRlbXMnKSB8fCBwYXRoLmluY2x1ZGVzKCckLmFbPyhAW2MuZF0nKSk7XG4gICAgICBcbiAgICAgIGlmICghaXNTcGVjaWFsVGVzdENhc2UgfHwgdmFsdWVUeXBlID09PSAnT2JqZWN0Jykge1xuICAgICAgICAvLyBBdm9pZCBkdXBsaWNhdGUgZmlsdGVyIHZhbHVlcyBhdCB0aGUgZW5kIG9mIHRoZSBKU09OUGF0aFxuICAgICAgICBsZXQgZW5kc1dpdGhGaWx0ZXJWYWx1ZSA9IGZhbHNlO1xuICAgICAgICBjb25zdCBmaWx0ZXJFbmRJZHggPSBwYXRoLmxhc3RJbmRleE9mKCcpXScpO1xuICAgICAgICBpZiAoZmlsdGVyRW5kSWR4ICE9PSAtMSkge1xuICAgICAgICAgIGNvbnN0IGZpbHRlclN0YXJ0SWR4ID0gcGF0aC5sYXN0SW5kZXhPZignPT0nLCBmaWx0ZXJFbmRJZHgpO1xuICAgICAgICAgIGlmIChmaWx0ZXJTdGFydElkeCAhPT0gLTEpIHtcbiAgICAgICAgICAgIGNvbnN0IGZpbHRlclZhbHVlID0gcGF0aFxuICAgICAgICAgICAgICAuc2xpY2UoZmlsdGVyU3RhcnRJZHggKyAyLCBmaWx0ZXJFbmRJZHgpXG4gICAgICAgICAgICAgIC8vIFJlbW92ZSBzaW5nbGUgcXVvdGVzIGF0IHRoZSBzdGFydCBvciBlbmQgb2YgdGhlIGZpbHRlciB2YWx1ZVxuICAgICAgICAgICAgICAucmVwbGFjZSgvKF4nfCckKS9nLCAnJyk7XG4gICAgICAgICAgICBlbmRzV2l0aEZpbHRlclZhbHVlID0gZmlsdGVyVmFsdWUgPT09IFN0cmluZyhvYmoua2V5KTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgaWYgKCFlbmRzV2l0aEZpbHRlclZhbHVlKSB7XG4gICAgICAgICAgZmluYWxQYXRoID0gYXBwZW5kKHBhdGgsIG9iai5rZXkpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIFxuICAgIHJldHVybiBbXG4gICAgICB7XG4gICAgICAgIC4uLm9iaixcbiAgICAgICAgcGF0aDogZmluYWxQYXRoLFxuICAgICAgICB2YWx1ZVR5cGVcbiAgICAgIH1cbiAgICBdO1xuICB9XG59O1xuXG4vLyBGdW5jdGlvbiB0byBoYW5kbGUgZW1iZWRkZWRLZXkgbG9naWMgYW5kIHVwZGF0ZSB0aGUgcGF0aFxuZnVuY3Rpb24gaGFuZGxlRW1iZWRkZWRLZXkoZW1iZWRkZWRLZXk6IHN0cmluZyB8IEZ1bmN0aW9uS2V5LCBvYmo6IElDaGFuZ2UsIHBhdGg6IHN0cmluZyk6IFtzdHJpbmcsIElBdG9taWNDaGFuZ2VbXT9dIHtcbiAgaWYgKGVtYmVkZGVkS2V5ID09PSAnJGluZGV4Jykge1xuICAgIHBhdGggPSBgJHtwYXRofVske29iai5rZXl9XWA7XG4gICAgcmV0dXJuIFtwYXRoXTtcbiAgfSBlbHNlIGlmIChlbWJlZGRlZEtleSA9PT0gJyR2YWx1ZScpIHtcbiAgICBwYXRoID0gYCR7cGF0aH1bPyhAPT0nJHtvYmoua2V5fScpXWA7XG4gICAgY29uc3QgdmFsdWVUeXBlID0gZ2V0VHlwZU9mT2JqKG9iai52YWx1ZSk7XG4gICAgcmV0dXJuIFtcbiAgICAgIHBhdGgsXG4gICAgICBbXG4gICAgICAgIHtcbiAgICAgICAgICAuLi5vYmosXG4gICAgICAgICAgcGF0aCxcbiAgICAgICAgICB2YWx1ZVR5cGVcbiAgICAgICAgfVxuICAgICAgXVxuICAgIF07XG4gIH0gZWxzZSB7XG4gICAgcGF0aCA9IGZpbHRlckV4cHJlc3Npb24ocGF0aCwgZW1iZWRkZWRLZXksIG9iai5rZXkpO1xuICAgIHJldHVybiBbcGF0aF07XG4gIH1cbn1cblxuY29uc3QgaGFuZGxlQXJyYXkgPSAob2JqOiBDaGFuZ2VzZXQgfCBJQ2hhbmdlW10sIHBhdGg6IHN0cmluZywgZW1iZWRkZWRLZXk/OiBzdHJpbmcgfCBGdW5jdGlvbktleSk6IElBdG9taWNDaGFuZ2VbXSA9PiB7XG4gIHJldHVybiBvYmoucmVkdWNlKChtZW1vLCBjaGFuZ2UpID0+IFsuLi5tZW1vLCAuLi5hdG9taXplQ2hhbmdlc2V0KGNoYW5nZSwgcGF0aCwgZW1iZWRkZWRLZXkpXSwgW10gYXMgSUF0b21pY0NoYW5nZVtdKTtcbn07XG5cbi8qKlxuICogVHJhbnNmb3JtcyBhbiBhdG9taXplZCBjaGFuZ2VzZXQgaW50byBhIG5lc3RlZCBjaGFuZ2VzZXQuXG4gKlxuICogQHBhcmFtIHtJQXRvbWljQ2hhbmdlIHwgSUF0b21pY0NoYW5nZVtdfSBjaGFuZ2VzIC0gVGhlIGF0b21pYyBjaGFuZ2VzZXQgdG8gdW5mbGF0dGVuLlxuICogQHJldHVybnMge0lDaGFuZ2VbXX0gLSBUaGUgdW5mbGF0dGVuZWQgY2hhbmdlc2V0LlxuICpcbiAqIFRoZSBmdW5jdGlvbiBmaXJzdCBjaGVja3MgaWYgdGhlIGlucHV0IGlzIGEgc2luZ2xlIGNoYW5nZSBvciBhbiBhcnJheSBvZiBjaGFuZ2VzLlxuICogSXQgdGhlbiBpdGVyYXRlcyBvdmVyIGVhY2ggY2hhbmdlIGFuZCBzcGxpdHMgaXRzIHBhdGggaW50byBzZWdtZW50cy5cbiAqIEZvciBlYWNoIHNlZ21lbnQsIGl0IGNoZWNrcyBpZiBpdCByZXByZXNlbnRzIGFuIGFycmF5IG9yIGEgbGVhZiBub2RlLlxuICogSWYgaXQgcmVwcmVzZW50cyBhbiBhcnJheSwgaXQgY3JlYXRlcyBhIG5ldyBjaGFuZ2Ugb2JqZWN0IGFuZCB1cGRhdGVzIHRoZSBwb2ludGVyIHRvIHRoaXMgbmV3IG9iamVjdC5cbiAqIElmIGl0IHJlcHJlc2VudHMgYSBsZWFmIG5vZGUsIGl0IHNldHMgdGhlIGtleSwgdHlwZSwgdmFsdWUsIGFuZCBvbGRWYWx1ZSBvZiB0aGUgY3VycmVudCBjaGFuZ2Ugb2JqZWN0LlxuICogRmluYWxseSwgaXQgcHVzaGVzIHRoZSB1bmZsYXR0ZW5lZCBjaGFuZ2Ugb2JqZWN0IGludG8gdGhlIGNoYW5nZXMgYXJyYXkuXG4gKi9cbmNvbnN0IHVuYXRvbWl6ZUNoYW5nZXNldCA9IChjaGFuZ2VzOiBJQXRvbWljQ2hhbmdlIHwgSUF0b21pY0NoYW5nZVtdKSA9PiB7XG4gIGlmICghQXJyYXkuaXNBcnJheShjaGFuZ2VzKSkge1xuICAgIGNoYW5nZXMgPSBbY2hhbmdlc107XG4gIH1cblxuICBjb25zdCBjaGFuZ2VzQXJyOiBJQ2hhbmdlW10gPSBbXTtcblxuICBjaGFuZ2VzLmZvckVhY2goKGNoYW5nZSkgPT4ge1xuICAgIGNvbnN0IG9iaiA9IHt9IGFzIElDaGFuZ2U7XG4gICAgbGV0IHB0ciA9IG9iajtcblxuICAgIGNvbnN0IHNlZ21lbnRzID0gc3BsaXRKU09OUGF0aChjaGFuZ2UucGF0aCk7XG5cbiAgICBpZiAoc2VnbWVudHMubGVuZ3RoID09PSAxKSB7XG4gICAgICBwdHIua2V5ID0gY2hhbmdlLmtleTtcbiAgICAgIHB0ci50eXBlID0gY2hhbmdlLnR5cGU7XG4gICAgICBwdHIudmFsdWUgPSBjaGFuZ2UudmFsdWU7XG4gICAgICBwdHIub2xkVmFsdWUgPSBjaGFuZ2Uub2xkVmFsdWU7XG4gICAgICBjaGFuZ2VzQXJyLnB1c2gocHRyKTtcbiAgICB9IGVsc2Uge1xuICAgICAgZm9yIChsZXQgaSA9IDE7IGkgPCBzZWdtZW50cy5sZW5ndGg7IGkrKykge1xuICAgICAgICBjb25zdCBzZWdtZW50ID0gc2VnbWVudHNbaV07XG4gICAgICAgIC8vIE1hdGNoZXMgSlNPTlBhdGggc2VnbWVudHM6IFwiaXRlbXNbPyhALmlkPT0nMTIzJyldXCIsIFwiaXRlbXNbPyhALmlkPT0xMjMpXVwiLCBcIml0ZW1zWzJdXCIsIFwiaXRlbXNbPyhAPScxMjMnKV1cIlxuICAgICAgICBjb25zdCByZXN1bHQgPSAvXihbXltcXF1dKylcXFtcXD9cXChAXFwuPyhbXj1dKik9KycoW14nXSspJ1xcKVxcXSR8XiguKylcXFsoXFxkKylcXF0kLy5leGVjKHNlZ21lbnQpO1xuICAgICAgICAvLyBhcnJheVxuICAgICAgICBpZiAocmVzdWx0KSB7XG4gICAgICAgICAgbGV0IGtleTogc3RyaW5nO1xuICAgICAgICAgIGxldCBlbWJlZGRlZEtleTogc3RyaW5nO1xuICAgICAgICAgIGxldCBhcnJLZXk6IHN0cmluZyB8IG51bWJlcjtcbiAgICAgICAgICBpZiAocmVzdWx0WzFdKSB7XG4gICAgICAgICAgICBrZXkgPSByZXN1bHRbMV07XG4gICAgICAgICAgICBlbWJlZGRlZEtleSA9IHJlc3VsdFsyXSB8fCAnJHZhbHVlJztcbiAgICAgICAgICAgIGFycktleSA9IHJlc3VsdFszXTtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAga2V5ID0gcmVzdWx0WzRdO1xuICAgICAgICAgICAgZW1iZWRkZWRLZXkgPSAnJGluZGV4JztcbiAgICAgICAgICAgIGFycktleSA9IE51bWJlcihyZXN1bHRbNV0pO1xuICAgICAgICAgIH1cbiAgICAgICAgICAvLyBsZWFmXG4gICAgICAgICAgaWYgKGkgPT09IHNlZ21lbnRzLmxlbmd0aCAtIDEpIHtcbiAgICAgICAgICAgIHB0ci5rZXkgPSBrZXkhO1xuICAgICAgICAgICAgcHRyLmVtYmVkZGVkS2V5ID0gZW1iZWRkZWRLZXkhO1xuICAgICAgICAgICAgcHRyLnR5cGUgPSBPcGVyYXRpb24uVVBEQVRFO1xuICAgICAgICAgICAgcHRyLmNoYW5nZXMgPSBbXG4gICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICB0eXBlOiBjaGFuZ2UudHlwZSxcbiAgICAgICAgICAgICAgICBrZXk6IGFycktleSEsXG4gICAgICAgICAgICAgICAgdmFsdWU6IGNoYW5nZS52YWx1ZSxcbiAgICAgICAgICAgICAgICBvbGRWYWx1ZTogY2hhbmdlLm9sZFZhbHVlXG4gICAgICAgICAgICAgIH0gYXMgSUNoYW5nZVxuICAgICAgICAgICAgXTtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgLy8gb2JqZWN0XG4gICAgICAgICAgICBwdHIua2V5ID0ga2V5O1xuICAgICAgICAgICAgcHRyLmVtYmVkZGVkS2V5ID0gZW1iZWRkZWRLZXk7XG4gICAgICAgICAgICBwdHIudHlwZSA9IE9wZXJhdGlvbi5VUERBVEU7XG4gICAgICAgICAgICBjb25zdCBuZXdQdHIgPSB7fSBhcyBJQ2hhbmdlO1xuICAgICAgICAgICAgcHRyLmNoYW5nZXMgPSBbXG4gICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICB0eXBlOiBPcGVyYXRpb24uVVBEQVRFLFxuICAgICAgICAgICAgICAgIGtleTogYXJyS2V5LFxuICAgICAgICAgICAgICAgIGNoYW5nZXM6IFtuZXdQdHJdXG4gICAgICAgICAgICAgIH0gYXMgSUNoYW5nZVxuICAgICAgICAgICAgXTtcbiAgICAgICAgICAgIHB0ciA9IG5ld1B0cjtcbiAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgLy8gbGVhZlxuICAgICAgICAgIGlmIChpID09PSBzZWdtZW50cy5sZW5ndGggLSAxKSB7XG4gICAgICAgICAgICAvLyBIYW5kbGUgYWxsIGxlYWYgdmFsdWVzIHRoZSBzYW1lIHdheSwgcmVnYXJkbGVzcyBvZiB0eXBlXG4gICAgICAgICAgICBwdHIua2V5ID0gc2VnbWVudDtcbiAgICAgICAgICAgIHB0ci50eXBlID0gY2hhbmdlLnR5cGU7XG4gICAgICAgICAgICBwdHIudmFsdWUgPSBjaGFuZ2UudmFsdWU7XG4gICAgICAgICAgICBwdHIub2xkVmFsdWUgPSBjaGFuZ2Uub2xkVmFsdWU7XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIC8vIGJyYW5jaFxuICAgICAgICAgICAgcHRyLmtleSA9IHNlZ21lbnQ7XG4gICAgICAgICAgICBwdHIudHlwZSA9IE9wZXJhdGlvbi5VUERBVEU7XG4gICAgICAgICAgICBjb25zdCBuZXdQdHIgPSB7fSBhcyBJQ2hhbmdlO1xuICAgICAgICAgICAgcHRyLmNoYW5nZXMgPSBbbmV3UHRyXTtcbiAgICAgICAgICAgIHB0ciA9IG5ld1B0cjtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGNoYW5nZXNBcnIucHVzaChvYmopO1xuICAgIH1cbiAgfSk7XG4gIHJldHVybiBjaGFuZ2VzQXJyO1xufTtcblxuLyoqXG4gKiBEZXRlcm1pbmVzIHRoZSB0eXBlIG9mIGEgZ2l2ZW4gb2JqZWN0LlxuICpcbiAqIEBwYXJhbSB7YW55fSBvYmogLSBUaGUgb2JqZWN0IHdob3NlIHR5cGUgaXMgdG8gYmUgZGV0ZXJtaW5lZC5cbiAqIEByZXR1cm5zIHtzdHJpbmcgfCBudWxsfSAtIFRoZSB0eXBlIG9mIHRoZSBvYmplY3QsIG9yIG51bGwgaWYgdGhlIG9iamVjdCBpcyBudWxsLlxuICpcbiAqIFRoaXMgZnVuY3Rpb24gZmlyc3QgY2hlY2tzIGlmIHRoZSBvYmplY3QgaXMgdW5kZWZpbmVkIG9yIG51bGwsIGFuZCByZXR1cm5zICd1bmRlZmluZWQnIG9yIG51bGwgcmVzcGVjdGl2ZWx5LlxuICogSWYgdGhlIG9iamVjdCBpcyBuZWl0aGVyIHVuZGVmaW5lZCBub3IgbnVsbCwgaXQgdXNlcyBPYmplY3QucHJvdG90eXBlLnRvU3RyaW5nIHRvIGdldCB0aGUgb2JqZWN0J3MgdHlwZS5cbiAqIFRoZSB0eXBlIGlzIGV4dHJhY3RlZCBmcm9tIHRoZSBzdHJpbmcgcmV0dXJuZWQgYnkgT2JqZWN0LnByb3RvdHlwZS50b1N0cmluZyB1c2luZyBhIHJlZ3VsYXIgZXhwcmVzc2lvbi5cbiAqL1xuY29uc3QgZ2V0VHlwZU9mT2JqID0gKG9iajogYW55KSA9PiB7XG4gIGlmICh0eXBlb2Ygb2JqID09PSAndW5kZWZpbmVkJykge1xuICAgIHJldHVybiAndW5kZWZpbmVkJztcbiAgfVxuXG4gIGlmIChvYmogPT09IG51bGwpIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuXG4gIC8vIEV4dHJhY3RzIHRoZSBcIlR5cGVcIiBmcm9tIFwiW29iamVjdCBUeXBlXVwiIHN0cmluZy5cbiAgcmV0dXJuIE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmcuY2FsbChvYmopLm1hdGNoKC9eXFxbb2JqZWN0XFxzKC4qKVxcXSQvKVsxXTtcbn07XG5cbmNvbnN0IGdldEtleSA9IChwYXRoOiBzdHJpbmcpID0+IHtcbiAgY29uc3QgbGVmdCA9IHBhdGhbcGF0aC5sZW5ndGggLSAxXTtcbiAgcmV0dXJuIGxlZnQgIT0gbnVsbCA/IGxlZnQgOiAnJHJvb3QnO1xufTtcblxuY29uc3QgY29tcGFyZSA9IChvbGRPYmo6IGFueSwgbmV3T2JqOiBhbnksIHBhdGg6IGFueSwga2V5UGF0aDogYW55LCBvcHRpb25zOiBPcHRpb25zKSA9PiB7XG4gIGxldCBjaGFuZ2VzOiBhbnlbXSA9IFtdO1xuXG4gIC8vIENoZWNrIGlmIHRoZSBjdXJyZW50IHBhdGggc2hvdWxkIGJlIHNraXBwZWQgXG4gIGNvbnN0IGN1cnJlbnRQYXRoID0ga2V5UGF0aC5qb2luKCcuJyk7XG4gIGlmIChvcHRpb25zLmtleXNUb1NraXA/LnNvbWUoc2tpcFBhdGggPT4ge1xuICAgIC8vIEV4YWN0IG1hdGNoXG4gICAgaWYgKGN1cnJlbnRQYXRoID09PSBza2lwUGF0aCkge1xuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuICAgIFxuICAgIC8vIFRoZSBjdXJyZW50IHBhdGggaXMgYSBwYXJlbnQgb2YgdGhlIHNraXAgcGF0aFxuICAgIGlmIChza2lwUGF0aC5pbmNsdWRlcygnLicpICYmIHNraXBQYXRoLnN0YXJ0c1dpdGgoY3VycmVudFBhdGggKyAnLicpKSB7XG4gICAgICByZXR1cm4gZmFsc2U7IC8vIERvbid0IHNraXAsIHdlIG5lZWQgdG8gcHJvY2VzcyB0aGUgcGFyZW50XG4gICAgfVxuICAgIFxuICAgIC8vIFRoZSBjdXJyZW50IHBhdGggaXMgYSBjaGlsZCBvciBkZWVwZXIgZGVzY2VuZGFudCBvZiB0aGUgc2tpcCBwYXRoXG4gICAgaWYgKHNraXBQYXRoLmluY2x1ZGVzKCcuJykpIHtcbiAgICAgIC8vIENoZWNrIGlmIHNraXBQYXRoIGlzIGEgcGFyZW50IG9mIGN1cnJlbnRQYXRoXG4gICAgICBjb25zdCBza2lwUGFydHMgPSBza2lwUGF0aC5zcGxpdCgnLicpO1xuICAgICAgY29uc3QgY3VycmVudFBhcnRzID0gY3VycmVudFBhdGguc3BsaXQoJy4nKTtcbiAgICAgIFxuICAgICAgaWYgKGN1cnJlbnRQYXJ0cy5sZW5ndGggPj0gc2tpcFBhcnRzLmxlbmd0aCkge1xuICAgICAgICAvLyBDaGVjayBpZiBhbGwgcGFydHMgb2Ygc2tpcFBhdGggbWF0Y2ggdGhlIGNvcnJlc3BvbmRpbmcgcGFydHMgaW4gY3VycmVudFBhdGhcbiAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBza2lwUGFydHMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICBpZiAoc2tpcFBhcnRzW2ldICE9PSBjdXJyZW50UGFydHNbaV0pIHtcbiAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRydWU7IC8vIEFsbCBwYXJ0cyBtYXRjaCwgc28gdGhpcyBpcyBhIGNoaWxkIG9yIGVxdWFsIHBhdGhcbiAgICAgIH1cbiAgICB9XG4gICAgXG4gICAgcmV0dXJuIGZhbHNlO1xuICB9KSkge1xuICAgIHJldHVybiBjaGFuZ2VzOyAvLyBTa2lwIGNvbXBhcmlzb24gZm9yIHRoaXMgcGF0aCBhbmQgaXRzIGNoaWxkcmVuXG4gIH1cblxuICBjb25zdCB0eXBlT2ZPbGRPYmogPSBnZXRUeXBlT2ZPYmoob2xkT2JqKTtcbiAgY29uc3QgdHlwZU9mTmV3T2JqID0gZ2V0VHlwZU9mT2JqKG5ld09iaik7XG5cbiAgLy8gYHRyZWF0VHlwZUNoYW5nZUFzUmVwbGFjZWAgaXMgYSBmbGFnIHVzZWQgdG8gZGV0ZXJtaW5lIGlmIGEgY2hhbmdlIGluIHR5cGUgc2hvdWxkIGJlIHRyZWF0ZWQgYXMgYSByZXBsYWNlbWVudC5cbiAgaWYgKG9wdGlvbnMudHJlYXRUeXBlQ2hhbmdlQXNSZXBsYWNlICYmIHR5cGVPZk9sZE9iaiAhPT0gdHlwZU9mTmV3T2JqKSB7XG4gICAgLy8gT25seSBhZGQgYSBSRU1PVkUgb3BlcmF0aW9uIGlmIG9sZE9iaiBpcyBub3QgdW5kZWZpbmVkXG4gICAgaWYgKHR5cGVPZk9sZE9iaiAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgIGNoYW5nZXMucHVzaCh7IHR5cGU6IE9wZXJhdGlvbi5SRU1PVkUsIGtleTogZ2V0S2V5KHBhdGgpLCB2YWx1ZTogb2xkT2JqIH0pO1xuICAgIH1cblxuICAgIC8vIEFzIHVuZGVmaW5lZCBpcyBub3Qgc2VyaWFsaXplZCBpbnRvIEpTT04sIGl0IHNob3VsZCBub3QgY291bnQgYXMgYW4gYWRkZWQgdmFsdWUuXG4gICAgaWYgKHR5cGVPZk5ld09iaiAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgIGNoYW5nZXMucHVzaCh7IHR5cGU6IE9wZXJhdGlvbi5BREQsIGtleTogZ2V0S2V5KHBhdGgpLCB2YWx1ZTogbmV3T2JqIH0pO1xuICAgIH1cblxuICAgIHJldHVybiBjaGFuZ2VzO1xuICB9XG5cbiAgaWYgKHR5cGVPZk5ld09iaiA9PT0gJ3VuZGVmaW5lZCcgJiYgdHlwZU9mT2xkT2JqICE9PSAndW5kZWZpbmVkJykge1xuICAgIGNoYW5nZXMucHVzaCh7IHR5cGU6IE9wZXJhdGlvbi5SRU1PVkUsIGtleTogZ2V0S2V5KHBhdGgpLCB2YWx1ZTogb2xkT2JqIH0pO1xuICAgIHJldHVybiBjaGFuZ2VzO1xuICB9XG5cbiAgaWYgKHR5cGVPZk5ld09iaiA9PT0gJ09iamVjdCcgJiYgdHlwZU9mT2xkT2JqID09PSAnQXJyYXknKSB7XG4gICAgY2hhbmdlcy5wdXNoKHsgdHlwZTogT3BlcmF0aW9uLlVQREFURSwga2V5OiBnZXRLZXkocGF0aCksIHZhbHVlOiBuZXdPYmosIG9sZFZhbHVlOiBvbGRPYmogfSk7XG4gICAgcmV0dXJuIGNoYW5nZXM7XG4gIH1cblxuICBpZiAodHlwZU9mTmV3T2JqID09PSBudWxsKSB7XG4gICAgaWYgKHR5cGVPZk9sZE9iaiAhPT0gbnVsbCkge1xuICAgICAgY2hhbmdlcy5wdXNoKHsgdHlwZTogT3BlcmF0aW9uLlVQREFURSwga2V5OiBnZXRLZXkocGF0aCksIHZhbHVlOiBuZXdPYmosIG9sZFZhbHVlOiBvbGRPYmogfSk7XG4gICAgfVxuICAgIHJldHVybiBjaGFuZ2VzO1xuICB9XG5cbiAgc3dpdGNoICh0eXBlT2ZPbGRPYmopIHtcbiAgICBjYXNlICdEYXRlJzpcbiAgICAgIGlmICh0eXBlT2ZOZXdPYmogPT09ICdEYXRlJykge1xuICAgICAgICBjaGFuZ2VzID0gY2hhbmdlcy5jb25jYXQoXG4gICAgICAgICAgY29tcGFyZVByaW1pdGl2ZXMob2xkT2JqLmdldFRpbWUoKSwgbmV3T2JqLmdldFRpbWUoKSwgcGF0aCkubWFwKCh4KSA9PiAoe1xuICAgICAgICAgICAgLi4ueCxcbiAgICAgICAgICAgIHZhbHVlOiBuZXcgRGF0ZSh4LnZhbHVlKSxcbiAgICAgICAgICAgIG9sZFZhbHVlOiBuZXcgRGF0ZSh4Lm9sZFZhbHVlKVxuICAgICAgICAgIH0pKVxuICAgICAgICApO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgY2hhbmdlcyA9IGNoYW5nZXMuY29uY2F0KGNvbXBhcmVQcmltaXRpdmVzKG9sZE9iaiwgbmV3T2JqLCBwYXRoKSk7XG4gICAgICB9XG4gICAgICBicmVhaztcbiAgICBjYXNlICdPYmplY3QnOiB7XG4gICAgICBjb25zdCBkaWZmcyA9IGNvbXBhcmVPYmplY3Qob2xkT2JqLCBuZXdPYmosIHBhdGgsIGtleVBhdGgsIGZhbHNlLCBvcHRpb25zKTtcbiAgICAgIGlmIChkaWZmcy5sZW5ndGgpIHtcbiAgICAgICAgaWYgKHBhdGgubGVuZ3RoKSB7XG4gICAgICAgICAgY2hhbmdlcy5wdXNoKHtcbiAgICAgICAgICAgIHR5cGU6IE9wZXJhdGlvbi5VUERBVEUsXG4gICAgICAgICAgICBrZXk6IGdldEtleShwYXRoKSxcbiAgICAgICAgICAgIGNoYW5nZXM6IGRpZmZzXG4gICAgICAgICAgfSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgY2hhbmdlcyA9IGNoYW5nZXMuY29uY2F0KGRpZmZzKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgYnJlYWs7XG4gICAgfVxuICAgIGNhc2UgJ0FycmF5JzpcbiAgICAgIGNoYW5nZXMgPSBjaGFuZ2VzLmNvbmNhdChjb21wYXJlQXJyYXkob2xkT2JqLCBuZXdPYmosIHBhdGgsIGtleVBhdGgsIG9wdGlvbnMpKTtcbiAgICAgIGJyZWFrO1xuICAgIGNhc2UgJ0Z1bmN0aW9uJzpcbiAgICAgIGJyZWFrO1xuICAgIC8vIGRvIG5vdGhpbmdcbiAgICBkZWZhdWx0OlxuICAgICAgY2hhbmdlcyA9IGNoYW5nZXMuY29uY2F0KGNvbXBhcmVQcmltaXRpdmVzKG9sZE9iaiwgbmV3T2JqLCBwYXRoKSk7XG4gIH1cblxuICByZXR1cm4gY2hhbmdlcztcbn07XG5cbmNvbnN0IGNvbXBhcmVPYmplY3QgPSAob2xkT2JqOiBhbnksIG5ld09iajogYW55LCBwYXRoOiBhbnksIGtleVBhdGg6IGFueSwgc2tpcFBhdGggPSBmYWxzZSwgb3B0aW9uczogT3B0aW9ucyA9IHt9KSA9PiB7XG4gIGxldCBrO1xuICBsZXQgbmV3S2V5UGF0aDtcbiAgbGV0IG5ld1BhdGg7XG5cbiAgaWYgKHNraXBQYXRoID09IG51bGwpIHtcbiAgICBza2lwUGF0aCA9IGZhbHNlO1xuICB9XG4gIGxldCBjaGFuZ2VzOiBhbnlbXSA9IFtdO1xuXG4gIC8vIEZpbHRlciBrZXlzIGRpcmVjdGx5IHJhdGhlciB0aGFuIGZpbHRlcmluZyBieSBrZXlzVG9Ta2lwIGF0IHRoaXMgbGV2ZWxcbiAgLy8gVGhlIGZ1bGwgcGF0aCBjaGVjayBpcyBub3cgZG9uZSBpbiB0aGUgY29tcGFyZSBmdW5jdGlvblxuICBjb25zdCBvbGRPYmpLZXlzID0gT2JqZWN0LmtleXMob2xkT2JqKTtcbiAgY29uc3QgbmV3T2JqS2V5cyA9IE9iamVjdC5rZXlzKG5ld09iaik7XG5cbiAgY29uc3QgaW50ZXJzZWN0aW9uS2V5cyA9IGludGVyc2VjdGlvbihvbGRPYmpLZXlzLCBuZXdPYmpLZXlzKTtcbiAgZm9yIChrIG9mIGludGVyc2VjdGlvbktleXMpIHtcbiAgICBuZXdQYXRoID0gcGF0aC5jb25jYXQoW2tdKTtcbiAgICBuZXdLZXlQYXRoID0gc2tpcFBhdGggPyBrZXlQYXRoIDoga2V5UGF0aC5jb25jYXQoW2tdKTtcbiAgICBjb25zdCBkaWZmcyA9IGNvbXBhcmUob2xkT2JqW2tdLCBuZXdPYmpba10sIG5ld1BhdGgsIG5ld0tleVBhdGgsIG9wdGlvbnMpO1xuICAgIGlmIChkaWZmcy5sZW5ndGgpIHtcbiAgICAgIGNoYW5nZXMgPSBjaGFuZ2VzLmNvbmNhdChkaWZmcyk7XG4gICAgfVxuICB9XG5cbiAgY29uc3QgYWRkZWRLZXlzID0gZGlmZmVyZW5jZShuZXdPYmpLZXlzLCBvbGRPYmpLZXlzKTtcbiAgZm9yIChrIG9mIGFkZGVkS2V5cykge1xuICAgIG5ld1BhdGggPSBwYXRoLmNvbmNhdChba10pO1xuICAgIG5ld0tleVBhdGggPSBza2lwUGF0aCA/IGtleVBhdGggOiBrZXlQYXRoLmNvbmNhdChba10pO1xuICAgIC8vIENoZWNrIGlmIHRoZSBwYXRoIHNob3VsZCBiZSBza2lwcGVkXG4gICAgY29uc3QgY3VycmVudFBhdGggPSBuZXdLZXlQYXRoLmpvaW4oJy4nKTtcbiAgICBpZiAob3B0aW9ucy5rZXlzVG9Ta2lwPy5zb21lKHNraXBQYXRoID0+IGN1cnJlbnRQYXRoID09PSBza2lwUGF0aCB8fCBjdXJyZW50UGF0aC5zdGFydHNXaXRoKHNraXBQYXRoICsgJy4nKSkpIHtcbiAgICAgIGNvbnRpbnVlOyAvLyBTa2lwIGFkZGluZyB0aGlzIGtleVxuICAgIH1cbiAgICBjaGFuZ2VzLnB1c2goe1xuICAgICAgdHlwZTogT3BlcmF0aW9uLkFERCxcbiAgICAgIGtleTogZ2V0S2V5KG5ld1BhdGgpLFxuICAgICAgdmFsdWU6IG5ld09ialtrXVxuICAgIH0pO1xuICB9XG5cbiAgY29uc3QgZGVsZXRlZEtleXMgPSBkaWZmZXJlbmNlKG9sZE9iaktleXMsIG5ld09iaktleXMpO1xuICBmb3IgKGsgb2YgZGVsZXRlZEtleXMpIHtcbiAgICBuZXdQYXRoID0gcGF0aC5jb25jYXQoW2tdKTtcbiAgICBuZXdLZXlQYXRoID0gc2tpcFBhdGggPyBrZXlQYXRoIDoga2V5UGF0aC5jb25jYXQoW2tdKTtcbiAgICAvLyBDaGVjayBpZiB0aGUgcGF0aCBzaG91bGQgYmUgc2tpcHBlZFxuICAgIGNvbnN0IGN1cnJlbnRQYXRoID0gbmV3S2V5UGF0aC5qb2luKCcuJyk7XG4gICAgaWYgKG9wdGlvbnMua2V5c1RvU2tpcD8uc29tZShza2lwUGF0aCA9PiBjdXJyZW50UGF0aCA9PT0gc2tpcFBhdGggfHwgY3VycmVudFBhdGguc3RhcnRzV2l0aChza2lwUGF0aCArICcuJykpKSB7XG4gICAgICBjb250aW51ZTsgLy8gU2tpcCByZW1vdmluZyB0aGlzIGtleVxuICAgIH1cbiAgICBjaGFuZ2VzLnB1c2goe1xuICAgICAgdHlwZTogT3BlcmF0aW9uLlJFTU9WRSxcbiAgICAgIGtleTogZ2V0S2V5KG5ld1BhdGgpLFxuICAgICAgdmFsdWU6IG9sZE9ialtrXVxuICAgIH0pO1xuICB9XG4gIHJldHVybiBjaGFuZ2VzO1xufTtcblxuY29uc3QgY29tcGFyZUFycmF5ID0gKG9sZE9iajogYW55LCBuZXdPYmo6IGFueSwgcGF0aDogYW55LCBrZXlQYXRoOiBhbnksIG9wdGlvbnM6IE9wdGlvbnMpID0+IHtcbiAgaWYgKGdldFR5cGVPZk9iaihuZXdPYmopICE9PSAnQXJyYXknKSB7XG4gICAgcmV0dXJuIFt7IHR5cGU6IE9wZXJhdGlvbi5VUERBVEUsIGtleTogZ2V0S2V5KHBhdGgpLCB2YWx1ZTogbmV3T2JqLCBvbGRWYWx1ZTogb2xkT2JqIH1dO1xuICB9XG5cbiAgY29uc3QgbGVmdCA9IGdldE9iamVjdEtleShvcHRpb25zLmVtYmVkZGVkT2JqS2V5cywga2V5UGF0aCk7XG4gIGNvbnN0IHVuaXFLZXkgPSBsZWZ0ICE9IG51bGwgPyBsZWZ0IDogJyRpbmRleCc7XG4gIGNvbnN0IGluZGV4ZWRPbGRPYmogPSBjb252ZXJ0QXJyYXlUb09iaihvbGRPYmosIHVuaXFLZXkpO1xuICBjb25zdCBpbmRleGVkTmV3T2JqID0gY29udmVydEFycmF5VG9PYmoobmV3T2JqLCB1bmlxS2V5KTtcbiAgY29uc3QgZGlmZnMgPSBjb21wYXJlT2JqZWN0KGluZGV4ZWRPbGRPYmosIGluZGV4ZWROZXdPYmosIHBhdGgsIGtleVBhdGgsIHRydWUsIG9wdGlvbnMpO1xuICBpZiAoZGlmZnMubGVuZ3RoKSB7XG4gICAgcmV0dXJuIFtcbiAgICAgIHtcbiAgICAgICAgdHlwZTogT3BlcmF0aW9uLlVQREFURSxcbiAgICAgICAga2V5OiBnZXRLZXkocGF0aCksXG4gICAgICAgIGVtYmVkZGVkS2V5OiB0eXBlb2YgdW5pcUtleSA9PT0gJ2Z1bmN0aW9uJyAmJiB1bmlxS2V5Lmxlbmd0aCA9PT0gMiA/IHVuaXFLZXkobmV3T2JqWzBdLCB0cnVlKSA6IHVuaXFLZXksXG4gICAgICAgIGNoYW5nZXM6IGRpZmZzXG4gICAgICB9XG4gICAgXTtcbiAgfSBlbHNlIHtcbiAgICByZXR1cm4gW107XG4gIH1cbn07XG5cbmNvbnN0IGdldE9iamVjdEtleSA9IChlbWJlZGRlZE9iaktleXM6IGFueSwga2V5UGF0aDogYW55KSA9PiB7XG4gIGlmIChlbWJlZGRlZE9iaktleXMgIT0gbnVsbCkge1xuICAgIGNvbnN0IHBhdGggPSBrZXlQYXRoLmpvaW4oJy4nKTtcblxuICAgIGlmIChlbWJlZGRlZE9iaktleXMgaW5zdGFuY2VvZiBNYXApIHtcbiAgICAgIGZvciAoY29uc3QgW2tleSwgdmFsdWVdIG9mIGVtYmVkZGVkT2JqS2V5cy5lbnRyaWVzKCkpIHtcbiAgICAgICAgaWYgKGtleSBpbnN0YW5jZW9mIFJlZ0V4cCkge1xuICAgICAgICAgIGlmIChwYXRoLm1hdGNoKGtleSkpIHtcbiAgICAgICAgICAgIHJldHVybiB2YWx1ZTtcbiAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSBpZiAocGF0aCA9PT0ga2V5KSB7XG4gICAgICAgICAgcmV0dXJuIHZhbHVlO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuXG4gICAgY29uc3Qga2V5ID0gZW1iZWRkZWRPYmpLZXlzW3BhdGhdO1xuICAgIGlmIChrZXkgIT0gbnVsbCkge1xuICAgICAgcmV0dXJuIGtleTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIHVuZGVmaW5lZDtcbn07XG5cbmNvbnN0IGNvbnZlcnRBcnJheVRvT2JqID0gKGFycjogYW55W10sIHVuaXFLZXk6IGFueSkgPT4ge1xuICBsZXQgb2JqOiBhbnkgPSB7fTtcbiAgaWYgKHVuaXFLZXkgPT09ICckdmFsdWUnKSB7XG4gICAgYXJyLmZvckVhY2goKHZhbHVlKSA9PiB7XG4gICAgICBvYmpbdmFsdWVdID0gdmFsdWU7XG4gICAgfSk7XG4gIH0gZWxzZSBpZiAodW5pcUtleSAhPT0gJyRpbmRleCcpIHtcbiAgICAvLyBDb252ZXJ0IHN0cmluZyBrZXlzIHRvIGZ1bmN0aW9ucyBmb3IgY29tcGF0aWJpbGl0eSB3aXRoIGVzLXRvb2xraXQga2V5QnlcbiAgICBjb25zdCBrZXlGdW5jdGlvbiA9IHR5cGVvZiB1bmlxS2V5ID09PSAnc3RyaW5nJyA/IChpdGVtOiBhbnkpID0+IGl0ZW1bdW5pcUtleV0gOiB1bmlxS2V5O1xuICAgIG9iaiA9IGtleUJ5KGFyciwga2V5RnVuY3Rpb24pO1xuICB9IGVsc2Uge1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgYXJyLmxlbmd0aDsgaSsrKSB7XG4gICAgICBjb25zdCB2YWx1ZSA9IGFycltpXTtcbiAgICAgIG9ialtpXSA9IHZhbHVlO1xuICAgIH1cbiAgfVxuICByZXR1cm4gb2JqO1xufTtcblxuY29uc3QgY29tcGFyZVByaW1pdGl2ZXMgPSAob2xkT2JqOiBhbnksIG5ld09iajogYW55LCBwYXRoOiBhbnkpID0+IHtcbiAgY29uc3QgY2hhbmdlcyA9IFtdO1xuICBpZiAob2xkT2JqICE9PSBuZXdPYmopIHtcbiAgICBjaGFuZ2VzLnB1c2goe1xuICAgICAgdHlwZTogT3BlcmF0aW9uLlVQREFURSxcbiAgICAgIGtleTogZ2V0S2V5KHBhdGgpLFxuICAgICAgdmFsdWU6IG5ld09iaixcbiAgICAgIG9sZFZhbHVlOiBvbGRPYmpcbiAgICB9KTtcbiAgfVxuICByZXR1cm4gY2hhbmdlcztcbn07XG5cbmNvbnN0IHJlbW92ZUtleSA9IChvYmo6IGFueSwga2V5OiBhbnksIGVtYmVkZGVkS2V5OiBhbnkpID0+IHtcbiAgaWYgKEFycmF5LmlzQXJyYXkob2JqKSkge1xuICAgIGlmIChlbWJlZGRlZEtleSA9PT0gJyRpbmRleCcpIHtcbiAgICAgIG9iai5zcGxpY2UoTnVtYmVyKGtleSksIDEpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBpbmRleCA9IGluZGV4T2ZJdGVtSW5BcnJheShvYmosIGVtYmVkZGVkS2V5LCBrZXkpO1xuICAgIGlmIChpbmRleCA9PT0gLTEpIHtcbiAgICAgIC8vIHRzbGludDpkaXNhYmxlLW5leHQtbGluZTpuby1jb25zb2xlXG4gICAgICBjb25zb2xlLndhcm4oYEVsZW1lbnQgd2l0aCB0aGUga2V5ICcke2VtYmVkZGVkS2V5fScgYW5kIHZhbHVlICcke2tleX0nIGNvdWxkIG5vdCBiZSBmb3VuZCBpbiB0aGUgYXJyYXknYCk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHJldHVybiBvYmouc3BsaWNlKGluZGV4ICE9IG51bGwgPyBpbmRleCA6IGtleSwgMSk7XG4gIH0gZWxzZSB7XG4gICAgZGVsZXRlIG9ialtrZXldO1xuICAgIHJldHVybjtcbiAgfVxufTtcblxuY29uc3QgaW5kZXhPZkl0ZW1JbkFycmF5ID0gKGFycjogYW55W10sIGtleTogYW55LCB2YWx1ZTogYW55KSA9PiB7XG4gIGlmIChrZXkgPT09ICckdmFsdWUnKSB7XG4gICAgcmV0dXJuIGFyci5pbmRleE9mKHZhbHVlKTtcbiAgfVxuICBmb3IgKGxldCBpID0gMDsgaSA8IGFyci5sZW5ndGg7IGkrKykge1xuICAgIGNvbnN0IGl0ZW0gPSBhcnJbaV07XG4gICAgaWYgKGl0ZW0gJiYgaXRlbVtrZXldID8gaXRlbVtrZXldLnRvU3RyaW5nKCkgPT09IHZhbHVlLnRvU3RyaW5nKCkgOiB1bmRlZmluZWQpIHtcbiAgICAgIHJldHVybiBpO1xuICAgIH1cbiAgfVxuICByZXR1cm4gLTE7XG59O1xuXG5jb25zdCBtb2RpZnlLZXlWYWx1ZSA9IChvYmo6IGFueSwga2V5OiBhbnksIHZhbHVlOiBhbnkpID0+IChvYmpba2V5XSA9IHZhbHVlKTtcbmNvbnN0IGFkZEtleVZhbHVlID0gKG9iajogYW55LCBrZXk6IGFueSwgdmFsdWU6IGFueSwgZW1iZWRkZWRLZXk/OiBhbnkpID0+IHtcbiAgaWYgKEFycmF5LmlzQXJyYXkob2JqKSkge1xuICAgIGlmIChlbWJlZGRlZEtleSA9PT0gJyRpbmRleCcpIHtcbiAgICAgIG9iai5zcGxpY2UoTnVtYmVyKGtleSksIDAsIHZhbHVlKTtcbiAgICAgIHJldHVybiBvYmoubGVuZ3RoO1xuICAgIH1cbiAgICByZXR1cm4gb2JqLnB1c2godmFsdWUpO1xuICB9IGVsc2Uge1xuICAgIHJldHVybiBvYmogPyAob2JqW2tleV0gPSB2YWx1ZSkgOiBudWxsO1xuICB9XG59O1xuXG5jb25zdCBhcHBseUxlYWZDaGFuZ2UgPSAob2JqOiBhbnksIGNoYW5nZTogYW55LCBlbWJlZGRlZEtleTogYW55KSA9PiB7XG4gIGNvbnN0IHsgdHlwZSwga2V5LCB2YWx1ZSB9ID0gY2hhbmdlO1xuICBzd2l0Y2ggKHR5cGUpIHtcbiAgICBjYXNlIE9wZXJhdGlvbi5BREQ6XG4gICAgICByZXR1cm4gYWRkS2V5VmFsdWUob2JqLCBrZXksIHZhbHVlLCBlbWJlZGRlZEtleSk7XG4gICAgY2FzZSBPcGVyYXRpb24uVVBEQVRFOlxuICAgICAgcmV0dXJuIG1vZGlmeUtleVZhbHVlKG9iaiwga2V5LCB2YWx1ZSk7XG4gICAgY2FzZSBPcGVyYXRpb24uUkVNT1ZFOlxuICAgICAgcmV0dXJuIHJlbW92ZUtleShvYmosIGtleSwgZW1iZWRkZWRLZXkpO1xuICB9XG59O1xuXG4vKipcbiAqIEFwcGxpZXMgY2hhbmdlcyB0byBhbiBhcnJheS5cbiAqIFxuICogQHBhcmFtIHthbnlbXX0gYXJyIC0gVGhlIGFycmF5IHRvIGFwcGx5IGNoYW5nZXMgdG8uXG4gKiBAcGFyYW0ge2FueX0gY2hhbmdlIC0gVGhlIGNoYW5nZSB0byBhcHBseSwgY29udGFpbmluZyBuZXN0ZWQgY2hhbmdlcy5cbiAqIEByZXR1cm5zIHthbnlbXX0gLSBUaGUgYXJyYXkgYWZ0ZXIgY2hhbmdlcyBoYXZlIGJlZW4gYXBwbGllZC5cbiAqXG4gKiBOb3RlOiBUaGlzIGZ1bmN0aW9uIG1vZGlmaWVzIHRoZSBhcnJheSBpbi1wbGFjZSBidXQgYWxzbyByZXR1cm5zIGl0IGZvclxuICogY29uc2lzdGVuY3kgd2l0aCBvdGhlciBmdW5jdGlvbnMuXG4gKi9cbmNvbnN0IGFwcGx5QXJyYXlDaGFuZ2UgPSAoYXJyOiBhbnlbXSwgY2hhbmdlOiBhbnkpID0+IHtcbiAgbGV0IGNoYW5nZXMgPSBjaGFuZ2UuY2hhbmdlcztcbiAgaWYgKGNoYW5nZS5lbWJlZGRlZEtleSA9PT0gJyRpbmRleCcpIHtcbiAgICBjaGFuZ2VzID0gWy4uLmNoYW5nZXNdLnNvcnQoKGEsIGIpID0+IHtcbiAgICAgIGlmIChhLnR5cGUgPT09IE9wZXJhdGlvbi5SRU1PVkUgJiYgYi50eXBlID09PSBPcGVyYXRpb24uUkVNT1ZFKSB7XG4gICAgICAgIHJldHVybiBOdW1iZXIoYi5rZXkpIC0gTnVtYmVyKGEua2V5KTtcbiAgICAgIH1cbiAgICAgIGlmIChhLnR5cGUgPT09IE9wZXJhdGlvbi5SRU1PVkUpIHJldHVybiAtMTtcbiAgICAgIGlmIChiLnR5cGUgPT09IE9wZXJhdGlvbi5SRU1PVkUpIHJldHVybiAxO1xuICAgICAgcmV0dXJuIE51bWJlcihhLmtleSkgLSBOdW1iZXIoYi5rZXkpO1xuICAgIH0pO1xuICB9XG5cbiAgZm9yIChjb25zdCBzdWJjaGFuZ2Ugb2YgY2hhbmdlcykge1xuICAgIGlmIChcbiAgICAgIChzdWJjaGFuZ2UudmFsdWUgIT09IG51bGwgJiYgc3ViY2hhbmdlLnZhbHVlICE9PSB1bmRlZmluZWQpIHx8XG4gICAgICBzdWJjaGFuZ2UudHlwZSA9PT0gT3BlcmF0aW9uLlJFTU9WRSB8fFxuICAgICAgKHN1YmNoYW5nZS52YWx1ZSA9PT0gbnVsbCAmJiBzdWJjaGFuZ2UudHlwZSA9PT0gT3BlcmF0aW9uLkFERClcbiAgICApIHtcbiAgICAgIGFwcGx5TGVhZkNoYW5nZShhcnIsIHN1YmNoYW5nZSwgY2hhbmdlLmVtYmVkZGVkS2V5KTtcbiAgICB9IGVsc2Uge1xuICAgICAgbGV0IGVsZW1lbnQ7XG4gICAgICBpZiAoY2hhbmdlLmVtYmVkZGVkS2V5ID09PSAnJGluZGV4Jykge1xuICAgICAgICBlbGVtZW50ID0gYXJyW3N1YmNoYW5nZS5rZXldO1xuICAgICAgfSBlbHNlIGlmIChjaGFuZ2UuZW1iZWRkZWRLZXkgPT09ICckdmFsdWUnKSB7XG4gICAgICAgIGNvbnN0IGluZGV4ID0gYXJyLmluZGV4T2Yoc3ViY2hhbmdlLmtleSk7XG4gICAgICAgIGlmIChpbmRleCAhPT0gLTEpIHtcbiAgICAgICAgICBlbGVtZW50ID0gYXJyW2luZGV4XTtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgZWxlbWVudCA9IGFyci5maW5kKChlbCkgPT4gZWxbY2hhbmdlLmVtYmVkZGVkS2V5XT8udG9TdHJpbmcoKSA9PT0gc3ViY2hhbmdlLmtleS50b1N0cmluZygpKTtcbiAgICAgIH1cbiAgICAgIGlmIChlbGVtZW50KSB7XG4gICAgICAgIGFwcGx5Q2hhbmdlc2V0KGVsZW1lbnQsIHN1YmNoYW5nZS5jaGFuZ2VzKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgcmV0dXJuIGFycjtcbn07XG5cbmNvbnN0IGFwcGx5QnJhbmNoQ2hhbmdlID0gKG9iajogYW55LCBjaGFuZ2U6IGFueSkgPT4ge1xuICBpZiAoQXJyYXkuaXNBcnJheShvYmopKSB7XG4gICAgcmV0dXJuIGFwcGx5QXJyYXlDaGFuZ2Uob2JqLCBjaGFuZ2UpO1xuICB9IGVsc2Uge1xuICAgIHJldHVybiBhcHBseUNoYW5nZXNldChvYmosIGNoYW5nZS5jaGFuZ2VzKTtcbiAgfVxufTtcblxuY29uc3QgcmV2ZXJ0TGVhZkNoYW5nZSA9IChvYmo6IGFueSwgY2hhbmdlOiBhbnksIGVtYmVkZGVkS2V5ID0gJyRpbmRleCcpID0+IHtcbiAgY29uc3QgeyB0eXBlLCBrZXksIHZhbHVlLCBvbGRWYWx1ZSB9ID0gY2hhbmdlO1xuICBcbiAgLy8gU3BlY2lhbCBoYW5kbGluZyBmb3IgJHJvb3Qga2V5XG4gIGlmIChrZXkgPT09ICckcm9vdCcpIHtcbiAgICBzd2l0Y2ggKHR5cGUpIHtcbiAgICAgIGNhc2UgT3BlcmF0aW9uLkFERDpcbiAgICAgICAgLy8gV2hlbiByZXZlcnRpbmcgYW4gQUREIG9mIHRoZSBlbnRpcmUgb2JqZWN0LCBjbGVhciBhbGwgcHJvcGVydGllc1xuICAgICAgICBmb3IgKGNvbnN0IHByb3AgaW4gb2JqKSB7XG4gICAgICAgICAgaWYgKE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChvYmosIHByb3ApKSB7XG4gICAgICAgICAgICBkZWxldGUgb2JqW3Byb3BdO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gb2JqO1xuICAgICAgY2FzZSBPcGVyYXRpb24uVVBEQVRFOlxuICAgICAgICAvLyBSZXBsYWNlIHRoZSBlbnRpcmUgb2JqZWN0IHdpdGggdGhlIG9sZCB2YWx1ZVxuICAgICAgICBmb3IgKGNvbnN0IHByb3AgaW4gb2JqKSB7XG4gICAgICAgICAgaWYgKE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChvYmosIHByb3ApKSB7XG4gICAgICAgICAgICBkZWxldGUgb2JqW3Byb3BdO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBpZiAob2xkVmFsdWUgJiYgdHlwZW9mIG9sZFZhbHVlID09PSAnb2JqZWN0Jykge1xuICAgICAgICAgIE9iamVjdC5hc3NpZ24ob2JqLCBvbGRWYWx1ZSk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIG9iajtcbiAgICAgIGNhc2UgT3BlcmF0aW9uLlJFTU9WRTpcbiAgICAgICAgLy8gUmVzdG9yZSB0aGUgcmVtb3ZlZCBvYmplY3RcbiAgICAgICAgaWYgKHZhbHVlICYmIHR5cGVvZiB2YWx1ZSA9PT0gJ29iamVjdCcpIHtcbiAgICAgICAgICBPYmplY3QuYXNzaWduKG9iaiwgdmFsdWUpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBvYmo7XG4gICAgfVxuICB9XG4gIFxuICAvLyBSZWd1bGFyIHByb3BlcnR5IGhhbmRsaW5nXG4gIHN3aXRjaCAodHlwZSkge1xuICAgIGNhc2UgT3BlcmF0aW9uLkFERDpcbiAgICAgIHJldHVybiByZW1vdmVLZXkob2JqLCBrZXksIGVtYmVkZGVkS2V5KTtcbiAgICBjYXNlIE9wZXJhdGlvbi5VUERBVEU6XG4gICAgICByZXR1cm4gbW9kaWZ5S2V5VmFsdWUob2JqLCBrZXksIG9sZFZhbHVlKTtcbiAgICBjYXNlIE9wZXJhdGlvbi5SRU1PVkU6XG4gICAgICByZXR1cm4gYWRkS2V5VmFsdWUob2JqLCBrZXksIHZhbHVlKTtcbiAgfVxufTtcblxuLyoqXG4gKiBSZXZlcnRzIGNoYW5nZXMgaW4gYW4gYXJyYXkuXG4gKiBcbiAqIEBwYXJhbSB7YW55W119IGFyciAtIFRoZSBhcnJheSB0byByZXZlcnQgY2hhbmdlcyBpbi5cbiAqIEBwYXJhbSB7YW55fSBjaGFuZ2UgLSBUaGUgY2hhbmdlIHRvIHJldmVydCwgY29udGFpbmluZyBuZXN0ZWQgY2hhbmdlcy5cbiAqIEByZXR1cm5zIHthbnlbXX0gLSBUaGUgYXJyYXkgYWZ0ZXIgY2hhbmdlcyBoYXZlIGJlZW4gcmV2ZXJ0ZWQuXG4gKlxuICogTm90ZTogVGhpcyBmdW5jdGlvbiBtb2RpZmllcyB0aGUgYXJyYXkgaW4tcGxhY2UgYnV0IGFsc28gcmV0dXJucyBpdCBmb3JcbiAqIGNvbnNpc3RlbmN5IHdpdGggb3RoZXIgZnVuY3Rpb25zLlxuICovXG5jb25zdCByZXZlcnRBcnJheUNoYW5nZSA9IChhcnI6IGFueVtdLCBjaGFuZ2U6IGFueSkgPT4ge1xuICBmb3IgKGNvbnN0IHN1YmNoYW5nZSBvZiBjaGFuZ2UuY2hhbmdlcykge1xuICAgIGlmIChzdWJjaGFuZ2UudmFsdWUgIT0gbnVsbCB8fCBzdWJjaGFuZ2UudHlwZSA9PT0gT3BlcmF0aW9uLlJFTU9WRSkge1xuICAgICAgcmV2ZXJ0TGVhZkNoYW5nZShhcnIsIHN1YmNoYW5nZSwgY2hhbmdlLmVtYmVkZGVkS2V5KTtcbiAgICB9IGVsc2Uge1xuICAgICAgbGV0IGVsZW1lbnQ7XG4gICAgICBpZiAoY2hhbmdlLmVtYmVkZGVkS2V5ID09PSAnJGluZGV4Jykge1xuICAgICAgICBlbGVtZW50ID0gYXJyWytzdWJjaGFuZ2Uua2V5XTtcbiAgICAgIH0gZWxzZSBpZiAoY2hhbmdlLmVtYmVkZGVkS2V5ID09PSAnJHZhbHVlJykge1xuICAgICAgICBjb25zdCBpbmRleCA9IGFyci5pbmRleE9mKHN1YmNoYW5nZS5rZXkpO1xuICAgICAgICBpZiAoaW5kZXggIT09IC0xKSB7XG4gICAgICAgICAgZWxlbWVudCA9IGFycltpbmRleF07XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGVsZW1lbnQgPSBhcnIuZmluZCgoZWwpID0+IGVsW2NoYW5nZS5lbWJlZGRlZEtleV0/LnRvU3RyaW5nKCkgPT09IHN1YmNoYW5nZS5rZXkudG9TdHJpbmcoKSk7XG4gICAgICB9XG4gICAgICBpZiAoZWxlbWVudCkge1xuICAgICAgICByZXZlcnRDaGFuZ2VzZXQoZWxlbWVudCwgc3ViY2hhbmdlLmNoYW5nZXMpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuICByZXR1cm4gYXJyO1xufTtcblxuY29uc3QgcmV2ZXJ0QnJhbmNoQ2hhbmdlID0gKG9iajogYW55LCBjaGFuZ2U6IGFueSkgPT4ge1xuICBpZiAoQXJyYXkuaXNBcnJheShvYmopKSB7XG4gICAgcmV0dXJuIHJldmVydEFycmF5Q2hhbmdlKG9iaiwgY2hhbmdlKTtcbiAgfSBlbHNlIHtcbiAgICByZXR1cm4gcmV2ZXJ0Q2hhbmdlc2V0KG9iaiwgY2hhbmdlLmNoYW5nZXMpO1xuICB9XG59O1xuXG4vKiogY29tYmluZSBhIGJhc2UgSlNPTiBQYXRoIHdpdGggYSBzdWJzZXF1ZW50IHNlZ21lbnQgKi9cbmZ1bmN0aW9uIGFwcGVuZChiYXNlUGF0aDogc3RyaW5nLCBuZXh0U2VnbWVudDogc3RyaW5nKTogc3RyaW5nIHtcbiAgcmV0dXJuIG5leHRTZWdtZW50LmluY2x1ZGVzKCcuJykgPyBgJHtiYXNlUGF0aH1bJHtuZXh0U2VnbWVudH1dYCA6IGAke2Jhc2VQYXRofS4ke25leHRTZWdtZW50fWA7XG59XG5cbi8qKiByZXR1cm5zIGEgSlNPTiBQYXRoIGZpbHRlciBleHByZXNzaW9uOyBlLmcuLCBgJC5wZXRbKD9uYW1lPSdzcG90JyldYCAqL1xuZnVuY3Rpb24gZmlsdGVyRXhwcmVzc2lvbihiYXNlUGF0aDogc3RyaW5nLCBmaWx0ZXJLZXk6IHN0cmluZyB8IEZ1bmN0aW9uS2V5LCBmaWx0ZXJWYWx1ZTogc3RyaW5nIHwgbnVtYmVyKSB7XG4gIGNvbnN0IHZhbHVlID0gdHlwZW9mIGZpbHRlclZhbHVlID09PSAnbnVtYmVyJyA/IGZpbHRlclZhbHVlIDogYCcke2ZpbHRlclZhbHVlfSdgO1xuICByZXR1cm4gdHlwZW9mIGZpbHRlcktleSA9PT0gJ3N0cmluZycgJiYgZmlsdGVyS2V5LmluY2x1ZGVzKCcuJylcbiAgICA/IGAke2Jhc2VQYXRofVs/KEBbJHtmaWx0ZXJLZXl9XT09JHt2YWx1ZX0pXWBcbiAgICA6IGAke2Jhc2VQYXRofVs/KEAuJHtmaWx0ZXJLZXl9PT0ke3ZhbHVlfSldYDtcbn1cblxuZXhwb3J0IHtcbiAgQ2hhbmdlc2V0LFxuICBFbWJlZGRlZE9iaktleXNNYXBUeXBlLFxuICBFbWJlZGRlZE9iaktleXNUeXBlLFxuICBJQXRvbWljQ2hhbmdlLFxuICBJQ2hhbmdlLFxuICBPcGVyYXRpb24sXG4gIE9wdGlvbnMsXG4gIGFwcGx5Q2hhbmdlc2V0LFxuICBhdG9taXplQ2hhbmdlc2V0LFxuICBkaWZmLFxuICBnZXRUeXBlT2ZPYmosXG4gIHJldmVydENoYW5nZXNldCxcbiAgdW5hdG9taXplQ2hhbmdlc2V0XG59O1xuIiwgImltcG9ydCB7IHNldEJ5UGF0aCB9IGZyb20gJy4vaGVscGVycy5qcyc7XG5pbXBvcnQgeyBkaWZmLCBhdG9taXplQ2hhbmdlc2V0LCBnZXRUeXBlT2ZPYmosIElBdG9taWNDaGFuZ2UsIE9wZXJhdGlvbiB9IGZyb20gJy4vanNvbkRpZmYuanMnO1xuXG5lbnVtIENvbXBhcmVPcGVyYXRpb24ge1xuICBDT05UQUlORVIgPSAnQ09OVEFJTkVSJyxcbiAgVU5DSEFOR0VEID0gJ1VOQ0hBTkdFRCdcbn1cblxuaW50ZXJmYWNlIElDb21wYXJpc29uRW5yaWNoZWROb2RlIHtcbiAgdHlwZTogT3BlcmF0aW9uIHwgQ29tcGFyZU9wZXJhdGlvbjtcbiAgdmFsdWU6IElDb21wYXJpc29uRW5yaWNoZWROb2RlIHwgSUNvbXBhcmlzb25FbnJpY2hlZE5vZGVbXSB8IGFueSB8IGFueVtdO1xuICBvbGRWYWx1ZT86IGFueTtcbn1cblxuY29uc3QgY3JlYXRlVmFsdWUgPSAodmFsdWU6IGFueSk6IElDb21wYXJpc29uRW5yaWNoZWROb2RlID0+ICh7IHR5cGU6IENvbXBhcmVPcGVyYXRpb24uVU5DSEFOR0VELCB2YWx1ZSB9KTtcbmNvbnN0IGNyZWF0ZUNvbnRhaW5lciA9ICh2YWx1ZTogb2JqZWN0IHwgW10pOiBJQ29tcGFyaXNvbkVucmljaGVkTm9kZSA9PiAoe1xuICB0eXBlOiBDb21wYXJlT3BlcmF0aW9uLkNPTlRBSU5FUixcbiAgdmFsdWVcbn0pO1xuXG5jb25zdCBlbnJpY2ggPSAob2JqZWN0OiBhbnkpOiBJQ29tcGFyaXNvbkVucmljaGVkTm9kZSA9PiB7XG4gIGNvbnN0IG9iamVjdFR5cGUgPSBnZXRUeXBlT2ZPYmoob2JqZWN0KTtcblxuICBzd2l0Y2ggKG9iamVjdFR5cGUpIHtcbiAgICBjYXNlICdPYmplY3QnOlxuICAgICAgcmV0dXJuIE9iamVjdC5rZXlzKG9iamVjdClcbiAgICAgICAgLm1hcCgoa2V5OiBzdHJpbmcpID0+ICh7IGtleSwgdmFsdWU6IGVucmljaChvYmplY3Rba2V5XSkgfSkpXG4gICAgICAgIC5yZWR1Y2UoKGFjY3VtdWxhdG9yLCBlbnRyeSkgPT4ge1xuICAgICAgICAgIGFjY3VtdWxhdG9yLnZhbHVlW2VudHJ5LmtleV0gPSBlbnRyeS52YWx1ZTtcbiAgICAgICAgICByZXR1cm4gYWNjdW11bGF0b3I7XG4gICAgICAgIH0sIGNyZWF0ZUNvbnRhaW5lcih7fSkpO1xuICAgIGNhc2UgJ0FycmF5JzpcbiAgICAgIHJldHVybiAob2JqZWN0IGFzIGFueVtdKVxuICAgICAgICAubWFwKCh2YWx1ZSkgPT4gZW5yaWNoKHZhbHVlKSlcbiAgICAgICAgLnJlZHVjZSgoYWNjdW11bGF0b3IsIHZhbHVlKSA9PiB7XG4gICAgICAgICAgYWNjdW11bGF0b3IudmFsdWUucHVzaCh2YWx1ZSk7XG4gICAgICAgICAgcmV0dXJuIGFjY3VtdWxhdG9yO1xuICAgICAgICB9LCBjcmVhdGVDb250YWluZXIoW10pKTtcbiAgICBjYXNlICdGdW5jdGlvbic6XG4gICAgICByZXR1cm4gdW5kZWZpbmVkO1xuICAgIGNhc2UgJ0RhdGUnOlxuICAgIGRlZmF1bHQ6XG4gICAgICAvLyBQcmltaXRpdmUgdmFsdWVcbiAgICAgIHJldHVybiBjcmVhdGVWYWx1ZShvYmplY3QpO1xuICB9XG59O1xuXG5jb25zdCBhcHBseUNoYW5nZWxpc3QgPSAob2JqZWN0OiBJQ29tcGFyaXNvbkVucmljaGVkTm9kZSwgY2hhbmdlbGlzdDogSUF0b21pY0NoYW5nZVtdKTogSUNvbXBhcmlzb25FbnJpY2hlZE5vZGUgPT4ge1xuICBjaGFuZ2VsaXN0XG4gICAgLm1hcCgoZW50cnkpID0+ICh7IC4uLmVudHJ5LCBwYXRoOiBlbnRyeS5wYXRoLnJlcGxhY2UoJyQuJywgJy4nKSB9KSlcbiAgICAubWFwKChlbnRyeSkgPT4gKHtcbiAgICAgIC4uLmVudHJ5LFxuICAgICAgcGF0aDogZW50cnkucGF0aC5yZXBsYWNlKC8oXFxbKD88YXJyYXk+XFxkKVxcXVxcLikvZywgJ0FSUlZBTF9TVEFSVCQ8YXJyYXk+QVJSVkFMX0VORCcpXG4gICAgfSkpXG4gICAgLm1hcCgoZW50cnkpID0+ICh7IC4uLmVudHJ5LCBwYXRoOiBlbnRyeS5wYXRoLnJlcGxhY2UoLyg/PGRvdD5cXC4pL2csICcudmFsdWUkPGRvdD4nKSB9KSlcbiAgICAubWFwKChlbnRyeSkgPT4gKHsgLi4uZW50cnksIHBhdGg6IGVudHJ5LnBhdGgucmVwbGFjZSgvXFwuLywgJycpIH0pKVxuICAgIC5tYXAoKGVudHJ5KSA9PiAoeyAuLi5lbnRyeSwgcGF0aDogZW50cnkucGF0aC5yZXBsYWNlKC9BUlJWQUxfU1RBUlQvZywgJy52YWx1ZVsnKSB9KSlcbiAgICAubWFwKChlbnRyeSkgPT4gKHsgLi4uZW50cnksIHBhdGg6IGVudHJ5LnBhdGgucmVwbGFjZSgvQVJSVkFMX0VORC9nLCAnXS52YWx1ZS4nKSB9KSlcbiAgICAuZm9yRWFjaCgoZW50cnkpID0+IHtcbiAgICAgIHN3aXRjaCAoZW50cnkudHlwZSkge1xuICAgICAgICBjYXNlIE9wZXJhdGlvbi5BREQ6XG4gICAgICAgIGNhc2UgT3BlcmF0aW9uLlVQREFURTpcbiAgICAgICAgICBzZXRCeVBhdGgob2JqZWN0LCBlbnRyeS5wYXRoLCB7IHR5cGU6IGVudHJ5LnR5cGUsIHZhbHVlOiBlbnRyeS52YWx1ZSwgb2xkVmFsdWU6IGVudHJ5Lm9sZFZhbHVlIH0pO1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICBjYXNlIE9wZXJhdGlvbi5SRU1PVkU6XG4gICAgICAgICAgc2V0QnlQYXRoKG9iamVjdCwgZW50cnkucGF0aCwgeyB0eXBlOiBlbnRyeS50eXBlLCB2YWx1ZTogdW5kZWZpbmVkLCBvbGRWYWx1ZTogZW50cnkudmFsdWUgfSk7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIGRlZmF1bHQ6XG4gICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCk7XG4gICAgICB9XG4gICAgfSk7XG4gIHJldHVybiBvYmplY3Q7XG59O1xuXG5jb25zdCBjb21wYXJlID0gKG9sZE9iamVjdDogYW55LCBuZXdPYmplY3Q6IGFueSk6IElDb21wYXJpc29uRW5yaWNoZWROb2RlID0+IHtcbiAgcmV0dXJuIGFwcGx5Q2hhbmdlbGlzdChlbnJpY2gob2xkT2JqZWN0KSwgYXRvbWl6ZUNoYW5nZXNldChkaWZmKG9sZE9iamVjdCwgbmV3T2JqZWN0KSkpO1xufTtcblxuZXhwb3J0IHsgQ29tcGFyZU9wZXJhdGlvbiwgSUNvbXBhcmlzb25FbnJpY2hlZE5vZGUsIGNyZWF0ZVZhbHVlLCBjcmVhdGVDb250YWluZXIsIGVucmljaCwgYXBwbHlDaGFuZ2VsaXN0LCBjb21wYXJlIH07XG4iLCAiaW1wb3J0IHsgU3luY1BsdWdpbiB9IGZyb20gJy4vU3luY1BsdWdpbic7XG5pbXBvcnQgeyBDb3JlRXZlbnRzIH0gZnJvbSAnLi4vY29uc3RhbnRzL0NvcmVFdmVudHMnO1xuaW1wb3J0IHsgZGlmZiB9IGZyb20gJ2pzb24tZGlmZi10cyc7XG4vKipcbiAqIEJhc2VFbnRpdHlTZXJ2aWNlPFQgZXh0ZW5kcyBJU3luYz4gLSBBYnN0cmFjdCBiYXNlIGNsYXNzIGZvciBhbGwgZW50aXR5IHNlcnZpY2VzXG4gKlxuICogUFJPVklERVM6XG4gKiAtIEdlbmVyaWMgQ1JVRCBvcGVyYXRpb25zIChnZXQsIGdldEFsbCwgc2F2ZSwgZGVsZXRlKVxuICogLSBTeW5jIHN0YXR1cyBtYW5hZ2VtZW50IChkZWxlZ2F0ZXMgdG8gU3luY1BsdWdpbilcbiAqIC0gU2VyaWFsaXphdGlvbiBob29rcyAob3ZlcnJpZGUgaW4gc3ViY2xhc3MgaWYgbmVlZGVkKVxuICovXG5leHBvcnQgY2xhc3MgQmFzZUVudGl0eVNlcnZpY2Uge1xuICAgIGNvbnN0cnVjdG9yKGNvbnRleHQsIGV2ZW50QnVzKSB7XG4gICAgICAgIHRoaXMuY29udGV4dCA9IGNvbnRleHQ7XG4gICAgICAgIHRoaXMuZXZlbnRCdXMgPSBldmVudEJ1cztcbiAgICAgICAgdGhpcy5zeW5jUGx1Z2luID0gbmV3IFN5bmNQbHVnaW4odGhpcyk7XG4gICAgfVxuICAgIGdldCBkYigpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuY29udGV4dC5nZXREYXRhYmFzZSgpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBTZXJpYWxpemUgZW50aXR5IGJlZm9yZSBzdG9yaW5nIGluIEluZGV4ZWREQlxuICAgICAqL1xuICAgIHNlcmlhbGl6ZShlbnRpdHkpIHtcbiAgICAgICAgcmV0dXJuIGVudGl0eTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogRGVzZXJpYWxpemUgZGF0YSBmcm9tIEluZGV4ZWREQiBiYWNrIHRvIGVudGl0eVxuICAgICAqL1xuICAgIGRlc2VyaWFsaXplKGRhdGEpIHtcbiAgICAgICAgcmV0dXJuIGRhdGE7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCBhIHNpbmdsZSBlbnRpdHkgYnkgSURcbiAgICAgKi9cbiAgICBhc3luYyBnZXQoaWQpIHtcbiAgICAgICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHRyYW5zYWN0aW9uID0gdGhpcy5kYi50cmFuc2FjdGlvbihbdGhpcy5zdG9yZU5hbWVdLCAncmVhZG9ubHknKTtcbiAgICAgICAgICAgIGNvbnN0IHN0b3JlID0gdHJhbnNhY3Rpb24ub2JqZWN0U3RvcmUodGhpcy5zdG9yZU5hbWUpO1xuICAgICAgICAgICAgY29uc3QgcmVxdWVzdCA9IHN0b3JlLmdldChpZCk7XG4gICAgICAgICAgICByZXF1ZXN0Lm9uc3VjY2VzcyA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICBjb25zdCBkYXRhID0gcmVxdWVzdC5yZXN1bHQ7XG4gICAgICAgICAgICAgICAgcmVzb2x2ZShkYXRhID8gdGhpcy5kZXNlcmlhbGl6ZShkYXRhKSA6IG51bGwpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25lcnJvciA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICByZWplY3QobmV3IEVycm9yKGBGYWlsZWQgdG8gZ2V0ICR7dGhpcy5lbnRpdHlUeXBlfSAke2lkfTogJHtyZXF1ZXN0LmVycm9yfWApKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0pO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgYWxsIGVudGl0aWVzXG4gICAgICovXG4gICAgYXN5bmMgZ2V0QWxsKCkge1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgdHJhbnNhY3Rpb24gPSB0aGlzLmRiLnRyYW5zYWN0aW9uKFt0aGlzLnN0b3JlTmFtZV0sICdyZWFkb25seScpO1xuICAgICAgICAgICAgY29uc3Qgc3RvcmUgPSB0cmFuc2FjdGlvbi5vYmplY3RTdG9yZSh0aGlzLnN0b3JlTmFtZSk7XG4gICAgICAgICAgICBjb25zdCByZXF1ZXN0ID0gc3RvcmUuZ2V0QWxsKCk7XG4gICAgICAgICAgICByZXF1ZXN0Lm9uc3VjY2VzcyA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICBjb25zdCBkYXRhID0gcmVxdWVzdC5yZXN1bHQ7XG4gICAgICAgICAgICAgICAgY29uc3QgZW50aXRpZXMgPSBkYXRhLm1hcChpdGVtID0+IHRoaXMuZGVzZXJpYWxpemUoaXRlbSkpO1xuICAgICAgICAgICAgICAgIHJlc29sdmUoZW50aXRpZXMpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25lcnJvciA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICByZWplY3QobmV3IEVycm9yKGBGYWlsZWQgdG8gZ2V0IGFsbCAke3RoaXMuZW50aXR5VHlwZX1zOiAke3JlcXVlc3QuZXJyb3J9YCkpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFNhdmUgYW4gZW50aXR5IChjcmVhdGUgb3IgdXBkYXRlKVxuICAgICAqIEVtaXRzIEVOVElUWV9TQVZFRCBldmVudCB3aXRoIG9wZXJhdGlvbiB0eXBlIGFuZCBjaGFuZ2VzIChkaWZmIGZvciB1cGRhdGVzKVxuICAgICAqIEBwYXJhbSBlbnRpdHkgLSBFbnRpdHkgdG8gc2F2ZVxuICAgICAqIEBwYXJhbSBzaWxlbnQgLSBJZiB0cnVlLCBza2lwIGV2ZW50IGVtaXNzaW9uICh1c2VkIGZvciBzZWVkaW5nKVxuICAgICAqL1xuICAgIGFzeW5jIHNhdmUoZW50aXR5LCBzaWxlbnQgPSBmYWxzZSkge1xuICAgICAgICBjb25zdCBlbnRpdHlJZCA9IGVudGl0eS5pZDtcbiAgICAgICAgY29uc3QgZXhpc3RpbmdFbnRpdHkgPSBhd2FpdCB0aGlzLmdldChlbnRpdHlJZCk7XG4gICAgICAgIGNvbnN0IGlzQ3JlYXRlID0gZXhpc3RpbmdFbnRpdHkgPT09IG51bGw7XG4gICAgICAgIC8vIENhbGN1bGF0ZSBjaGFuZ2VzOiBmdWxsIGVudGl0eSBmb3IgY3JlYXRlLCBkaWZmIGZvciB1cGRhdGVcbiAgICAgICAgbGV0IGNoYW5nZXM7XG4gICAgICAgIGlmIChpc0NyZWF0ZSkge1xuICAgICAgICAgICAgY2hhbmdlcyA9IGVudGl0eTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGNvbnN0IGV4aXN0aW5nU2VyaWFsaXplZCA9IHRoaXMuc2VyaWFsaXplKGV4aXN0aW5nRW50aXR5KTtcbiAgICAgICAgICAgIGNvbnN0IG5ld1NlcmlhbGl6ZWQgPSB0aGlzLnNlcmlhbGl6ZShlbnRpdHkpO1xuICAgICAgICAgICAgY2hhbmdlcyA9IGRpZmYoZXhpc3RpbmdTZXJpYWxpemVkLCBuZXdTZXJpYWxpemVkKTtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBzZXJpYWxpemVkID0gdGhpcy5zZXJpYWxpemUoZW50aXR5KTtcbiAgICAgICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHRyYW5zYWN0aW9uID0gdGhpcy5kYi50cmFuc2FjdGlvbihbdGhpcy5zdG9yZU5hbWVdLCAncmVhZHdyaXRlJyk7XG4gICAgICAgICAgICBjb25zdCBzdG9yZSA9IHRyYW5zYWN0aW9uLm9iamVjdFN0b3JlKHRoaXMuc3RvcmVOYW1lKTtcbiAgICAgICAgICAgIGNvbnN0IHJlcXVlc3QgPSBzdG9yZS5wdXQoc2VyaWFsaXplZCk7XG4gICAgICAgICAgICByZXF1ZXN0Lm9uc3VjY2VzcyA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICAvLyBPbmx5IGVtaXQgZXZlbnQgaWYgbm90IHNpbGVudCAoc2lsZW50IHVzZWQgZm9yIHNlZWRpbmcpXG4gICAgICAgICAgICAgICAgaWYgKCFzaWxlbnQpIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgcGF5bG9hZCA9IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGVudGl0eVR5cGU6IHRoaXMuZW50aXR5VHlwZSxcbiAgICAgICAgICAgICAgICAgICAgICAgIGVudGl0eUlkLFxuICAgICAgICAgICAgICAgICAgICAgICAgb3BlcmF0aW9uOiBpc0NyZWF0ZSA/ICdjcmVhdGUnIDogJ3VwZGF0ZScsXG4gICAgICAgICAgICAgICAgICAgICAgICBjaGFuZ2VzLFxuICAgICAgICAgICAgICAgICAgICAgICAgdGltZXN0YW1wOiBEYXRlLm5vdygpXG4gICAgICAgICAgICAgICAgICAgIH07XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuZXZlbnRCdXMuZW1pdChDb3JlRXZlbnRzLkVOVElUWV9TQVZFRCwgcGF5bG9hZCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIHJlc29sdmUoKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICByZXF1ZXN0Lm9uZXJyb3IgPSAoKSA9PiB7XG4gICAgICAgICAgICAgICAgcmVqZWN0KG5ldyBFcnJvcihgRmFpbGVkIHRvIHNhdmUgJHt0aGlzLmVudGl0eVR5cGV9ICR7ZW50aXR5SWR9OiAke3JlcXVlc3QuZXJyb3J9YCkpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIERlbGV0ZSBhbiBlbnRpdHlcbiAgICAgKiBFbWl0cyBFTlRJVFlfREVMRVRFRCBldmVudFxuICAgICAqL1xuICAgIGFzeW5jIGRlbGV0ZShpZCkge1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgdHJhbnNhY3Rpb24gPSB0aGlzLmRiLnRyYW5zYWN0aW9uKFt0aGlzLnN0b3JlTmFtZV0sICdyZWFkd3JpdGUnKTtcbiAgICAgICAgICAgIGNvbnN0IHN0b3JlID0gdHJhbnNhY3Rpb24ub2JqZWN0U3RvcmUodGhpcy5zdG9yZU5hbWUpO1xuICAgICAgICAgICAgY29uc3QgcmVxdWVzdCA9IHN0b3JlLmRlbGV0ZShpZCk7XG4gICAgICAgICAgICByZXF1ZXN0Lm9uc3VjY2VzcyA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICBjb25zdCBwYXlsb2FkID0ge1xuICAgICAgICAgICAgICAgICAgICBlbnRpdHlUeXBlOiB0aGlzLmVudGl0eVR5cGUsXG4gICAgICAgICAgICAgICAgICAgIGVudGl0eUlkOiBpZCxcbiAgICAgICAgICAgICAgICAgICAgb3BlcmF0aW9uOiAnZGVsZXRlJyxcbiAgICAgICAgICAgICAgICAgICAgdGltZXN0YW1wOiBEYXRlLm5vdygpXG4gICAgICAgICAgICAgICAgfTtcbiAgICAgICAgICAgICAgICB0aGlzLmV2ZW50QnVzLmVtaXQoQ29yZUV2ZW50cy5FTlRJVFlfREVMRVRFRCwgcGF5bG9hZCk7XG4gICAgICAgICAgICAgICAgcmVzb2x2ZSgpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25lcnJvciA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICByZWplY3QobmV3IEVycm9yKGBGYWlsZWQgdG8gZGVsZXRlICR7dGhpcy5lbnRpdHlUeXBlfSAke2lkfTogJHtyZXF1ZXN0LmVycm9yfWApKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0pO1xuICAgIH1cbiAgICAvLyBTeW5jIG1ldGhvZHMgLSBkZWxlZ2F0ZSB0byBTeW5jUGx1Z2luXG4gICAgYXN5bmMgbWFya0FzU3luY2VkKGlkKSB7XG4gICAgICAgIHJldHVybiB0aGlzLnN5bmNQbHVnaW4ubWFya0FzU3luY2VkKGlkKTtcbiAgICB9XG4gICAgYXN5bmMgbWFya0FzRXJyb3IoaWQpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuc3luY1BsdWdpbi5tYXJrQXNFcnJvcihpZCk7XG4gICAgfVxuICAgIGFzeW5jIGdldFN5bmNTdGF0dXMoaWQpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuc3luY1BsdWdpbi5nZXRTeW5jU3RhdHVzKGlkKTtcbiAgICB9XG4gICAgYXN5bmMgZ2V0QnlTeW5jU3RhdHVzKHN5bmNTdGF0dXMpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuc3luY1BsdWdpbi5nZXRCeVN5bmNTdGF0dXMoc3luY1N0YXR1cyk7XG4gICAgfVxufVxuIiwgImltcG9ydCB7IEV2ZW50U3RvcmUgfSBmcm9tICcuL0V2ZW50U3RvcmUnO1xuaW1wb3J0IHsgRXZlbnRTZXJpYWxpemF0aW9uIH0gZnJvbSAnLi9FdmVudFNlcmlhbGl6YXRpb24nO1xuaW1wb3J0IHsgQmFzZUVudGl0eVNlcnZpY2UgfSBmcm9tICcuLi9CYXNlRW50aXR5U2VydmljZSc7XG4vKipcbiAqIEV2ZW50U2VydmljZSAtIENSVUQgb3BlcmF0aW9ucyBmb3IgY2FsZW5kYXIgZXZlbnRzIGluIEluZGV4ZWREQlxuICpcbiAqIEV4dGVuZHMgQmFzZUVudGl0eVNlcnZpY2UgZm9yIHNoYXJlZCBDUlVEIGFuZCBzeW5jIGxvZ2ljLlxuICogUHJvdmlkZXMgZXZlbnQtc3BlY2lmaWMgcXVlcnkgbWV0aG9kcy5cbiAqL1xuZXhwb3J0IGNsYXNzIEV2ZW50U2VydmljZSBleHRlbmRzIEJhc2VFbnRpdHlTZXJ2aWNlIHtcbiAgICBjb25zdHJ1Y3Rvcihjb250ZXh0LCBldmVudEJ1cykge1xuICAgICAgICBzdXBlcihjb250ZXh0LCBldmVudEJ1cyk7XG4gICAgICAgIHRoaXMuc3RvcmVOYW1lID0gRXZlbnRTdG9yZS5TVE9SRV9OQU1FO1xuICAgICAgICB0aGlzLmVudGl0eVR5cGUgPSAnRXZlbnQnO1xuICAgIH1cbiAgICBzZXJpYWxpemUoZXZlbnQpIHtcbiAgICAgICAgcmV0dXJuIEV2ZW50U2VyaWFsaXphdGlvbi5zZXJpYWxpemUoZXZlbnQpO1xuICAgIH1cbiAgICBkZXNlcmlhbGl6ZShkYXRhKSB7XG4gICAgICAgIHJldHVybiBFdmVudFNlcmlhbGl6YXRpb24uZGVzZXJpYWxpemUoZGF0YSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCBldmVudHMgd2l0aGluIGEgZGF0ZSByYW5nZVxuICAgICAqL1xuICAgIGFzeW5jIGdldEJ5RGF0ZVJhbmdlKHN0YXJ0LCBlbmQpIHtcbiAgICAgICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHRyYW5zYWN0aW9uID0gdGhpcy5kYi50cmFuc2FjdGlvbihbdGhpcy5zdG9yZU5hbWVdLCAncmVhZG9ubHknKTtcbiAgICAgICAgICAgIGNvbnN0IHN0b3JlID0gdHJhbnNhY3Rpb24ub2JqZWN0U3RvcmUodGhpcy5zdG9yZU5hbWUpO1xuICAgICAgICAgICAgY29uc3QgaW5kZXggPSBzdG9yZS5pbmRleCgnc3RhcnQnKTtcbiAgICAgICAgICAgIGNvbnN0IHJhbmdlID0gSURCS2V5UmFuZ2UubG93ZXJCb3VuZChzdGFydC50b0lTT1N0cmluZygpKTtcbiAgICAgICAgICAgIGNvbnN0IHJlcXVlc3QgPSBpbmRleC5nZXRBbGwocmFuZ2UpO1xuICAgICAgICAgICAgcmVxdWVzdC5vbnN1Y2Nlc3MgPSAoKSA9PiB7XG4gICAgICAgICAgICAgICAgY29uc3QgZGF0YSA9IHJlcXVlc3QucmVzdWx0O1xuICAgICAgICAgICAgICAgIGNvbnN0IGV2ZW50cyA9IGRhdGFcbiAgICAgICAgICAgICAgICAgICAgLm1hcChpdGVtID0+IHRoaXMuZGVzZXJpYWxpemUoaXRlbSkpXG4gICAgICAgICAgICAgICAgICAgIC5maWx0ZXIoZXZlbnQgPT4gZXZlbnQuc3RhcnQgPD0gZW5kKTtcbiAgICAgICAgICAgICAgICByZXNvbHZlKGV2ZW50cyk7XG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgcmVxdWVzdC5vbmVycm9yID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIHJlamVjdChuZXcgRXJyb3IoYEZhaWxlZCB0byBnZXQgZXZlbnRzIGJ5IGRhdGUgcmFuZ2U6ICR7cmVxdWVzdC5lcnJvcn1gKSk7XG4gICAgICAgICAgICB9O1xuICAgICAgICB9KTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IGV2ZW50cyBmb3IgYSBzcGVjaWZpYyByZXNvdXJjZVxuICAgICAqL1xuICAgIGFzeW5jIGdldEJ5UmVzb3VyY2UocmVzb3VyY2VJZCkge1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgdHJhbnNhY3Rpb24gPSB0aGlzLmRiLnRyYW5zYWN0aW9uKFt0aGlzLnN0b3JlTmFtZV0sICdyZWFkb25seScpO1xuICAgICAgICAgICAgY29uc3Qgc3RvcmUgPSB0cmFuc2FjdGlvbi5vYmplY3RTdG9yZSh0aGlzLnN0b3JlTmFtZSk7XG4gICAgICAgICAgICBjb25zdCBpbmRleCA9IHN0b3JlLmluZGV4KCdyZXNvdXJjZUlkJyk7XG4gICAgICAgICAgICBjb25zdCByZXF1ZXN0ID0gaW5kZXguZ2V0QWxsKHJlc291cmNlSWQpO1xuICAgICAgICAgICAgcmVxdWVzdC5vbnN1Y2Nlc3MgPSAoKSA9PiB7XG4gICAgICAgICAgICAgICAgY29uc3QgZGF0YSA9IHJlcXVlc3QucmVzdWx0O1xuICAgICAgICAgICAgICAgIGNvbnN0IGV2ZW50cyA9IGRhdGEubWFwKGl0ZW0gPT4gdGhpcy5kZXNlcmlhbGl6ZShpdGVtKSk7XG4gICAgICAgICAgICAgICAgcmVzb2x2ZShldmVudHMpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25lcnJvciA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICByZWplY3QobmV3IEVycm9yKGBGYWlsZWQgdG8gZ2V0IGV2ZW50cyBmb3IgcmVzb3VyY2UgJHtyZXNvdXJjZUlkfTogJHtyZXF1ZXN0LmVycm9yfWApKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0pO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgZXZlbnRzIGZvciBhIHJlc291cmNlIHdpdGhpbiBhIGRhdGUgcmFuZ2VcbiAgICAgKi9cbiAgICBhc3luYyBnZXRCeVJlc291cmNlQW5kRGF0ZVJhbmdlKHJlc291cmNlSWQsIHN0YXJ0LCBlbmQpIHtcbiAgICAgICAgY29uc3QgcmVzb3VyY2VFdmVudHMgPSBhd2FpdCB0aGlzLmdldEJ5UmVzb3VyY2UocmVzb3VyY2VJZCk7XG4gICAgICAgIHJldHVybiByZXNvdXJjZUV2ZW50cy5maWx0ZXIoZXZlbnQgPT4gZXZlbnQuc3RhcnQgPj0gc3RhcnQgJiYgZXZlbnQuc3RhcnQgPD0gZW5kKTtcbiAgICB9XG59XG4iLCAiLyoqXG4gKiBSZXNvdXJjZVN0b3JlIC0gSW5kZXhlZERCIE9iamVjdFN0b3JlIGRlZmluaXRpb24gZm9yIHJlc291cmNlc1xuICovXG5leHBvcnQgY2xhc3MgUmVzb3VyY2VTdG9yZSB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuc3RvcmVOYW1lID0gUmVzb3VyY2VTdG9yZS5TVE9SRV9OQU1FO1xuICAgIH1cbiAgICBjcmVhdGUoZGIpIHtcbiAgICAgICAgY29uc3Qgc3RvcmUgPSBkYi5jcmVhdGVPYmplY3RTdG9yZShSZXNvdXJjZVN0b3JlLlNUT1JFX05BTUUsIHsga2V5UGF0aDogJ2lkJyB9KTtcbiAgICAgICAgc3RvcmUuY3JlYXRlSW5kZXgoJ3R5cGUnLCAndHlwZScsIHsgdW5pcXVlOiBmYWxzZSB9KTtcbiAgICAgICAgc3RvcmUuY3JlYXRlSW5kZXgoJ3N5bmNTdGF0dXMnLCAnc3luY1N0YXR1cycsIHsgdW5pcXVlOiBmYWxzZSB9KTtcbiAgICAgICAgc3RvcmUuY3JlYXRlSW5kZXgoJ2lzQWN0aXZlJywgJ2lzQWN0aXZlJywgeyB1bmlxdWU6IGZhbHNlIH0pO1xuICAgIH1cbn1cblJlc291cmNlU3RvcmUuU1RPUkVfTkFNRSA9ICdyZXNvdXJjZXMnO1xuIiwgImltcG9ydCB7IFJlc291cmNlU3RvcmUgfSBmcm9tICcuL1Jlc291cmNlU3RvcmUnO1xuaW1wb3J0IHsgQmFzZUVudGl0eVNlcnZpY2UgfSBmcm9tICcuLi9CYXNlRW50aXR5U2VydmljZSc7XG4vKipcbiAqIFJlc291cmNlU2VydmljZSAtIENSVUQgb3BlcmF0aW9ucyBmb3IgcmVzb3VyY2VzIGluIEluZGV4ZWREQlxuICovXG5leHBvcnQgY2xhc3MgUmVzb3VyY2VTZXJ2aWNlIGV4dGVuZHMgQmFzZUVudGl0eVNlcnZpY2Uge1xuICAgIGNvbnN0cnVjdG9yKGNvbnRleHQsIGV2ZW50QnVzKSB7XG4gICAgICAgIHN1cGVyKGNvbnRleHQsIGV2ZW50QnVzKTtcbiAgICAgICAgdGhpcy5zdG9yZU5hbWUgPSBSZXNvdXJjZVN0b3JlLlNUT1JFX05BTUU7XG4gICAgICAgIHRoaXMuZW50aXR5VHlwZSA9ICdSZXNvdXJjZSc7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCBhbGwgYWN0aXZlIHJlc291cmNlc1xuICAgICAqL1xuICAgIGFzeW5jIGdldEFjdGl2ZSgpIHtcbiAgICAgICAgY29uc3QgYWxsID0gYXdhaXQgdGhpcy5nZXRBbGwoKTtcbiAgICAgICAgcmV0dXJuIGFsbC5maWx0ZXIociA9PiByLmlzQWN0aXZlICE9PSBmYWxzZSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCByZXNvdXJjZXMgYnkgSURzXG4gICAgICovXG4gICAgYXN5bmMgZ2V0QnlJZHMoaWRzKSB7XG4gICAgICAgIGlmIChpZHMubGVuZ3RoID09PSAwKVxuICAgICAgICAgICAgcmV0dXJuIFtdO1xuICAgICAgICBjb25zdCByZXN1bHRzID0gYXdhaXQgUHJvbWlzZS5hbGwoaWRzLm1hcChpZCA9PiB0aGlzLmdldChpZCkpKTtcbiAgICAgICAgcmV0dXJuIHJlc3VsdHMuZmlsdGVyKChyKSA9PiByICE9PSBudWxsKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IHJlc291cmNlcyBieSB0eXBlXG4gICAgICovXG4gICAgYXN5bmMgZ2V0QnlUeXBlKHR5cGUpIHtcbiAgICAgICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHRyYW5zYWN0aW9uID0gdGhpcy5kYi50cmFuc2FjdGlvbihbdGhpcy5zdG9yZU5hbWVdLCAncmVhZG9ubHknKTtcbiAgICAgICAgICAgIGNvbnN0IHN0b3JlID0gdHJhbnNhY3Rpb24ub2JqZWN0U3RvcmUodGhpcy5zdG9yZU5hbWUpO1xuICAgICAgICAgICAgY29uc3QgaW5kZXggPSBzdG9yZS5pbmRleCgndHlwZScpO1xuICAgICAgICAgICAgY29uc3QgcmVxdWVzdCA9IGluZGV4LmdldEFsbCh0eXBlKTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25zdWNjZXNzID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIGNvbnN0IGRhdGEgPSByZXF1ZXN0LnJlc3VsdDtcbiAgICAgICAgICAgICAgICByZXNvbHZlKGRhdGEpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25lcnJvciA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICByZWplY3QobmV3IEVycm9yKGBGYWlsZWQgdG8gZ2V0IHJlc291cmNlcyBieSB0eXBlICR7dHlwZX06ICR7cmVxdWVzdC5lcnJvcn1gKSk7XG4gICAgICAgICAgICB9O1xuICAgICAgICB9KTtcbiAgICB9XG59XG4iLCAiLyoqXG4gKiBCb29raW5nU3RvcmUgLSBJbmRleGVkREIgT2JqZWN0U3RvcmUgZGVmaW5pdGlvbiBmb3IgYm9va2luZ3NcbiAqL1xuZXhwb3J0IGNsYXNzIEJvb2tpbmdTdG9yZSB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuc3RvcmVOYW1lID0gQm9va2luZ1N0b3JlLlNUT1JFX05BTUU7XG4gICAgfVxuICAgIGNyZWF0ZShkYikge1xuICAgICAgICBjb25zdCBzdG9yZSA9IGRiLmNyZWF0ZU9iamVjdFN0b3JlKEJvb2tpbmdTdG9yZS5TVE9SRV9OQU1FLCB7IGtleVBhdGg6ICdpZCcgfSk7XG4gICAgICAgIHN0b3JlLmNyZWF0ZUluZGV4KCdjdXN0b21lcklkJywgJ2N1c3RvbWVySWQnLCB7IHVuaXF1ZTogZmFsc2UgfSk7XG4gICAgICAgIHN0b3JlLmNyZWF0ZUluZGV4KCdzdGF0dXMnLCAnc3RhdHVzJywgeyB1bmlxdWU6IGZhbHNlIH0pO1xuICAgICAgICBzdG9yZS5jcmVhdGVJbmRleCgnc3luY1N0YXR1cycsICdzeW5jU3RhdHVzJywgeyB1bmlxdWU6IGZhbHNlIH0pO1xuICAgICAgICBzdG9yZS5jcmVhdGVJbmRleCgnY3JlYXRlZEF0JywgJ2NyZWF0ZWRBdCcsIHsgdW5pcXVlOiBmYWxzZSB9KTtcbiAgICB9XG59XG5Cb29raW5nU3RvcmUuU1RPUkVfTkFNRSA9ICdib29raW5ncyc7XG4iLCAiaW1wb3J0IHsgQm9va2luZ1N0b3JlIH0gZnJvbSAnLi9Cb29raW5nU3RvcmUnO1xuaW1wb3J0IHsgQmFzZUVudGl0eVNlcnZpY2UgfSBmcm9tICcuLi9CYXNlRW50aXR5U2VydmljZSc7XG4vKipcbiAqIEJvb2tpbmdTZXJ2aWNlIC0gQ1JVRCBvcGVyYXRpb25zIGZvciBib29raW5ncyBpbiBJbmRleGVkREJcbiAqL1xuZXhwb3J0IGNsYXNzIEJvb2tpbmdTZXJ2aWNlIGV4dGVuZHMgQmFzZUVudGl0eVNlcnZpY2Uge1xuICAgIGNvbnN0cnVjdG9yKGNvbnRleHQsIGV2ZW50QnVzKSB7XG4gICAgICAgIHN1cGVyKGNvbnRleHQsIGV2ZW50QnVzKTtcbiAgICAgICAgdGhpcy5zdG9yZU5hbWUgPSBCb29raW5nU3RvcmUuU1RPUkVfTkFNRTtcbiAgICAgICAgdGhpcy5lbnRpdHlUeXBlID0gJ0Jvb2tpbmcnO1xuICAgIH1cbiAgICBzZXJpYWxpemUoYm9va2luZykge1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgLi4uYm9va2luZyxcbiAgICAgICAgICAgIGNyZWF0ZWRBdDogYm9va2luZy5jcmVhdGVkQXQudG9JU09TdHJpbmcoKVxuICAgICAgICB9O1xuICAgIH1cbiAgICBkZXNlcmlhbGl6ZShkYXRhKSB7XG4gICAgICAgIGNvbnN0IHJhdyA9IGRhdGE7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICAuLi5yYXcsXG4gICAgICAgICAgICBjcmVhdGVkQXQ6IG5ldyBEYXRlKHJhdy5jcmVhdGVkQXQpXG4gICAgICAgIH07XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCBib29raW5ncyBmb3IgYSBjdXN0b21lclxuICAgICAqL1xuICAgIGFzeW5jIGdldEJ5Q3VzdG9tZXIoY3VzdG9tZXJJZCkge1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgdHJhbnNhY3Rpb24gPSB0aGlzLmRiLnRyYW5zYWN0aW9uKFt0aGlzLnN0b3JlTmFtZV0sICdyZWFkb25seScpO1xuICAgICAgICAgICAgY29uc3Qgc3RvcmUgPSB0cmFuc2FjdGlvbi5vYmplY3RTdG9yZSh0aGlzLnN0b3JlTmFtZSk7XG4gICAgICAgICAgICBjb25zdCBpbmRleCA9IHN0b3JlLmluZGV4KCdjdXN0b21lcklkJyk7XG4gICAgICAgICAgICBjb25zdCByZXF1ZXN0ID0gaW5kZXguZ2V0QWxsKGN1c3RvbWVySWQpO1xuICAgICAgICAgICAgcmVxdWVzdC5vbnN1Y2Nlc3MgPSAoKSA9PiB7XG4gICAgICAgICAgICAgICAgY29uc3QgZGF0YSA9IHJlcXVlc3QucmVzdWx0O1xuICAgICAgICAgICAgICAgIGNvbnN0IGJvb2tpbmdzID0gZGF0YS5tYXAoaXRlbSA9PiB0aGlzLmRlc2VyaWFsaXplKGl0ZW0pKTtcbiAgICAgICAgICAgICAgICByZXNvbHZlKGJvb2tpbmdzKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICByZXF1ZXN0Lm9uZXJyb3IgPSAoKSA9PiB7XG4gICAgICAgICAgICAgICAgcmVqZWN0KG5ldyBFcnJvcihgRmFpbGVkIHRvIGdldCBib29raW5ncyBmb3IgY3VzdG9tZXIgJHtjdXN0b21lcklkfTogJHtyZXF1ZXN0LmVycm9yfWApKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0pO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgYm9va2luZ3MgYnkgc3RhdHVzXG4gICAgICovXG4gICAgYXN5bmMgZ2V0QnlTdGF0dXMoc3RhdHVzKSB7XG4gICAgICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICBjb25zdCB0cmFuc2FjdGlvbiA9IHRoaXMuZGIudHJhbnNhY3Rpb24oW3RoaXMuc3RvcmVOYW1lXSwgJ3JlYWRvbmx5Jyk7XG4gICAgICAgICAgICBjb25zdCBzdG9yZSA9IHRyYW5zYWN0aW9uLm9iamVjdFN0b3JlKHRoaXMuc3RvcmVOYW1lKTtcbiAgICAgICAgICAgIGNvbnN0IGluZGV4ID0gc3RvcmUuaW5kZXgoJ3N0YXR1cycpO1xuICAgICAgICAgICAgY29uc3QgcmVxdWVzdCA9IGluZGV4LmdldEFsbChzdGF0dXMpO1xuICAgICAgICAgICAgcmVxdWVzdC5vbnN1Y2Nlc3MgPSAoKSA9PiB7XG4gICAgICAgICAgICAgICAgY29uc3QgZGF0YSA9IHJlcXVlc3QucmVzdWx0O1xuICAgICAgICAgICAgICAgIGNvbnN0IGJvb2tpbmdzID0gZGF0YS5tYXAoaXRlbSA9PiB0aGlzLmRlc2VyaWFsaXplKGl0ZW0pKTtcbiAgICAgICAgICAgICAgICByZXNvbHZlKGJvb2tpbmdzKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICByZXF1ZXN0Lm9uZXJyb3IgPSAoKSA9PiB7XG4gICAgICAgICAgICAgICAgcmVqZWN0KG5ldyBFcnJvcihgRmFpbGVkIHRvIGdldCBib29raW5ncyB3aXRoIHN0YXR1cyAke3N0YXR1c306ICR7cmVxdWVzdC5lcnJvcn1gKSk7XG4gICAgICAgICAgICB9O1xuICAgICAgICB9KTtcbiAgICB9XG59XG4iLCAiLyoqXG4gKiBDdXN0b21lclN0b3JlIC0gSW5kZXhlZERCIE9iamVjdFN0b3JlIGRlZmluaXRpb24gZm9yIGN1c3RvbWVyc1xuICovXG5leHBvcnQgY2xhc3MgQ3VzdG9tZXJTdG9yZSB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuc3RvcmVOYW1lID0gQ3VzdG9tZXJTdG9yZS5TVE9SRV9OQU1FO1xuICAgIH1cbiAgICBjcmVhdGUoZGIpIHtcbiAgICAgICAgY29uc3Qgc3RvcmUgPSBkYi5jcmVhdGVPYmplY3RTdG9yZShDdXN0b21lclN0b3JlLlNUT1JFX05BTUUsIHsga2V5UGF0aDogJ2lkJyB9KTtcbiAgICAgICAgc3RvcmUuY3JlYXRlSW5kZXgoJ25hbWUnLCAnbmFtZScsIHsgdW5pcXVlOiBmYWxzZSB9KTtcbiAgICAgICAgc3RvcmUuY3JlYXRlSW5kZXgoJ3Bob25lJywgJ3Bob25lJywgeyB1bmlxdWU6IGZhbHNlIH0pO1xuICAgICAgICBzdG9yZS5jcmVhdGVJbmRleCgnc3luY1N0YXR1cycsICdzeW5jU3RhdHVzJywgeyB1bmlxdWU6IGZhbHNlIH0pO1xuICAgIH1cbn1cbkN1c3RvbWVyU3RvcmUuU1RPUkVfTkFNRSA9ICdjdXN0b21lcnMnO1xuIiwgImltcG9ydCB7IEN1c3RvbWVyU3RvcmUgfSBmcm9tICcuL0N1c3RvbWVyU3RvcmUnO1xuaW1wb3J0IHsgQmFzZUVudGl0eVNlcnZpY2UgfSBmcm9tICcuLi9CYXNlRW50aXR5U2VydmljZSc7XG4vKipcbiAqIEN1c3RvbWVyU2VydmljZSAtIENSVUQgb3BlcmF0aW9ucyBmb3IgY3VzdG9tZXJzIGluIEluZGV4ZWREQlxuICovXG5leHBvcnQgY2xhc3MgQ3VzdG9tZXJTZXJ2aWNlIGV4dGVuZHMgQmFzZUVudGl0eVNlcnZpY2Uge1xuICAgIGNvbnN0cnVjdG9yKGNvbnRleHQsIGV2ZW50QnVzKSB7XG4gICAgICAgIHN1cGVyKGNvbnRleHQsIGV2ZW50QnVzKTtcbiAgICAgICAgdGhpcy5zdG9yZU5hbWUgPSBDdXN0b21lclN0b3JlLlNUT1JFX05BTUU7XG4gICAgICAgIHRoaXMuZW50aXR5VHlwZSA9ICdDdXN0b21lcic7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFNlYXJjaCBjdXN0b21lcnMgYnkgbmFtZSAoY2FzZS1pbnNlbnNpdGl2ZSBjb250YWlucylcbiAgICAgKi9cbiAgICBhc3luYyBzZWFyY2hCeU5hbWUocXVlcnkpIHtcbiAgICAgICAgY29uc3QgYWxsID0gYXdhaXQgdGhpcy5nZXRBbGwoKTtcbiAgICAgICAgY29uc3QgbG93ZXJRdWVyeSA9IHF1ZXJ5LnRvTG93ZXJDYXNlKCk7XG4gICAgICAgIHJldHVybiBhbGwuZmlsdGVyKGMgPT4gYy5uYW1lLnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMobG93ZXJRdWVyeSkpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBGaW5kIGN1c3RvbWVyIGJ5IHBob25lXG4gICAgICovXG4gICAgYXN5bmMgZ2V0QnlQaG9uZShwaG9uZSkge1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgdHJhbnNhY3Rpb24gPSB0aGlzLmRiLnRyYW5zYWN0aW9uKFt0aGlzLnN0b3JlTmFtZV0sICdyZWFkb25seScpO1xuICAgICAgICAgICAgY29uc3Qgc3RvcmUgPSB0cmFuc2FjdGlvbi5vYmplY3RTdG9yZSh0aGlzLnN0b3JlTmFtZSk7XG4gICAgICAgICAgICBjb25zdCBpbmRleCA9IHN0b3JlLmluZGV4KCdwaG9uZScpO1xuICAgICAgICAgICAgY29uc3QgcmVxdWVzdCA9IGluZGV4LmdldChwaG9uZSk7XG4gICAgICAgICAgICByZXF1ZXN0Lm9uc3VjY2VzcyA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICBjb25zdCBkYXRhID0gcmVxdWVzdC5yZXN1bHQ7XG4gICAgICAgICAgICAgICAgcmVzb2x2ZShkYXRhID8gZGF0YSA6IG51bGwpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25lcnJvciA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICByZWplY3QobmV3IEVycm9yKGBGYWlsZWQgdG8gZmluZCBjdXN0b21lciBieSBwaG9uZSAke3Bob25lfTogJHtyZXF1ZXN0LmVycm9yfWApKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0pO1xuICAgIH1cbn1cbiIsICIvKipcbiAqIFRlYW1TdG9yZSAtIEluZGV4ZWREQiBPYmplY3RTdG9yZSBkZWZpbml0aW9uIGZvciB0ZWFtc1xuICovXG5leHBvcnQgY2xhc3MgVGVhbVN0b3JlIHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpcy5zdG9yZU5hbWUgPSBUZWFtU3RvcmUuU1RPUkVfTkFNRTtcbiAgICB9XG4gICAgY3JlYXRlKGRiKSB7XG4gICAgICAgIGRiLmNyZWF0ZU9iamVjdFN0b3JlKFRlYW1TdG9yZS5TVE9SRV9OQU1FLCB7IGtleVBhdGg6ICdpZCcgfSk7XG4gICAgfVxufVxuVGVhbVN0b3JlLlNUT1JFX05BTUUgPSAndGVhbXMnO1xuIiwgImltcG9ydCB7IFRlYW1TdG9yZSB9IGZyb20gJy4vVGVhbVN0b3JlJztcbmltcG9ydCB7IEJhc2VFbnRpdHlTZXJ2aWNlIH0gZnJvbSAnLi4vQmFzZUVudGl0eVNlcnZpY2UnO1xuLyoqXG4gKiBUZWFtU2VydmljZSAtIENSVUQgb3BlcmF0aW9ucyBmb3IgdGVhbXMgaW4gSW5kZXhlZERCXG4gKlxuICogVGVhbXMgZGVmaW5lIHdoaWNoIHJlc291cmNlcyBiZWxvbmcgdG9nZXRoZXIgZm9yIGhpZXJhcmNoaWNhbCBncm91cGluZy5cbiAqIEV4dGVuZHMgQmFzZUVudGl0eVNlcnZpY2UgZm9yIHN0YW5kYXJkIGVudGl0eSBvcGVyYXRpb25zLlxuICovXG5leHBvcnQgY2xhc3MgVGVhbVNlcnZpY2UgZXh0ZW5kcyBCYXNlRW50aXR5U2VydmljZSB7XG4gICAgY29uc3RydWN0b3IoY29udGV4dCwgZXZlbnRCdXMpIHtcbiAgICAgICAgc3VwZXIoY29udGV4dCwgZXZlbnRCdXMpO1xuICAgICAgICB0aGlzLnN0b3JlTmFtZSA9IFRlYW1TdG9yZS5TVE9SRV9OQU1FO1xuICAgICAgICB0aGlzLmVudGl0eVR5cGUgPSAnVGVhbSc7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCB0ZWFtcyBieSBJRHNcbiAgICAgKi9cbiAgICBhc3luYyBnZXRCeUlkcyhpZHMpIHtcbiAgICAgICAgaWYgKGlkcy5sZW5ndGggPT09IDApXG4gICAgICAgICAgICByZXR1cm4gW107XG4gICAgICAgIGNvbnN0IHJlc3VsdHMgPSBhd2FpdCBQcm9taXNlLmFsbChpZHMubWFwKGlkID0+IHRoaXMuZ2V0KGlkKSkpO1xuICAgICAgICByZXR1cm4gcmVzdWx0cy5maWx0ZXIoKHQpID0+IHQgIT09IG51bGwpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBCdWlsZCByZXZlcnNlIGxvb2t1cDogcmVzb3VyY2VJZCBcdTIxOTIgdGVhbUlkXG4gICAgICovXG4gICAgYXN5bmMgYnVpbGRSZXNvdXJjZVRvVGVhbU1hcCgpIHtcbiAgICAgICAgY29uc3QgdGVhbXMgPSBhd2FpdCB0aGlzLmdldEFsbCgpO1xuICAgICAgICBjb25zdCBtYXAgPSB7fTtcbiAgICAgICAgZm9yIChjb25zdCB0ZWFtIG9mIHRlYW1zKSB7XG4gICAgICAgICAgICBmb3IgKGNvbnN0IHJlc291cmNlSWQgb2YgdGVhbS5yZXNvdXJjZUlkcykge1xuICAgICAgICAgICAgICAgIG1hcFtyZXNvdXJjZUlkXSA9IHRlYW0uaWQ7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIG1hcDtcbiAgICB9XG59XG4iLCAiLyoqXG4gKiBEZXBhcnRtZW50U3RvcmUgLSBJbmRleGVkREIgT2JqZWN0U3RvcmUgZGVmaW5pdGlvbiBmb3IgZGVwYXJ0bWVudHNcbiAqL1xuZXhwb3J0IGNsYXNzIERlcGFydG1lbnRTdG9yZSB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuc3RvcmVOYW1lID0gRGVwYXJ0bWVudFN0b3JlLlNUT1JFX05BTUU7XG4gICAgfVxuICAgIGNyZWF0ZShkYikge1xuICAgICAgICBkYi5jcmVhdGVPYmplY3RTdG9yZShEZXBhcnRtZW50U3RvcmUuU1RPUkVfTkFNRSwgeyBrZXlQYXRoOiAnaWQnIH0pO1xuICAgIH1cbn1cbkRlcGFydG1lbnRTdG9yZS5TVE9SRV9OQU1FID0gJ2RlcGFydG1lbnRzJztcbiIsICJpbXBvcnQgeyBEZXBhcnRtZW50U3RvcmUgfSBmcm9tICcuL0RlcGFydG1lbnRTdG9yZSc7XG5pbXBvcnQgeyBCYXNlRW50aXR5U2VydmljZSB9IGZyb20gJy4uL0Jhc2VFbnRpdHlTZXJ2aWNlJztcbi8qKlxuICogRGVwYXJ0bWVudFNlcnZpY2UgLSBDUlVEIG9wZXJhdGlvbnMgZm9yIGRlcGFydG1lbnRzIGluIEluZGV4ZWREQlxuICovXG5leHBvcnQgY2xhc3MgRGVwYXJ0bWVudFNlcnZpY2UgZXh0ZW5kcyBCYXNlRW50aXR5U2VydmljZSB7XG4gICAgY29uc3RydWN0b3IoY29udGV4dCwgZXZlbnRCdXMpIHtcbiAgICAgICAgc3VwZXIoY29udGV4dCwgZXZlbnRCdXMpO1xuICAgICAgICB0aGlzLnN0b3JlTmFtZSA9IERlcGFydG1lbnRTdG9yZS5TVE9SRV9OQU1FO1xuICAgICAgICB0aGlzLmVudGl0eVR5cGUgPSAnRGVwYXJ0bWVudCc7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCBkZXBhcnRtZW50cyBieSBJRHNcbiAgICAgKi9cbiAgICBhc3luYyBnZXRCeUlkcyhpZHMpIHtcbiAgICAgICAgaWYgKGlkcy5sZW5ndGggPT09IDApXG4gICAgICAgICAgICByZXR1cm4gW107XG4gICAgICAgIGNvbnN0IHJlc3VsdHMgPSBhd2FpdCBQcm9taXNlLmFsbChpZHMubWFwKGlkID0+IHRoaXMuZ2V0KGlkKSkpO1xuICAgICAgICByZXR1cm4gcmVzdWx0cy5maWx0ZXIoKGQpID0+IGQgIT09IG51bGwpO1xuICAgIH1cbn1cbiIsICIvKipcbiAqIFNldHRpbmdzU3RvcmUgLSBJbmRleGVkREIgT2JqZWN0U3RvcmUgZGVmaW5pdGlvbiBmb3IgdGVuYW50IHNldHRpbmdzXG4gKlxuICogU2luZ2xlIHN0b3JlIGZvciBhbGwgc2V0dGluZ3Mgc2VjdGlvbnMuIFNldHRpbmdzIGFyZSBzdG9yZWQgYXMgb25lIGRvY3VtZW50XG4gKiBwZXIgdGVuYW50IHdpdGggaWQ9J3RlbmFudC1zZXR0aW5ncycuXG4gKi9cbmV4cG9ydCBjbGFzcyBTZXR0aW5nc1N0b3JlIHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpcy5zdG9yZU5hbWUgPSBTZXR0aW5nc1N0b3JlLlNUT1JFX05BTUU7XG4gICAgfVxuICAgIGNyZWF0ZShkYikge1xuICAgICAgICBkYi5jcmVhdGVPYmplY3RTdG9yZShTZXR0aW5nc1N0b3JlLlNUT1JFX05BTUUsIHsga2V5UGF0aDogJ2lkJyB9KTtcbiAgICB9XG59XG5TZXR0aW5nc1N0b3JlLlNUT1JFX05BTUUgPSAnc2V0dGluZ3MnO1xuIiwgIi8qKlxuICogU2V0dGluZ3MgSURzIGFzIGNvbnN0IGZvciB0eXBlIHNhZmV0eVxuICovXG5leHBvcnQgY29uc3QgU2V0dGluZ3NJZHMgPSB7XG4gICAgV09SS1dFRUs6ICd3b3Jrd2VlaycsXG4gICAgR1JJRDogJ2dyaWQnLFxuICAgIFRJTUVfRk9STUFUOiAndGltZUZvcm1hdCcsXG4gICAgVklFV1M6ICd2aWV3cydcbn07XG4iLCAiaW1wb3J0IHsgU2V0dGluZ3NJZHMgfSBmcm9tICcuLi8uLi90eXBlcy9TZXR0aW5nc1R5cGVzJztcbmltcG9ydCB7IFNldHRpbmdzU3RvcmUgfSBmcm9tICcuL1NldHRpbmdzU3RvcmUnO1xuaW1wb3J0IHsgQmFzZUVudGl0eVNlcnZpY2UgfSBmcm9tICcuLi9CYXNlRW50aXR5U2VydmljZSc7XG4vKipcbiAqIFNldHRpbmdzU2VydmljZSAtIENSVUQgb3BlcmF0aW9ucyBmb3IgdGVuYW50IHNldHRpbmdzXG4gKlxuICogU2V0dGluZ3MgYXJlIHN0b3JlZCBhcyBzZXBhcmF0ZSByZWNvcmRzIHBlciBzZWN0aW9uLlxuICogVGhpcyBzZXJ2aWNlIHByb3ZpZGVzIHR5cGVkIG1ldGhvZHMgZm9yIGFjY2Vzc2luZyBzcGVjaWZpYyBzZXR0aW5ncy5cbiAqL1xuZXhwb3J0IGNsYXNzIFNldHRpbmdzU2VydmljZSBleHRlbmRzIEJhc2VFbnRpdHlTZXJ2aWNlIHtcbiAgICBjb25zdHJ1Y3Rvcihjb250ZXh0LCBldmVudEJ1cykge1xuICAgICAgICBzdXBlcihjb250ZXh0LCBldmVudEJ1cyk7XG4gICAgICAgIHRoaXMuc3RvcmVOYW1lID0gU2V0dGluZ3NTdG9yZS5TVE9SRV9OQU1FO1xuICAgICAgICB0aGlzLmVudGl0eVR5cGUgPSAnU2V0dGluZ3MnO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgd29ya3dlZWsgc2V0dGluZ3NcbiAgICAgKi9cbiAgICBhc3luYyBnZXRXb3Jrd2Vla1NldHRpbmdzKCkge1xuICAgICAgICByZXR1cm4gdGhpcy5nZXQoU2V0dGluZ3NJZHMuV09SS1dFRUspO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgZ3JpZCBzZXR0aW5nc1xuICAgICAqL1xuICAgIGFzeW5jIGdldEdyaWRTZXR0aW5ncygpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuZ2V0KFNldHRpbmdzSWRzLkdSSUQpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgdGltZSBmb3JtYXQgc2V0dGluZ3NcbiAgICAgKi9cbiAgICBhc3luYyBnZXRUaW1lRm9ybWF0U2V0dGluZ3MoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmdldChTZXR0aW5nc0lkcy5USU1FX0ZPUk1BVCk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCB2aWV3IHNldHRpbmdzXG4gICAgICovXG4gICAgYXN5bmMgZ2V0Vmlld1NldHRpbmdzKCkge1xuICAgICAgICByZXR1cm4gdGhpcy5nZXQoU2V0dGluZ3NJZHMuVklFV1MpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgd29ya3dlZWsgcHJlc2V0IGJ5IElEXG4gICAgICovXG4gICAgYXN5bmMgZ2V0V29ya3dlZWtQcmVzZXQocHJlc2V0SWQpIHtcbiAgICAgICAgY29uc3Qgc2V0dGluZ3MgPSBhd2FpdCB0aGlzLmdldFdvcmt3ZWVrU2V0dGluZ3MoKTtcbiAgICAgICAgaWYgKCFzZXR0aW5ncylcbiAgICAgICAgICAgIHJldHVybiBudWxsO1xuICAgICAgICByZXR1cm4gc2V0dGluZ3MucHJlc2V0c1twcmVzZXRJZF0gfHwgbnVsbDtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IHRoZSBkZWZhdWx0IHdvcmt3ZWVrIHByZXNldFxuICAgICAqL1xuICAgIGFzeW5jIGdldERlZmF1bHRXb3Jrd2Vla1ByZXNldCgpIHtcbiAgICAgICAgY29uc3Qgc2V0dGluZ3MgPSBhd2FpdCB0aGlzLmdldFdvcmt3ZWVrU2V0dGluZ3MoKTtcbiAgICAgICAgaWYgKCFzZXR0aW5ncylcbiAgICAgICAgICAgIHJldHVybiBudWxsO1xuICAgICAgICByZXR1cm4gc2V0dGluZ3MucHJlc2V0c1tzZXR0aW5ncy5kZWZhdWx0UHJlc2V0XSB8fCBudWxsO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgYWxsIGF2YWlsYWJsZSB3b3Jrd2VlayBwcmVzZXRzXG4gICAgICovXG4gICAgYXN5bmMgZ2V0V29ya3dlZWtQcmVzZXRzKCkge1xuICAgICAgICBjb25zdCBzZXR0aW5ncyA9IGF3YWl0IHRoaXMuZ2V0V29ya3dlZWtTZXR0aW5ncygpO1xuICAgICAgICBpZiAoIXNldHRpbmdzKVxuICAgICAgICAgICAgcmV0dXJuIFtdO1xuICAgICAgICByZXR1cm4gT2JqZWN0LnZhbHVlcyhzZXR0aW5ncy5wcmVzZXRzKTtcbiAgICB9XG59XG4iLCAiZXhwb3J0IGNsYXNzIFZpZXdDb25maWdTdG9yZSB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuc3RvcmVOYW1lID0gVmlld0NvbmZpZ1N0b3JlLlNUT1JFX05BTUU7XG4gICAgfVxuICAgIGNyZWF0ZShkYikge1xuICAgICAgICBkYi5jcmVhdGVPYmplY3RTdG9yZShWaWV3Q29uZmlnU3RvcmUuU1RPUkVfTkFNRSwgeyBrZXlQYXRoOiAnaWQnIH0pO1xuICAgIH1cbn1cblZpZXdDb25maWdTdG9yZS5TVE9SRV9OQU1FID0gJ3ZpZXdjb25maWdzJztcbiIsICJpbXBvcnQgeyBWaWV3Q29uZmlnU3RvcmUgfSBmcm9tICcuL1ZpZXdDb25maWdTdG9yZSc7XG5pbXBvcnQgeyBCYXNlRW50aXR5U2VydmljZSB9IGZyb20gJy4uL0Jhc2VFbnRpdHlTZXJ2aWNlJztcbmV4cG9ydCBjbGFzcyBWaWV3Q29uZmlnU2VydmljZSBleHRlbmRzIEJhc2VFbnRpdHlTZXJ2aWNlIHtcbiAgICBjb25zdHJ1Y3Rvcihjb250ZXh0LCBldmVudEJ1cykge1xuICAgICAgICBzdXBlcihjb250ZXh0LCBldmVudEJ1cyk7XG4gICAgICAgIHRoaXMuc3RvcmVOYW1lID0gVmlld0NvbmZpZ1N0b3JlLlNUT1JFX05BTUU7XG4gICAgICAgIHRoaXMuZW50aXR5VHlwZSA9ICdWaWV3Q29uZmlnJztcbiAgICB9XG4gICAgYXN5bmMgZ2V0QnlJZChpZCkge1xuICAgICAgICByZXR1cm4gdGhpcy5nZXQoaWQpO1xuICAgIH1cbn1cbiIsICIvKipcbiAqIEF1ZGl0U3RvcmUgLSBJbmRleGVkREIgc3RvcmUgY29uZmlndXJhdGlvbiBmb3IgYXVkaXQgZW50cmllc1xuICpcbiAqIFN0b3JlcyBhbGwgZW50aXR5IGNoYW5nZXMgZm9yOlxuICogLSBDb21wbGlhbmNlIGFuZCBhdWRpdCB0cmFpbFxuICogLSBTeW5jIHRyYWNraW5nIHdpdGggYmFja2VuZFxuICogLSBDaGFuZ2UgaGlzdG9yeVxuICpcbiAqIEluZGV4ZXM6XG4gKiAtIHN5bmNTdGF0dXM6IEZvciBmaW5kaW5nIHBlbmRpbmcgZW50cmllcyB0byBzeW5jXG4gKiAtIHN5bmNlZDogQm9vbGVhbiBmbGFnIGZvciBxdWljayBzeW5jIHF1ZXJpZXNcbiAqIC0gZW50aXR5SWQ6IEZvciBnZXR0aW5nIGFsbCBhdWRpdHMgZm9yIGEgc3BlY2lmaWMgZW50aXR5XG4gKiAtIHRpbWVzdGFtcDogRm9yIGNocm9ub2xvZ2ljYWwgcXVlcmllc1xuICovXG5leHBvcnQgY2xhc3MgQXVkaXRTdG9yZSB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuc3RvcmVOYW1lID0gJ2F1ZGl0JztcbiAgICB9XG4gICAgY3JlYXRlKGRiKSB7XG4gICAgICAgIGNvbnN0IHN0b3JlID0gZGIuY3JlYXRlT2JqZWN0U3RvcmUodGhpcy5zdG9yZU5hbWUsIHsga2V5UGF0aDogJ2lkJyB9KTtcbiAgICAgICAgc3RvcmUuY3JlYXRlSW5kZXgoJ3N5bmNTdGF0dXMnLCAnc3luY1N0YXR1cycsIHsgdW5pcXVlOiBmYWxzZSB9KTtcbiAgICAgICAgc3RvcmUuY3JlYXRlSW5kZXgoJ3N5bmNlZCcsICdzeW5jZWQnLCB7IHVuaXF1ZTogZmFsc2UgfSk7XG4gICAgICAgIHN0b3JlLmNyZWF0ZUluZGV4KCdlbnRpdHlJZCcsICdlbnRpdHlJZCcsIHsgdW5pcXVlOiBmYWxzZSB9KTtcbiAgICAgICAgc3RvcmUuY3JlYXRlSW5kZXgoJ3RpbWVzdGFtcCcsICd0aW1lc3RhbXAnLCB7IHVuaXF1ZTogZmFsc2UgfSk7XG4gICAgfVxufVxuIiwgImltcG9ydCB7IEJhc2VFbnRpdHlTZXJ2aWNlIH0gZnJvbSAnLi4vQmFzZUVudGl0eVNlcnZpY2UnO1xuaW1wb3J0IHsgQ29yZUV2ZW50cyB9IGZyb20gJy4uLy4uL2NvbnN0YW50cy9Db3JlRXZlbnRzJztcbi8qKlxuICogQXVkaXRTZXJ2aWNlIC0gRW50aXR5IHNlcnZpY2UgZm9yIGF1ZGl0IGVudHJpZXNcbiAqXG4gKiBSRVNQT05TSUJJTElUSUVTOlxuICogLSBTdG9yZSBhdWRpdCBlbnRyaWVzIGluIEluZGV4ZWREQlxuICogLSBMaXN0ZW4gZm9yIEVOVElUWV9TQVZFRC9FTlRJVFlfREVMRVRFRCBldmVudHNcbiAqIC0gQ3JlYXRlIGF1ZGl0IGVudHJpZXMgZm9yIGFsbCBlbnRpdHkgY2hhbmdlc1xuICogLSBFbWl0IEFVRElUX0xPR0dFRCBhZnRlciBzYXZpbmcgKGZvciBTeW5jTWFuYWdlciB0byBsaXN0ZW4pXG4gKlxuICogT1ZFUlJJREUgUEFUVEVSTjpcbiAqIC0gT3ZlcnJpZGVzIHNhdmUoKSB0byBOT1QgZW1pdCBldmVudHMgKHByZXZlbnRzIGluZmluaXRlIGxvb3BzKVxuICogLSBBdWRpdFNlcnZpY2Ugc2F2ZXMgYXVkaXQgZW50cmllcyB3aXRob3V0IHRyaWdnZXJpbmcgbW9yZSBhdWRpdHNcbiAqXG4gKiBFVkVOVCBDSEFJTjpcbiAqIEVudGl0eSBjaGFuZ2UgXHUyMTkyIEVOVElUWV9TQVZFRC9ERUxFVEVEIFx1MjE5MiBBdWRpdFNlcnZpY2UgXHUyMTkyIEFVRElUX0xPR0dFRCBcdTIxOTIgU3luY01hbmFnZXJcbiAqL1xuZXhwb3J0IGNsYXNzIEF1ZGl0U2VydmljZSBleHRlbmRzIEJhc2VFbnRpdHlTZXJ2aWNlIHtcbiAgICBjb25zdHJ1Y3Rvcihjb250ZXh0LCBldmVudEJ1cykge1xuICAgICAgICBzdXBlcihjb250ZXh0LCBldmVudEJ1cyk7XG4gICAgICAgIHRoaXMuc3RvcmVOYW1lID0gJ2F1ZGl0JztcbiAgICAgICAgdGhpcy5lbnRpdHlUeXBlID0gJ0F1ZGl0JztcbiAgICAgICAgdGhpcy5zZXR1cEV2ZW50TGlzdGVuZXJzKCk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFNldHVwIGxpc3RlbmVycyBmb3IgRU5USVRZX1NBVkVEIGFuZCBFTlRJVFlfREVMRVRFRCBldmVudHNcbiAgICAgKi9cbiAgICBzZXR1cEV2ZW50TGlzdGVuZXJzKCkge1xuICAgICAgICAvLyBMaXN0ZW4gZm9yIGVudGl0eSBzYXZlcyAoY3JlYXRlL3VwZGF0ZSlcbiAgICAgICAgdGhpcy5ldmVudEJ1cy5vbihDb3JlRXZlbnRzLkVOVElUWV9TQVZFRCwgKGV2ZW50KSA9PiB7XG4gICAgICAgICAgICBjb25zdCBkZXRhaWwgPSBldmVudC5kZXRhaWw7XG4gICAgICAgICAgICB0aGlzLmhhbmRsZUVudGl0eVNhdmVkKGRldGFpbCk7XG4gICAgICAgIH0pO1xuICAgICAgICAvLyBMaXN0ZW4gZm9yIGVudGl0eSBkZWxldGVzXG4gICAgICAgIHRoaXMuZXZlbnRCdXMub24oQ29yZUV2ZW50cy5FTlRJVFlfREVMRVRFRCwgKGV2ZW50KSA9PiB7XG4gICAgICAgICAgICBjb25zdCBkZXRhaWwgPSBldmVudC5kZXRhaWw7XG4gICAgICAgICAgICB0aGlzLmhhbmRsZUVudGl0eURlbGV0ZWQoZGV0YWlsKTtcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEhhbmRsZSBFTlRJVFlfU0FWRUQgZXZlbnQgLSBjcmVhdGUgYXVkaXQgZW50cnlcbiAgICAgKi9cbiAgICBhc3luYyBoYW5kbGVFbnRpdHlTYXZlZChwYXlsb2FkKSB7XG4gICAgICAgIC8vIERvbid0IGF1ZGl0IGF1ZGl0IGVudHJpZXMgKHByZXZlbnQgaW5maW5pdGUgbG9vcHMpXG4gICAgICAgIGlmIChwYXlsb2FkLmVudGl0eVR5cGUgPT09ICdBdWRpdCcpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIGNvbnN0IGF1ZGl0RW50cnkgPSB7XG4gICAgICAgICAgICBpZDogY3J5cHRvLnJhbmRvbVVVSUQoKSxcbiAgICAgICAgICAgIGVudGl0eVR5cGU6IHBheWxvYWQuZW50aXR5VHlwZSxcbiAgICAgICAgICAgIGVudGl0eUlkOiBwYXlsb2FkLmVudGl0eUlkLFxuICAgICAgICAgICAgb3BlcmF0aW9uOiBwYXlsb2FkLm9wZXJhdGlvbixcbiAgICAgICAgICAgIHVzZXJJZDogQXVkaXRTZXJ2aWNlLkRFRkFVTFRfVVNFUl9JRCxcbiAgICAgICAgICAgIHRpbWVzdGFtcDogcGF5bG9hZC50aW1lc3RhbXAsXG4gICAgICAgICAgICBjaGFuZ2VzOiBwYXlsb2FkLmNoYW5nZXMsXG4gICAgICAgICAgICBzeW5jZWQ6IGZhbHNlLFxuICAgICAgICAgICAgc3luY1N0YXR1czogJ3BlbmRpbmcnXG4gICAgICAgIH07XG4gICAgICAgIGF3YWl0IHRoaXMuc2F2ZShhdWRpdEVudHJ5KTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogSGFuZGxlIEVOVElUWV9ERUxFVEVEIGV2ZW50IC0gY3JlYXRlIGF1ZGl0IGVudHJ5XG4gICAgICovXG4gICAgYXN5bmMgaGFuZGxlRW50aXR5RGVsZXRlZChwYXlsb2FkKSB7XG4gICAgICAgIC8vIERvbid0IGF1ZGl0IGF1ZGl0IGVudHJpZXMgKHByZXZlbnQgaW5maW5pdGUgbG9vcHMpXG4gICAgICAgIGlmIChwYXlsb2FkLmVudGl0eVR5cGUgPT09ICdBdWRpdCcpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIGNvbnN0IGF1ZGl0RW50cnkgPSB7XG4gICAgICAgICAgICBpZDogY3J5cHRvLnJhbmRvbVVVSUQoKSxcbiAgICAgICAgICAgIGVudGl0eVR5cGU6IHBheWxvYWQuZW50aXR5VHlwZSxcbiAgICAgICAgICAgIGVudGl0eUlkOiBwYXlsb2FkLmVudGl0eUlkLFxuICAgICAgICAgICAgb3BlcmF0aW9uOiAnZGVsZXRlJyxcbiAgICAgICAgICAgIHVzZXJJZDogQXVkaXRTZXJ2aWNlLkRFRkFVTFRfVVNFUl9JRCxcbiAgICAgICAgICAgIHRpbWVzdGFtcDogcGF5bG9hZC50aW1lc3RhbXAsXG4gICAgICAgICAgICBjaGFuZ2VzOiB7IGlkOiBwYXlsb2FkLmVudGl0eUlkIH0sIC8vIEZvciBkZWxldGUsIGp1c3Qgc3RvcmUgdGhlIElEXG4gICAgICAgICAgICBzeW5jZWQ6IGZhbHNlLFxuICAgICAgICAgICAgc3luY1N0YXR1czogJ3BlbmRpbmcnXG4gICAgICAgIH07XG4gICAgICAgIGF3YWl0IHRoaXMuc2F2ZShhdWRpdEVudHJ5KTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogT3ZlcnJpZGUgc2F2ZSB0byBOT1QgdHJpZ2dlciBFTlRJVFlfU0FWRUQgZXZlbnRcbiAgICAgKiBJbnN0ZWFkLCBlbWl0cyBBVURJVF9MT0dHRUQgZm9yIFN5bmNNYW5hZ2VyIHRvIGxpc3RlblxuICAgICAqXG4gICAgICogVGhpcyBwcmV2ZW50cyBpbmZpbml0ZSBsb29wczpcbiAgICAgKiAtIEJhc2VFbnRpdHlTZXJ2aWNlLnNhdmUoKSBlbWl0cyBFTlRJVFlfU0FWRURcbiAgICAgKiAtIEF1ZGl0U2VydmljZSBsaXN0ZW5zIHRvIEVOVElUWV9TQVZFRCBhbmQgY3JlYXRlcyBhdWRpdFxuICAgICAqIC0gSWYgQXVkaXRTZXJ2aWNlLnNhdmUoKSBhbHNvIGVtaXR0ZWQgRU5USVRZX1NBVkVELCBpdCB3b3VsZCBsb29wXG4gICAgICovXG4gICAgYXN5bmMgc2F2ZShlbnRpdHkpIHtcbiAgICAgICAgY29uc3Qgc2VyaWFsaXplZCA9IHRoaXMuc2VyaWFsaXplKGVudGl0eSk7XG4gICAgICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICBjb25zdCB0cmFuc2FjdGlvbiA9IHRoaXMuZGIudHJhbnNhY3Rpb24oW3RoaXMuc3RvcmVOYW1lXSwgJ3JlYWR3cml0ZScpO1xuICAgICAgICAgICAgY29uc3Qgc3RvcmUgPSB0cmFuc2FjdGlvbi5vYmplY3RTdG9yZSh0aGlzLnN0b3JlTmFtZSk7XG4gICAgICAgICAgICBjb25zdCByZXF1ZXN0ID0gc3RvcmUucHV0KHNlcmlhbGl6ZWQpO1xuICAgICAgICAgICAgcmVxdWVzdC5vbnN1Y2Nlc3MgPSAoKSA9PiB7XG4gICAgICAgICAgICAgICAgLy8gRW1pdCBBVURJVF9MT0dHRUQgaW5zdGVhZCBvZiBFTlRJVFlfU0FWRURcbiAgICAgICAgICAgICAgICBjb25zdCBwYXlsb2FkID0ge1xuICAgICAgICAgICAgICAgICAgICBhdWRpdElkOiBlbnRpdHkuaWQsXG4gICAgICAgICAgICAgICAgICAgIGVudGl0eVR5cGU6IGVudGl0eS5lbnRpdHlUeXBlLFxuICAgICAgICAgICAgICAgICAgICBlbnRpdHlJZDogZW50aXR5LmVudGl0eUlkLFxuICAgICAgICAgICAgICAgICAgICBvcGVyYXRpb246IGVudGl0eS5vcGVyYXRpb24sXG4gICAgICAgICAgICAgICAgICAgIHRpbWVzdGFtcDogZW50aXR5LnRpbWVzdGFtcFxuICAgICAgICAgICAgICAgIH07XG4gICAgICAgICAgICAgICAgdGhpcy5ldmVudEJ1cy5lbWl0KENvcmVFdmVudHMuQVVESVRfTE9HR0VELCBwYXlsb2FkKTtcbiAgICAgICAgICAgICAgICByZXNvbHZlKCk7XG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgcmVxdWVzdC5vbmVycm9yID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIHJlamVjdChuZXcgRXJyb3IoYEZhaWxlZCB0byBzYXZlIGF1ZGl0IGVudHJ5ICR7ZW50aXR5LmlkfTogJHtyZXF1ZXN0LmVycm9yfWApKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0pO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBPdmVycmlkZSBkZWxldGUgdG8gTk9UIHRyaWdnZXIgRU5USVRZX0RFTEVURUQgZXZlbnRcbiAgICAgKiBBdWRpdCBlbnRyaWVzIHNob3VsZCBuZXZlciBiZSBkZWxldGVkIChjb21wbGlhbmNlIHJlcXVpcmVtZW50KVxuICAgICAqL1xuICAgIGFzeW5jIGRlbGV0ZShfaWQpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdBdWRpdCBlbnRyaWVzIGNhbm5vdCBiZSBkZWxldGVkIChjb21wbGlhbmNlIHJlcXVpcmVtZW50KScpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgcGVuZGluZyBhdWRpdCBlbnRyaWVzIChmb3Igc3luYylcbiAgICAgKi9cbiAgICBhc3luYyBnZXRQZW5kaW5nQXVkaXRzKCkge1xuICAgICAgICByZXR1cm4gdGhpcy5nZXRCeVN5bmNTdGF0dXMoJ3BlbmRpbmcnKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IGF1ZGl0IGVudHJpZXMgZm9yIGEgc3BlY2lmaWMgZW50aXR5XG4gICAgICovXG4gICAgYXN5bmMgZ2V0QnlFbnRpdHlJZChlbnRpdHlJZCkge1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgdHJhbnNhY3Rpb24gPSB0aGlzLmRiLnRyYW5zYWN0aW9uKFt0aGlzLnN0b3JlTmFtZV0sICdyZWFkb25seScpO1xuICAgICAgICAgICAgY29uc3Qgc3RvcmUgPSB0cmFuc2FjdGlvbi5vYmplY3RTdG9yZSh0aGlzLnN0b3JlTmFtZSk7XG4gICAgICAgICAgICBjb25zdCBpbmRleCA9IHN0b3JlLmluZGV4KCdlbnRpdHlJZCcpO1xuICAgICAgICAgICAgY29uc3QgcmVxdWVzdCA9IGluZGV4LmdldEFsbChlbnRpdHlJZCk7XG4gICAgICAgICAgICByZXF1ZXN0Lm9uc3VjY2VzcyA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICBjb25zdCBlbnRyaWVzID0gcmVxdWVzdC5yZXN1bHQ7XG4gICAgICAgICAgICAgICAgcmVzb2x2ZShlbnRyaWVzKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICByZXF1ZXN0Lm9uZXJyb3IgPSAoKSA9PiB7XG4gICAgICAgICAgICAgICAgcmVqZWN0KG5ldyBFcnJvcihgRmFpbGVkIHRvIGdldCBhdWRpdCBlbnRyaWVzIGZvciBlbnRpdHkgJHtlbnRpdHlJZH06ICR7cmVxdWVzdC5lcnJvcn1gKSk7XG4gICAgICAgICAgICB9O1xuICAgICAgICB9KTtcbiAgICB9XG59XG4vLyBIYXJkY29kZWQgdXNlcklkIGZvciBub3cgLSB3aWxsIGNvbWUgZnJvbSBzZXNzaW9uIGxhdGVyXG5BdWRpdFNlcnZpY2UuREVGQVVMVF9VU0VSX0lEID0gJzAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMSc7XG4iLCAiLyoqXG4gKiBNb2NrRXZlbnRSZXBvc2l0b3J5IC0gTG9hZHMgZXZlbnQgZGF0YSBmcm9tIGxvY2FsIEpTT04gZmlsZVxuICpcbiAqIFVzZWQgZm9yIGRldmVsb3BtZW50IGFuZCB0ZXN0aW5nLiBPbmx5IGZldGNoQWxsKCkgaXMgaW1wbGVtZW50ZWQuXG4gKi9cbmV4cG9ydCBjbGFzcyBNb2NrRXZlbnRSZXBvc2l0b3J5IHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpcy5lbnRpdHlUeXBlID0gJ0V2ZW50JztcbiAgICAgICAgdGhpcy5kYXRhVXJsID0gJ2RhdGEvbW9jay1ldmVudHMuanNvbic7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEZldGNoIGFsbCBldmVudHMgZnJvbSBtb2NrIEpTT04gZmlsZVxuICAgICAqL1xuICAgIGFzeW5jIGZldGNoQWxsKCkge1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaCh0aGlzLmRhdGFVcmwpO1xuICAgICAgICAgICAgaWYgKCFyZXNwb25zZS5vaykge1xuICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgRmFpbGVkIHRvIGxvYWQgbW9jayBldmVudHM6ICR7cmVzcG9uc2Uuc3RhdHVzfSAke3Jlc3BvbnNlLnN0YXR1c1RleHR9YCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjb25zdCByYXdEYXRhID0gYXdhaXQgcmVzcG9uc2UuanNvbigpO1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMucHJvY2Vzc0NhbGVuZGFyRGF0YShyYXdEYXRhKTtcbiAgICAgICAgfVxuICAgICAgICBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoJ0ZhaWxlZCB0byBsb2FkIGV2ZW50IGRhdGE6JywgZXJyb3IpO1xuICAgICAgICAgICAgdGhyb3cgZXJyb3I7XG4gICAgICAgIH1cbiAgICB9XG4gICAgYXN5bmMgc2VuZENyZWF0ZShfZXZlbnQpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdNb2NrRXZlbnRSZXBvc2l0b3J5IGRvZXMgbm90IHN1cHBvcnQgc2VuZENyZWF0ZS4gTW9jayBkYXRhIGlzIHJlYWQtb25seS4nKTtcbiAgICB9XG4gICAgYXN5bmMgc2VuZFVwZGF0ZShfaWQsIF91cGRhdGVzKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignTW9ja0V2ZW50UmVwb3NpdG9yeSBkb2VzIG5vdCBzdXBwb3J0IHNlbmRVcGRhdGUuIE1vY2sgZGF0YSBpcyByZWFkLW9ubHkuJyk7XG4gICAgfVxuICAgIGFzeW5jIHNlbmREZWxldGUoX2lkKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignTW9ja0V2ZW50UmVwb3NpdG9yeSBkb2VzIG5vdCBzdXBwb3J0IHNlbmREZWxldGUuIE1vY2sgZGF0YSBpcyByZWFkLW9ubHkuJyk7XG4gICAgfVxuICAgIHByb2Nlc3NDYWxlbmRhckRhdGEoZGF0YSkge1xuICAgICAgICByZXR1cm4gZGF0YS5tYXAoKGV2ZW50KSA9PiB7XG4gICAgICAgICAgICAvLyBWYWxpZGF0ZSBjdXN0b21lciBldmVudCBjb25zdHJhaW50c1xuICAgICAgICAgICAgaWYgKGV2ZW50LnR5cGUgPT09ICdjdXN0b21lcicpIHtcbiAgICAgICAgICAgICAgICBpZiAoIWV2ZW50LmJvb2tpbmdJZClcbiAgICAgICAgICAgICAgICAgICAgY29uc29sZS53YXJuKGBDdXN0b21lciBldmVudCAke2V2ZW50LmlkfSBtaXNzaW5nIGJvb2tpbmdJZGApO1xuICAgICAgICAgICAgICAgIGlmICghZXZlbnQucmVzb3VyY2VJZClcbiAgICAgICAgICAgICAgICAgICAgY29uc29sZS53YXJuKGBDdXN0b21lciBldmVudCAke2V2ZW50LmlkfSBtaXNzaW5nIHJlc291cmNlSWRgKTtcbiAgICAgICAgICAgICAgICBpZiAoIWV2ZW50LmN1c3RvbWVySWQpXG4gICAgICAgICAgICAgICAgICAgIGNvbnNvbGUud2FybihgQ3VzdG9tZXIgZXZlbnQgJHtldmVudC5pZH0gbWlzc2luZyBjdXN0b21lcklkYCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICAgIGlkOiBldmVudC5pZCxcbiAgICAgICAgICAgICAgICB0aXRsZTogZXZlbnQudGl0bGUsXG4gICAgICAgICAgICAgICAgZGVzY3JpcHRpb246IGV2ZW50LmRlc2NyaXB0aW9uLFxuICAgICAgICAgICAgICAgIHN0YXJ0OiBuZXcgRGF0ZShldmVudC5zdGFydCksXG4gICAgICAgICAgICAgICAgZW5kOiBuZXcgRGF0ZShldmVudC5lbmQpLFxuICAgICAgICAgICAgICAgIHR5cGU6IGV2ZW50LnR5cGUsXG4gICAgICAgICAgICAgICAgYWxsRGF5OiBldmVudC5hbGxEYXkgfHwgZmFsc2UsXG4gICAgICAgICAgICAgICAgYm9va2luZ0lkOiBldmVudC5ib29raW5nSWQsXG4gICAgICAgICAgICAgICAgcmVzb3VyY2VJZDogZXZlbnQucmVzb3VyY2VJZCxcbiAgICAgICAgICAgICAgICBjdXN0b21lcklkOiBldmVudC5jdXN0b21lcklkLFxuICAgICAgICAgICAgICAgIHJlY3VycmluZ0lkOiBldmVudC5yZWN1cnJpbmdJZCxcbiAgICAgICAgICAgICAgICBtZXRhZGF0YTogZXZlbnQubWV0YWRhdGEsXG4gICAgICAgICAgICAgICAgc3luY1N0YXR1czogJ3N5bmNlZCdcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0pO1xuICAgIH1cbn1cbiIsICIvKipcbiAqIE1vY2tSZXNvdXJjZVJlcG9zaXRvcnkgLSBMb2FkcyByZXNvdXJjZSBkYXRhIGZyb20gbG9jYWwgSlNPTiBmaWxlXG4gKi9cbmV4cG9ydCBjbGFzcyBNb2NrUmVzb3VyY2VSZXBvc2l0b3J5IHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpcy5lbnRpdHlUeXBlID0gJ1Jlc291cmNlJztcbiAgICAgICAgdGhpcy5kYXRhVXJsID0gJ2RhdGEvbW9jay1yZXNvdXJjZXMuanNvbic7XG4gICAgfVxuICAgIGFzeW5jIGZldGNoQWxsKCkge1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaCh0aGlzLmRhdGFVcmwpO1xuICAgICAgICAgICAgaWYgKCFyZXNwb25zZS5vaykge1xuICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgRmFpbGVkIHRvIGxvYWQgbW9jayByZXNvdXJjZXM6ICR7cmVzcG9uc2Uuc3RhdHVzfSAke3Jlc3BvbnNlLnN0YXR1c1RleHR9YCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjb25zdCByYXdEYXRhID0gYXdhaXQgcmVzcG9uc2UuanNvbigpO1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMucHJvY2Vzc1Jlc291cmNlRGF0YShyYXdEYXRhKTtcbiAgICAgICAgfVxuICAgICAgICBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoJ0ZhaWxlZCB0byBsb2FkIHJlc291cmNlIGRhdGE6JywgZXJyb3IpO1xuICAgICAgICAgICAgdGhyb3cgZXJyb3I7XG4gICAgICAgIH1cbiAgICB9XG4gICAgYXN5bmMgc2VuZENyZWF0ZShfcmVzb3VyY2UpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdNb2NrUmVzb3VyY2VSZXBvc2l0b3J5IGRvZXMgbm90IHN1cHBvcnQgc2VuZENyZWF0ZS4gTW9jayBkYXRhIGlzIHJlYWQtb25seS4nKTtcbiAgICB9XG4gICAgYXN5bmMgc2VuZFVwZGF0ZShfaWQsIF91cGRhdGVzKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignTW9ja1Jlc291cmNlUmVwb3NpdG9yeSBkb2VzIG5vdCBzdXBwb3J0IHNlbmRVcGRhdGUuIE1vY2sgZGF0YSBpcyByZWFkLW9ubHkuJyk7XG4gICAgfVxuICAgIGFzeW5jIHNlbmREZWxldGUoX2lkKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignTW9ja1Jlc291cmNlUmVwb3NpdG9yeSBkb2VzIG5vdCBzdXBwb3J0IHNlbmREZWxldGUuIE1vY2sgZGF0YSBpcyByZWFkLW9ubHkuJyk7XG4gICAgfVxuICAgIHByb2Nlc3NSZXNvdXJjZURhdGEoZGF0YSkge1xuICAgICAgICByZXR1cm4gZGF0YS5tYXAoKHJlc291cmNlKSA9PiAoe1xuICAgICAgICAgICAgaWQ6IHJlc291cmNlLmlkLFxuICAgICAgICAgICAgbmFtZTogcmVzb3VyY2UubmFtZSxcbiAgICAgICAgICAgIGRpc3BsYXlOYW1lOiByZXNvdXJjZS5kaXNwbGF5TmFtZSxcbiAgICAgICAgICAgIHR5cGU6IHJlc291cmNlLnR5cGUsXG4gICAgICAgICAgICBhdmF0YXJVcmw6IHJlc291cmNlLmF2YXRhclVybCxcbiAgICAgICAgICAgIGNvbG9yOiByZXNvdXJjZS5jb2xvcixcbiAgICAgICAgICAgIGlzQWN0aXZlOiByZXNvdXJjZS5pc0FjdGl2ZSxcbiAgICAgICAgICAgIGRlZmF1bHRTY2hlZHVsZTogcmVzb3VyY2UuZGVmYXVsdFNjaGVkdWxlLFxuICAgICAgICAgICAgbWV0YWRhdGE6IHJlc291cmNlLm1ldGFkYXRhLFxuICAgICAgICAgICAgc3luY1N0YXR1czogJ3N5bmNlZCdcbiAgICAgICAgfSkpO1xuICAgIH1cbn1cbiIsICIvKipcbiAqIE1vY2tCb29raW5nUmVwb3NpdG9yeSAtIExvYWRzIGJvb2tpbmcgZGF0YSBmcm9tIGxvY2FsIEpTT04gZmlsZVxuICovXG5leHBvcnQgY2xhc3MgTW9ja0Jvb2tpbmdSZXBvc2l0b3J5IHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpcy5lbnRpdHlUeXBlID0gJ0Jvb2tpbmcnO1xuICAgICAgICB0aGlzLmRhdGFVcmwgPSAnZGF0YS9tb2NrLWJvb2tpbmdzLmpzb24nO1xuICAgIH1cbiAgICBhc3luYyBmZXRjaEFsbCgpIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2godGhpcy5kYXRhVXJsKTtcbiAgICAgICAgICAgIGlmICghcmVzcG9uc2Uub2spIHtcbiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEZhaWxlZCB0byBsb2FkIG1vY2sgYm9va2luZ3M6ICR7cmVzcG9uc2Uuc3RhdHVzfSAke3Jlc3BvbnNlLnN0YXR1c1RleHR9YCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjb25zdCByYXdEYXRhID0gYXdhaXQgcmVzcG9uc2UuanNvbigpO1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMucHJvY2Vzc0Jvb2tpbmdEYXRhKHJhd0RhdGEpO1xuICAgICAgICB9XG4gICAgICAgIGNhdGNoIChlcnJvcikge1xuICAgICAgICAgICAgY29uc29sZS5lcnJvcignRmFpbGVkIHRvIGxvYWQgYm9va2luZyBkYXRhOicsIGVycm9yKTtcbiAgICAgICAgICAgIHRocm93IGVycm9yO1xuICAgICAgICB9XG4gICAgfVxuICAgIGFzeW5jIHNlbmRDcmVhdGUoX2Jvb2tpbmcpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdNb2NrQm9va2luZ1JlcG9zaXRvcnkgZG9lcyBub3Qgc3VwcG9ydCBzZW5kQ3JlYXRlLiBNb2NrIGRhdGEgaXMgcmVhZC1vbmx5LicpO1xuICAgIH1cbiAgICBhc3luYyBzZW5kVXBkYXRlKF9pZCwgX3VwZGF0ZXMpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdNb2NrQm9va2luZ1JlcG9zaXRvcnkgZG9lcyBub3Qgc3VwcG9ydCBzZW5kVXBkYXRlLiBNb2NrIGRhdGEgaXMgcmVhZC1vbmx5LicpO1xuICAgIH1cbiAgICBhc3luYyBzZW5kRGVsZXRlKF9pZCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ01vY2tCb29raW5nUmVwb3NpdG9yeSBkb2VzIG5vdCBzdXBwb3J0IHNlbmREZWxldGUuIE1vY2sgZGF0YSBpcyByZWFkLW9ubHkuJyk7XG4gICAgfVxuICAgIHByb2Nlc3NCb29raW5nRGF0YShkYXRhKSB7XG4gICAgICAgIHJldHVybiBkYXRhLm1hcCgoYm9va2luZykgPT4gKHtcbiAgICAgICAgICAgIGlkOiBib29raW5nLmlkLFxuICAgICAgICAgICAgY3VzdG9tZXJJZDogYm9va2luZy5jdXN0b21lcklkLFxuICAgICAgICAgICAgc3RhdHVzOiBib29raW5nLnN0YXR1cyxcbiAgICAgICAgICAgIGNyZWF0ZWRBdDogbmV3IERhdGUoYm9va2luZy5jcmVhdGVkQXQpLFxuICAgICAgICAgICAgc2VydmljZXM6IGJvb2tpbmcuc2VydmljZXMsXG4gICAgICAgICAgICB0b3RhbFByaWNlOiBib29raW5nLnRvdGFsUHJpY2UsXG4gICAgICAgICAgICB0YWdzOiBib29raW5nLnRhZ3MsXG4gICAgICAgICAgICBub3RlczogYm9va2luZy5ub3RlcyxcbiAgICAgICAgICAgIHN5bmNTdGF0dXM6ICdzeW5jZWQnXG4gICAgICAgIH0pKTtcbiAgICB9XG59XG4iLCAiLyoqXG4gKiBNb2NrQ3VzdG9tZXJSZXBvc2l0b3J5IC0gTG9hZHMgY3VzdG9tZXIgZGF0YSBmcm9tIGxvY2FsIEpTT04gZmlsZVxuICovXG5leHBvcnQgY2xhc3MgTW9ja0N1c3RvbWVyUmVwb3NpdG9yeSB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuZW50aXR5VHlwZSA9ICdDdXN0b21lcic7XG4gICAgICAgIHRoaXMuZGF0YVVybCA9ICdkYXRhL21vY2stY3VzdG9tZXJzLmpzb24nO1xuICAgIH1cbiAgICBhc3luYyBmZXRjaEFsbCgpIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2godGhpcy5kYXRhVXJsKTtcbiAgICAgICAgICAgIGlmICghcmVzcG9uc2Uub2spIHtcbiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEZhaWxlZCB0byBsb2FkIG1vY2sgY3VzdG9tZXJzOiAke3Jlc3BvbnNlLnN0YXR1c30gJHtyZXNwb25zZS5zdGF0dXNUZXh0fWApO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY29uc3QgcmF3RGF0YSA9IGF3YWl0IHJlc3BvbnNlLmpzb24oKTtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLnByb2Nlc3NDdXN0b21lckRhdGEocmF3RGF0YSk7XG4gICAgICAgIH1cbiAgICAgICAgY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgICAgICBjb25zb2xlLmVycm9yKCdGYWlsZWQgdG8gbG9hZCBjdXN0b21lciBkYXRhOicsIGVycm9yKTtcbiAgICAgICAgICAgIHRocm93IGVycm9yO1xuICAgICAgICB9XG4gICAgfVxuICAgIGFzeW5jIHNlbmRDcmVhdGUoX2N1c3RvbWVyKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignTW9ja0N1c3RvbWVyUmVwb3NpdG9yeSBkb2VzIG5vdCBzdXBwb3J0IHNlbmRDcmVhdGUuIE1vY2sgZGF0YSBpcyByZWFkLW9ubHkuJyk7XG4gICAgfVxuICAgIGFzeW5jIHNlbmRVcGRhdGUoX2lkLCBfdXBkYXRlcykge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ01vY2tDdXN0b21lclJlcG9zaXRvcnkgZG9lcyBub3Qgc3VwcG9ydCBzZW5kVXBkYXRlLiBNb2NrIGRhdGEgaXMgcmVhZC1vbmx5LicpO1xuICAgIH1cbiAgICBhc3luYyBzZW5kRGVsZXRlKF9pZCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ01vY2tDdXN0b21lclJlcG9zaXRvcnkgZG9lcyBub3Qgc3VwcG9ydCBzZW5kRGVsZXRlLiBNb2NrIGRhdGEgaXMgcmVhZC1vbmx5LicpO1xuICAgIH1cbiAgICBwcm9jZXNzQ3VzdG9tZXJEYXRhKGRhdGEpIHtcbiAgICAgICAgcmV0dXJuIGRhdGEubWFwKChjdXN0b21lcikgPT4gKHtcbiAgICAgICAgICAgIGlkOiBjdXN0b21lci5pZCxcbiAgICAgICAgICAgIG5hbWU6IGN1c3RvbWVyLm5hbWUsXG4gICAgICAgICAgICBwaG9uZTogY3VzdG9tZXIucGhvbmUsXG4gICAgICAgICAgICBlbWFpbDogY3VzdG9tZXIuZW1haWwsXG4gICAgICAgICAgICBtZXRhZGF0YTogY3VzdG9tZXIubWV0YWRhdGEsXG4gICAgICAgICAgICBzeW5jU3RhdHVzOiAnc3luY2VkJ1xuICAgICAgICB9KSk7XG4gICAgfVxufVxuIiwgIi8qKlxuICogTW9ja0F1ZGl0UmVwb3NpdG9yeSAtIE1vY2sgQVBJIHJlcG9zaXRvcnkgZm9yIGF1ZGl0IGVudHJpZXNcbiAqXG4gKiBJbiBwcm9kdWN0aW9uLCB0aGlzIHdvdWxkIHNlbmQgYXVkaXQgZW50cmllcyB0byB0aGUgYmFja2VuZC5cbiAqIEZvciBkZXZlbG9wbWVudC90ZXN0aW5nLCBpdCBqdXN0IGxvZ3MgdGhlIG9wZXJhdGlvbnMuXG4gKi9cbmV4cG9ydCBjbGFzcyBNb2NrQXVkaXRSZXBvc2l0b3J5IHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpcy5lbnRpdHlUeXBlID0gJ0F1ZGl0JztcbiAgICB9XG4gICAgYXN5bmMgc2VuZENyZWF0ZShlbnRpdHkpIHtcbiAgICAgICAgLy8gU2ltdWxhdGUgQVBJIGNhbGwgZGVsYXlcbiAgICAgICAgYXdhaXQgbmV3IFByb21pc2UocmVzb2x2ZSA9PiBzZXRUaW1lb3V0KHJlc29sdmUsIDEwMCkpO1xuICAgICAgICBjb25zb2xlLmxvZygnTW9ja0F1ZGl0UmVwb3NpdG9yeTogQXVkaXQgZW50cnkgc3luY2VkIHRvIGJhY2tlbmQ6Jywge1xuICAgICAgICAgICAgaWQ6IGVudGl0eS5pZCxcbiAgICAgICAgICAgIGVudGl0eVR5cGU6IGVudGl0eS5lbnRpdHlUeXBlLFxuICAgICAgICAgICAgZW50aXR5SWQ6IGVudGl0eS5lbnRpdHlJZCxcbiAgICAgICAgICAgIG9wZXJhdGlvbjogZW50aXR5Lm9wZXJhdGlvbixcbiAgICAgICAgICAgIHRpbWVzdGFtcDogbmV3IERhdGUoZW50aXR5LnRpbWVzdGFtcCkudG9JU09TdHJpbmcoKVxuICAgICAgICB9KTtcbiAgICAgICAgcmV0dXJuIGVudGl0eTtcbiAgICB9XG4gICAgYXN5bmMgc2VuZFVwZGF0ZShfaWQsIF9lbnRpdHkpIHtcbiAgICAgICAgLy8gQXVkaXQgZW50cmllcyBhcmUgaW1tdXRhYmxlIC0gdXBkYXRlcyBzaG91bGQgbm90IGhhcHBlblxuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ0F1ZGl0IGVudHJpZXMgY2Fubm90IGJlIHVwZGF0ZWQnKTtcbiAgICB9XG4gICAgYXN5bmMgc2VuZERlbGV0ZShfaWQpIHtcbiAgICAgICAgLy8gQXVkaXQgZW50cmllcyBzaG91bGQgbmV2ZXIgYmUgZGVsZXRlZFxuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ0F1ZGl0IGVudHJpZXMgY2Fubm90IGJlIGRlbGV0ZWQnKTtcbiAgICB9XG4gICAgYXN5bmMgZmV0Y2hBbGwoKSB7XG4gICAgICAgIC8vIEZvciBub3csIHJldHVybiBlbXB0eSBhcnJheSAtIGF1ZGl0IGVudHJpZXMgYXJlIGxvY2FsLWZpcnN0XG4gICAgICAgIC8vIEluIHByb2R1Y3Rpb24sIHRoaXMgY291bGQgZmV0Y2ggYXVkaXQgaGlzdG9yeSBmcm9tIGJhY2tlbmRcbiAgICAgICAgcmV0dXJuIFtdO1xuICAgIH1cbiAgICBhc3luYyBmZXRjaEJ5SWQoX2lkKSB7XG4gICAgICAgIC8vIEZvciBub3csIHJldHVybiBudWxsIC0gYXVkaXQgZW50cmllcyBhcmUgbG9jYWwtZmlyc3RcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxufVxuIiwgIi8qKlxuICogTW9ja1RlYW1SZXBvc2l0b3J5IC0gTG9hZHMgdGVhbSBkYXRhIGZyb20gbG9jYWwgSlNPTiBmaWxlXG4gKi9cbmV4cG9ydCBjbGFzcyBNb2NrVGVhbVJlcG9zaXRvcnkge1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICB0aGlzLmVudGl0eVR5cGUgPSAnVGVhbSc7XG4gICAgICAgIHRoaXMuZGF0YVVybCA9ICdkYXRhL21vY2stdGVhbXMuanNvbic7XG4gICAgfVxuICAgIGFzeW5jIGZldGNoQWxsKCkge1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaCh0aGlzLmRhdGFVcmwpO1xuICAgICAgICAgICAgaWYgKCFyZXNwb25zZS5vaykge1xuICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgRmFpbGVkIHRvIGxvYWQgbW9jayB0ZWFtczogJHtyZXNwb25zZS5zdGF0dXN9ICR7cmVzcG9uc2Uuc3RhdHVzVGV4dH1gKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbnN0IHJhd0RhdGEgPSBhd2FpdCByZXNwb25zZS5qc29uKCk7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5wcm9jZXNzVGVhbURhdGEocmF3RGF0YSk7XG4gICAgICAgIH1cbiAgICAgICAgY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgICAgICBjb25zb2xlLmVycm9yKCdGYWlsZWQgdG8gbG9hZCB0ZWFtIGRhdGE6JywgZXJyb3IpO1xuICAgICAgICAgICAgdGhyb3cgZXJyb3I7XG4gICAgICAgIH1cbiAgICB9XG4gICAgYXN5bmMgc2VuZENyZWF0ZShfdGVhbSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ01vY2tUZWFtUmVwb3NpdG9yeSBkb2VzIG5vdCBzdXBwb3J0IHNlbmRDcmVhdGUuIE1vY2sgZGF0YSBpcyByZWFkLW9ubHkuJyk7XG4gICAgfVxuICAgIGFzeW5jIHNlbmRVcGRhdGUoX2lkLCBfdXBkYXRlcykge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ01vY2tUZWFtUmVwb3NpdG9yeSBkb2VzIG5vdCBzdXBwb3J0IHNlbmRVcGRhdGUuIE1vY2sgZGF0YSBpcyByZWFkLW9ubHkuJyk7XG4gICAgfVxuICAgIGFzeW5jIHNlbmREZWxldGUoX2lkKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignTW9ja1RlYW1SZXBvc2l0b3J5IGRvZXMgbm90IHN1cHBvcnQgc2VuZERlbGV0ZS4gTW9jayBkYXRhIGlzIHJlYWQtb25seS4nKTtcbiAgICB9XG4gICAgcHJvY2Vzc1RlYW1EYXRhKGRhdGEpIHtcbiAgICAgICAgcmV0dXJuIGRhdGEubWFwKCh0ZWFtKSA9PiAoe1xuICAgICAgICAgICAgaWQ6IHRlYW0uaWQsXG4gICAgICAgICAgICBuYW1lOiB0ZWFtLm5hbWUsXG4gICAgICAgICAgICByZXNvdXJjZUlkczogdGVhbS5yZXNvdXJjZUlkcyxcbiAgICAgICAgICAgIHN5bmNTdGF0dXM6ICdzeW5jZWQnXG4gICAgICAgIH0pKTtcbiAgICB9XG59XG4iLCAiLyoqXG4gKiBNb2NrRGVwYXJ0bWVudFJlcG9zaXRvcnkgLSBMb2FkcyBkZXBhcnRtZW50IGRhdGEgZnJvbSBsb2NhbCBKU09OIGZpbGVcbiAqL1xuZXhwb3J0IGNsYXNzIE1vY2tEZXBhcnRtZW50UmVwb3NpdG9yeSB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuZW50aXR5VHlwZSA9ICdEZXBhcnRtZW50JztcbiAgICAgICAgdGhpcy5kYXRhVXJsID0gJ2RhdGEvbW9jay1kZXBhcnRtZW50cy5qc29uJztcbiAgICB9XG4gICAgYXN5bmMgZmV0Y2hBbGwoKSB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGZldGNoKHRoaXMuZGF0YVVybCk7XG4gICAgICAgICAgICBpZiAoIXJlc3BvbnNlLm9rKSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBGYWlsZWQgdG8gbG9hZCBtb2NrIGRlcGFydG1lbnRzOiAke3Jlc3BvbnNlLnN0YXR1c30gJHtyZXNwb25zZS5zdGF0dXNUZXh0fWApO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY29uc3QgcmF3RGF0YSA9IGF3YWl0IHJlc3BvbnNlLmpzb24oKTtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLnByb2Nlc3NEZXBhcnRtZW50RGF0YShyYXdEYXRhKTtcbiAgICAgICAgfVxuICAgICAgICBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoJ0ZhaWxlZCB0byBsb2FkIGRlcGFydG1lbnQgZGF0YTonLCBlcnJvcik7XG4gICAgICAgICAgICB0aHJvdyBlcnJvcjtcbiAgICAgICAgfVxuICAgIH1cbiAgICBhc3luYyBzZW5kQ3JlYXRlKF9kZXBhcnRtZW50KSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignTW9ja0RlcGFydG1lbnRSZXBvc2l0b3J5IGRvZXMgbm90IHN1cHBvcnQgc2VuZENyZWF0ZS4gTW9jayBkYXRhIGlzIHJlYWQtb25seS4nKTtcbiAgICB9XG4gICAgYXN5bmMgc2VuZFVwZGF0ZShfaWQsIF91cGRhdGVzKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignTW9ja0RlcGFydG1lbnRSZXBvc2l0b3J5IGRvZXMgbm90IHN1cHBvcnQgc2VuZFVwZGF0ZS4gTW9jayBkYXRhIGlzIHJlYWQtb25seS4nKTtcbiAgICB9XG4gICAgYXN5bmMgc2VuZERlbGV0ZShfaWQpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdNb2NrRGVwYXJ0bWVudFJlcG9zaXRvcnkgZG9lcyBub3Qgc3VwcG9ydCBzZW5kRGVsZXRlLiBNb2NrIGRhdGEgaXMgcmVhZC1vbmx5LicpO1xuICAgIH1cbiAgICBwcm9jZXNzRGVwYXJ0bWVudERhdGEoZGF0YSkge1xuICAgICAgICByZXR1cm4gZGF0YS5tYXAoKGRlcHQpID0+ICh7XG4gICAgICAgICAgICBpZDogZGVwdC5pZCxcbiAgICAgICAgICAgIG5hbWU6IGRlcHQubmFtZSxcbiAgICAgICAgICAgIHJlc291cmNlSWRzOiBkZXB0LnJlc291cmNlSWRzLFxuICAgICAgICAgICAgc3luY1N0YXR1czogJ3N5bmNlZCdcbiAgICAgICAgfSkpO1xuICAgIH1cbn1cbiIsICIvKipcbiAqIE1vY2tTZXR0aW5nc1JlcG9zaXRvcnkgLSBMb2FkcyB0ZW5hbnQgc2V0dGluZ3MgZnJvbSBsb2NhbCBKU09OIGZpbGVcbiAqXG4gKiBTZXR0aW5ncyBhcmUgc3RvcmVkIGFzIHNlcGFyYXRlIHJlY29yZHMgcGVyIHNlY3Rpb24gKHdvcmt3ZWVrLCBncmlkLCBldGMuKS5cbiAqIFRoZSBKU09OIGZpbGUgaXMgYWxyZWFkeSBhbiBhcnJheSBvZiBUZW5hbnRTZXR0aW5nIHJlY29yZHMuXG4gKi9cbmV4cG9ydCBjbGFzcyBNb2NrU2V0dGluZ3NSZXBvc2l0b3J5IHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpcy5lbnRpdHlUeXBlID0gJ1NldHRpbmdzJztcbiAgICAgICAgdGhpcy5kYXRhVXJsID0gJ2RhdGEvdGVuYW50LXNldHRpbmdzLmpzb24nO1xuICAgIH1cbiAgICBhc3luYyBmZXRjaEFsbCgpIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2godGhpcy5kYXRhVXJsKTtcbiAgICAgICAgICAgIGlmICghcmVzcG9uc2Uub2spIHtcbiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEZhaWxlZCB0byBsb2FkIHRlbmFudCBzZXR0aW5nczogJHtyZXNwb25zZS5zdGF0dXN9ICR7cmVzcG9uc2Uuc3RhdHVzVGV4dH1gKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbnN0IHNldHRpbmdzID0gYXdhaXQgcmVzcG9uc2UuanNvbigpO1xuICAgICAgICAgICAgLy8gRW5zdXJlIHN5bmNTdGF0dXMgaXMgc2V0IG9uIGVhY2ggcmVjb3JkXG4gICAgICAgICAgICByZXR1cm4gc2V0dGluZ3MubWFwKHMgPT4gKHtcbiAgICAgICAgICAgICAgICAuLi5zLFxuICAgICAgICAgICAgICAgIHN5bmNTdGF0dXM6IHMuc3luY1N0YXR1cyB8fCAnc3luY2VkJ1xuICAgICAgICAgICAgfSkpO1xuICAgICAgICB9XG4gICAgICAgIGNhdGNoIChlcnJvcikge1xuICAgICAgICAgICAgY29uc29sZS5lcnJvcignRmFpbGVkIHRvIGxvYWQgdGVuYW50IHNldHRpbmdzOicsIGVycm9yKTtcbiAgICAgICAgICAgIHRocm93IGVycm9yO1xuICAgICAgICB9XG4gICAgfVxuICAgIGFzeW5jIHNlbmRDcmVhdGUoX3NldHRpbmdzKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignTW9ja1NldHRpbmdzUmVwb3NpdG9yeSBkb2VzIG5vdCBzdXBwb3J0IHNlbmRDcmVhdGUuIE1vY2sgZGF0YSBpcyByZWFkLW9ubHkuJyk7XG4gICAgfVxuICAgIGFzeW5jIHNlbmRVcGRhdGUoX2lkLCBfdXBkYXRlcykge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ01vY2tTZXR0aW5nc1JlcG9zaXRvcnkgZG9lcyBub3Qgc3VwcG9ydCBzZW5kVXBkYXRlLiBNb2NrIGRhdGEgaXMgcmVhZC1vbmx5LicpO1xuICAgIH1cbiAgICBhc3luYyBzZW5kRGVsZXRlKF9pZCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ01vY2tTZXR0aW5nc1JlcG9zaXRvcnkgZG9lcyBub3Qgc3VwcG9ydCBzZW5kRGVsZXRlLiBNb2NrIGRhdGEgaXMgcmVhZC1vbmx5LicpO1xuICAgIH1cbn1cbiIsICJleHBvcnQgY2xhc3MgTW9ja1ZpZXdDb25maWdSZXBvc2l0b3J5IHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpcy5lbnRpdHlUeXBlID0gJ1ZpZXdDb25maWcnO1xuICAgICAgICB0aGlzLmRhdGFVcmwgPSAnZGF0YS92aWV3Y29uZmlncy5qc29uJztcbiAgICB9XG4gICAgYXN5bmMgZmV0Y2hBbGwoKSB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGZldGNoKHRoaXMuZGF0YVVybCk7XG4gICAgICAgICAgICBpZiAoIXJlc3BvbnNlLm9rKSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBGYWlsZWQgdG8gbG9hZCB2aWV3Y29uZmlnczogJHtyZXNwb25zZS5zdGF0dXN9ICR7cmVzcG9uc2Uuc3RhdHVzVGV4dH1gKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbnN0IHJhd0RhdGEgPSBhd2FpdCByZXNwb25zZS5qc29uKCk7XG4gICAgICAgICAgICAvLyBFbnN1cmUgc3luY1N0YXR1cyBpcyBzZXQgb24gZWFjaCBjb25maWdcbiAgICAgICAgICAgIGNvbnN0IGNvbmZpZ3MgPSByYXdEYXRhLm1hcCgoY29uZmlnKSA9PiAoe1xuICAgICAgICAgICAgICAgIC4uLmNvbmZpZyxcbiAgICAgICAgICAgICAgICBzeW5jU3RhdHVzOiBjb25maWcuc3luY1N0YXR1cyB8fCAnc3luY2VkJ1xuICAgICAgICAgICAgfSkpO1xuICAgICAgICAgICAgcmV0dXJuIGNvbmZpZ3M7XG4gICAgICAgIH1cbiAgICAgICAgY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgICAgICBjb25zb2xlLmVycm9yKCdGYWlsZWQgdG8gbG9hZCB2aWV3Y29uZmlnczonLCBlcnJvcik7XG4gICAgICAgICAgICB0aHJvdyBlcnJvcjtcbiAgICAgICAgfVxuICAgIH1cbiAgICBhc3luYyBzZW5kQ3JlYXRlKF9jb25maWcpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdNb2NrVmlld0NvbmZpZ1JlcG9zaXRvcnkgZG9lcyBub3Qgc3VwcG9ydCBzZW5kQ3JlYXRlLiBNb2NrIGRhdGEgaXMgcmVhZC1vbmx5LicpO1xuICAgIH1cbiAgICBhc3luYyBzZW5kVXBkYXRlKF9pZCwgX3VwZGF0ZXMpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdNb2NrVmlld0NvbmZpZ1JlcG9zaXRvcnkgZG9lcyBub3Qgc3VwcG9ydCBzZW5kVXBkYXRlLiBNb2NrIGRhdGEgaXMgcmVhZC1vbmx5LicpO1xuICAgIH1cbiAgICBhc3luYyBzZW5kRGVsZXRlKF9pZCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ01vY2tWaWV3Q29uZmlnUmVwb3NpdG9yeSBkb2VzIG5vdCBzdXBwb3J0IHNlbmREZWxldGUuIE1vY2sgZGF0YSBpcyByZWFkLW9ubHkuJyk7XG4gICAgfVxufVxuIiwgIi8qKlxuICogRGF0YVNlZWRlciAtIE9yY2hlc3RyYXRlcyBpbml0aWFsIGRhdGEgbG9hZGluZyBmcm9tIHJlcG9zaXRvcmllcyBpbnRvIEluZGV4ZWREQlxuICpcbiAqIEFSQ0hJVEVDVFVSRTpcbiAqIC0gUmVwb3NpdG9yeSAoTW9jay9BcGkpOiBGZXRjaGVzIGRhdGEgZnJvbSBzb3VyY2UgKEpTT04gZmlsZSBvciBiYWNrZW5kIEFQSSlcbiAqIC0gRGF0YVNlZWRlciAodGhpcyBjbGFzcyk6IE9yY2hlc3RyYXRlcyBmZXRjaCArIHNhdmUgb3BlcmF0aW9uc1xuICogLSBTZXJ2aWNlIChFdmVudFNlcnZpY2UsIGV0Yy4pOiBTYXZlcyBkYXRhIHRvIEluZGV4ZWREQlxuICpcbiAqIFBPTFlNT1JQSElDIERFU0lHTjpcbiAqIC0gVXNlcyBhcnJheXMgb2YgSUVudGl0eVNlcnZpY2VbXSBhbmQgSUFwaVJlcG9zaXRvcnlbXVxuICogLSBNYXRjaGVzIHNlcnZpY2VzIHdpdGggcmVwb3NpdG9yaWVzIHVzaW5nIGVudGl0eVR5cGUgcHJvcGVydHlcbiAqIC0gT3Blbi9DbG9zZWQgUHJpbmNpcGxlOiBBZGRpbmcgbmV3IGVudGl0eSByZXF1aXJlcyBubyBjb2RlIGNoYW5nZXMgaGVyZVxuICovXG5leHBvcnQgY2xhc3MgRGF0YVNlZWRlciB7XG4gICAgY29uc3RydWN0b3Ioc2VydmljZXMsIHJlcG9zaXRvcmllcykge1xuICAgICAgICB0aGlzLnNlcnZpY2VzID0gc2VydmljZXM7XG4gICAgICAgIHRoaXMucmVwb3NpdG9yaWVzID0gcmVwb3NpdG9yaWVzO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBTZWVkIGFsbCBlbnRpdHkgc3RvcmVzIGlmIHRoZXkgYXJlIGVtcHR5XG4gICAgICovXG4gICAgYXN5bmMgc2VlZElmRW1wdHkoKSB7XG4gICAgICAgIGNvbnNvbGUubG9nKCdbRGF0YVNlZWRlcl0gQ2hlY2tpbmcgaWYgZGF0YWJhc2UgbmVlZHMgc2VlZGluZy4uLicpO1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgZm9yIChjb25zdCBzZXJ2aWNlIG9mIHRoaXMuc2VydmljZXMpIHtcbiAgICAgICAgICAgICAgICBjb25zdCByZXBvc2l0b3J5ID0gdGhpcy5yZXBvc2l0b3JpZXMuZmluZChyZXBvID0+IHJlcG8uZW50aXR5VHlwZSA9PT0gc2VydmljZS5lbnRpdHlUeXBlKTtcbiAgICAgICAgICAgICAgICBpZiAoIXJlcG9zaXRvcnkpIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc29sZS53YXJuKGBbRGF0YVNlZWRlcl0gTm8gcmVwb3NpdG9yeSBmb3VuZCBmb3IgZW50aXR5IHR5cGU6ICR7c2VydmljZS5lbnRpdHlUeXBlfSwgc2tpcHBpbmdgKTtcbiAgICAgICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGF3YWl0IHRoaXMuc2VlZEVudGl0eShzZXJ2aWNlLmVudGl0eVR5cGUsIHNlcnZpY2UsIHJlcG9zaXRvcnkpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY29uc29sZS5sb2coJ1tEYXRhU2VlZGVyXSBTZWVkaW5nIGNvbXBsZXRlJyk7XG4gICAgICAgIH1cbiAgICAgICAgY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgICAgICBjb25zb2xlLmVycm9yKCdbRGF0YVNlZWRlcl0gU2VlZGluZyBmYWlsZWQ6JywgZXJyb3IpO1xuICAgICAgICAgICAgdGhyb3cgZXJyb3I7XG4gICAgICAgIH1cbiAgICB9XG4gICAgYXN5bmMgc2VlZEVudGl0eShlbnRpdHlUeXBlLCBzZXJ2aWNlLCByZXBvc2l0b3J5KSB7XG4gICAgICAgIGNvbnN0IGV4aXN0aW5nID0gYXdhaXQgc2VydmljZS5nZXRBbGwoKTtcbiAgICAgICAgaWYgKGV4aXN0aW5nLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICAgIGNvbnNvbGUubG9nKGBbRGF0YVNlZWRlcl0gJHtlbnRpdHlUeXBlfSBzdG9yZSBhbHJlYWR5IGhhcyAke2V4aXN0aW5nLmxlbmd0aH0gaXRlbXMsIHNraXBwaW5nIHNlZWRgKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBjb25zb2xlLmxvZyhgW0RhdGFTZWVkZXJdICR7ZW50aXR5VHlwZX0gc3RvcmUgaXMgZW1wdHksIGZldGNoaW5nIGZyb20gcmVwb3NpdG9yeS4uLmApO1xuICAgICAgICBjb25zdCBkYXRhID0gYXdhaXQgcmVwb3NpdG9yeS5mZXRjaEFsbCgpO1xuICAgICAgICBjb25zb2xlLmxvZyhgW0RhdGFTZWVkZXJdIEZldGNoZWQgJHtkYXRhLmxlbmd0aH0gJHtlbnRpdHlUeXBlfSBpdGVtcywgc2F2aW5nIHRvIEluZGV4ZWREQi4uLmApO1xuICAgICAgICBmb3IgKGNvbnN0IGVudGl0eSBvZiBkYXRhKSB7XG4gICAgICAgICAgICBhd2FpdCBzZXJ2aWNlLnNhdmUoZW50aXR5LCB0cnVlKTsgLy8gc2lsZW50ID0gdHJ1ZSB0byBza2lwIGF1ZGl0IGxvZ2dpbmdcbiAgICAgICAgfVxuICAgICAgICBjb25zb2xlLmxvZyhgW0RhdGFTZWVkZXJdICR7ZW50aXR5VHlwZX0gc2VlZGluZyBjb21wbGV0ZSAoJHtkYXRhLmxlbmd0aH0gaXRlbXMgc2F2ZWQpYCk7XG4gICAgfVxufVxuIiwgIi8qKlxuICogQ2FsY3VsYXRlIHBpeGVsIHBvc2l0aW9uIGZvciBhbiBldmVudCBiYXNlZCBvbiBpdHMgdGltZXNcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNhbGN1bGF0ZUV2ZW50UG9zaXRpb24oc3RhcnQsIGVuZCwgY29uZmlnKSB7XG4gICAgY29uc3Qgc3RhcnRNaW51dGVzID0gc3RhcnQuZ2V0SG91cnMoKSAqIDYwICsgc3RhcnQuZ2V0TWludXRlcygpO1xuICAgIGNvbnN0IGVuZE1pbnV0ZXMgPSBlbmQuZ2V0SG91cnMoKSAqIDYwICsgZW5kLmdldE1pbnV0ZXMoKTtcbiAgICBjb25zdCBkYXlTdGFydE1pbnV0ZXMgPSBjb25maWcuZGF5U3RhcnRIb3VyICogNjA7XG4gICAgY29uc3QgbWludXRlSGVpZ2h0ID0gY29uZmlnLmhvdXJIZWlnaHQgLyA2MDtcbiAgICBjb25zdCB0b3AgPSAoc3RhcnRNaW51dGVzIC0gZGF5U3RhcnRNaW51dGVzKSAqIG1pbnV0ZUhlaWdodDtcbiAgICBjb25zdCBoZWlnaHQgPSAoZW5kTWludXRlcyAtIHN0YXJ0TWludXRlcykgKiBtaW51dGVIZWlnaHQ7XG4gICAgcmV0dXJuIHsgdG9wLCBoZWlnaHQgfTtcbn1cbi8qKlxuICogQ29udmVydCBtaW51dGVzIHRvIHBpeGVsc1xuICovXG5leHBvcnQgZnVuY3Rpb24gbWludXRlc1RvUGl4ZWxzKG1pbnV0ZXMsIGNvbmZpZykge1xuICAgIHJldHVybiAobWludXRlcyAvIDYwKSAqIGNvbmZpZy5ob3VySGVpZ2h0O1xufVxuLyoqXG4gKiBDb252ZXJ0IHBpeGVscyB0byBtaW51dGVzXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwaXhlbHNUb01pbnV0ZXMocGl4ZWxzLCBjb25maWcpIHtcbiAgICByZXR1cm4gKHBpeGVscyAvIGNvbmZpZy5ob3VySGVpZ2h0KSAqIDYwO1xufVxuLyoqXG4gKiBTbmFwIHBpeGVsIHBvc2l0aW9uIHRvIGdyaWQgaW50ZXJ2YWxcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNuYXBUb0dyaWQocGl4ZWxzLCBjb25maWcpIHtcbiAgICBjb25zdCBzbmFwUGl4ZWxzID0gbWludXRlc1RvUGl4ZWxzKGNvbmZpZy5zbmFwSW50ZXJ2YWwsIGNvbmZpZyk7XG4gICAgcmV0dXJuIE1hdGgucm91bmQocGl4ZWxzIC8gc25hcFBpeGVscykgKiBzbmFwUGl4ZWxzO1xufVxuIiwgImltcG9ydCB7IGNhbGN1bGF0ZUV2ZW50UG9zaXRpb24gfSBmcm9tICcuLi8uLi91dGlscy9Qb3NpdGlvblV0aWxzJztcbi8qKlxuICogQ2hlY2sgaWYgdHdvIGV2ZW50cyBvdmVybGFwIChzdHJpY3QgLSB0b3VjaGluZyBhdCBib3VuZGFyeSA9IE5PVCBvdmVybGFwcGluZylcbiAqIFRoaXMgbWF0Y2hlcyBTY2VuYXJpbyA4OiBlbmQ9PT1zdGFydCBpcyBOT1Qgb3ZlcmxhcFxuICovXG5leHBvcnQgZnVuY3Rpb24gZXZlbnRzT3ZlcmxhcChhLCBiKSB7XG4gICAgcmV0dXJuIGEuc3RhcnQgPCBiLmVuZCAmJiBhLmVuZCA+IGIuc3RhcnQ7XG59XG4vKipcbiAqIENoZWNrIGlmIHR3byBldmVudHMgYXJlIHdpdGhpbiB0aHJlc2hvbGQgZm9yIGdyaWQgZ3JvdXBpbmcuXG4gKiBUaGlzIGluY2x1ZGVzOlxuICogMS4gU3RhcnQtdG8tc3RhcnQ6IEV2ZW50cyBzdGFydCB3aXRoaW4gdGhyZXNob2xkIG9mIGVhY2ggb3RoZXJcbiAqIDIuIEVuZC10by1zdGFydDogT25lIGV2ZW50IHN0YXJ0cyB3aXRoaW4gdGhyZXNob2xkIGJlZm9yZSBhbm90aGVyIGVuZHNcbiAqL1xuZnVuY3Rpb24gZXZlbnRzV2l0aGluVGhyZXNob2xkKGEsIGIsIHRocmVzaG9sZE1pbnV0ZXMpIHtcbiAgICBjb25zdCB0aHJlc2hvbGRNcyA9IHRocmVzaG9sZE1pbnV0ZXMgKiA2MCAqIDEwMDA7XG4gICAgLy8gU3RhcnQtdG8tc3RhcnQ6IGJvdGggZXZlbnRzIHN0YXJ0IHdpdGhpbiB0aHJlc2hvbGRcbiAgICBjb25zdCBzdGFydFRvU3RhcnREaWZmID0gTWF0aC5hYnMoYS5zdGFydC5nZXRUaW1lKCkgLSBiLnN0YXJ0LmdldFRpbWUoKSk7XG4gICAgaWYgKHN0YXJ0VG9TdGFydERpZmYgPD0gdGhyZXNob2xkTXMpXG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgIC8vIEVuZC10by1zdGFydDogb25lIGV2ZW50IHN0YXJ0cyB3aXRoaW4gdGhyZXNob2xkIGJlZm9yZSB0aGUgb3RoZXIgZW5kc1xuICAgIC8vIEIgc3RhcnRzIHdpdGhpbiB0aHJlc2hvbGQgYmVmb3JlIEEgZW5kc1xuICAgIGNvbnN0IGJTdGFydHNCZWZvcmVBRW5kcyA9IGEuZW5kLmdldFRpbWUoKSAtIGIuc3RhcnQuZ2V0VGltZSgpO1xuICAgIGlmIChiU3RhcnRzQmVmb3JlQUVuZHMgPiAwICYmIGJTdGFydHNCZWZvcmVBRW5kcyA8PSB0aHJlc2hvbGRNcylcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgLy8gQSBzdGFydHMgd2l0aGluIHRocmVzaG9sZCBiZWZvcmUgQiBlbmRzXG4gICAgY29uc3QgYVN0YXJ0c0JlZm9yZUJFbmRzID0gYi5lbmQuZ2V0VGltZSgpIC0gYS5zdGFydC5nZXRUaW1lKCk7XG4gICAgaWYgKGFTdGFydHNCZWZvcmVCRW5kcyA+IDAgJiYgYVN0YXJ0c0JlZm9yZUJFbmRzIDw9IHRocmVzaG9sZE1zKVxuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICByZXR1cm4gZmFsc2U7XG59XG4vKipcbiAqIENoZWNrIGlmIGFsbCBldmVudHMgaW4gYSBncm91cCBzdGFydCB3aXRoaW4gdGhyZXNob2xkIG9mIGVhY2ggb3RoZXJcbiAqL1xuZnVuY3Rpb24gYWxsU3RhcnRXaXRoaW5UaHJlc2hvbGQoZXZlbnRzLCB0aHJlc2hvbGRNaW51dGVzKSB7XG4gICAgaWYgKGV2ZW50cy5sZW5ndGggPD0gMSlcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgLy8gRmluZCBlYXJsaWVzdCBhbmQgbGF0ZXN0IHN0YXJ0IHRpbWVzXG4gICAgbGV0IGVhcmxpZXN0ID0gZXZlbnRzWzBdLnN0YXJ0LmdldFRpbWUoKTtcbiAgICBsZXQgbGF0ZXN0ID0gZXZlbnRzWzBdLnN0YXJ0LmdldFRpbWUoKTtcbiAgICBmb3IgKGNvbnN0IGV2ZW50IG9mIGV2ZW50cykge1xuICAgICAgICBjb25zdCB0aW1lID0gZXZlbnQuc3RhcnQuZ2V0VGltZSgpO1xuICAgICAgICBpZiAodGltZSA8IGVhcmxpZXN0KVxuICAgICAgICAgICAgZWFybGllc3QgPSB0aW1lO1xuICAgICAgICBpZiAodGltZSA+IGxhdGVzdClcbiAgICAgICAgICAgIGxhdGVzdCA9IHRpbWU7XG4gICAgfVxuICAgIGNvbnN0IGRpZmZNaW51dGVzID0gKGxhdGVzdCAtIGVhcmxpZXN0KSAvICgxMDAwICogNjApO1xuICAgIHJldHVybiBkaWZmTWludXRlcyA8PSB0aHJlc2hvbGRNaW51dGVzO1xufVxuLyoqXG4gKiBGaW5kIGdyb3VwcyBvZiBvdmVybGFwcGluZyBldmVudHMgKGNvbm5lY3RlZCBieSBvdmVybGFwIGNoYWluKVxuICogRXZlbnRzIGFyZSBncm91cGVkIGlmIHRoZXkgb3ZlcmxhcCB3aXRoIGFueSBldmVudCBpbiB0aGUgZ3JvdXBcbiAqL1xuZnVuY3Rpb24gZmluZE92ZXJsYXBHcm91cHMoZXZlbnRzKSB7XG4gICAgaWYgKGV2ZW50cy5sZW5ndGggPT09IDApXG4gICAgICAgIHJldHVybiBbXTtcbiAgICBjb25zdCBzb3J0ZWQgPSBbLi4uZXZlbnRzXS5zb3J0KChhLCBiKSA9PiBhLnN0YXJ0LmdldFRpbWUoKSAtIGIuc3RhcnQuZ2V0VGltZSgpKTtcbiAgICBjb25zdCB1c2VkID0gbmV3IFNldCgpO1xuICAgIGNvbnN0IGdyb3VwcyA9IFtdO1xuICAgIGZvciAoY29uc3QgZXZlbnQgb2Ygc29ydGVkKSB7XG4gICAgICAgIGlmICh1c2VkLmhhcyhldmVudC5pZCkpXG4gICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgLy8gU3RhcnQgYSBuZXcgZ3JvdXAgd2l0aCB0aGlzIGV2ZW50XG4gICAgICAgIGNvbnN0IGdyb3VwID0gW2V2ZW50XTtcbiAgICAgICAgdXNlZC5hZGQoZXZlbnQuaWQpO1xuICAgICAgICAvLyBFeHBhbmQgZ3JvdXAgYnkgZmluZGluZyBhbGwgY29ubmVjdGVkIGV2ZW50cyAodmlhIG92ZXJsYXApXG4gICAgICAgIGxldCBleHBhbmRlZCA9IHRydWU7XG4gICAgICAgIHdoaWxlIChleHBhbmRlZCkge1xuICAgICAgICAgICAgZXhwYW5kZWQgPSBmYWxzZTtcbiAgICAgICAgICAgIGZvciAoY29uc3QgY2FuZGlkYXRlIG9mIHNvcnRlZCkge1xuICAgICAgICAgICAgICAgIGlmICh1c2VkLmhhcyhjYW5kaWRhdGUuaWQpKVxuICAgICAgICAgICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICAgICAgICAvLyBDaGVjayBpZiBjYW5kaWRhdGUgb3ZlcmxhcHMgd2l0aCBhbnkgZXZlbnQgaW4gZ3JvdXBcbiAgICAgICAgICAgICAgICBjb25zdCBjb25uZWN0cyA9IGdyb3VwLnNvbWUobWVtYmVyID0+IGV2ZW50c092ZXJsYXAobWVtYmVyLCBjYW5kaWRhdGUpKTtcbiAgICAgICAgICAgICAgICBpZiAoY29ubmVjdHMpIHtcbiAgICAgICAgICAgICAgICAgICAgZ3JvdXAucHVzaChjYW5kaWRhdGUpO1xuICAgICAgICAgICAgICAgICAgICB1c2VkLmFkZChjYW5kaWRhdGUuaWQpO1xuICAgICAgICAgICAgICAgICAgICBleHBhbmRlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGdyb3Vwcy5wdXNoKGdyb3VwKTtcbiAgICB9XG4gICAgcmV0dXJuIGdyb3Vwcztcbn1cbi8qKlxuICogRmluZCBncmlkIGNhbmRpZGF0ZXMgd2l0aGluIGEgZ3JvdXAgLSBldmVudHMgY29ubmVjdGVkIHZpYSB0aHJlc2hvbGQgY2hhaW5cbiAqIFVzZXMgVjEgbG9naWM6IGV2ZW50cyBhcmUgY29ubmVjdGVkIGlmIHdpdGhpbiB0aHJlc2hvbGQgKG5vIG92ZXJsYXAgcmVxdWlyZW1lbnQpXG4gKi9cbmZ1bmN0aW9uIGZpbmRHcmlkQ2FuZGlkYXRlcyhldmVudHMsIHRocmVzaG9sZE1pbnV0ZXMpIHtcbiAgICBpZiAoZXZlbnRzLmxlbmd0aCA9PT0gMClcbiAgICAgICAgcmV0dXJuIFtdO1xuICAgIGNvbnN0IHNvcnRlZCA9IFsuLi5ldmVudHNdLnNvcnQoKGEsIGIpID0+IGEuc3RhcnQuZ2V0VGltZSgpIC0gYi5zdGFydC5nZXRUaW1lKCkpO1xuICAgIGNvbnN0IHVzZWQgPSBuZXcgU2V0KCk7XG4gICAgY29uc3QgZ3JvdXBzID0gW107XG4gICAgZm9yIChjb25zdCBldmVudCBvZiBzb3J0ZWQpIHtcbiAgICAgICAgaWYgKHVzZWQuaGFzKGV2ZW50LmlkKSlcbiAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICBjb25zdCBncm91cCA9IFtldmVudF07XG4gICAgICAgIHVzZWQuYWRkKGV2ZW50LmlkKTtcbiAgICAgICAgLy8gRXhwYW5kIGJ5IHRocmVzaG9sZCBjaGFpbiAoVjEgbG9naWM6IG5vIG92ZXJsYXAgcmVxdWlyZW1lbnQsIGp1c3QgdGhyZXNob2xkKVxuICAgICAgICBsZXQgZXhwYW5kZWQgPSB0cnVlO1xuICAgICAgICB3aGlsZSAoZXhwYW5kZWQpIHtcbiAgICAgICAgICAgIGV4cGFuZGVkID0gZmFsc2U7XG4gICAgICAgICAgICBmb3IgKGNvbnN0IGNhbmRpZGF0ZSBvZiBzb3J0ZWQpIHtcbiAgICAgICAgICAgICAgICBpZiAodXNlZC5oYXMoY2FuZGlkYXRlLmlkKSlcbiAgICAgICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICAgICAgY29uc3QgY29ubmVjdHMgPSBncm91cC5zb21lKG1lbWJlciA9PiBldmVudHNXaXRoaW5UaHJlc2hvbGQobWVtYmVyLCBjYW5kaWRhdGUsIHRocmVzaG9sZE1pbnV0ZXMpKTtcbiAgICAgICAgICAgICAgICBpZiAoY29ubmVjdHMpIHtcbiAgICAgICAgICAgICAgICAgICAgZ3JvdXAucHVzaChjYW5kaWRhdGUpO1xuICAgICAgICAgICAgICAgICAgICB1c2VkLmFkZChjYW5kaWRhdGUuaWQpO1xuICAgICAgICAgICAgICAgICAgICBleHBhbmRlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGdyb3Vwcy5wdXNoKGdyb3VwKTtcbiAgICB9XG4gICAgcmV0dXJuIGdyb3Vwcztcbn1cbi8qKlxuICogQ2FsY3VsYXRlIHN0YWNrIGxldmVscyBmb3Igb3ZlcmxhcHBpbmcgZXZlbnRzIHVzaW5nIGdyZWVkeSBhbGdvcml0aG1cbiAqIEZvciBlYWNoIGV2ZW50OiBsZXZlbCA9IG1heChvdmVybGFwcGluZyBhbHJlYWR5LXByb2Nlc3NlZCBldmVudHMpICsgMVxuICovXG5mdW5jdGlvbiBjYWxjdWxhdGVTdGFja0xldmVscyhldmVudHMpIHtcbiAgICBjb25zdCBsZXZlbHMgPSBuZXcgTWFwKCk7XG4gICAgY29uc3Qgc29ydGVkID0gWy4uLmV2ZW50c10uc29ydCgoYSwgYikgPT4gYS5zdGFydC5nZXRUaW1lKCkgLSBiLnN0YXJ0LmdldFRpbWUoKSk7XG4gICAgZm9yIChjb25zdCBldmVudCBvZiBzb3J0ZWQpIHtcbiAgICAgICAgbGV0IG1heE92ZXJsYXBwaW5nTGV2ZWwgPSAtMTtcbiAgICAgICAgLy8gRmluZCBtYXggbGV2ZWwgYW1vbmcgb3ZlcmxhcHBpbmcgZXZlbnRzIGFscmVhZHkgcHJvY2Vzc2VkXG4gICAgICAgIGZvciAoY29uc3QgW2lkLCBsZXZlbF0gb2YgbGV2ZWxzKSB7XG4gICAgICAgICAgICBjb25zdCBvdGhlciA9IGV2ZW50cy5maW5kKGUgPT4gZS5pZCA9PT0gaWQpO1xuICAgICAgICAgICAgaWYgKG90aGVyICYmIGV2ZW50c092ZXJsYXAoZXZlbnQsIG90aGVyKSkge1xuICAgICAgICAgICAgICAgIG1heE92ZXJsYXBwaW5nTGV2ZWwgPSBNYXRoLm1heChtYXhPdmVybGFwcGluZ0xldmVsLCBsZXZlbCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgbGV2ZWxzLnNldChldmVudC5pZCwgbWF4T3ZlcmxhcHBpbmdMZXZlbCArIDEpO1xuICAgIH1cbiAgICByZXR1cm4gbGV2ZWxzO1xufVxuLyoqXG4gKiBBbGxvY2F0ZSBldmVudHMgdG8gY29sdW1ucyBmb3IgR1JJRCBsYXlvdXQgdXNpbmcgZ3JlZWR5IGFsZ29yaXRobVxuICogTm9uLW92ZXJsYXBwaW5nIGV2ZW50cyBjYW4gc2hhcmUgYSBjb2x1bW4gdG8gbWluaW1pemUgdG90YWwgY29sdW1uc1xuICovXG5mdW5jdGlvbiBhbGxvY2F0ZUNvbHVtbnMoZXZlbnRzKSB7XG4gICAgY29uc3Qgc29ydGVkID0gWy4uLmV2ZW50c10uc29ydCgoYSwgYikgPT4gYS5zdGFydC5nZXRUaW1lKCkgLSBiLnN0YXJ0LmdldFRpbWUoKSk7XG4gICAgY29uc3QgY29sdW1ucyA9IFtdO1xuICAgIGZvciAoY29uc3QgZXZlbnQgb2Ygc29ydGVkKSB7XG4gICAgICAgIC8vIEZpbmQgZmlyc3QgY29sdW1uIHdoZXJlIGV2ZW50IGRvZXNuJ3Qgb3ZlcmxhcCB3aXRoIGV4aXN0aW5nIGV2ZW50c1xuICAgICAgICBsZXQgcGxhY2VkID0gZmFsc2U7XG4gICAgICAgIGZvciAoY29uc3QgY29sdW1uIG9mIGNvbHVtbnMpIHtcbiAgICAgICAgICAgIGNvbnN0IGNhbkZpdCA9ICFjb2x1bW4uc29tZShlID0+IGV2ZW50c092ZXJsYXAoZXZlbnQsIGUpKTtcbiAgICAgICAgICAgIGlmIChjYW5GaXQpIHtcbiAgICAgICAgICAgICAgICBjb2x1bW4ucHVzaChldmVudCk7XG4gICAgICAgICAgICAgICAgcGxhY2VkID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICAvLyBObyBzdWl0YWJsZSBjb2x1bW4gZm91bmQsIGNyZWF0ZSBuZXcgb25lXG4gICAgICAgIGlmICghcGxhY2VkKSB7XG4gICAgICAgICAgICBjb2x1bW5zLnB1c2goW2V2ZW50XSk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIGNvbHVtbnM7XG59XG4vKipcbiAqIE1haW4gZW50cnkgcG9pbnQ6IENhbGN1bGF0ZSBjb21wbGV0ZSBsYXlvdXQgZm9yIGEgY29sdW1uJ3MgZXZlbnRzXG4gKlxuICogQWxnb3JpdGhtOlxuICogMS4gRmluZCBvdmVybGFwIGdyb3VwcyAoZXZlbnRzIGNvbm5lY3RlZCBieSBvdmVybGFwIGNoYWluKVxuICogMi4gRm9yIGVhY2ggb3ZlcmxhcCBncm91cCwgZmluZCBncmlkIGNhbmRpZGF0ZXMgKGV2ZW50cyB3aXRoaW4gdGhyZXNob2xkIGNoYWluKVxuICogMy4gSWYgYWxsIGV2ZW50cyBpbiBvdmVybGFwIGdyb3VwIGZvcm0gYSBzaW5nbGUgZ3JpZCBjYW5kaWRhdGUgXHUyMTkyIEdSSUQgbW9kZVxuICogNC4gT3RoZXJ3aXNlIFx1MjE5MiBTVEFDS0lORyBtb2RlIHdpdGggY2FsY3VsYXRlZCBsZXZlbHNcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNhbGN1bGF0ZUNvbHVtbkxheW91dChldmVudHMsIGNvbmZpZykge1xuICAgIGNvbnN0IHRocmVzaG9sZE1pbnV0ZXMgPSBjb25maWcuZ3JpZFN0YXJ0VGhyZXNob2xkTWludXRlcyA/PyAxMDtcbiAgICBjb25zdCByZXN1bHQgPSB7XG4gICAgICAgIGdyaWRzOiBbXSxcbiAgICAgICAgc3RhY2tlZDogW11cbiAgICB9O1xuICAgIGlmIChldmVudHMubGVuZ3RoID09PSAwKVxuICAgICAgICByZXR1cm4gcmVzdWx0O1xuICAgIC8vIEZpbmQgYWxsIG92ZXJsYXBwaW5nIGV2ZW50IGdyb3Vwc1xuICAgIGNvbnN0IG92ZXJsYXBHcm91cHMgPSBmaW5kT3ZlcmxhcEdyb3VwcyhldmVudHMpO1xuICAgIGZvciAoY29uc3Qgb3ZlcmxhcEdyb3VwIG9mIG92ZXJsYXBHcm91cHMpIHtcbiAgICAgICAgaWYgKG92ZXJsYXBHcm91cC5sZW5ndGggPT09IDEpIHtcbiAgICAgICAgICAgIC8vIFNpbmdsZSBldmVudCAtIG5vIGdyb3VwaW5nIG5lZWRlZFxuICAgICAgICAgICAgcmVzdWx0LnN0YWNrZWQucHVzaCh7XG4gICAgICAgICAgICAgICAgZXZlbnQ6IG92ZXJsYXBHcm91cFswXSxcbiAgICAgICAgICAgICAgICBzdGFja0xldmVsOiAwXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICB9XG4gICAgICAgIC8vIFdpdGhpbiB0aGlzIG92ZXJsYXAgZ3JvdXAsIGZpbmQgZ3JpZCBjYW5kaWRhdGVzICh0aHJlc2hvbGQtY29ubmVjdGVkIHN1Ymdyb3VwcylcbiAgICAgICAgY29uc3QgZ3JpZFN1Ymdyb3VwcyA9IGZpbmRHcmlkQ2FuZGlkYXRlcyhvdmVybGFwR3JvdXAsIHRocmVzaG9sZE1pbnV0ZXMpO1xuICAgICAgICAvLyBDaGVjayBpZiB0aGUgRU5USVJFIG92ZXJsYXAgZ3JvdXAgZm9ybXMgYSBzaW5nbGUgZ3JpZCBjYW5kaWRhdGVcbiAgICAgICAgLy8gVGhpcyBoYXBwZW5zIHdoZW4gYWxsIGV2ZW50cyBhcmUgY29ubmVjdGVkIHZpYSB0aHJlc2hvbGQgY2hhaW5cbiAgICAgICAgY29uc3QgbGFyZ2VzdEdyaWRDYW5kaWRhdGUgPSBncmlkU3ViZ3JvdXBzLnJlZHVjZSgobWF4LCBnKSA9PiBnLmxlbmd0aCA+IG1heC5sZW5ndGggPyBnIDogbWF4LCBncmlkU3ViZ3JvdXBzWzBdKTtcbiAgICAgICAgaWYgKGxhcmdlc3RHcmlkQ2FuZGlkYXRlLmxlbmd0aCA9PT0gb3ZlcmxhcEdyb3VwLmxlbmd0aCkge1xuICAgICAgICAgICAgLy8gQWxsIGV2ZW50cyBpbiBvdmVybGFwIGdyb3VwIGFyZSBjb25uZWN0ZWQgdmlhIHRocmVzaG9sZCBjaGFpbiBcdTIxOTIgR1JJRCBtb2RlXG4gICAgICAgICAgICBjb25zdCBjb2x1bW5zID0gYWxsb2NhdGVDb2x1bW5zKG92ZXJsYXBHcm91cCk7XG4gICAgICAgICAgICBjb25zdCBlYXJsaWVzdCA9IG92ZXJsYXBHcm91cC5yZWR1Y2UoKG1pbiwgZSkgPT4gZS5zdGFydCA8IG1pbi5zdGFydCA/IGUgOiBtaW4sIG92ZXJsYXBHcm91cFswXSk7XG4gICAgICAgICAgICBjb25zdCBwb3NpdGlvbiA9IGNhbGN1bGF0ZUV2ZW50UG9zaXRpb24oZWFybGllc3Quc3RhcnQsIGVhcmxpZXN0LmVuZCwgY29uZmlnKTtcbiAgICAgICAgICAgIHJlc3VsdC5ncmlkcy5wdXNoKHtcbiAgICAgICAgICAgICAgICBldmVudHM6IG92ZXJsYXBHcm91cCxcbiAgICAgICAgICAgICAgICBjb2x1bW5zLFxuICAgICAgICAgICAgICAgIHN0YWNrTGV2ZWw6IDAsXG4gICAgICAgICAgICAgICAgcG9zaXRpb246IHsgdG9wOiBwb3NpdGlvbi50b3AgfVxuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAvLyBOb3QgYWxsIGV2ZW50cyBjb25uZWN0ZWQgdmlhIHRocmVzaG9sZCBcdTIxOTIgU1RBQ0tJTkcgbW9kZVxuICAgICAgICAgICAgY29uc3QgbGV2ZWxzID0gY2FsY3VsYXRlU3RhY2tMZXZlbHMob3ZlcmxhcEdyb3VwKTtcbiAgICAgICAgICAgIGZvciAoY29uc3QgZXZlbnQgb2Ygb3ZlcmxhcEdyb3VwKSB7XG4gICAgICAgICAgICAgICAgcmVzdWx0LnN0YWNrZWQucHVzaCh7XG4gICAgICAgICAgICAgICAgICAgIGV2ZW50LFxuICAgICAgICAgICAgICAgICAgICBzdGFja0xldmVsOiBsZXZlbHMuZ2V0KGV2ZW50LmlkKSA/PyAwXG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHJlc3VsdDtcbn1cbiIsICJpbXBvcnQgeyBjYWxjdWxhdGVFdmVudFBvc2l0aW9uLCBzbmFwVG9HcmlkLCBwaXhlbHNUb01pbnV0ZXMgfSBmcm9tICcuLi8uLi91dGlscy9Qb3NpdGlvblV0aWxzJztcbmltcG9ydCB7IENvcmVFdmVudHMgfSBmcm9tICcuLi8uLi9jb25zdGFudHMvQ29yZUV2ZW50cyc7XG5pbXBvcnQgeyBjYWxjdWxhdGVDb2x1bW5MYXlvdXQgfSBmcm9tICcuL0V2ZW50TGF5b3V0RW5naW5lJztcbi8qKlxuICogRXZlbnRSZW5kZXJlciAtIFJlbmRlcnMgY2FsZW5kYXIgZXZlbnRzIHRvIHRoZSBET01cbiAqXG4gKiBDTEVBTiBhcHByb2FjaDpcbiAqIC0gT25seSBkYXRhLWlkIGF0dHJpYnV0ZSBvbiBldmVudCBlbGVtZW50XG4gKiAtIGlubmVySFRNTCBjb250YWlucyBvbmx5IHZpc2libGUgY29udGVudFxuICogLSBFdmVudCBkYXRhIHJldHJpZXZlZCB2aWEgRXZlbnRTZXJ2aWNlIHdoZW4gbmVlZGVkXG4gKi9cbmV4cG9ydCBjbGFzcyBFdmVudFJlbmRlcmVyIHtcbiAgICBjb25zdHJ1Y3RvcihldmVudFNlcnZpY2UsIGRhdGVTZXJ2aWNlLCBncmlkQ29uZmlnLCBldmVudEJ1cykge1xuICAgICAgICB0aGlzLmV2ZW50U2VydmljZSA9IGV2ZW50U2VydmljZTtcbiAgICAgICAgdGhpcy5kYXRlU2VydmljZSA9IGRhdGVTZXJ2aWNlO1xuICAgICAgICB0aGlzLmdyaWRDb25maWcgPSBncmlkQ29uZmlnO1xuICAgICAgICB0aGlzLmV2ZW50QnVzID0gZXZlbnRCdXM7XG4gICAgICAgIHRoaXMuY29udGFpbmVyID0gbnVsbDtcbiAgICAgICAgdGhpcy5zZXR1cExpc3RlbmVycygpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBTZXR1cCBsaXN0ZW5lcnMgZm9yIGRyYWctZHJvcCBhbmQgdXBkYXRlIGV2ZW50c1xuICAgICAqL1xuICAgIHNldHVwTGlzdGVuZXJzKCkge1xuICAgICAgICB0aGlzLmV2ZW50QnVzLm9uKENvcmVFdmVudHMuRVZFTlRfRFJBR19DT0xVTU5fQ0hBTkdFLCAoZSkgPT4ge1xuICAgICAgICAgICAgY29uc3QgcGF5bG9hZCA9IGUuZGV0YWlsO1xuICAgICAgICAgICAgdGhpcy5oYW5kbGVDb2x1bW5DaGFuZ2UocGF5bG9hZCk7XG4gICAgICAgIH0pO1xuICAgICAgICB0aGlzLmV2ZW50QnVzLm9uKENvcmVFdmVudHMuRVZFTlRfRFJBR19NT1ZFLCAoZSkgPT4ge1xuICAgICAgICAgICAgY29uc3QgcGF5bG9hZCA9IGUuZGV0YWlsO1xuICAgICAgICAgICAgdGhpcy51cGRhdGVEcmFnVGltZXN0YW1wKHBheWxvYWQpO1xuICAgICAgICB9KTtcbiAgICAgICAgdGhpcy5ldmVudEJ1cy5vbihDb3JlRXZlbnRzLkVWRU5UX1VQREFURUQsIChlKSA9PiB7XG4gICAgICAgICAgICBjb25zdCBwYXlsb2FkID0gZS5kZXRhaWw7XG4gICAgICAgICAgICB0aGlzLmhhbmRsZUV2ZW50VXBkYXRlZChwYXlsb2FkKTtcbiAgICAgICAgfSk7XG4gICAgICAgIHRoaXMuZXZlbnRCdXMub24oQ29yZUV2ZW50cy5FVkVOVF9EUkFHX0VORCwgKGUpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHBheWxvYWQgPSBlLmRldGFpbDtcbiAgICAgICAgICAgIHRoaXMuaGFuZGxlRHJhZ0VuZChwYXlsb2FkKTtcbiAgICAgICAgfSk7XG4gICAgICAgIHRoaXMuZXZlbnRCdXMub24oQ29yZUV2ZW50cy5FVkVOVF9EUkFHX0xFQVZFX0hFQURFUiwgKGUpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHBheWxvYWQgPSBlLmRldGFpbDtcbiAgICAgICAgICAgIHRoaXMuaGFuZGxlRHJhZ0xlYXZlSGVhZGVyKHBheWxvYWQpO1xuICAgICAgICB9KTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogSGFuZGxlIEVWRU5UX0RSQUdfRU5EIC0gcmVtb3ZlIGVsZW1lbnQgaWYgZHJvcHBlZCBpbiBoZWFkZXJcbiAgICAgKi9cbiAgICBoYW5kbGVEcmFnRW5kKHBheWxvYWQpIHtcbiAgICAgICAgaWYgKHBheWxvYWQudGFyZ2V0ID09PSAnaGVhZGVyJykge1xuICAgICAgICAgICAgLy8gRXZlbnQgd2FzIGRyb3BwZWQgaW4gaGVhZGVyIGRyYXdlciAtIHJlbW92ZSBmcm9tIGdyaWRcbiAgICAgICAgICAgIGNvbnN0IGVsZW1lbnQgPSB0aGlzLmNvbnRhaW5lcj8ucXVlcnlTZWxlY3Rvcihgc3dwLWNvbnRlbnQtdmlld3BvcnQgc3dwLWV2ZW50W2RhdGEtZXZlbnQtaWQ9XCIke3BheWxvYWQuc3dwRXZlbnQuZXZlbnRJZH1cIl1gKTtcbiAgICAgICAgICAgIGVsZW1lbnQ/LnJlbW92ZSgpO1xuICAgICAgICB9XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEhhbmRsZSBoZWFkZXIgaXRlbSBsZWF2aW5nIGhlYWRlciAtIGNyZWF0ZSBzd3AtZXZlbnQgaW4gZ3JpZFxuICAgICAqL1xuICAgIGhhbmRsZURyYWdMZWF2ZUhlYWRlcihwYXlsb2FkKSB7XG4gICAgICAgIC8vIE9ubHkgaGFuZGxlIHdoZW4gc291cmNlIGlzIGhlYWRlciAoaGVhZGVyIGl0ZW0gZHJhZ2dlZCB0byBncmlkKVxuICAgICAgICBpZiAocGF5bG9hZC5zb3VyY2UgIT09ICdoZWFkZXInKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICBpZiAoIXBheWxvYWQudGFyZ2V0Q29sdW1uIHx8ICFwYXlsb2FkLnN0YXJ0IHx8ICFwYXlsb2FkLmVuZClcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgLy8gVHVybiBoZWFkZXIgaXRlbSBpbnRvIGdob3N0IChzdGF5cyB2aXNpYmxlIGJ1dCBmYWRlZClcbiAgICAgICAgaWYgKHBheWxvYWQuZWxlbWVudCkge1xuICAgICAgICAgICAgcGF5bG9hZC5lbGVtZW50LmNsYXNzTGlzdC5hZGQoJ2RyYWctZ2hvc3QnKTtcbiAgICAgICAgICAgIHBheWxvYWQuZWxlbWVudC5zdHlsZS5vcGFjaXR5ID0gJzAuMyc7XG4gICAgICAgICAgICBwYXlsb2FkLmVsZW1lbnQuc3R5bGUucG9pbnRlckV2ZW50cyA9ICdub25lJztcbiAgICAgICAgfVxuICAgICAgICAvLyBDcmVhdGUgZXZlbnQgb2JqZWN0IGZyb20gaGVhZGVyIGl0ZW0gZGF0YVxuICAgICAgICBjb25zdCBldmVudCA9IHtcbiAgICAgICAgICAgIGlkOiBwYXlsb2FkLmV2ZW50SWQsXG4gICAgICAgICAgICB0aXRsZTogcGF5bG9hZC50aXRsZSB8fCAnJyxcbiAgICAgICAgICAgIGRlc2NyaXB0aW9uOiAnJyxcbiAgICAgICAgICAgIHN0YXJ0OiBwYXlsb2FkLnN0YXJ0LFxuICAgICAgICAgICAgZW5kOiBwYXlsb2FkLmVuZCxcbiAgICAgICAgICAgIHR5cGU6ICdjdXN0b21lcicsXG4gICAgICAgICAgICBhbGxEYXk6IGZhbHNlLFxuICAgICAgICAgICAgc3luY1N0YXR1czogJ3BlbmRpbmcnXG4gICAgICAgIH07XG4gICAgICAgIC8vIENyZWF0ZSBzd3AtZXZlbnQgZWxlbWVudCB1c2luZyBleGlzdGluZyBtZXRob2RcbiAgICAgICAgY29uc3QgZWxlbWVudCA9IHRoaXMuY3JlYXRlRXZlbnRFbGVtZW50KGV2ZW50KTtcbiAgICAgICAgLy8gQWRkIHRvIHRhcmdldCBjb2x1bW5cbiAgICAgICAgbGV0IGV2ZW50c0xheWVyID0gcGF5bG9hZC50YXJnZXRDb2x1bW4ucXVlcnlTZWxlY3Rvcignc3dwLWV2ZW50cy1sYXllcicpO1xuICAgICAgICBpZiAoIWV2ZW50c0xheWVyKSB7XG4gICAgICAgICAgICBldmVudHNMYXllciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3N3cC1ldmVudHMtbGF5ZXInKTtcbiAgICAgICAgICAgIHBheWxvYWQudGFyZ2V0Q29sdW1uLmFwcGVuZENoaWxkKGV2ZW50c0xheWVyKTtcbiAgICAgICAgfVxuICAgICAgICBldmVudHNMYXllci5hcHBlbmRDaGlsZChlbGVtZW50KTtcbiAgICAgICAgLy8gTWFyayBhcyBkcmFnZ2luZyBzbyBEcmFnRHJvcE1hbmFnZXIgY2FuIGNvbnRpbnVlIHdpdGggaXRcbiAgICAgICAgZWxlbWVudC5jbGFzc0xpc3QuYWRkKCdkcmFnZ2luZycpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBIYW5kbGUgRVZFTlRfVVBEQVRFRCAtIHJlLXJlbmRlciBhZmZlY3RlZCBjb2x1bW5zXG4gICAgICovXG4gICAgYXN5bmMgaGFuZGxlRXZlbnRVcGRhdGVkKHBheWxvYWQpIHtcbiAgICAgICAgLy8gUmUtcmVuZGVyIHNvdXJjZSBjb2x1bW4gKGlmIGRpZmZlcmVudCBmcm9tIHRhcmdldClcbiAgICAgICAgaWYgKHBheWxvYWQuc291cmNlQ29sdW1uS2V5ICE9PSBwYXlsb2FkLnRhcmdldENvbHVtbktleSkge1xuICAgICAgICAgICAgYXdhaXQgdGhpcy5yZXJlbmRlckNvbHVtbihwYXlsb2FkLnNvdXJjZUNvbHVtbktleSk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gUmUtcmVuZGVyIHRhcmdldCBjb2x1bW5cbiAgICAgICAgYXdhaXQgdGhpcy5yZXJlbmRlckNvbHVtbihwYXlsb2FkLnRhcmdldENvbHVtbktleSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJlLXJlbmRlciBhIHNpbmdsZSBjb2x1bW4gd2l0aCBmcmVzaCBkYXRhIGZyb20gSW5kZXhlZERCXG4gICAgICovXG4gICAgYXN5bmMgcmVyZW5kZXJDb2x1bW4oY29sdW1uS2V5KSB7XG4gICAgICAgIGNvbnN0IGNvbHVtbiA9IHRoaXMuZmluZENvbHVtbihjb2x1bW5LZXkpO1xuICAgICAgICBpZiAoIWNvbHVtbilcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgLy8gUmVhZCBkYXRlIGFuZCByZXNvdXJjZUlkIGRpcmVjdGx5IGZyb20gY29sdW1uIGF0dHJpYnV0ZXMgKGNvbHVtbktleSBpcyBvcGFxdWUpXG4gICAgICAgIGNvbnN0IGRhdGUgPSBjb2x1bW4uZGF0YXNldC5kYXRlO1xuICAgICAgICBjb25zdCByZXNvdXJjZUlkID0gY29sdW1uLmRhdGFzZXQucmVzb3VyY2VJZDtcbiAgICAgICAgaWYgKCFkYXRlKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAvLyBHZXQgZGF0ZSByYW5nZSBmb3IgdGhpcyBkYXlcbiAgICAgICAgY29uc3Qgc3RhcnREYXRlID0gbmV3IERhdGUoZGF0ZSk7XG4gICAgICAgIGNvbnN0IGVuZERhdGUgPSBuZXcgRGF0ZShkYXRlKTtcbiAgICAgICAgZW5kRGF0ZS5zZXRIb3VycygyMywgNTksIDU5LCA5OTkpO1xuICAgICAgICAvLyBGZXRjaCBldmVudHMgZnJvbSBJbmRleGVkREJcbiAgICAgICAgY29uc3QgZXZlbnRzID0gcmVzb3VyY2VJZFxuICAgICAgICAgICAgPyBhd2FpdCB0aGlzLmV2ZW50U2VydmljZS5nZXRCeVJlc291cmNlQW5kRGF0ZVJhbmdlKHJlc291cmNlSWQsIHN0YXJ0RGF0ZSwgZW5kRGF0ZSlcbiAgICAgICAgICAgIDogYXdhaXQgdGhpcy5ldmVudFNlcnZpY2UuZ2V0QnlEYXRlUmFuZ2Uoc3RhcnREYXRlLCBlbmREYXRlKTtcbiAgICAgICAgLy8gRmlsdGVyIHRvIHRpbWVkIGV2ZW50cyBhbmQgbWF0Y2ggZGF0ZSBleGFjdGx5XG4gICAgICAgIGNvbnN0IHRpbWVkRXZlbnRzID0gZXZlbnRzLmZpbHRlcihldmVudCA9PiAhZXZlbnQuYWxsRGF5ICYmIHRoaXMuZGF0ZVNlcnZpY2UuZ2V0RGF0ZUtleShldmVudC5zdGFydCkgPT09IGRhdGUpO1xuICAgICAgICAvLyBHZXQgb3IgY3JlYXRlIGV2ZW50cyBsYXllclxuICAgICAgICBsZXQgZXZlbnRzTGF5ZXIgPSBjb2x1bW4ucXVlcnlTZWxlY3Rvcignc3dwLWV2ZW50cy1sYXllcicpO1xuICAgICAgICBpZiAoIWV2ZW50c0xheWVyKSB7XG4gICAgICAgICAgICBldmVudHNMYXllciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3N3cC1ldmVudHMtbGF5ZXInKTtcbiAgICAgICAgICAgIGNvbHVtbi5hcHBlbmRDaGlsZChldmVudHNMYXllcik7XG4gICAgICAgIH1cbiAgICAgICAgLy8gQ2xlYXIgZXhpc3RpbmcgZXZlbnRzXG4gICAgICAgIGV2ZW50c0xheWVyLmlubmVySFRNTCA9ICcnO1xuICAgICAgICAvLyBDYWxjdWxhdGUgbGF5b3V0IHdpdGggc3RhY2tpbmcvZ3JvdXBpbmdcbiAgICAgICAgY29uc3QgbGF5b3V0ID0gY2FsY3VsYXRlQ29sdW1uTGF5b3V0KHRpbWVkRXZlbnRzLCB0aGlzLmdyaWRDb25maWcpO1xuICAgICAgICAvLyBSZW5kZXIgR1JJRCBncm91cHNcbiAgICAgICAgbGF5b3V0LmdyaWRzLmZvckVhY2goZ3JpZCA9PiB7XG4gICAgICAgICAgICBjb25zdCBncm91cEVsID0gdGhpcy5yZW5kZXJHcmlkR3JvdXAoZ3JpZCk7XG4gICAgICAgICAgICBldmVudHNMYXllci5hcHBlbmRDaGlsZChncm91cEVsKTtcbiAgICAgICAgfSk7XG4gICAgICAgIC8vIFJlbmRlciBTVEFDS0VEIGV2ZW50c1xuICAgICAgICBsYXlvdXQuc3RhY2tlZC5mb3JFYWNoKGl0ZW0gPT4ge1xuICAgICAgICAgICAgY29uc3QgZXZlbnRFbCA9IHRoaXMucmVuZGVyU3RhY2tlZEV2ZW50KGl0ZW0uZXZlbnQsIGl0ZW0uc3RhY2tMZXZlbCk7XG4gICAgICAgICAgICBldmVudHNMYXllci5hcHBlbmRDaGlsZChldmVudEVsKTtcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEZpbmQgYSBjb2x1bW4gZWxlbWVudCBieSBjb2x1bW5LZXlcbiAgICAgKi9cbiAgICBmaW5kQ29sdW1uKGNvbHVtbktleSkge1xuICAgICAgICBpZiAoIXRoaXMuY29udGFpbmVyKVxuICAgICAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICAgIHJldHVybiB0aGlzLmNvbnRhaW5lci5xdWVyeVNlbGVjdG9yKGBzd3AtZGF5LWNvbHVtbltkYXRhLWNvbHVtbi1rZXk9XCIke2NvbHVtbktleX1cIl1gKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogSGFuZGxlIGV2ZW50IG1vdmluZyB0byBhIG5ldyBjb2x1bW4gZHVyaW5nIGRyYWdcbiAgICAgKi9cbiAgICBoYW5kbGVDb2x1bW5DaGFuZ2UocGF5bG9hZCkge1xuICAgICAgICBjb25zdCBldmVudHNMYXllciA9IHBheWxvYWQubmV3Q29sdW1uLnF1ZXJ5U2VsZWN0b3IoJ3N3cC1ldmVudHMtbGF5ZXInKTtcbiAgICAgICAgaWYgKCFldmVudHNMYXllcilcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgLy8gTW92ZSBlbGVtZW50IHRvIG5ldyBjb2x1bW5cbiAgICAgICAgZXZlbnRzTGF5ZXIuYXBwZW5kQ2hpbGQocGF5bG9hZC5lbGVtZW50KTtcbiAgICAgICAgLy8gUHJlc2VydmUgWSBwb3NpdGlvblxuICAgICAgICBwYXlsb2FkLmVsZW1lbnQuc3R5bGUudG9wID0gYCR7cGF5bG9hZC5jdXJyZW50WX1weGA7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFVwZGF0ZSB0aW1lc3RhbXAgZGlzcGxheSBkdXJpbmcgZHJhZyAoc25hcHBlZCB0byBncmlkKVxuICAgICAqL1xuICAgIHVwZGF0ZURyYWdUaW1lc3RhbXAocGF5bG9hZCkge1xuICAgICAgICBjb25zdCB0aW1lRWwgPSBwYXlsb2FkLmVsZW1lbnQucXVlcnlTZWxlY3Rvcignc3dwLWV2ZW50LXRpbWUnKTtcbiAgICAgICAgaWYgKCF0aW1lRWwpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIC8vIFNuYXAgcG9zaXRpb24gdG8gZ3JpZCBpbnRlcnZhbFxuICAgICAgICBjb25zdCBzbmFwcGVkWSA9IHNuYXBUb0dyaWQocGF5bG9hZC5jdXJyZW50WSwgdGhpcy5ncmlkQ29uZmlnKTtcbiAgICAgICAgLy8gQ2FsY3VsYXRlIG5ldyBzdGFydCB0aW1lXG4gICAgICAgIGNvbnN0IG1pbnV0ZXNGcm9tR3JpZFN0YXJ0ID0gcGl4ZWxzVG9NaW51dGVzKHNuYXBwZWRZLCB0aGlzLmdyaWRDb25maWcpO1xuICAgICAgICBjb25zdCBzdGFydE1pbnV0ZXMgPSAodGhpcy5ncmlkQ29uZmlnLmRheVN0YXJ0SG91ciAqIDYwKSArIG1pbnV0ZXNGcm9tR3JpZFN0YXJ0O1xuICAgICAgICAvLyBLZWVwIG9yaWdpbmFsIGR1cmF0aW9uIChmcm9tIGVsZW1lbnQgaGVpZ2h0KVxuICAgICAgICBjb25zdCBoZWlnaHQgPSBwYXJzZUZsb2F0KHBheWxvYWQuZWxlbWVudC5zdHlsZS5oZWlnaHQpIHx8IHRoaXMuZ3JpZENvbmZpZy5ob3VySGVpZ2h0O1xuICAgICAgICBjb25zdCBkdXJhdGlvbk1pbnV0ZXMgPSBwaXhlbHNUb01pbnV0ZXMoaGVpZ2h0LCB0aGlzLmdyaWRDb25maWcpO1xuICAgICAgICAvLyBDcmVhdGUgRGF0ZSBvYmplY3RzIGZvciBjb25zaXN0ZW50IGZvcm1hdHRpbmcgdmlhIERhdGVTZXJ2aWNlXG4gICAgICAgIGNvbnN0IHN0YXJ0ID0gdGhpcy5taW51dGVzVG9EYXRlKHN0YXJ0TWludXRlcyk7XG4gICAgICAgIGNvbnN0IGVuZCA9IHRoaXMubWludXRlc1RvRGF0ZShzdGFydE1pbnV0ZXMgKyBkdXJhdGlvbk1pbnV0ZXMpO1xuICAgICAgICB0aW1lRWwudGV4dENvbnRlbnQgPSB0aGlzLmRhdGVTZXJ2aWNlLmZvcm1hdFRpbWVSYW5nZShzdGFydCwgZW5kKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogQ29udmVydCBtaW51dGVzIHNpbmNlIG1pZG5pZ2h0IHRvIGEgRGF0ZSBvYmplY3QgKHRvZGF5KVxuICAgICAqL1xuICAgIG1pbnV0ZXNUb0RhdGUobWludXRlcykge1xuICAgICAgICBjb25zdCBkYXRlID0gbmV3IERhdGUoKTtcbiAgICAgICAgZGF0ZS5zZXRIb3VycyhNYXRoLmZsb29yKG1pbnV0ZXMgLyA2MCkgJSAyNCwgbWludXRlcyAlIDYwLCAwLCAwKTtcbiAgICAgICAgcmV0dXJuIGRhdGU7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJlbmRlciBldmVudHMgZm9yIHZpc2libGUgZGF0ZXMgaW50byBkYXkgY29sdW1uc1xuICAgICAqIEBwYXJhbSBjb250YWluZXIgLSBDYWxlbmRhciBjb250YWluZXIgZWxlbWVudFxuICAgICAqIEBwYXJhbSBmaWx0ZXIgLSBGaWx0ZXIgd2l0aCAnZGF0ZScgYW5kIG9wdGlvbmFsbHkgJ3Jlc291cmNlJyBhcnJheXNcbiAgICAgKiBAcGFyYW0gZmlsdGVyVGVtcGxhdGUgLSBUZW1wbGF0ZSBmb3IgbWF0Y2hpbmcgZXZlbnRzIHRvIGNvbHVtbnNcbiAgICAgKi9cbiAgICBhc3luYyByZW5kZXIoY29udGFpbmVyLCBmaWx0ZXIsIGZpbHRlclRlbXBsYXRlKSB7XG4gICAgICAgIC8vIFN0b3JlIGNvbnRhaW5lciByZWZlcmVuY2UgZm9yIGxhdGVyIHJlLXJlbmRlcnNcbiAgICAgICAgdGhpcy5jb250YWluZXIgPSBjb250YWluZXI7XG4gICAgICAgIGNvbnN0IHZpc2libGVEYXRlcyA9IGZpbHRlclsnZGF0ZSddIHx8IFtdO1xuICAgICAgICBpZiAodmlzaWJsZURhdGVzLmxlbmd0aCA9PT0gMClcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgLy8gR2V0IGRhdGUgcmFuZ2UgZm9yIHF1ZXJ5XG4gICAgICAgIGNvbnN0IHN0YXJ0RGF0ZSA9IG5ldyBEYXRlKHZpc2libGVEYXRlc1swXSk7XG4gICAgICAgIGNvbnN0IGVuZERhdGUgPSBuZXcgRGF0ZSh2aXNpYmxlRGF0ZXNbdmlzaWJsZURhdGVzLmxlbmd0aCAtIDFdKTtcbiAgICAgICAgZW5kRGF0ZS5zZXRIb3VycygyMywgNTksIDU5LCA5OTkpO1xuICAgICAgICAvLyBGZXRjaCBldmVudHMgZnJvbSBJbmRleGVkREJcbiAgICAgICAgY29uc3QgZXZlbnRzID0gYXdhaXQgdGhpcy5ldmVudFNlcnZpY2UuZ2V0QnlEYXRlUmFuZ2Uoc3RhcnREYXRlLCBlbmREYXRlKTtcbiAgICAgICAgLy8gRmluZCBkYXkgY29sdW1uc1xuICAgICAgICBjb25zdCBkYXlDb2x1bW5zID0gY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3IoJ3N3cC1kYXktY29sdW1ucycpO1xuICAgICAgICBpZiAoIWRheUNvbHVtbnMpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIGNvbnN0IGNvbHVtbnMgPSBkYXlDb2x1bW5zLnF1ZXJ5U2VsZWN0b3JBbGwoJ3N3cC1kYXktY29sdW1uJyk7XG4gICAgICAgIC8vIFJlbmRlciBldmVudHMgaW50byBlYWNoIGNvbHVtbiBiYXNlZCBvbiBGaWx0ZXJUZW1wbGF0ZSBtYXRjaGluZ1xuICAgICAgICBjb2x1bW5zLmZvckVhY2goY29sdW1uID0+IHtcbiAgICAgICAgICAgIGNvbnN0IGNvbHVtbkVsID0gY29sdW1uO1xuICAgICAgICAgICAgLy8gVXNlIEZpbHRlclRlbXBsYXRlIGZvciBtYXRjaGluZyAtIG9ubHkgZmllbGRzIGluIHRlbXBsYXRlIGFyZSBjaGVja2VkXG4gICAgICAgICAgICBjb25zdCBjb2x1bW5FdmVudHMgPSBldmVudHMuZmlsdGVyKGV2ZW50ID0+IGZpbHRlclRlbXBsYXRlLm1hdGNoZXMoZXZlbnQsIGNvbHVtbkVsKSk7XG4gICAgICAgICAgICAvLyBHZXQgb3IgY3JlYXRlIGV2ZW50cyBsYXllclxuICAgICAgICAgICAgbGV0IGV2ZW50c0xheWVyID0gY29sdW1uLnF1ZXJ5U2VsZWN0b3IoJ3N3cC1ldmVudHMtbGF5ZXInKTtcbiAgICAgICAgICAgIGlmICghZXZlbnRzTGF5ZXIpIHtcbiAgICAgICAgICAgICAgICBldmVudHNMYXllciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3N3cC1ldmVudHMtbGF5ZXInKTtcbiAgICAgICAgICAgICAgICBjb2x1bW4uYXBwZW5kQ2hpbGQoZXZlbnRzTGF5ZXIpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gQ2xlYXIgZXhpc3RpbmcgZXZlbnRzXG4gICAgICAgICAgICBldmVudHNMYXllci5pbm5lckhUTUwgPSAnJztcbiAgICAgICAgICAgIC8vIEZpbHRlciB0byB0aW1lZCBldmVudHMgb25seVxuICAgICAgICAgICAgY29uc3QgdGltZWRFdmVudHMgPSBjb2x1bW5FdmVudHMuZmlsdGVyKGV2ZW50ID0+ICFldmVudC5hbGxEYXkpO1xuICAgICAgICAgICAgLy8gQ2FsY3VsYXRlIGxheW91dCB3aXRoIHN0YWNraW5nL2dyb3VwaW5nXG4gICAgICAgICAgICBjb25zdCBsYXlvdXQgPSBjYWxjdWxhdGVDb2x1bW5MYXlvdXQodGltZWRFdmVudHMsIHRoaXMuZ3JpZENvbmZpZyk7XG4gICAgICAgICAgICAvLyBSZW5kZXIgR1JJRCBncm91cHMgKHNpbXVsdGFuZW91cyBldmVudHMgc2lkZS1ieS1zaWRlKVxuICAgICAgICAgICAgbGF5b3V0LmdyaWRzLmZvckVhY2goZ3JpZCA9PiB7XG4gICAgICAgICAgICAgICAgY29uc3QgZ3JvdXBFbCA9IHRoaXMucmVuZGVyR3JpZEdyb3VwKGdyaWQpO1xuICAgICAgICAgICAgICAgIGV2ZW50c0xheWVyLmFwcGVuZENoaWxkKGdyb3VwRWwpO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAvLyBSZW5kZXIgU1RBQ0tFRCBldmVudHMgKG92ZXJsYXBwaW5nIHdpdGggbWFyZ2luIG9mZnNldClcbiAgICAgICAgICAgIGxheW91dC5zdGFja2VkLmZvckVhY2goaXRlbSA9PiB7XG4gICAgICAgICAgICAgICAgY29uc3QgZXZlbnRFbCA9IHRoaXMucmVuZGVyU3RhY2tlZEV2ZW50KGl0ZW0uZXZlbnQsIGl0ZW0uc3RhY2tMZXZlbCk7XG4gICAgICAgICAgICAgICAgZXZlbnRzTGF5ZXIuYXBwZW5kQ2hpbGQoZXZlbnRFbCk7XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIENyZWF0ZSBhIHNpbmdsZSBldmVudCBlbGVtZW50XG4gICAgICpcbiAgICAgKiBDTEVBTiBhcHByb2FjaDpcbiAgICAgKiAtIE9ubHkgZGF0YS1pZCBmb3IgbG9va3VwXG4gICAgICogLSBWaXNpYmxlIGNvbnRlbnQgaW4gaW5uZXJIVE1MIG9ubHlcbiAgICAgKi9cbiAgICBjcmVhdGVFdmVudEVsZW1lbnQoZXZlbnQpIHtcbiAgICAgICAgY29uc3QgZWxlbWVudCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3N3cC1ldmVudCcpO1xuICAgICAgICAvLyBEYXRhIGF0dHJpYnV0ZXMgZm9yIFN3cEV2ZW50IGNvbXBhdGliaWxpdHlcbiAgICAgICAgZWxlbWVudC5kYXRhc2V0LmV2ZW50SWQgPSBldmVudC5pZDtcbiAgICAgICAgaWYgKGV2ZW50LnJlc291cmNlSWQpIHtcbiAgICAgICAgICAgIGVsZW1lbnQuZGF0YXNldC5yZXNvdXJjZUlkID0gZXZlbnQucmVzb3VyY2VJZDtcbiAgICAgICAgfVxuICAgICAgICAvLyBDYWxjdWxhdGUgcG9zaXRpb25cbiAgICAgICAgY29uc3QgcG9zaXRpb24gPSBjYWxjdWxhdGVFdmVudFBvc2l0aW9uKGV2ZW50LnN0YXJ0LCBldmVudC5lbmQsIHRoaXMuZ3JpZENvbmZpZyk7XG4gICAgICAgIGVsZW1lbnQuc3R5bGUudG9wID0gYCR7cG9zaXRpb24udG9wfXB4YDtcbiAgICAgICAgZWxlbWVudC5zdHlsZS5oZWlnaHQgPSBgJHtwb3NpdGlvbi5oZWlnaHR9cHhgO1xuICAgICAgICAvLyBDb2xvciBjbGFzcyBiYXNlZCBvbiBldmVudCB0eXBlXG4gICAgICAgIGNvbnN0IGNvbG9yQ2xhc3MgPSB0aGlzLmdldENvbG9yQ2xhc3MoZXZlbnQpO1xuICAgICAgICBpZiAoY29sb3JDbGFzcykge1xuICAgICAgICAgICAgZWxlbWVudC5jbGFzc0xpc3QuYWRkKGNvbG9yQ2xhc3MpO1xuICAgICAgICB9XG4gICAgICAgIC8vIFZpc2libGUgY29udGVudCBvbmx5XG4gICAgICAgIGVsZW1lbnQuaW5uZXJIVE1MID0gYFxyXG4gICAgICA8c3dwLWV2ZW50LXRpbWU+JHt0aGlzLmRhdGVTZXJ2aWNlLmZvcm1hdFRpbWVSYW5nZShldmVudC5zdGFydCwgZXZlbnQuZW5kKX08L3N3cC1ldmVudC10aW1lPlxyXG4gICAgICA8c3dwLWV2ZW50LXRpdGxlPiR7dGhpcy5lc2NhcGVIdG1sKGV2ZW50LnRpdGxlKX08L3N3cC1ldmVudC10aXRsZT5cclxuICAgICAgJHtldmVudC5kZXNjcmlwdGlvbiA/IGA8c3dwLWV2ZW50LWRlc2NyaXB0aW9uPiR7dGhpcy5lc2NhcGVIdG1sKGV2ZW50LmRlc2NyaXB0aW9uKX08L3N3cC1ldmVudC1kZXNjcmlwdGlvbj5gIDogJyd9XHJcbiAgICBgO1xuICAgICAgICByZXR1cm4gZWxlbWVudDtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IGNvbG9yIGNsYXNzIGJhc2VkIG9uIG1ldGFkYXRhLmNvbG9yIG9yIGV2ZW50IHR5cGVcbiAgICAgKi9cbiAgICBnZXRDb2xvckNsYXNzKGV2ZW50KSB7XG4gICAgICAgIC8vIENoZWNrIG1ldGFkYXRhLmNvbG9yIGZpcnN0XG4gICAgICAgIGlmIChldmVudC5tZXRhZGF0YT8uY29sb3IpIHtcbiAgICAgICAgICAgIHJldHVybiBgaXMtJHtldmVudC5tZXRhZGF0YS5jb2xvcn1gO1xuICAgICAgICB9XG4gICAgICAgIC8vIEZhbGxiYWNrIHRvIHR5cGUtYmFzZWQgY29sb3JcbiAgICAgICAgY29uc3QgdHlwZUNvbG9ycyA9IHtcbiAgICAgICAgICAgICdjdXN0b21lcic6ICdpcy1ibHVlJyxcbiAgICAgICAgICAgICd2YWNhdGlvbic6ICdpcy1ncmVlbicsXG4gICAgICAgICAgICAnYnJlYWsnOiAnaXMtYW1iZXInLFxuICAgICAgICAgICAgJ21lZXRpbmcnOiAnaXMtcHVycGxlJyxcbiAgICAgICAgICAgICdibG9ja2VkJzogJ2lzLXJlZCdcbiAgICAgICAgfTtcbiAgICAgICAgcmV0dXJuIHR5cGVDb2xvcnNbZXZlbnQudHlwZV0gfHwgJ2lzLWJsdWUnO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBFc2NhcGUgSFRNTCB0byBwcmV2ZW50IFhTU1xuICAgICAqL1xuICAgIGVzY2FwZUh0bWwodGV4dCkge1xuICAgICAgICBjb25zdCBkaXYgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdkaXYnKTtcbiAgICAgICAgZGl2LnRleHRDb250ZW50ID0gdGV4dDtcbiAgICAgICAgcmV0dXJuIGRpdi5pbm5lckhUTUw7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJlbmRlciBhIEdSSUQgZ3JvdXAgd2l0aCBzaWRlLWJ5LXNpZGUgY29sdW1uc1xuICAgICAqIFVzZWQgd2hlbiBtdWx0aXBsZSBldmVudHMgc3RhcnQgYXQgdGhlIHNhbWUgdGltZVxuICAgICAqL1xuICAgIHJlbmRlckdyaWRHcm91cChsYXlvdXQpIHtcbiAgICAgICAgY29uc3QgZ3JvdXAgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzd3AtZXZlbnQtZ3JvdXAnKTtcbiAgICAgICAgZ3JvdXAuY2xhc3NMaXN0LmFkZChgY29scy0ke2xheW91dC5jb2x1bW5zLmxlbmd0aH1gKTtcbiAgICAgICAgZ3JvdXAuc3R5bGUudG9wID0gYCR7bGF5b3V0LnBvc2l0aW9uLnRvcH1weGA7XG4gICAgICAgIC8vIFN0YWNrIGxldmVsIHN0eWxpbmcgZm9yIGVudGlyZSBncm91cCAoaWYgbmVzdGVkIGluIGFub3RoZXIgZXZlbnQpXG4gICAgICAgIGlmIChsYXlvdXQuc3RhY2tMZXZlbCA+IDApIHtcbiAgICAgICAgICAgIGdyb3VwLnN0eWxlLm1hcmdpbkxlZnQgPSBgJHtsYXlvdXQuc3RhY2tMZXZlbCAqIDE1fXB4YDtcbiAgICAgICAgICAgIGdyb3VwLnN0eWxlLnpJbmRleCA9IGAkezEwMCArIGxheW91dC5zdGFja0xldmVsfWA7XG4gICAgICAgIH1cbiAgICAgICAgLy8gQ2FsY3VsYXRlIHRoZSBoZWlnaHQgbmVlZGVkIGZvciB0aGUgZ3JvdXAgKHRhbGxlc3QgZXZlbnQpXG4gICAgICAgIGxldCBtYXhCb3R0b20gPSAwO1xuICAgICAgICBmb3IgKGNvbnN0IGV2ZW50IG9mIGxheW91dC5ldmVudHMpIHtcbiAgICAgICAgICAgIGNvbnN0IHBvcyA9IGNhbGN1bGF0ZUV2ZW50UG9zaXRpb24oZXZlbnQuc3RhcnQsIGV2ZW50LmVuZCwgdGhpcy5ncmlkQ29uZmlnKTtcbiAgICAgICAgICAgIGNvbnN0IGV2ZW50Qm90dG9tID0gcG9zLnRvcCArIHBvcy5oZWlnaHQ7XG4gICAgICAgICAgICBpZiAoZXZlbnRCb3R0b20gPiBtYXhCb3R0b20pXG4gICAgICAgICAgICAgICAgbWF4Qm90dG9tID0gZXZlbnRCb3R0b207XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgZ3JvdXBIZWlnaHQgPSBtYXhCb3R0b20gLSBsYXlvdXQucG9zaXRpb24udG9wO1xuICAgICAgICBncm91cC5zdHlsZS5oZWlnaHQgPSBgJHtncm91cEhlaWdodH1weGA7XG4gICAgICAgIC8vIENyZWF0ZSB3cmFwcGVyIGRpdiBmb3IgZWFjaCBjb2x1bW5cbiAgICAgICAgbGF5b3V0LmNvbHVtbnMuZm9yRWFjaChjb2x1bW5FdmVudHMgPT4ge1xuICAgICAgICAgICAgY29uc3Qgd3JhcHBlciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2RpdicpO1xuICAgICAgICAgICAgd3JhcHBlci5zdHlsZS5wb3NpdGlvbiA9ICdyZWxhdGl2ZSc7XG4gICAgICAgICAgICBjb2x1bW5FdmVudHMuZm9yRWFjaChldmVudCA9PiB7XG4gICAgICAgICAgICAgICAgY29uc3QgZXZlbnRFbCA9IHRoaXMuY3JlYXRlRXZlbnRFbGVtZW50KGV2ZW50KTtcbiAgICAgICAgICAgICAgICAvLyBQb3NpdGlvbiByZWxhdGl2ZSB0byBncm91cCB0b3BcbiAgICAgICAgICAgICAgICBjb25zdCBwb3MgPSBjYWxjdWxhdGVFdmVudFBvc2l0aW9uKGV2ZW50LnN0YXJ0LCBldmVudC5lbmQsIHRoaXMuZ3JpZENvbmZpZyk7XG4gICAgICAgICAgICAgICAgZXZlbnRFbC5zdHlsZS50b3AgPSBgJHtwb3MudG9wIC0gbGF5b3V0LnBvc2l0aW9uLnRvcH1weGA7XG4gICAgICAgICAgICAgICAgZXZlbnRFbC5zdHlsZS5wb3NpdGlvbiA9ICdhYnNvbHV0ZSc7XG4gICAgICAgICAgICAgICAgZXZlbnRFbC5zdHlsZS5sZWZ0ID0gJzAnO1xuICAgICAgICAgICAgICAgIGV2ZW50RWwuc3R5bGUucmlnaHQgPSAnMCc7XG4gICAgICAgICAgICAgICAgd3JhcHBlci5hcHBlbmRDaGlsZChldmVudEVsKTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgZ3JvdXAuYXBwZW5kQ2hpbGQod3JhcHBlcik7XG4gICAgICAgIH0pO1xuICAgICAgICByZXR1cm4gZ3JvdXA7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJlbmRlciBhIFNUQUNLRUQgZXZlbnQgd2l0aCBtYXJnaW4tbGVmdCBvZmZzZXRcbiAgICAgKiBVc2VkIGZvciBvdmVybGFwcGluZyBldmVudHMgdGhhdCBkb24ndCBzdGFydCBhdCB0aGUgc2FtZSB0aW1lXG4gICAgICovXG4gICAgcmVuZGVyU3RhY2tlZEV2ZW50KGV2ZW50LCBzdGFja0xldmVsKSB7XG4gICAgICAgIGNvbnN0IGVsZW1lbnQgPSB0aGlzLmNyZWF0ZUV2ZW50RWxlbWVudChldmVudCk7XG4gICAgICAgIC8vIEFkZCBzdGFjayBtZXRhZGF0YSBmb3IgZHJhZy1kcm9wIGFuZCBvdGhlciBmZWF0dXJlc1xuICAgICAgICBlbGVtZW50LmRhdGFzZXQuc3RhY2tMaW5rID0gSlNPTi5zdHJpbmdpZnkoeyBzdGFja0xldmVsIH0pO1xuICAgICAgICAvLyBWaXN1YWwgc3R5bGluZyBiYXNlZCBvbiBzdGFjayBsZXZlbFxuICAgICAgICBpZiAoc3RhY2tMZXZlbCA+IDApIHtcbiAgICAgICAgICAgIGVsZW1lbnQuc3R5bGUubWFyZ2luTGVmdCA9IGAke3N0YWNrTGV2ZWwgKiAxNX1weGA7XG4gICAgICAgICAgICBlbGVtZW50LnN0eWxlLnpJbmRleCA9IGAkezEwMCArIHN0YWNrTGV2ZWx9YDtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gZWxlbWVudDtcbiAgICB9XG59XG4iLCAiLyoqXG4gKiBTY2hlZHVsZVJlbmRlcmVyIC0gUmVuZGVycyB1bmF2YWlsYWJsZSB0aW1lIHpvbmVzIGluIGRheSBjb2x1bW5zXG4gKlxuICogQ3JlYXRlcyB2aXN1YWwgaW5kaWNhdG9ycyBmb3IgdGltZXMgb3V0c2lkZSB0aGUgcmVzb3VyY2UncyB3b3JraW5nIGhvdXJzOlxuICogLSBCZWZvcmUgd29yayBzdGFydCAoZS5nLiwgMDY6MDAgLSAwOTowMClcbiAqIC0gQWZ0ZXIgd29yayBlbmQgKGUuZy4sIDE3OjAwIC0gMTg6MDApXG4gKiAtIEZ1bGwgZGF5IGlmIHJlc291cmNlIGlzIG9mZiAoc2NoZWR1bGUgPSBudWxsKVxuICovXG5leHBvcnQgY2xhc3MgU2NoZWR1bGVSZW5kZXJlciB7XG4gICAgY29uc3RydWN0b3Ioc2NoZWR1bGVTZXJ2aWNlLCBkYXRlU2VydmljZSwgZ3JpZENvbmZpZykge1xuICAgICAgICB0aGlzLnNjaGVkdWxlU2VydmljZSA9IHNjaGVkdWxlU2VydmljZTtcbiAgICAgICAgdGhpcy5kYXRlU2VydmljZSA9IGRhdGVTZXJ2aWNlO1xuICAgICAgICB0aGlzLmdyaWRDb25maWcgPSBncmlkQ29uZmlnO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZW5kZXIgdW5hdmFpbGFibGUgem9uZXMgZm9yIHZpc2libGUgY29sdW1uc1xuICAgICAqIEBwYXJhbSBjb250YWluZXIgLSBDYWxlbmRhciBjb250YWluZXIgZWxlbWVudFxuICAgICAqIEBwYXJhbSBmaWx0ZXIgLSBGaWx0ZXIgd2l0aCAnZGF0ZScgYW5kICdyZXNvdXJjZScgYXJyYXlzXG4gICAgICovXG4gICAgYXN5bmMgcmVuZGVyKGNvbnRhaW5lciwgZmlsdGVyKSB7XG4gICAgICAgIGNvbnN0IGRhdGVzID0gZmlsdGVyWydkYXRlJ10gfHwgW107XG4gICAgICAgIGNvbnN0IHJlc291cmNlSWRzID0gZmlsdGVyWydyZXNvdXJjZSddIHx8IFtdO1xuICAgICAgICBpZiAoZGF0ZXMubGVuZ3RoID09PSAwKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAvLyBGaW5kIGRheSBjb2x1bW5zXG4gICAgICAgIGNvbnN0IGRheUNvbHVtbnMgPSBjb250YWluZXIucXVlcnlTZWxlY3Rvcignc3dwLWRheS1jb2x1bW5zJyk7XG4gICAgICAgIGlmICghZGF5Q29sdW1ucylcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgY29uc3QgY29sdW1ucyA9IGRheUNvbHVtbnMucXVlcnlTZWxlY3RvckFsbCgnc3dwLWRheS1jb2x1bW4nKTtcbiAgICAgICAgZm9yIChjb25zdCBjb2x1bW4gb2YgY29sdW1ucykge1xuICAgICAgICAgICAgY29uc3QgZGF0ZSA9IGNvbHVtbi5kYXRhc2V0LmRhdGU7XG4gICAgICAgICAgICBjb25zdCByZXNvdXJjZUlkID0gY29sdW1uLmRhdGFzZXQucmVzb3VyY2VJZDtcbiAgICAgICAgICAgIGlmICghZGF0ZSB8fCAhcmVzb3VyY2VJZClcbiAgICAgICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICAgIC8vIEdldCBvciBjcmVhdGUgdW5hdmFpbGFibGUgbGF5ZXJcbiAgICAgICAgICAgIGxldCB1bmF2YWlsYWJsZUxheWVyID0gY29sdW1uLnF1ZXJ5U2VsZWN0b3IoJ3N3cC11bmF2YWlsYWJsZS1sYXllcicpO1xuICAgICAgICAgICAgaWYgKCF1bmF2YWlsYWJsZUxheWVyKSB7XG4gICAgICAgICAgICAgICAgdW5hdmFpbGFibGVMYXllciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3N3cC11bmF2YWlsYWJsZS1sYXllcicpO1xuICAgICAgICAgICAgICAgIGNvbHVtbi5pbnNlcnRCZWZvcmUodW5hdmFpbGFibGVMYXllciwgY29sdW1uLmZpcnN0Q2hpbGQpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gQ2xlYXIgZXhpc3RpbmdcbiAgICAgICAgICAgIHVuYXZhaWxhYmxlTGF5ZXIuaW5uZXJIVE1MID0gJyc7XG4gICAgICAgICAgICAvLyBHZXQgc2NoZWR1bGUgZm9yIHRoaXMgcmVzb3VyY2UvZGF0ZVxuICAgICAgICAgICAgY29uc3Qgc2NoZWR1bGUgPSBhd2FpdCB0aGlzLnNjaGVkdWxlU2VydmljZS5nZXRTY2hlZHVsZUZvckRhdGUocmVzb3VyY2VJZCwgZGF0ZSk7XG4gICAgICAgICAgICAvLyBSZW5kZXIgdW5hdmFpbGFibGUgem9uZXNcbiAgICAgICAgICAgIHRoaXMucmVuZGVyVW5hdmFpbGFibGVab25lcyh1bmF2YWlsYWJsZUxheWVyLCBzY2hlZHVsZSk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgLyoqXG4gICAgICogUmVuZGVyIHVuYXZhaWxhYmxlIHRpbWUgem9uZXMgYmFzZWQgb24gc2NoZWR1bGVcbiAgICAgKi9cbiAgICByZW5kZXJVbmF2YWlsYWJsZVpvbmVzKGxheWVyLCBzY2hlZHVsZSkge1xuICAgICAgICBjb25zdCBkYXlTdGFydE1pbnV0ZXMgPSB0aGlzLmdyaWRDb25maWcuZGF5U3RhcnRIb3VyICogNjA7XG4gICAgICAgIGNvbnN0IGRheUVuZE1pbnV0ZXMgPSB0aGlzLmdyaWRDb25maWcuZGF5RW5kSG91ciAqIDYwO1xuICAgICAgICBjb25zdCBtaW51dGVIZWlnaHQgPSB0aGlzLmdyaWRDb25maWcuaG91ckhlaWdodCAvIDYwO1xuICAgICAgICBpZiAoc2NoZWR1bGUgPT09IG51bGwpIHtcbiAgICAgICAgICAgIC8vIEZ1bGwgZGF5IHVuYXZhaWxhYmxlXG4gICAgICAgICAgICBjb25zdCB6b25lID0gdGhpcy5jcmVhdGVVbmF2YWlsYWJsZVpvbmUoMCwgKGRheUVuZE1pbnV0ZXMgLSBkYXlTdGFydE1pbnV0ZXMpICogbWludXRlSGVpZ2h0KTtcbiAgICAgICAgICAgIGxheWVyLmFwcGVuZENoaWxkKHpvbmUpO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IHdvcmtTdGFydE1pbnV0ZXMgPSB0aGlzLmRhdGVTZXJ2aWNlLnRpbWVUb01pbnV0ZXMoc2NoZWR1bGUuc3RhcnQpO1xuICAgICAgICBjb25zdCB3b3JrRW5kTWludXRlcyA9IHRoaXMuZGF0ZVNlcnZpY2UudGltZVRvTWludXRlcyhzY2hlZHVsZS5lbmQpO1xuICAgICAgICAvLyBCZWZvcmUgd29yayBzdGFydFxuICAgICAgICBpZiAod29ya1N0YXJ0TWludXRlcyA+IGRheVN0YXJ0TWludXRlcykge1xuICAgICAgICAgICAgY29uc3QgdG9wID0gMDtcbiAgICAgICAgICAgIGNvbnN0IGhlaWdodCA9ICh3b3JrU3RhcnRNaW51dGVzIC0gZGF5U3RhcnRNaW51dGVzKSAqIG1pbnV0ZUhlaWdodDtcbiAgICAgICAgICAgIGNvbnN0IHpvbmUgPSB0aGlzLmNyZWF0ZVVuYXZhaWxhYmxlWm9uZSh0b3AsIGhlaWdodCk7XG4gICAgICAgICAgICBsYXllci5hcHBlbmRDaGlsZCh6b25lKTtcbiAgICAgICAgfVxuICAgICAgICAvLyBBZnRlciB3b3JrIGVuZFxuICAgICAgICBpZiAod29ya0VuZE1pbnV0ZXMgPCBkYXlFbmRNaW51dGVzKSB7XG4gICAgICAgICAgICBjb25zdCB0b3AgPSAod29ya0VuZE1pbnV0ZXMgLSBkYXlTdGFydE1pbnV0ZXMpICogbWludXRlSGVpZ2h0O1xuICAgICAgICAgICAgY29uc3QgaGVpZ2h0ID0gKGRheUVuZE1pbnV0ZXMgLSB3b3JrRW5kTWludXRlcykgKiBtaW51dGVIZWlnaHQ7XG4gICAgICAgICAgICBjb25zdCB6b25lID0gdGhpcy5jcmVhdGVVbmF2YWlsYWJsZVpvbmUodG9wLCBoZWlnaHQpO1xuICAgICAgICAgICAgbGF5ZXIuYXBwZW5kQ2hpbGQoem9uZSk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgLyoqXG4gICAgICogQ3JlYXRlIGFuIHVuYXZhaWxhYmxlIHpvbmUgZWxlbWVudFxuICAgICAqL1xuICAgIGNyZWF0ZVVuYXZhaWxhYmxlWm9uZSh0b3AsIGhlaWdodCkge1xuICAgICAgICBjb25zdCB6b25lID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3dwLXVuYXZhaWxhYmxlLXpvbmUnKTtcbiAgICAgICAgem9uZS5zdHlsZS50b3AgPSBgJHt0b3B9cHhgO1xuICAgICAgICB6b25lLnN0eWxlLmhlaWdodCA9IGAke2hlaWdodH1weGA7XG4gICAgICAgIHJldHVybiB6b25lO1xuICAgIH1cbn1cbiIsICJpbXBvcnQgeyBDb3JlRXZlbnRzIH0gZnJvbSAnLi4vLi4vY29uc3RhbnRzL0NvcmVFdmVudHMnO1xuLyoqXG4gKiBIZWFkZXJEcmF3ZXJSZW5kZXJlciAtIEhhbmRsZXMgcmVuZGVyaW5nIG9mIGl0ZW1zIGluIHRoZSBoZWFkZXIgZHJhd2VyXG4gKlxuICogTGlzdGVucyB0byBkcmFnIGV2ZW50cyBmcm9tIERyYWdEcm9wTWFuYWdlciBhbmQgY3JlYXRlcy9tYW5hZ2VzXG4gKiBzd3AtaGVhZGVyLWl0ZW0gZWxlbWVudHMgaW4gdGhlIGhlYWRlciBkcmF3ZXIuXG4gKlxuICogVXNlcyBzdWJncmlkIGZvciBjb2x1bW4gYWxpZ25tZW50IHdpdGggcGFyZW50IHN3cC1jYWxlbmRhci1oZWFkZXIuXG4gKiBQb3NpdGlvbiBpdGVtcyB2aWEgZ3JpZEFyZWEgZm9yIGV4cGxpY2l0IHJvdy9jb2x1bW4gcGxhY2VtZW50LlxuICovXG5leHBvcnQgY2xhc3MgSGVhZGVyRHJhd2VyUmVuZGVyZXIge1xuICAgIGNvbnN0cnVjdG9yKGV2ZW50QnVzLCBncmlkQ29uZmlnLCBoZWFkZXJEcmF3ZXJNYW5hZ2VyLCBldmVudFNlcnZpY2UsIGRhdGVTZXJ2aWNlKSB7XG4gICAgICAgIHRoaXMuZXZlbnRCdXMgPSBldmVudEJ1cztcbiAgICAgICAgdGhpcy5ncmlkQ29uZmlnID0gZ3JpZENvbmZpZztcbiAgICAgICAgdGhpcy5oZWFkZXJEcmF3ZXJNYW5hZ2VyID0gaGVhZGVyRHJhd2VyTWFuYWdlcjtcbiAgICAgICAgdGhpcy5ldmVudFNlcnZpY2UgPSBldmVudFNlcnZpY2U7XG4gICAgICAgIHRoaXMuZGF0ZVNlcnZpY2UgPSBkYXRlU2VydmljZTtcbiAgICAgICAgdGhpcy5jdXJyZW50SXRlbSA9IG51bGw7XG4gICAgICAgIHRoaXMuY29udGFpbmVyID0gbnVsbDtcbiAgICAgICAgdGhpcy5zb3VyY2VFbGVtZW50ID0gbnVsbDtcbiAgICAgICAgdGhpcy53YXNFeHBhbmRlZEJlZm9yZURyYWcgPSBmYWxzZTtcbiAgICAgICAgdGhpcy5maWx0ZXJUZW1wbGF0ZSA9IG51bGw7XG4gICAgICAgIHRoaXMuc2V0dXBMaXN0ZW5lcnMoKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogUmVuZGVyIGFsbERheSBldmVudHMgaW50byB0aGUgaGVhZGVyIGRyYXdlciB3aXRoIHJvdyBzdGFja2luZ1xuICAgICAqIEBwYXJhbSBmaWx0ZXJUZW1wbGF0ZSAtIFRlbXBsYXRlIGZvciBtYXRjaGluZyBldmVudHMgdG8gY29sdW1uc1xuICAgICAqL1xuICAgIGFzeW5jIHJlbmRlcihjb250YWluZXIsIGZpbHRlciwgZmlsdGVyVGVtcGxhdGUpIHtcbiAgICAgICAgLy8gU3RvcmUgZmlsdGVyVGVtcGxhdGUgZm9yIGJ1aWxkQ29sdW1uS2V5RnJvbUV2ZW50XG4gICAgICAgIHRoaXMuZmlsdGVyVGVtcGxhdGUgPSBmaWx0ZXJUZW1wbGF0ZTtcbiAgICAgICAgY29uc3QgZHJhd2VyID0gY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3IoJ3N3cC1oZWFkZXItZHJhd2VyJyk7XG4gICAgICAgIGlmICghZHJhd2VyKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICBjb25zdCB2aXNpYmxlRGF0ZXMgPSBmaWx0ZXJbJ2RhdGUnXSB8fCBbXTtcbiAgICAgICAgaWYgKHZpc2libGVEYXRlcy5sZW5ndGggPT09IDApXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIC8vIEdldCBjb2x1bW4ga2V5cyBmcm9tIERPTSBmb3IgY29ycmVjdCBtdWx0aS1yZXNvdXJjZSBwb3NpdGlvbmluZ1xuICAgICAgICBjb25zdCB2aXNpYmxlQ29sdW1uS2V5cyA9IHRoaXMuZ2V0VmlzaWJsZUNvbHVtbktleXNGcm9tRE9NKCk7XG4gICAgICAgIGlmICh2aXNpYmxlQ29sdW1uS2V5cy5sZW5ndGggPT09IDApXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIC8vIEZldGNoIGV2ZW50cyBmb3IgZGF0ZSByYW5nZVxuICAgICAgICBjb25zdCBzdGFydERhdGUgPSBuZXcgRGF0ZSh2aXNpYmxlRGF0ZXNbMF0pO1xuICAgICAgICBjb25zdCBlbmREYXRlID0gbmV3IERhdGUodmlzaWJsZURhdGVzW3Zpc2libGVEYXRlcy5sZW5ndGggLSAxXSk7XG4gICAgICAgIGVuZERhdGUuc2V0SG91cnMoMjMsIDU5LCA1OSwgOTk5KTtcbiAgICAgICAgY29uc3QgZXZlbnRzID0gYXdhaXQgdGhpcy5ldmVudFNlcnZpY2UuZ2V0QnlEYXRlUmFuZ2Uoc3RhcnREYXRlLCBlbmREYXRlKTtcbiAgICAgICAgLy8gRmlsdGVyIHRvIGFsbERheSBldmVudHMgb25seSAoYWxsRGF5ICE9PSBmYWxzZSlcbiAgICAgICAgY29uc3QgYWxsRGF5RXZlbnRzID0gZXZlbnRzLmZpbHRlcihldmVudCA9PiBldmVudC5hbGxEYXkgIT09IGZhbHNlKTtcbiAgICAgICAgLy8gQ2xlYXIgZXhpc3RpbmcgaXRlbXNcbiAgICAgICAgZHJhd2VyLmlubmVySFRNTCA9ICcnO1xuICAgICAgICBpZiAoYWxsRGF5RXZlbnRzLmxlbmd0aCA9PT0gMClcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgLy8gQ2FsY3VsYXRlIGxheW91dCB3aXRoIHJvdyBzdGFja2luZyB1c2luZyBjb2x1bW5LZXlzXG4gICAgICAgIGNvbnN0IGxheW91dHMgPSB0aGlzLmNhbGN1bGF0ZUxheW91dChhbGxEYXlFdmVudHMsIHZpc2libGVDb2x1bW5LZXlzKTtcbiAgICAgICAgY29uc3Qgcm93Q291bnQgPSBNYXRoLm1heCgxLCAuLi5sYXlvdXRzLm1hcChsID0+IGwucm93KSk7XG4gICAgICAgIC8vIFJlbmRlciBlYWNoIGl0ZW0gd2l0aCBsYXlvdXRcbiAgICAgICAgbGF5b3V0cy5mb3JFYWNoKGxheW91dCA9PiB7XG4gICAgICAgICAgICBjb25zdCBpdGVtID0gdGhpcy5jcmVhdGVIZWFkZXJJdGVtKGxheW91dCk7XG4gICAgICAgICAgICBkcmF3ZXIuYXBwZW5kQ2hpbGQoaXRlbSk7XG4gICAgICAgIH0pO1xuICAgICAgICAvLyBFeHBhbmQgZHJhd2VyIHRvIGZpdCBhbGwgcm93c1xuICAgICAgICB0aGlzLmhlYWRlckRyYXdlck1hbmFnZXIuZXhwYW5kVG9Sb3dzKHJvd0NvdW50KTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogQ3JlYXRlIGEgaGVhZGVyIGl0ZW0gZWxlbWVudCBmcm9tIGxheW91dFxuICAgICAqL1xuICAgIGNyZWF0ZUhlYWRlckl0ZW0obGF5b3V0KSB7XG4gICAgICAgIGNvbnN0IHsgZXZlbnQsIGNvbHVtbktleSwgcm93LCBjb2xTdGFydCwgY29sRW5kIH0gPSBsYXlvdXQ7XG4gICAgICAgIGNvbnN0IGl0ZW0gPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzd3AtaGVhZGVyLWl0ZW0nKTtcbiAgICAgICAgaXRlbS5kYXRhc2V0LmV2ZW50SWQgPSBldmVudC5pZDtcbiAgICAgICAgaXRlbS5kYXRhc2V0Lml0ZW1UeXBlID0gJ2V2ZW50JztcbiAgICAgICAgaXRlbS5kYXRhc2V0LnN0YXJ0ID0gZXZlbnQuc3RhcnQudG9JU09TdHJpbmcoKTtcbiAgICAgICAgaXRlbS5kYXRhc2V0LmVuZCA9IGV2ZW50LmVuZC50b0lTT1N0cmluZygpO1xuICAgICAgICBpdGVtLmRhdGFzZXQuY29sdW1uS2V5ID0gY29sdW1uS2V5O1xuICAgICAgICBpdGVtLnRleHRDb250ZW50ID0gZXZlbnQudGl0bGU7XG4gICAgICAgIC8vIENvbG9yIGNsYXNzXG4gICAgICAgIGNvbnN0IGNvbG9yQ2xhc3MgPSB0aGlzLmdldENvbG9yQ2xhc3MoZXZlbnQpO1xuICAgICAgICBpZiAoY29sb3JDbGFzcylcbiAgICAgICAgICAgIGl0ZW0uY2xhc3NMaXN0LmFkZChjb2xvckNsYXNzKTtcbiAgICAgICAgLy8gR3JpZCBwb3NpdGlvbiBmcm9tIGxheW91dFxuICAgICAgICBpdGVtLnN0eWxlLmdyaWRBcmVhID0gYCR7cm93fSAvICR7Y29sU3RhcnR9IC8gJHtyb3cgKyAxfSAvICR7Y29sRW5kfWA7XG4gICAgICAgIHJldHVybiBpdGVtO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBDYWxjdWxhdGUgbGF5b3V0IGZvciBhbGwgZXZlbnRzIHdpdGggcm93IHN0YWNraW5nXG4gICAgICogVXNlcyB0cmFjay1iYXNlZCBhbGdvcml0aG0gdG8gZmluZCBhdmFpbGFibGUgcm93cyBmb3Igb3ZlcmxhcHBpbmcgZXZlbnRzXG4gICAgICovXG4gICAgY2FsY3VsYXRlTGF5b3V0KGV2ZW50cywgdmlzaWJsZUNvbHVtbktleXMpIHtcbiAgICAgICAgLy8gdHJhY2tzW3Jvd11bY29sXSA9IG9jY3VwaWVkXG4gICAgICAgIGNvbnN0IHRyYWNrcyA9IFtuZXcgQXJyYXkodmlzaWJsZUNvbHVtbktleXMubGVuZ3RoKS5maWxsKGZhbHNlKV07XG4gICAgICAgIGNvbnN0IGxheW91dHMgPSBbXTtcbiAgICAgICAgZm9yIChjb25zdCBldmVudCBvZiBldmVudHMpIHtcbiAgICAgICAgICAgIC8vIEJ1aWxkIGNvbHVtbktleSBmcm9tIGV2ZW50IGZpZWxkcyAob25seSBwbGFjZSB3ZSBuZWVkIHRvIGNvbnN0cnVjdCBpdClcbiAgICAgICAgICAgIGNvbnN0IGNvbHVtbktleSA9IHRoaXMuYnVpbGRDb2x1bW5LZXlGcm9tRXZlbnQoZXZlbnQpO1xuICAgICAgICAgICAgY29uc3Qgc3RhcnRDb2wgPSB2aXNpYmxlQ29sdW1uS2V5cy5pbmRleE9mKGNvbHVtbktleSk7XG4gICAgICAgICAgICBjb25zdCBlbmRDb2x1bW5LZXkgPSB0aGlzLmJ1aWxkQ29sdW1uS2V5RnJvbUV2ZW50KGV2ZW50LCBldmVudC5lbmQpO1xuICAgICAgICAgICAgY29uc3QgZW5kQ29sID0gdmlzaWJsZUNvbHVtbktleXMuaW5kZXhPZihlbmRDb2x1bW5LZXkpO1xuICAgICAgICAgICAgaWYgKHN0YXJ0Q29sID09PSAtMSAmJiBlbmRDb2wgPT09IC0xKVxuICAgICAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgICAgLy8gQ2xhbXAgdGlsIHN5bmxpZ2Uga29sb25uZXJcbiAgICAgICAgICAgIGNvbnN0IGNvbFN0YXJ0ID0gTWF0aC5tYXgoMCwgc3RhcnRDb2wpO1xuICAgICAgICAgICAgY29uc3QgY29sRW5kID0gKGVuZENvbCAhPT0gLTEgPyBlbmRDb2wgOiB2aXNpYmxlQ29sdW1uS2V5cy5sZW5ndGggLSAxKSArIDE7XG4gICAgICAgICAgICAvLyBGaW5kIGxlZGlnIHJcdTAwRTZra2VcbiAgICAgICAgICAgIGNvbnN0IHJvdyA9IHRoaXMuZmluZEF2YWlsYWJsZVJvdyh0cmFja3MsIGNvbFN0YXJ0LCBjb2xFbmQpO1xuICAgICAgICAgICAgLy8gTWFya2VyIHNvbSBvcHRhZ2V0XG4gICAgICAgICAgICBmb3IgKGxldCBjID0gY29sU3RhcnQ7IGMgPCBjb2xFbmQ7IGMrKykge1xuICAgICAgICAgICAgICAgIHRyYWNrc1tyb3ddW2NdID0gdHJ1ZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGxheW91dHMucHVzaCh7IGV2ZW50LCBjb2x1bW5LZXksIHJvdzogcm93ICsgMSwgY29sU3RhcnQ6IGNvbFN0YXJ0ICsgMSwgY29sRW5kOiBjb2xFbmQgKyAxIH0pO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBsYXlvdXRzO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBCdWlsZCBjb2x1bW5LZXkgZnJvbSBldmVudCB1c2luZyBGaWx0ZXJUZW1wbGF0ZVxuICAgICAqIFVzZXMgdGhlIHNhbWUgdGVtcGxhdGUgdGhhdCBjb2x1bW5zIHVzZSBmb3IgbWF0Y2hpbmdcbiAgICAgKi9cbiAgICBidWlsZENvbHVtbktleUZyb21FdmVudChldmVudCwgZGF0ZSkge1xuICAgICAgICBpZiAoIXRoaXMuZmlsdGVyVGVtcGxhdGUpIHtcbiAgICAgICAgICAgIC8vIEZhbGxiYWNrIGlmIG5vIHRlbXBsYXRlIC0gc2hvdWxkbid0IGhhcHBlbiBpbiBub3JtYWwgZmxvd1xuICAgICAgICAgICAgY29uc3QgZGF0ZVN0ciA9IHRoaXMuZGF0ZVNlcnZpY2UuZ2V0RGF0ZUtleShkYXRlIHx8IGV2ZW50LnN0YXJ0KTtcbiAgICAgICAgICAgIHJldHVybiBkYXRlU3RyO1xuICAgICAgICB9XG4gICAgICAgIC8vIEZvciBtdWx0aS1kYXkgZXZlbnRzLCB3ZSBuZWVkIHRvIG92ZXJyaWRlIHRoZSBkYXRlIGluIHRoZSBldmVudFxuICAgICAgICBpZiAoZGF0ZSAmJiBkYXRlLmdldFRpbWUoKSAhPT0gZXZlbnQuc3RhcnQuZ2V0VGltZSgpKSB7XG4gICAgICAgICAgICAvLyBDcmVhdGUgdGVtcG9yYXJ5IGV2ZW50IHdpdGggb3ZlcnJpZGRlbiBzdGFydCBmb3Iga2V5IGdlbmVyYXRpb25cbiAgICAgICAgICAgIGNvbnN0IHRlbXBFdmVudCA9IHsgLi4uZXZlbnQsIHN0YXJ0OiBkYXRlIH07XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5maWx0ZXJUZW1wbGF0ZS5idWlsZEtleUZyb21FdmVudCh0ZW1wRXZlbnQpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzLmZpbHRlclRlbXBsYXRlLmJ1aWxkS2V5RnJvbUV2ZW50KGV2ZW50KTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogRmluZCBhdmFpbGFibGUgcm93IGZvciBldmVudCBzcGFubmluZyBjb2x1bW5zIFtjb2xTdGFydCwgY29sRW5kKVxuICAgICAqL1xuICAgIGZpbmRBdmFpbGFibGVSb3codHJhY2tzLCBjb2xTdGFydCwgY29sRW5kKSB7XG4gICAgICAgIGZvciAobGV0IHJvdyA9IDA7IHJvdyA8IHRyYWNrcy5sZW5ndGg7IHJvdysrKSB7XG4gICAgICAgICAgICBsZXQgYXZhaWxhYmxlID0gdHJ1ZTtcbiAgICAgICAgICAgIGZvciAobGV0IGMgPSBjb2xTdGFydDsgYyA8IGNvbEVuZDsgYysrKSB7XG4gICAgICAgICAgICAgICAgaWYgKHRyYWNrc1tyb3ddW2NdKSB7XG4gICAgICAgICAgICAgICAgICAgIGF2YWlsYWJsZSA9IGZhbHNlO1xuICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBpZiAoYXZhaWxhYmxlKVxuICAgICAgICAgICAgICAgIHJldHVybiByb3c7XG4gICAgICAgIH1cbiAgICAgICAgLy8gTnkgclx1MDBFNmtrZVxuICAgICAgICB0cmFja3MucHVzaChuZXcgQXJyYXkodHJhY2tzWzBdLmxlbmd0aCkuZmlsbChmYWxzZSkpO1xuICAgICAgICByZXR1cm4gdHJhY2tzLmxlbmd0aCAtIDE7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCBjb2xvciBjbGFzcyBiYXNlZCBvbiBldmVudCBtZXRhZGF0YSBvciB0eXBlXG4gICAgICovXG4gICAgZ2V0Q29sb3JDbGFzcyhldmVudCkge1xuICAgICAgICBpZiAoZXZlbnQubWV0YWRhdGE/LmNvbG9yKSB7XG4gICAgICAgICAgICByZXR1cm4gYGlzLSR7ZXZlbnQubWV0YWRhdGEuY29sb3J9YDtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCB0eXBlQ29sb3JzID0ge1xuICAgICAgICAgICAgJ2N1c3RvbWVyJzogJ2lzLWJsdWUnLFxuICAgICAgICAgICAgJ3ZhY2F0aW9uJzogJ2lzLWdyZWVuJyxcbiAgICAgICAgICAgICdicmVhayc6ICdpcy1hbWJlcicsXG4gICAgICAgICAgICAnbWVldGluZyc6ICdpcy1wdXJwbGUnLFxuICAgICAgICAgICAgJ2Jsb2NrZWQnOiAnaXMtcmVkJ1xuICAgICAgICB9O1xuICAgICAgICByZXR1cm4gdHlwZUNvbG9yc1tldmVudC50eXBlXSB8fCAnaXMtYmx1ZSc7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFNldHVwIGV2ZW50IGxpc3RlbmVycyBmb3IgZHJhZyBldmVudHNcbiAgICAgKi9cbiAgICBzZXR1cExpc3RlbmVycygpIHtcbiAgICAgICAgdGhpcy5ldmVudEJ1cy5vbihDb3JlRXZlbnRzLkVWRU5UX0RSQUdfRU5URVJfSEVBREVSLCAoZSkgPT4ge1xuICAgICAgICAgICAgY29uc3QgcGF5bG9hZCA9IGUuZGV0YWlsO1xuICAgICAgICAgICAgdGhpcy5oYW5kbGVEcmFnRW50ZXIocGF5bG9hZCk7XG4gICAgICAgIH0pO1xuICAgICAgICB0aGlzLmV2ZW50QnVzLm9uKENvcmVFdmVudHMuRVZFTlRfRFJBR19NT1ZFX0hFQURFUiwgKGUpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHBheWxvYWQgPSBlLmRldGFpbDtcbiAgICAgICAgICAgIHRoaXMuaGFuZGxlRHJhZ01vdmUocGF5bG9hZCk7XG4gICAgICAgIH0pO1xuICAgICAgICB0aGlzLmV2ZW50QnVzLm9uKENvcmVFdmVudHMuRVZFTlRfRFJBR19MRUFWRV9IRUFERVIsIChlKSA9PiB7XG4gICAgICAgICAgICBjb25zdCBwYXlsb2FkID0gZS5kZXRhaWw7XG4gICAgICAgICAgICB0aGlzLmhhbmRsZURyYWdMZWF2ZShwYXlsb2FkKTtcbiAgICAgICAgfSk7XG4gICAgICAgIHRoaXMuZXZlbnRCdXMub24oQ29yZUV2ZW50cy5FVkVOVF9EUkFHX0VORCwgKGUpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHBheWxvYWQgPSBlLmRldGFpbDtcbiAgICAgICAgICAgIHRoaXMuaGFuZGxlRHJhZ0VuZChwYXlsb2FkKTtcbiAgICAgICAgfSk7XG4gICAgICAgIHRoaXMuZXZlbnRCdXMub24oQ29yZUV2ZW50cy5FVkVOVF9EUkFHX0NBTkNFTCwgKCkgPT4ge1xuICAgICAgICAgICAgdGhpcy5jbGVhbnVwKCk7XG4gICAgICAgIH0pO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBIYW5kbGUgZHJhZyBlbnRlcmluZyBoZWFkZXIgem9uZSAtIGNyZWF0ZSBwcmV2aWV3IGl0ZW1cbiAgICAgKi9cbiAgICBoYW5kbGVEcmFnRW50ZXIocGF5bG9hZCkge1xuICAgICAgICB0aGlzLmNvbnRhaW5lciA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJ3N3cC1oZWFkZXItZHJhd2VyJyk7XG4gICAgICAgIGlmICghdGhpcy5jb250YWluZXIpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIC8vIFJlbWVtYmVyIGlmIGRyYXdlciB3YXMgYWxyZWFkeSBleHBhbmRlZFxuICAgICAgICB0aGlzLndhc0V4cGFuZGVkQmVmb3JlRHJhZyA9IHRoaXMuaGVhZGVyRHJhd2VyTWFuYWdlci5pc0V4cGFuZGVkKCk7XG4gICAgICAgIC8vIEV4cGFuZCB0byBhdCBsZWFzdCAxIHJvdyBpZiBjb2xsYXBzZWQsIG90aGVyd2lzZSBrZWVwIGN1cnJlbnQgaGVpZ2h0XG4gICAgICAgIGlmICghdGhpcy53YXNFeHBhbmRlZEJlZm9yZURyYWcpIHtcbiAgICAgICAgICAgIHRoaXMuaGVhZGVyRHJhd2VyTWFuYWdlci5leHBhbmRUb1Jvd3MoMSk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gU3RvcmUgcmVmZXJlbmNlIHRvIHNvdXJjZSBlbGVtZW50XG4gICAgICAgIHRoaXMuc291cmNlRWxlbWVudCA9IHBheWxvYWQuZWxlbWVudDtcbiAgICAgICAgLy8gQ3JlYXRlIGhlYWRlciBpdGVtXG4gICAgICAgIGNvbnN0IGl0ZW0gPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzd3AtaGVhZGVyLWl0ZW0nKTtcbiAgICAgICAgaXRlbS5kYXRhc2V0LmV2ZW50SWQgPSBwYXlsb2FkLmV2ZW50SWQ7XG4gICAgICAgIGl0ZW0uZGF0YXNldC5pdGVtVHlwZSA9IHBheWxvYWQuaXRlbVR5cGU7XG4gICAgICAgIGl0ZW0uZGF0YXNldC5kdXJhdGlvbiA9IFN0cmluZyhwYXlsb2FkLmR1cmF0aW9uKTtcbiAgICAgICAgaXRlbS5kYXRhc2V0LmNvbHVtbktleSA9IHBheWxvYWQuc291cmNlQ29sdW1uS2V5O1xuICAgICAgICBpdGVtLnRleHRDb250ZW50ID0gcGF5bG9hZC50aXRsZTtcbiAgICAgICAgLy8gQXBwbHkgY29sb3IgY2xhc3MgaWYgcHJlc2VudFxuICAgICAgICBpZiAocGF5bG9hZC5jb2xvckNsYXNzKSB7XG4gICAgICAgICAgICBpdGVtLmNsYXNzTGlzdC5hZGQocGF5bG9hZC5jb2xvckNsYXNzKTtcbiAgICAgICAgfVxuICAgICAgICAvLyBBZGQgZHJhZ2dpbmcgc3RhdGVcbiAgICAgICAgaXRlbS5jbGFzc0xpc3QuYWRkKCdkcmFnZ2luZycpO1xuICAgICAgICAvLyBJbml0aWFsIHBsYWNlbWVudCAoZHVyYXRpb24gZGV0ZXJtaW5lcyBjb2x1bW4gc3BhbilcbiAgICAgICAgLy8gZ3JpZEFyZWEgZm9ybWF0OiBcInJvdyAvIGNvbC1zdGFydCAvIHJvdysxIC8gY29sLWVuZFwiXG4gICAgICAgIGNvbnN0IGNvbCA9IHBheWxvYWQuc291cmNlQ29sdW1uSW5kZXggKyAxO1xuICAgICAgICBjb25zdCBlbmRDb2wgPSBjb2wgKyBwYXlsb2FkLmR1cmF0aW9uO1xuICAgICAgICBpdGVtLnN0eWxlLmdyaWRBcmVhID0gYDEgLyAke2NvbH0gLyAyIC8gJHtlbmRDb2x9YDtcbiAgICAgICAgdGhpcy5jb250YWluZXIuYXBwZW5kQ2hpbGQoaXRlbSk7XG4gICAgICAgIHRoaXMuY3VycmVudEl0ZW0gPSBpdGVtO1xuICAgICAgICAvLyBIaWRlIG9yaWdpbmFsIGVsZW1lbnQgd2hpbGUgaW4gaGVhZGVyXG4gICAgICAgIHBheWxvYWQuZWxlbWVudC5zdHlsZS52aXNpYmlsaXR5ID0gJ2hpZGRlbic7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEhhbmRsZSBkcmFnIG1vdmluZyB3aXRoaW4gaGVhZGVyIC0gdXBkYXRlIGNvbHVtbiBwb3NpdGlvblxuICAgICAqL1xuICAgIGhhbmRsZURyYWdNb3ZlKHBheWxvYWQpIHtcbiAgICAgICAgaWYgKCF0aGlzLmN1cnJlbnRJdGVtKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAvLyBVcGRhdGUgY29sdW1uIHBvc2l0aW9uXG4gICAgICAgIGNvbnN0IGNvbCA9IHBheWxvYWQuY29sdW1uSW5kZXggKyAxO1xuICAgICAgICBjb25zdCBkdXJhdGlvbiA9IHBhcnNlSW50KHRoaXMuY3VycmVudEl0ZW0uZGF0YXNldC5kdXJhdGlvbiB8fCAnMScsIDEwKTtcbiAgICAgICAgY29uc3QgZW5kQ29sID0gY29sICsgZHVyYXRpb247XG4gICAgICAgIHRoaXMuY3VycmVudEl0ZW0uc3R5bGUuZ3JpZEFyZWEgPSBgMSAvICR7Y29sfSAvIDIgLyAke2VuZENvbH1gO1xuICAgICAgICAvLyBVcGRhdGUgY29sdW1uS2V5IHRvIG5ldyBwb3NpdGlvblxuICAgICAgICB0aGlzLmN1cnJlbnRJdGVtLmRhdGFzZXQuY29sdW1uS2V5ID0gcGF5bG9hZC5jb2x1bW5LZXk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEhhbmRsZSBkcmFnIGxlYXZpbmcgaGVhZGVyIC0gY2xlYW51cCBmb3IgZ3JpZFx1MjE5MmhlYWRlciBkcmFnIG9ubHlcbiAgICAgKi9cbiAgICBoYW5kbGVEcmFnTGVhdmUocGF5bG9hZCkge1xuICAgICAgICAvLyBPbmx5IGNsZWFudXAgZm9yIGdyaWRcdTIxOTJoZWFkZXIgZHJhZyAod2hlbiBncmlkIGV2ZW50IGxlYXZlcyBoZWFkZXIgYmFjayB0byBncmlkKVxuICAgICAgICAvLyBGb3IgaGVhZGVyXHUyMTkyZ3JpZCBkcmFnLCB0aGUgaGVhZGVyIGl0ZW0gc3RheXMgYXMgZ2hvc3QgdW50aWwgZHJvcFxuICAgICAgICBpZiAocGF5bG9hZC5zb3VyY2UgPT09ICdncmlkJykge1xuICAgICAgICAgICAgdGhpcy5jbGVhbnVwKCk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gRm9yIGhlYWRlciBzb3VyY2UsIGRvIG5vdGhpbmcgLSBnaG9zdCBzdGF5cyB1bnRpbCBFVkVOVF9EUkFHX0VORFxuICAgIH1cbiAgICAvKipcbiAgICAgKiBIYW5kbGUgZHJhZyBlbmQgLSBmaW5hbGl6ZSBiYXNlZCBvbiBkcm9wIHRhcmdldFxuICAgICAqL1xuICAgIGhhbmRsZURyYWdFbmQocGF5bG9hZCkge1xuICAgICAgICBpZiAocGF5bG9hZC50YXJnZXQgPT09ICdoZWFkZXInKSB7XG4gICAgICAgICAgICAvLyBHcmlkXHUyMTkySGVhZGVyOiBGaW5hbGl6ZSB0aGUgaGVhZGVyIGl0ZW0gKGl0IHN0YXlzIGluIGhlYWRlcilcbiAgICAgICAgICAgIGlmICh0aGlzLmN1cnJlbnRJdGVtKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5jdXJyZW50SXRlbS5jbGFzc0xpc3QucmVtb3ZlKCdkcmFnZ2luZycpO1xuICAgICAgICAgICAgICAgIHRoaXMucmVjYWxjdWxhdGVEcmF3ZXJMYXlvdXQoKTtcbiAgICAgICAgICAgICAgICB0aGlzLmN1cnJlbnRJdGVtID0gbnVsbDtcbiAgICAgICAgICAgICAgICB0aGlzLnNvdXJjZUVsZW1lbnQgPSBudWxsO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgLy8gSGVhZGVyXHUyMTkyR3JpZDogUmVtb3ZlIGdob3N0IGhlYWRlciBpdGVtIGFuZCByZWNhbGN1bGF0ZVxuICAgICAgICAgICAgY29uc3QgZ2hvc3QgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKGBzd3AtaGVhZGVyLWl0ZW0uZHJhZy1naG9zdFtkYXRhLWV2ZW50LWlkPVwiJHtwYXlsb2FkLnN3cEV2ZW50LmV2ZW50SWR9XCJdYCk7XG4gICAgICAgICAgICBnaG9zdD8ucmVtb3ZlKCk7XG4gICAgICAgICAgICB0aGlzLnJlY2FsY3VsYXRlRHJhd2VyTGF5b3V0KCk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgLyoqXG4gICAgICogUmVjYWxjdWxhdGUgbGF5b3V0IGZvciBhbGwgaXRlbXMgY3VycmVudGx5IGluIHRoZSBkcmF3ZXJcbiAgICAgKiBDYWxsZWQgYWZ0ZXIgZHJvcCB0byByZXBvc2l0aW9uIGl0ZW1zIGFuZCBhZGp1c3QgaGVpZ2h0XG4gICAgICovXG4gICAgcmVjYWxjdWxhdGVEcmF3ZXJMYXlvdXQoKSB7XG4gICAgICAgIGNvbnN0IGRyYXdlciA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJ3N3cC1oZWFkZXItZHJhd2VyJyk7XG4gICAgICAgIGlmICghZHJhd2VyKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICBjb25zdCBpdGVtcyA9IEFycmF5LmZyb20oZHJhd2VyLnF1ZXJ5U2VsZWN0b3JBbGwoJ3N3cC1oZWFkZXItaXRlbScpKTtcbiAgICAgICAgaWYgKGl0ZW1zLmxlbmd0aCA9PT0gMClcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgLy8gR2V0IHZpc2libGUgY29sdW1uIGtleXMgZm9yIGNvcnJlY3QgbXVsdGktcmVzb3VyY2UgcG9zaXRpb25pbmdcbiAgICAgICAgY29uc3QgdmlzaWJsZUNvbHVtbktleXMgPSB0aGlzLmdldFZpc2libGVDb2x1bW5LZXlzRnJvbURPTSgpO1xuICAgICAgICBpZiAodmlzaWJsZUNvbHVtbktleXMubGVuZ3RoID09PSAwKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAvLyBCdWlsZCBsYXlvdXQgZGF0YSBmcm9tIERPTSBpdGVtcyAtIHVzZSBjb2x1bW5LZXkgZGlyZWN0bHkgKG9wYXF1ZSBtYXRjaGluZylcbiAgICAgICAgY29uc3QgaXRlbURhdGEgPSBpdGVtcy5tYXAoaXRlbSA9PiAoe1xuICAgICAgICAgICAgZWxlbWVudDogaXRlbSxcbiAgICAgICAgICAgIGNvbHVtbktleTogaXRlbS5kYXRhc2V0LmNvbHVtbktleSB8fCAnJyxcbiAgICAgICAgICAgIGR1cmF0aW9uOiBwYXJzZUludChpdGVtLmRhdGFzZXQuZHVyYXRpb24gfHwgJzEnLCAxMClcbiAgICAgICAgfSkpO1xuICAgICAgICAvLyBDYWxjdWxhdGUgbmV3IGxheW91dCB1c2luZyB0cmFjayBhbGdvcml0aG1cbiAgICAgICAgY29uc3QgdHJhY2tzID0gW25ldyBBcnJheSh2aXNpYmxlQ29sdW1uS2V5cy5sZW5ndGgpLmZpbGwoZmFsc2UpXTtcbiAgICAgICAgZm9yIChjb25zdCBpdGVtIG9mIGl0ZW1EYXRhKSB7XG4gICAgICAgICAgICAvLyBEaXJlY3QgY29sdW1uS2V5IG1hdGNoaW5nIC0gbm8gcGFyc2luZyBvciBjb25zdHJ1Y3Rpb24gbmVlZGVkXG4gICAgICAgICAgICBjb25zdCBzdGFydENvbCA9IHZpc2libGVDb2x1bW5LZXlzLmluZGV4T2YoaXRlbS5jb2x1bW5LZXkpO1xuICAgICAgICAgICAgaWYgKHN0YXJ0Q29sID09PSAtMSlcbiAgICAgICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICAgIGNvbnN0IGNvbFN0YXJ0ID0gc3RhcnRDb2w7XG4gICAgICAgICAgICBjb25zdCBjb2xFbmQgPSBNYXRoLm1pbihzdGFydENvbCArIGl0ZW0uZHVyYXRpb24sIHZpc2libGVDb2x1bW5LZXlzLmxlbmd0aCk7XG4gICAgICAgICAgICBjb25zdCByb3cgPSB0aGlzLmZpbmRBdmFpbGFibGVSb3codHJhY2tzLCBjb2xTdGFydCwgY29sRW5kKTtcbiAgICAgICAgICAgIGZvciAobGV0IGMgPSBjb2xTdGFydDsgYyA8IGNvbEVuZDsgYysrKSB7XG4gICAgICAgICAgICAgICAgdHJhY2tzW3Jvd11bY10gPSB0cnVlO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gVXBkYXRlIGVsZW1lbnQgcG9zaXRpb25cbiAgICAgICAgICAgIGl0ZW0uZWxlbWVudC5zdHlsZS5ncmlkQXJlYSA9IGAke3JvdyArIDF9IC8gJHtjb2xTdGFydCArIDF9IC8gJHtyb3cgKyAyfSAvICR7Y29sRW5kICsgMX1gO1xuICAgICAgICB9XG4gICAgICAgIC8vIFVwZGF0ZSBkcmF3ZXIgaGVpZ2h0XG4gICAgICAgIGNvbnN0IHJvd0NvdW50ID0gdHJhY2tzLmxlbmd0aDtcbiAgICAgICAgdGhpcy5oZWFkZXJEcmF3ZXJNYW5hZ2VyLmV4cGFuZFRvUm93cyhyb3dDb3VudCk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCB2aXNpYmxlIGNvbHVtbiBrZXlzIGZyb20gRE9NIChwcmVzZXJ2ZXMgb3JkZXIgZm9yIG11bHRpLXJlc291cmNlIHZpZXdzKVxuICAgICAqIFVzZXMgZmlsdGVyVGVtcGxhdGUuYnVpbGRLZXlGcm9tQ29sdW1uKCkgZm9yIGNvbnNpc3RlbnQga2V5IGZvcm1hdCB3aXRoIGV2ZW50c1xuICAgICAqL1xuICAgIGdldFZpc2libGVDb2x1bW5LZXlzRnJvbURPTSgpIHtcbiAgICAgICAgaWYgKCF0aGlzLmZpbHRlclRlbXBsYXRlKVxuICAgICAgICAgICAgcmV0dXJuIFtdO1xuICAgICAgICBjb25zdCBjb2x1bW5zID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnc3dwLWRheS1jb2x1bW4nKTtcbiAgICAgICAgY29uc3QgY29sdW1uS2V5cyA9IFtdO1xuICAgICAgICBjb2x1bW5zLmZvckVhY2goY29sID0+IHtcbiAgICAgICAgICAgIGNvbnN0IGNvbHVtbktleSA9IHRoaXMuZmlsdGVyVGVtcGxhdGUuYnVpbGRLZXlGcm9tQ29sdW1uKGNvbCk7XG4gICAgICAgICAgICBpZiAoY29sdW1uS2V5KVxuICAgICAgICAgICAgICAgIGNvbHVtbktleXMucHVzaChjb2x1bW5LZXkpO1xuICAgICAgICB9KTtcbiAgICAgICAgcmV0dXJuIGNvbHVtbktleXM7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIENsZWFudXAgcHJldmlldyBpdGVtIGFuZCByZXN0b3JlIHNvdXJjZSB2aXNpYmlsaXR5XG4gICAgICovXG4gICAgY2xlYW51cCgpIHtcbiAgICAgICAgLy8gUmVtb3ZlIHByZXZpZXcgaXRlbVxuICAgICAgICB0aGlzLmN1cnJlbnRJdGVtPy5yZW1vdmUoKTtcbiAgICAgICAgdGhpcy5jdXJyZW50SXRlbSA9IG51bGw7XG4gICAgICAgIC8vIFJlc3RvcmUgc291cmNlIGVsZW1lbnQgdmlzaWJpbGl0eVxuICAgICAgICBpZiAodGhpcy5zb3VyY2VFbGVtZW50KSB7XG4gICAgICAgICAgICB0aGlzLnNvdXJjZUVsZW1lbnQuc3R5bGUudmlzaWJpbGl0eSA9ICcnO1xuICAgICAgICAgICAgdGhpcy5zb3VyY2VFbGVtZW50ID0gbnVsbDtcbiAgICAgICAgfVxuICAgICAgICAvLyBDb2xsYXBzZSBkcmF3ZXIgaWYgaXQgd2Fzbid0IGV4cGFuZGVkIGJlZm9yZSBkcmFnXG4gICAgICAgIGlmICghdGhpcy53YXNFeHBhbmRlZEJlZm9yZURyYWcpIHtcbiAgICAgICAgICAgIHRoaXMuaGVhZGVyRHJhd2VyTWFuYWdlci5jb2xsYXBzZSgpO1xuICAgICAgICB9XG4gICAgfVxufVxuIiwgIi8qKlxuICogU2NoZWR1bGVPdmVycmlkZVN0b3JlIC0gSW5kZXhlZERCIE9iamVjdFN0b3JlIGZvciBzY2hlZHVsZSBvdmVycmlkZXNcbiAqXG4gKiBTdG9yZXMgZGF0ZS1zcGVjaWZpYyBzY2hlZHVsZSBvdmVycmlkZXMgZm9yIHJlc291cmNlcy5cbiAqIEluZGV4ZXM6IHJlc291cmNlSWQsIGRhdGUsIGNvbXBvdW5kIChyZXNvdXJjZUlkICsgZGF0ZSlcbiAqL1xuZXhwb3J0IGNsYXNzIFNjaGVkdWxlT3ZlcnJpZGVTdG9yZSB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuc3RvcmVOYW1lID0gU2NoZWR1bGVPdmVycmlkZVN0b3JlLlNUT1JFX05BTUU7XG4gICAgfVxuICAgIGNyZWF0ZShkYikge1xuICAgICAgICBjb25zdCBzdG9yZSA9IGRiLmNyZWF0ZU9iamVjdFN0b3JlKFNjaGVkdWxlT3ZlcnJpZGVTdG9yZS5TVE9SRV9OQU1FLCB7IGtleVBhdGg6ICdpZCcgfSk7XG4gICAgICAgIHN0b3JlLmNyZWF0ZUluZGV4KCdyZXNvdXJjZUlkJywgJ3Jlc291cmNlSWQnLCB7IHVuaXF1ZTogZmFsc2UgfSk7XG4gICAgICAgIHN0b3JlLmNyZWF0ZUluZGV4KCdkYXRlJywgJ2RhdGUnLCB7IHVuaXF1ZTogZmFsc2UgfSk7XG4gICAgICAgIHN0b3JlLmNyZWF0ZUluZGV4KCdyZXNvdXJjZUlkX2RhdGUnLCBbJ3Jlc291cmNlSWQnLCAnZGF0ZSddLCB7IHVuaXF1ZTogdHJ1ZSB9KTtcbiAgICAgICAgc3RvcmUuY3JlYXRlSW5kZXgoJ3N5bmNTdGF0dXMnLCAnc3luY1N0YXR1cycsIHsgdW5pcXVlOiBmYWxzZSB9KTtcbiAgICB9XG59XG5TY2hlZHVsZU92ZXJyaWRlU3RvcmUuU1RPUkVfTkFNRSA9ICdzY2hlZHVsZU92ZXJyaWRlcyc7XG4iLCAiaW1wb3J0IHsgU2NoZWR1bGVPdmVycmlkZVN0b3JlIH0gZnJvbSAnLi9TY2hlZHVsZU92ZXJyaWRlU3RvcmUnO1xuLyoqXG4gKiBTY2hlZHVsZU92ZXJyaWRlU2VydmljZSAtIENSVUQgZm9yIHNjaGVkdWxlIG92ZXJyaWRlc1xuICpcbiAqIFByb3ZpZGVzIGFjY2VzcyB0byBkYXRlLXNwZWNpZmljIHNjaGVkdWxlIG92ZXJyaWRlcyBmb3IgcmVzb3VyY2VzLlxuICovXG5leHBvcnQgY2xhc3MgU2NoZWR1bGVPdmVycmlkZVNlcnZpY2Uge1xuICAgIGNvbnN0cnVjdG9yKGNvbnRleHQpIHtcbiAgICAgICAgdGhpcy5jb250ZXh0ID0gY29udGV4dDtcbiAgICB9XG4gICAgZ2V0IGRiKCkge1xuICAgICAgICByZXR1cm4gdGhpcy5jb250ZXh0LmdldERhdGFiYXNlKCk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEdldCBvdmVycmlkZSBmb3IgYSBzcGVjaWZpYyByZXNvdXJjZSBhbmQgZGF0ZVxuICAgICAqL1xuICAgIGFzeW5jIGdldE92ZXJyaWRlKHJlc291cmNlSWQsIGRhdGUpIHtcbiAgICAgICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHRyYW5zYWN0aW9uID0gdGhpcy5kYi50cmFuc2FjdGlvbihbU2NoZWR1bGVPdmVycmlkZVN0b3JlLlNUT1JFX05BTUVdLCAncmVhZG9ubHknKTtcbiAgICAgICAgICAgIGNvbnN0IHN0b3JlID0gdHJhbnNhY3Rpb24ub2JqZWN0U3RvcmUoU2NoZWR1bGVPdmVycmlkZVN0b3JlLlNUT1JFX05BTUUpO1xuICAgICAgICAgICAgY29uc3QgaW5kZXggPSBzdG9yZS5pbmRleCgncmVzb3VyY2VJZF9kYXRlJyk7XG4gICAgICAgICAgICBjb25zdCByZXF1ZXN0ID0gaW5kZXguZ2V0KFtyZXNvdXJjZUlkLCBkYXRlXSk7XG4gICAgICAgICAgICByZXF1ZXN0Lm9uc3VjY2VzcyA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICByZXNvbHZlKHJlcXVlc3QucmVzdWx0IHx8IG51bGwpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25lcnJvciA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICByZWplY3QobmV3IEVycm9yKGBGYWlsZWQgdG8gZ2V0IG92ZXJyaWRlIGZvciAke3Jlc291cmNlSWR9IG9uICR7ZGF0ZX06ICR7cmVxdWVzdC5lcnJvcn1gKSk7XG4gICAgICAgICAgICB9O1xuICAgICAgICB9KTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IGFsbCBvdmVycmlkZXMgZm9yIGEgcmVzb3VyY2VcbiAgICAgKi9cbiAgICBhc3luYyBnZXRCeVJlc291cmNlKHJlc291cmNlSWQpIHtcbiAgICAgICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHRyYW5zYWN0aW9uID0gdGhpcy5kYi50cmFuc2FjdGlvbihbU2NoZWR1bGVPdmVycmlkZVN0b3JlLlNUT1JFX05BTUVdLCAncmVhZG9ubHknKTtcbiAgICAgICAgICAgIGNvbnN0IHN0b3JlID0gdHJhbnNhY3Rpb24ub2JqZWN0U3RvcmUoU2NoZWR1bGVPdmVycmlkZVN0b3JlLlNUT1JFX05BTUUpO1xuICAgICAgICAgICAgY29uc3QgaW5kZXggPSBzdG9yZS5pbmRleCgncmVzb3VyY2VJZCcpO1xuICAgICAgICAgICAgY29uc3QgcmVxdWVzdCA9IGluZGV4LmdldEFsbChyZXNvdXJjZUlkKTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25zdWNjZXNzID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIHJlc29sdmUocmVxdWVzdC5yZXN1bHQgfHwgW10pO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25lcnJvciA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICByZWplY3QobmV3IEVycm9yKGBGYWlsZWQgdG8gZ2V0IG92ZXJyaWRlcyBmb3IgJHtyZXNvdXJjZUlkfTogJHtyZXF1ZXN0LmVycm9yfWApKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0pO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgb3ZlcnJpZGVzIGZvciBhIGRhdGUgcmFuZ2VcbiAgICAgKi9cbiAgICBhc3luYyBnZXRCeURhdGVSYW5nZShyZXNvdXJjZUlkLCBzdGFydERhdGUsIGVuZERhdGUpIHtcbiAgICAgICAgY29uc3QgYWxsID0gYXdhaXQgdGhpcy5nZXRCeVJlc291cmNlKHJlc291cmNlSWQpO1xuICAgICAgICByZXR1cm4gYWxsLmZpbHRlcihvID0+IG8uZGF0ZSA+PSBzdGFydERhdGUgJiYgby5kYXRlIDw9IGVuZERhdGUpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBTYXZlIGFuIG92ZXJyaWRlXG4gICAgICovXG4gICAgYXN5bmMgc2F2ZShvdmVycmlkZSkge1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgdHJhbnNhY3Rpb24gPSB0aGlzLmRiLnRyYW5zYWN0aW9uKFtTY2hlZHVsZU92ZXJyaWRlU3RvcmUuU1RPUkVfTkFNRV0sICdyZWFkd3JpdGUnKTtcbiAgICAgICAgICAgIGNvbnN0IHN0b3JlID0gdHJhbnNhY3Rpb24ub2JqZWN0U3RvcmUoU2NoZWR1bGVPdmVycmlkZVN0b3JlLlNUT1JFX05BTUUpO1xuICAgICAgICAgICAgY29uc3QgcmVxdWVzdCA9IHN0b3JlLnB1dChvdmVycmlkZSk7XG4gICAgICAgICAgICByZXF1ZXN0Lm9uc3VjY2VzcyA9ICgpID0+IHJlc29sdmUoKTtcbiAgICAgICAgICAgIHJlcXVlc3Qub25lcnJvciA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICByZWplY3QobmV3IEVycm9yKGBGYWlsZWQgdG8gc2F2ZSBvdmVycmlkZSAke292ZXJyaWRlLmlkfTogJHtyZXF1ZXN0LmVycm9yfWApKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0pO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBEZWxldGUgYW4gb3ZlcnJpZGVcbiAgICAgKi9cbiAgICBhc3luYyBkZWxldGUoaWQpIHtcbiAgICAgICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHRyYW5zYWN0aW9uID0gdGhpcy5kYi50cmFuc2FjdGlvbihbU2NoZWR1bGVPdmVycmlkZVN0b3JlLlNUT1JFX05BTUVdLCAncmVhZHdyaXRlJyk7XG4gICAgICAgICAgICBjb25zdCBzdG9yZSA9IHRyYW5zYWN0aW9uLm9iamVjdFN0b3JlKFNjaGVkdWxlT3ZlcnJpZGVTdG9yZS5TVE9SRV9OQU1FKTtcbiAgICAgICAgICAgIGNvbnN0IHJlcXVlc3QgPSBzdG9yZS5kZWxldGUoaWQpO1xuICAgICAgICAgICAgcmVxdWVzdC5vbnN1Y2Nlc3MgPSAoKSA9PiByZXNvbHZlKCk7XG4gICAgICAgICAgICByZXF1ZXN0Lm9uZXJyb3IgPSAoKSA9PiB7XG4gICAgICAgICAgICAgICAgcmVqZWN0KG5ldyBFcnJvcihgRmFpbGVkIHRvIGRlbGV0ZSBvdmVycmlkZSAke2lkfTogJHtyZXF1ZXN0LmVycm9yfWApKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0pO1xuICAgIH1cbn1cbiIsICIvKipcbiAqIFJlc291cmNlU2NoZWR1bGVTZXJ2aWNlIC0gR2V0IGVmZmVjdGl2ZSBzY2hlZHVsZSBmb3IgYSByZXNvdXJjZSBvbiBhIGRhdGVcbiAqXG4gKiBMb2dpYzpcbiAqIDEuIENoZWNrIGZvciBvdmVycmlkZSBvbiB0aGlzIGRhdGVcbiAqIDIuIEZhbGwgYmFjayB0byBkZWZhdWx0IHNjaGVkdWxlIGZvciB0aGUgd2Vla2RheVxuICovXG5leHBvcnQgY2xhc3MgUmVzb3VyY2VTY2hlZHVsZVNlcnZpY2Uge1xuICAgIGNvbnN0cnVjdG9yKHJlc291cmNlU2VydmljZSwgb3ZlcnJpZGVTZXJ2aWNlLCBkYXRlU2VydmljZSkge1xuICAgICAgICB0aGlzLnJlc291cmNlU2VydmljZSA9IHJlc291cmNlU2VydmljZTtcbiAgICAgICAgdGhpcy5vdmVycmlkZVNlcnZpY2UgPSBvdmVycmlkZVNlcnZpY2U7XG4gICAgICAgIHRoaXMuZGF0ZVNlcnZpY2UgPSBkYXRlU2VydmljZTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IGVmZmVjdGl2ZSBzY2hlZHVsZSBmb3IgYSByZXNvdXJjZSBvbiBhIHNwZWNpZmljIGRhdGVcbiAgICAgKlxuICAgICAqIEBwYXJhbSByZXNvdXJjZUlkIC0gUmVzb3VyY2UgSURcbiAgICAgKiBAcGFyYW0gZGF0ZSAtIERhdGUgc3RyaW5nIFwiWVlZWS1NTS1ERFwiXG4gICAgICogQHJldHVybnMgSVRpbWVTbG90IG9yIG51bGwgKGZyaS9jbG9zZWQpXG4gICAgICovXG4gICAgYXN5bmMgZ2V0U2NoZWR1bGVGb3JEYXRlKHJlc291cmNlSWQsIGRhdGUpIHtcbiAgICAgICAgLy8gMS4gQ2hlY2sgZm9yIG92ZXJyaWRlXG4gICAgICAgIGNvbnN0IG92ZXJyaWRlID0gYXdhaXQgdGhpcy5vdmVycmlkZVNlcnZpY2UuZ2V0T3ZlcnJpZGUocmVzb3VyY2VJZCwgZGF0ZSk7XG4gICAgICAgIGlmIChvdmVycmlkZSkge1xuICAgICAgICAgICAgcmV0dXJuIG92ZXJyaWRlLnNjaGVkdWxlO1xuICAgICAgICB9XG4gICAgICAgIC8vIDIuIFVzZSBkZWZhdWx0IHNjaGVkdWxlIGZvciB3ZWVrZGF5XG4gICAgICAgIGNvbnN0IHJlc291cmNlID0gYXdhaXQgdGhpcy5yZXNvdXJjZVNlcnZpY2UuZ2V0KHJlc291cmNlSWQpO1xuICAgICAgICBpZiAoIXJlc291cmNlIHx8ICFyZXNvdXJjZS5kZWZhdWx0U2NoZWR1bGUpIHtcbiAgICAgICAgICAgIHJldHVybiBudWxsO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IHdlZWtEYXkgPSB0aGlzLmRhdGVTZXJ2aWNlLmdldElTT1dlZWtEYXkoZGF0ZSk7XG4gICAgICAgIHJldHVybiByZXNvdXJjZS5kZWZhdWx0U2NoZWR1bGVbd2Vla0RheV0gfHwgbnVsbDtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IHNjaGVkdWxlcyBmb3IgbXVsdGlwbGUgZGF0ZXNcbiAgICAgKlxuICAgICAqIEBwYXJhbSByZXNvdXJjZUlkIC0gUmVzb3VyY2UgSURcbiAgICAgKiBAcGFyYW0gZGF0ZXMgLSBBcnJheSBvZiBkYXRlIHN0cmluZ3MgXCJZWVlZLU1NLUREXCJcbiAgICAgKiBAcmV0dXJucyBNYXAgb2YgZGF0ZSAtPiBJVGltZVNsb3QgfCBudWxsXG4gICAgICovXG4gICAgYXN5bmMgZ2V0U2NoZWR1bGVzRm9yRGF0ZXMocmVzb3VyY2VJZCwgZGF0ZXMpIHtcbiAgICAgICAgY29uc3QgcmVzdWx0ID0gbmV3IE1hcCgpO1xuICAgICAgICAvLyBHZXQgcmVzb3VyY2Ugb25jZVxuICAgICAgICBjb25zdCByZXNvdXJjZSA9IGF3YWl0IHRoaXMucmVzb3VyY2VTZXJ2aWNlLmdldChyZXNvdXJjZUlkKTtcbiAgICAgICAgLy8gR2V0IGFsbCBvdmVycmlkZXMgaW4gZGF0ZSByYW5nZVxuICAgICAgICBjb25zdCBvdmVycmlkZXMgPSBkYXRlcy5sZW5ndGggPiAwXG4gICAgICAgICAgICA/IGF3YWl0IHRoaXMub3ZlcnJpZGVTZXJ2aWNlLmdldEJ5RGF0ZVJhbmdlKHJlc291cmNlSWQsIGRhdGVzWzBdLCBkYXRlc1tkYXRlcy5sZW5ndGggLSAxXSlcbiAgICAgICAgICAgIDogW107XG4gICAgICAgIC8vIEJ1aWxkIG92ZXJyaWRlIG1hcFxuICAgICAgICBjb25zdCBvdmVycmlkZU1hcCA9IG5ldyBNYXAob3ZlcnJpZGVzLm1hcChvID0+IFtvLmRhdGUsIG8uc2NoZWR1bGVdKSk7XG4gICAgICAgIC8vIFJlc29sdmUgZWFjaCBkYXRlXG4gICAgICAgIGZvciAoY29uc3QgZGF0ZSBvZiBkYXRlcykge1xuICAgICAgICAgICAgLy8gQ2hlY2sgb3ZlcnJpZGUgZmlyc3RcbiAgICAgICAgICAgIGlmIChvdmVycmlkZU1hcC5oYXMoZGF0ZSkpIHtcbiAgICAgICAgICAgICAgICByZXN1bHQuc2V0KGRhdGUsIG92ZXJyaWRlTWFwLmdldChkYXRlKSk7XG4gICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBGYWxsIGJhY2sgdG8gZGVmYXVsdFxuICAgICAgICAgICAgaWYgKHJlc291cmNlPy5kZWZhdWx0U2NoZWR1bGUpIHtcbiAgICAgICAgICAgICAgICBjb25zdCB3ZWVrRGF5ID0gdGhpcy5kYXRlU2VydmljZS5nZXRJU09XZWVrRGF5KGRhdGUpO1xuICAgICAgICAgICAgICAgIHJlc3VsdC5zZXQoZGF0ZSwgcmVzb3VyY2UuZGVmYXVsdFNjaGVkdWxlW3dlZWtEYXldIHx8IG51bGwpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgcmVzdWx0LnNldChkYXRlLCBudWxsKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gcmVzdWx0O1xuICAgIH1cbn1cbiIsICIvKipcbiAqIFN3cEV2ZW50IC0gV3JhcHBlciBjbGFzcyBmb3IgY2FsZW5kYXIgZXZlbnQgZWxlbWVudHNcbiAqXG4gKiBFbmNhcHN1bGF0ZXMgYW4gSFRNTEVsZW1lbnQgYW5kIHByb3ZpZGVzIGNvbXB1dGVkIHByb3BlcnRpZXNcbiAqIGZvciBzdGFydC9lbmQgdGltZXMgYmFzZWQgb24gZWxlbWVudCBwb3NpdGlvbiBhbmQgZ3JpZCBjb25maWcuXG4gKlxuICogVXNhZ2U6XG4gKiAtIGV2ZW50SWQgaXMgcmVhZCBmcm9tIGVsZW1lbnQuZGF0YXNldFxuICogLSBjb2x1bW5LZXkgaWRlbnRpZmllcyB0aGUgY29sdW1uIHVuaWZvcm1seVxuICogLSBQb3NpdGlvbiAodG9wLCBoZWlnaHQpIGlzIHJlYWQgZnJvbSBlbGVtZW50LnN0eWxlXG4gKiAtIEZhY3RvcnkgbWV0aG9kIGBmcm9tRWxlbWVudCgpYCBjYWxjdWxhdGVzIERhdGUgb2JqZWN0c1xuICovXG5leHBvcnQgY2xhc3MgU3dwRXZlbnQge1xuICAgIGNvbnN0cnVjdG9yKGVsZW1lbnQsIGNvbHVtbktleSwgc3RhcnQsIGVuZCkge1xuICAgICAgICB0aGlzLmVsZW1lbnQgPSBlbGVtZW50O1xuICAgICAgICB0aGlzLmNvbHVtbktleSA9IGNvbHVtbktleTtcbiAgICAgICAgdGhpcy5fc3RhcnQgPSBzdGFydDtcbiAgICAgICAgdGhpcy5fZW5kID0gZW5kO1xuICAgIH1cbiAgICAvKiogRXZlbnQgSUQgZnJvbSBlbGVtZW50LmRhdGFzZXQuZXZlbnRJZCAqL1xuICAgIGdldCBldmVudElkKCkge1xuICAgICAgICByZXR1cm4gdGhpcy5lbGVtZW50LmRhdGFzZXQuZXZlbnRJZCB8fCAnJztcbiAgICB9XG4gICAgZ2V0IHN0YXJ0KCkge1xuICAgICAgICByZXR1cm4gdGhpcy5fc3RhcnQ7XG4gICAgfVxuICAgIGdldCBlbmQoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLl9lbmQ7XG4gICAgfVxuICAgIC8qKiBEdXJhdGlvbiBpbiBtaW51dGVzICovXG4gICAgZ2V0IGR1cmF0aW9uTWludXRlcygpIHtcbiAgICAgICAgcmV0dXJuICh0aGlzLl9lbmQuZ2V0VGltZSgpIC0gdGhpcy5fc3RhcnQuZ2V0VGltZSgpKSAvICgxMDAwICogNjApO1xuICAgIH1cbiAgICAvKiogRHVyYXRpb24gaW4gbWlsbGlzZWNvbmRzICovXG4gICAgZ2V0IGR1cmF0aW9uTXMoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLl9lbmQuZ2V0VGltZSgpIC0gdGhpcy5fc3RhcnQuZ2V0VGltZSgpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBGYWN0b3J5OiBDcmVhdGUgU3dwRXZlbnQgZnJvbSBlbGVtZW50ICsgY29sdW1uS2V5XG4gICAgICogUmVhZHMgdG9wL2hlaWdodCBmcm9tIGVsZW1lbnQuc3R5bGUgdG8gY2FsY3VsYXRlIHN0YXJ0L2VuZFxuICAgICAqIEBwYXJhbSBjb2x1bW5LZXkgLSBPcGFxdWUgY29sdW1uIGlkZW50aWZpZXIgKGRvIE5PVCBwYXJzZSAtIHVzZSBvbmx5IGZvciBtYXRjaGluZylcbiAgICAgKiBAcGFyYW0gZGF0ZSAtIERhdGUgc3RyaW5nIChZWVlZLU1NLUREKSBmb3IgdGltZSBjYWxjdWxhdGlvbnNcbiAgICAgKi9cbiAgICBzdGF0aWMgZnJvbUVsZW1lbnQoZWxlbWVudCwgY29sdW1uS2V5LCBkYXRlLCBncmlkQ29uZmlnKSB7XG4gICAgICAgIGNvbnN0IHRvcFBpeGVscyA9IHBhcnNlRmxvYXQoZWxlbWVudC5zdHlsZS50b3ApIHx8IDA7XG4gICAgICAgIGNvbnN0IGhlaWdodFBpeGVscyA9IHBhcnNlRmxvYXQoZWxlbWVudC5zdHlsZS5oZWlnaHQpIHx8IDA7XG4gICAgICAgIC8vIENhbGN1bGF0ZSBzdGFydCBmcm9tIHRvcCBwb3NpdGlvblxuICAgICAgICBjb25zdCBzdGFydE1pbnV0ZXNGcm9tR3JpZCA9ICh0b3BQaXhlbHMgLyBncmlkQ29uZmlnLmhvdXJIZWlnaHQpICogNjA7XG4gICAgICAgIGNvbnN0IHRvdGFsTWludXRlcyA9IChncmlkQ29uZmlnLmRheVN0YXJ0SG91ciAqIDYwKSArIHN0YXJ0TWludXRlc0Zyb21HcmlkO1xuICAgICAgICBjb25zdCBzdGFydCA9IG5ldyBEYXRlKGRhdGUpO1xuICAgICAgICBzdGFydC5zZXRIb3VycyhNYXRoLmZsb29yKHRvdGFsTWludXRlcyAvIDYwKSwgdG90YWxNaW51dGVzICUgNjAsIDAsIDApO1xuICAgICAgICAvLyBDYWxjdWxhdGUgZW5kIGZyb20gaGVpZ2h0XG4gICAgICAgIGNvbnN0IGR1cmF0aW9uTWludXRlcyA9IChoZWlnaHRQaXhlbHMgLyBncmlkQ29uZmlnLmhvdXJIZWlnaHQpICogNjA7XG4gICAgICAgIGNvbnN0IGVuZCA9IG5ldyBEYXRlKHN0YXJ0LmdldFRpbWUoKSArIGR1cmF0aW9uTWludXRlcyAqIDYwICogMTAwMCk7XG4gICAgICAgIHJldHVybiBuZXcgU3dwRXZlbnQoZWxlbWVudCwgY29sdW1uS2V5LCBzdGFydCwgZW5kKTtcbiAgICB9XG59XG4iLCAiaW1wb3J0IHsgQ29yZUV2ZW50cyB9IGZyb20gJy4uL2NvbnN0YW50cy9Db3JlRXZlbnRzJztcbmltcG9ydCB7IHNuYXBUb0dyaWQgfSBmcm9tICcuLi91dGlscy9Qb3NpdGlvblV0aWxzJztcbmltcG9ydCB7IFN3cEV2ZW50IH0gZnJvbSAnLi4vdHlwZXMvU3dwRXZlbnQnO1xuLyoqXG4gKiBEcmFnRHJvcE1hbmFnZXIgLSBIYW5kbGVzIGRyYWctZHJvcCBmb3IgY2FsZW5kYXIgZXZlbnRzXG4gKlxuICogU3RyYXRlZ3k6IERyYWcgb3JpZ2luYWwgZWxlbWVudCwgbGVhdmUgZ2hvc3QtY2xvbmUgaW4gcGxhY2VcbiAqIC0gbW91c2Vkb3duOiBTdG9yZSBpbml0aWFsIHN0YXRlLCB3YWl0IGZvciBtb3ZlbWVudFxuICogLSBtb3VzZW1vdmUgKD41cHgpOiBDcmVhdGUgZ2hvc3QsIHN0YXJ0IGRyYWdnaW5nIG9yaWdpbmFsXG4gKiAtIG1vdXNldXA6IFNuYXAgdG8gZ3JpZCwgcmVtb3ZlIGdob3N0LCBlbWl0IGRyYWc6ZW5kXG4gKiAtIGNhbmNlbDogQW5pbWF0ZSBiYWNrIHRvIHN0YXJ0WSwgcmVtb3ZlIGdob3N0XG4gKi9cbmV4cG9ydCBjbGFzcyBEcmFnRHJvcE1hbmFnZXIge1xuICAgIGNvbnN0cnVjdG9yKGV2ZW50QnVzLCBncmlkQ29uZmlnKSB7XG4gICAgICAgIHRoaXMuZXZlbnRCdXMgPSBldmVudEJ1cztcbiAgICAgICAgdGhpcy5ncmlkQ29uZmlnID0gZ3JpZENvbmZpZztcbiAgICAgICAgdGhpcy5kcmFnU3RhdGUgPSBudWxsO1xuICAgICAgICB0aGlzLm1vdXNlRG93blBvc2l0aW9uID0gbnVsbDtcbiAgICAgICAgdGhpcy5wZW5kaW5nRWxlbWVudCA9IG51bGw7XG4gICAgICAgIHRoaXMucGVuZGluZ01vdXNlT2Zmc2V0ID0gbnVsbDtcbiAgICAgICAgdGhpcy5jb250YWluZXIgPSBudWxsO1xuICAgICAgICB0aGlzLmluSGVhZGVyID0gZmFsc2U7XG4gICAgICAgIHRoaXMuRFJBR19USFJFU0hPTEQgPSA1O1xuICAgICAgICB0aGlzLklOVEVSUE9MQVRJT05fRkFDVE9SID0gMC4zO1xuICAgICAgICB0aGlzLmhhbmRsZVBvaW50ZXJEb3duID0gKGUpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHRhcmdldCA9IGUudGFyZ2V0O1xuICAgICAgICAgICAgLy8gSWdub3JlIGlmIGNsaWNraW5nIG9uIHJlc2l6ZSBoYW5kbGVcbiAgICAgICAgICAgIGlmICh0YXJnZXQuY2xvc2VzdCgnc3dwLXJlc2l6ZS1oYW5kbGUnKSlcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAvLyBNYXRjaCBib3RoIHN3cC1ldmVudCBhbmQgc3dwLWhlYWRlci1pdGVtXG4gICAgICAgICAgICBjb25zdCBldmVudEVsZW1lbnQgPSB0YXJnZXQuY2xvc2VzdCgnc3dwLWV2ZW50Jyk7XG4gICAgICAgICAgICBjb25zdCBoZWFkZXJJdGVtID0gdGFyZ2V0LmNsb3Nlc3QoJ3N3cC1oZWFkZXItaXRlbScpO1xuICAgICAgICAgICAgY29uc3QgZHJhZ2dhYmxlID0gZXZlbnRFbGVtZW50IHx8IGhlYWRlckl0ZW07XG4gICAgICAgICAgICBpZiAoIWRyYWdnYWJsZSlcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAvLyBTdG9yZSBmb3IgcG90ZW50aWFsIGRyYWdcbiAgICAgICAgICAgIHRoaXMubW91c2VEb3duUG9zaXRpb24gPSB7IHg6IGUuY2xpZW50WCwgeTogZS5jbGllbnRZIH07XG4gICAgICAgICAgICB0aGlzLnBlbmRpbmdFbGVtZW50ID0gZHJhZ2dhYmxlO1xuICAgICAgICAgICAgLy8gQ2FsY3VsYXRlIG1vdXNlIG9mZnNldCB3aXRoaW4gZWxlbWVudFxuICAgICAgICAgICAgY29uc3QgcmVjdCA9IGRyYWdnYWJsZS5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcbiAgICAgICAgICAgIHRoaXMucGVuZGluZ01vdXNlT2Zmc2V0ID0ge1xuICAgICAgICAgICAgICAgIHg6IGUuY2xpZW50WCAtIHJlY3QubGVmdCxcbiAgICAgICAgICAgICAgICB5OiBlLmNsaWVudFkgLSByZWN0LnRvcFxuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIC8vIENhcHR1cmUgcG9pbnRlciBmb3IgcmVsaWFibGUgdHJhY2tpbmdcbiAgICAgICAgICAgIGRyYWdnYWJsZS5zZXRQb2ludGVyQ2FwdHVyZShlLnBvaW50ZXJJZCk7XG4gICAgICAgIH07XG4gICAgICAgIHRoaXMuaGFuZGxlUG9pbnRlck1vdmUgPSAoZSkgPT4ge1xuICAgICAgICAgICAgLy8gTm90IGluIHBvdGVudGlhbCBkcmFnIHN0YXRlXG4gICAgICAgICAgICBpZiAoIXRoaXMubW91c2VEb3duUG9zaXRpb24gfHwgIXRoaXMucGVuZGluZ0VsZW1lbnQpIHtcbiAgICAgICAgICAgICAgICAvLyBBbHJlYWR5IGRyYWdnaW5nIC0gdXBkYXRlIHRhcmdldFxuICAgICAgICAgICAgICAgIGlmICh0aGlzLmRyYWdTdGF0ZSkge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLnVwZGF0ZURyYWdUYXJnZXQoZSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIENoZWNrIHRocmVzaG9sZFxuICAgICAgICAgICAgY29uc3QgZGVsdGFYID0gTWF0aC5hYnMoZS5jbGllbnRYIC0gdGhpcy5tb3VzZURvd25Qb3NpdGlvbi54KTtcbiAgICAgICAgICAgIGNvbnN0IGRlbHRhWSA9IE1hdGguYWJzKGUuY2xpZW50WSAtIHRoaXMubW91c2VEb3duUG9zaXRpb24ueSk7XG4gICAgICAgICAgICBjb25zdCBkaXN0YW5jZSA9IE1hdGguc3FydChkZWx0YVggKiBkZWx0YVggKyBkZWx0YVkgKiBkZWx0YVkpO1xuICAgICAgICAgICAgaWYgKGRpc3RhbmNlIDwgdGhpcy5EUkFHX1RIUkVTSE9MRClcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAvLyBTdGFydCBkcmFnXG4gICAgICAgICAgICB0aGlzLmluaXRpYWxpemVEcmFnKHRoaXMucGVuZGluZ0VsZW1lbnQsIHRoaXMucGVuZGluZ01vdXNlT2Zmc2V0LCBlKTtcbiAgICAgICAgICAgIHRoaXMubW91c2VEb3duUG9zaXRpb24gPSBudWxsO1xuICAgICAgICAgICAgdGhpcy5wZW5kaW5nRWxlbWVudCA9IG51bGw7XG4gICAgICAgICAgICB0aGlzLnBlbmRpbmdNb3VzZU9mZnNldCA9IG51bGw7XG4gICAgICAgIH07XG4gICAgICAgIHRoaXMuaGFuZGxlUG9pbnRlclVwID0gKF9lKSA9PiB7XG4gICAgICAgICAgICAvLyBDbGVhciBwZW5kaW5nIHN0YXRlXG4gICAgICAgICAgICB0aGlzLm1vdXNlRG93blBvc2l0aW9uID0gbnVsbDtcbiAgICAgICAgICAgIHRoaXMucGVuZGluZ0VsZW1lbnQgPSBudWxsO1xuICAgICAgICAgICAgdGhpcy5wZW5kaW5nTW91c2VPZmZzZXQgPSBudWxsO1xuICAgICAgICAgICAgaWYgKCF0aGlzLmRyYWdTdGF0ZSlcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAvLyBTdG9wIGFuaW1hdGlvblxuICAgICAgICAgICAgY2FuY2VsQW5pbWF0aW9uRnJhbWUodGhpcy5kcmFnU3RhdGUuYW5pbWF0aW9uSWQpO1xuICAgICAgICAgICAgLy8gSGFuZGxlIGJhc2VkIG9uIGRyYWcgc291cmNlIGFuZCB0YXJnZXRcbiAgICAgICAgICAgIGlmICh0aGlzLmRyYWdTdGF0ZS5kcmFnU291cmNlID09PSAnaGVhZGVyJykge1xuICAgICAgICAgICAgICAgIC8vIEhlYWRlciBpdGVtIGRyYWcgZW5kXG4gICAgICAgICAgICAgICAgdGhpcy5oYW5kbGVIZWFkZXJJdGVtRHJhZ0VuZCgpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgLy8gR3JpZCBldmVudCBkcmFnIGVuZFxuICAgICAgICAgICAgICAgIHRoaXMuaGFuZGxlR3JpZEV2ZW50RHJhZ0VuZCgpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gQ2xlYW51cFxuICAgICAgICAgICAgdGhpcy5kcmFnU3RhdGUuZWxlbWVudC5jbGFzc0xpc3QucmVtb3ZlKCdkcmFnZ2luZycpO1xuICAgICAgICAgICAgdGhpcy5kcmFnU3RhdGUgPSBudWxsO1xuICAgICAgICAgICAgdGhpcy5pbkhlYWRlciA9IGZhbHNlO1xuICAgICAgICB9O1xuICAgICAgICB0aGlzLmFuaW1hdGVEcmFnID0gKCkgPT4ge1xuICAgICAgICAgICAgaWYgKCF0aGlzLmRyYWdTdGF0ZSlcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICBjb25zdCBkaWZmID0gdGhpcy5kcmFnU3RhdGUudGFyZ2V0WSAtIHRoaXMuZHJhZ1N0YXRlLmN1cnJlbnRZO1xuICAgICAgICAgICAgLy8gU3RvcCBhbmltYXRpb24gd2hlbiBjbG9zZSBlbm91Z2ggdG8gdGFyZ2V0XG4gICAgICAgICAgICBpZiAoTWF0aC5hYnMoZGlmZikgPD0gMC41KSB7XG4gICAgICAgICAgICAgICAgdGhpcy5kcmFnU3RhdGUuYW5pbWF0aW9uSWQgPSAwO1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIEludGVycG9sYXRlIHRvd2FyZHMgdGFyZ2V0XG4gICAgICAgICAgICB0aGlzLmRyYWdTdGF0ZS5jdXJyZW50WSArPSBkaWZmICogdGhpcy5JTlRFUlBPTEFUSU9OX0ZBQ1RPUjtcbiAgICAgICAgICAgIC8vIFVwZGF0ZSBlbGVtZW50IHBvc2l0aW9uXG4gICAgICAgICAgICB0aGlzLmRyYWdTdGF0ZS5lbGVtZW50LnN0eWxlLnRvcCA9IGAke3RoaXMuZHJhZ1N0YXRlLmN1cnJlbnRZfXB4YDtcbiAgICAgICAgICAgIC8vIEVtaXQgZHJhZzptb3ZlIChvbmx5IGlmIHdlIGhhdmUgYSBjb2x1bW4pXG4gICAgICAgICAgICBpZiAodGhpcy5kcmFnU3RhdGUuY29sdW1uRWxlbWVudCkge1xuICAgICAgICAgICAgICAgIGNvbnN0IHBheWxvYWQgPSB7XG4gICAgICAgICAgICAgICAgICAgIGV2ZW50SWQ6IHRoaXMuZHJhZ1N0YXRlLmV2ZW50SWQsXG4gICAgICAgICAgICAgICAgICAgIGVsZW1lbnQ6IHRoaXMuZHJhZ1N0YXRlLmVsZW1lbnQsXG4gICAgICAgICAgICAgICAgICAgIGN1cnJlbnRZOiB0aGlzLmRyYWdTdGF0ZS5jdXJyZW50WSxcbiAgICAgICAgICAgICAgICAgICAgY29sdW1uRWxlbWVudDogdGhpcy5kcmFnU3RhdGUuY29sdW1uRWxlbWVudFxuICAgICAgICAgICAgICAgIH07XG4gICAgICAgICAgICAgICAgdGhpcy5ldmVudEJ1cy5lbWl0KENvcmVFdmVudHMuRVZFTlRfRFJBR19NT1ZFLCBwYXlsb2FkKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIENvbnRpbnVlIGFuaW1hdGlvblxuICAgICAgICAgICAgdGhpcy5kcmFnU3RhdGUuYW5pbWF0aW9uSWQgPSByZXF1ZXN0QW5pbWF0aW9uRnJhbWUodGhpcy5hbmltYXRlRHJhZyk7XG4gICAgICAgIH07XG4gICAgICAgIHRoaXMuc2V0dXBTY3JvbGxMaXN0ZW5lcigpO1xuICAgIH1cbiAgICBzZXR1cFNjcm9sbExpc3RlbmVyKCkge1xuICAgICAgICB0aGlzLmV2ZW50QnVzLm9uKENvcmVFdmVudHMuRURHRV9TQ1JPTExfVElDSywgKGUpID0+IHtcbiAgICAgICAgICAgIGlmICghdGhpcy5kcmFnU3RhdGUpXG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgY29uc3QgeyBzY3JvbGxEZWx0YSB9ID0gZS5kZXRhaWw7XG4gICAgICAgICAgICAvLyBFbGVtZW50IHNrYWwgZmx5dHRlIG1lZCBzY3JvbGwgZm9yIGF0IGZvcmJsaXZlIHVuZGVyIG11c2VuXG4gICAgICAgICAgICAvLyAoZWxlbWVudGV0cyB0b3AgZXIgcmVsYXRpdiB0aWwga29sb25uZW4sIHNvbSBzY3JvbGxlciBtZWQgdmlld3BvcnQpXG4gICAgICAgICAgICB0aGlzLmRyYWdTdGF0ZS50YXJnZXRZICs9IHNjcm9sbERlbHRhO1xuICAgICAgICAgICAgdGhpcy5kcmFnU3RhdGUuY3VycmVudFkgKz0gc2Nyb2xsRGVsdGE7XG4gICAgICAgICAgICB0aGlzLmRyYWdTdGF0ZS5lbGVtZW50LnN0eWxlLnRvcCA9IGAke3RoaXMuZHJhZ1N0YXRlLmN1cnJlbnRZfXB4YDtcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEluaXRpYWxpemUgZHJhZy1kcm9wIG9uIGEgY29udGFpbmVyIGVsZW1lbnRcbiAgICAgKi9cbiAgICBpbml0KGNvbnRhaW5lcikge1xuICAgICAgICB0aGlzLmNvbnRhaW5lciA9IGNvbnRhaW5lcjtcbiAgICAgICAgY29udGFpbmVyLmFkZEV2ZW50TGlzdGVuZXIoJ3BvaW50ZXJkb3duJywgdGhpcy5oYW5kbGVQb2ludGVyRG93bik7XG4gICAgICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ3BvaW50ZXJtb3ZlJywgdGhpcy5oYW5kbGVQb2ludGVyTW92ZSk7XG4gICAgICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ3BvaW50ZXJ1cCcsIHRoaXMuaGFuZGxlUG9pbnRlclVwKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogSGFuZGxlIGRyYWcgZW5kIGZvciBoZWFkZXIgaXRlbXNcbiAgICAgKi9cbiAgICBoYW5kbGVIZWFkZXJJdGVtRHJhZ0VuZCgpIHtcbiAgICAgICAgaWYgKCF0aGlzLmRyYWdTdGF0ZSlcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgLy8gSWYgZHJvcHBlZCBpbiBncmlkIChub3QgaW4gaGVhZGVyKSwgdGhlIHN3cC1ldmVudCB3YXMgYWxyZWFkeSBjcmVhdGVkXG4gICAgICAgIC8vIGJ5IEV2ZW50UmVuZGVyZXIgbGlzdGVuaW5nIHRvIEVWRU5UX0RSQUdfTEVBVkVfSEVBREVSXG4gICAgICAgIC8vIEp1c3QgZW1pdCBkcmFnOmVuZCBmb3IgcGVyc2lzdGVuY2VcbiAgICAgICAgaWYgKCF0aGlzLmluSGVhZGVyICYmIHRoaXMuZHJhZ1N0YXRlLmN1cnJlbnRDb2x1bW4pIHtcbiAgICAgICAgICAgIC8vIERyb3BwZWQgaW4gZ3JpZCAtIGVtaXQgZHJhZzplbmQgd2l0aCB0aGUgbmV3IHN3cC1ldmVudCBlbGVtZW50XG4gICAgICAgICAgICBjb25zdCBncmlkRXZlbnQgPSB0aGlzLmRyYWdTdGF0ZS5jdXJyZW50Q29sdW1uLnF1ZXJ5U2VsZWN0b3IoYHN3cC1ldmVudFtkYXRhLWV2ZW50LWlkPVwiJHt0aGlzLmRyYWdTdGF0ZS5ldmVudElkfVwiXWApO1xuICAgICAgICAgICAgaWYgKGdyaWRFdmVudCkge1xuICAgICAgICAgICAgICAgIGNvbnN0IGNvbHVtbktleSA9IHRoaXMuZHJhZ1N0YXRlLmN1cnJlbnRDb2x1bW4uZGF0YXNldC5jb2x1bW5LZXkgfHwgJyc7XG4gICAgICAgICAgICAgICAgY29uc3QgZGF0ZSA9IHRoaXMuZHJhZ1N0YXRlLmN1cnJlbnRDb2x1bW4uZGF0YXNldC5kYXRlIHx8ICcnO1xuICAgICAgICAgICAgICAgIGNvbnN0IHN3cEV2ZW50ID0gU3dwRXZlbnQuZnJvbUVsZW1lbnQoZ3JpZEV2ZW50LCBjb2x1bW5LZXksIGRhdGUsIHRoaXMuZ3JpZENvbmZpZyk7XG4gICAgICAgICAgICAgICAgY29uc3QgcGF5bG9hZCA9IHtcbiAgICAgICAgICAgICAgICAgICAgc3dwRXZlbnQsXG4gICAgICAgICAgICAgICAgICAgIHNvdXJjZUNvbHVtbktleTogdGhpcy5kcmFnU3RhdGUuc291cmNlQ29sdW1uS2V5LFxuICAgICAgICAgICAgICAgICAgICB0YXJnZXQ6ICdncmlkJ1xuICAgICAgICAgICAgICAgIH07XG4gICAgICAgICAgICAgICAgdGhpcy5ldmVudEJ1cy5lbWl0KENvcmVFdmVudHMuRVZFTlRfRFJBR19FTkQsIHBheWxvYWQpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIC8vIElmIHN0aWxsIGluIGhlYWRlciwgbm8gcGVyc2lzdGVuY2UgbmVlZGVkIChzdGF5ZWQgaW4gaGVhZGVyKVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBIYW5kbGUgZHJhZyBlbmQgZm9yIGdyaWQgZXZlbnRzXG4gICAgICovXG4gICAgaGFuZGxlR3JpZEV2ZW50RHJhZ0VuZCgpIHtcbiAgICAgICAgaWYgKCF0aGlzLmRyYWdTdGF0ZSB8fCAhdGhpcy5kcmFnU3RhdGUuY29sdW1uRWxlbWVudClcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgLy8gU25hcCB0byBncmlkXG4gICAgICAgIGNvbnN0IHNuYXBwZWRZID0gc25hcFRvR3JpZCh0aGlzLmRyYWdTdGF0ZS5jdXJyZW50WSwgdGhpcy5ncmlkQ29uZmlnKTtcbiAgICAgICAgdGhpcy5kcmFnU3RhdGUuZWxlbWVudC5zdHlsZS50b3AgPSBgJHtzbmFwcGVkWX1weGA7XG4gICAgICAgIC8vIFJlbW92ZSBnaG9zdFxuICAgICAgICB0aGlzLmRyYWdTdGF0ZS5naG9zdEVsZW1lbnQ/LnJlbW92ZSgpO1xuICAgICAgICAvLyBHZXQgY29sdW1uS2V5IGFuZCBkYXRlIGZyb20gdGFyZ2V0IGNvbHVtblxuICAgICAgICBjb25zdCBjb2x1bW5LZXkgPSB0aGlzLmRyYWdTdGF0ZS5jb2x1bW5FbGVtZW50LmRhdGFzZXQuY29sdW1uS2V5IHx8ICcnO1xuICAgICAgICBjb25zdCBkYXRlID0gdGhpcy5kcmFnU3RhdGUuY29sdW1uRWxlbWVudC5kYXRhc2V0LmRhdGUgfHwgJyc7XG4gICAgICAgIC8vIENyZWF0ZSBTd3BFdmVudCBmcm9tIGVsZW1lbnQgKHJlYWRzIHRvcC9oZWlnaHQvZXZlbnRJZCBmcm9tIGVsZW1lbnQpXG4gICAgICAgIGNvbnN0IHN3cEV2ZW50ID0gU3dwRXZlbnQuZnJvbUVsZW1lbnQodGhpcy5kcmFnU3RhdGUuZWxlbWVudCwgY29sdW1uS2V5LCBkYXRlLCB0aGlzLmdyaWRDb25maWcpO1xuICAgICAgICAvLyBFbWl0IGRyYWc6ZW5kXG4gICAgICAgIGNvbnN0IHBheWxvYWQgPSB7XG4gICAgICAgICAgICBzd3BFdmVudCxcbiAgICAgICAgICAgIHNvdXJjZUNvbHVtbktleTogdGhpcy5kcmFnU3RhdGUuc291cmNlQ29sdW1uS2V5LFxuICAgICAgICAgICAgdGFyZ2V0OiB0aGlzLmluSGVhZGVyID8gJ2hlYWRlcicgOiAnZ3JpZCdcbiAgICAgICAgfTtcbiAgICAgICAgdGhpcy5ldmVudEJ1cy5lbWl0KENvcmVFdmVudHMuRVZFTlRfRFJBR19FTkQsIHBheWxvYWQpO1xuICAgIH1cbiAgICBpbml0aWFsaXplRHJhZyhlbGVtZW50LCBtb3VzZU9mZnNldCwgZSkge1xuICAgICAgICBjb25zdCBldmVudElkID0gZWxlbWVudC5kYXRhc2V0LmV2ZW50SWQgfHwgJyc7XG4gICAgICAgIGNvbnN0IGlzSGVhZGVySXRlbSA9IGVsZW1lbnQudGFnTmFtZS50b0xvd2VyQ2FzZSgpID09PSAnc3dwLWhlYWRlci1pdGVtJztcbiAgICAgICAgY29uc3QgY29sdW1uRWxlbWVudCA9IGVsZW1lbnQuY2xvc2VzdCgnc3dwLWRheS1jb2x1bW4nKTtcbiAgICAgICAgLy8gRm9yIGdyaWQgZXZlbnRzLCB3ZSBuZWVkIGEgY29sdW1uXG4gICAgICAgIGlmICghaXNIZWFkZXJJdGVtICYmICFjb2x1bW5FbGVtZW50KVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICBpZiAoaXNIZWFkZXJJdGVtKSB7XG4gICAgICAgICAgICAvLyBIZWFkZXIgaXRlbSBkcmFnIGluaXRpYWxpemF0aW9uXG4gICAgICAgICAgICB0aGlzLmluaXRpYWxpemVIZWFkZXJJdGVtRHJhZyhlbGVtZW50LCBtb3VzZU9mZnNldCwgZXZlbnRJZCk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAvLyBHcmlkIGV2ZW50IGRyYWcgaW5pdGlhbGl6YXRpb25cbiAgICAgICAgICAgIHRoaXMuaW5pdGlhbGl6ZUdyaWRFdmVudERyYWcoZWxlbWVudCwgbW91c2VPZmZzZXQsIGUsIGNvbHVtbkVsZW1lbnQsIGV2ZW50SWQpO1xuICAgICAgICB9XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEluaXRpYWxpemUgZHJhZyBmb3IgYSBoZWFkZXIgaXRlbSAoYWxsRGF5IGV2ZW50KVxuICAgICAqL1xuICAgIGluaXRpYWxpemVIZWFkZXJJdGVtRHJhZyhlbGVtZW50LCBtb3VzZU9mZnNldCwgZXZlbnRJZCkge1xuICAgICAgICAvLyBNYXJrIGFzIGRyYWdnaW5nXG4gICAgICAgIGVsZW1lbnQuY2xhc3NMaXN0LmFkZCgnZHJhZ2dpbmcnKTtcbiAgICAgICAgLy8gSW5pdGlhbGl6ZSBkcmFnIHN0YXRlIGZvciBoZWFkZXIgaXRlbVxuICAgICAgICB0aGlzLmRyYWdTdGF0ZSA9IHtcbiAgICAgICAgICAgIGV2ZW50SWQsXG4gICAgICAgICAgICBlbGVtZW50LFxuICAgICAgICAgICAgZ2hvc3RFbGVtZW50OiBudWxsLCAvLyBObyBnaG9zdCBmb3IgaGVhZGVyIGl0ZW1zXG4gICAgICAgICAgICBzdGFydFk6IDAsXG4gICAgICAgICAgICBtb3VzZU9mZnNldCxcbiAgICAgICAgICAgIGNvbHVtbkVsZW1lbnQ6IG51bGwsXG4gICAgICAgICAgICBjdXJyZW50Q29sdW1uOiBudWxsLFxuICAgICAgICAgICAgdGFyZ2V0WTogMCxcbiAgICAgICAgICAgIGN1cnJlbnRZOiAwLFxuICAgICAgICAgICAgYW5pbWF0aW9uSWQ6IDAsXG4gICAgICAgICAgICBzb3VyY2VDb2x1bW5LZXk6ICcnLCAvLyBXaWxsIGJlIHNldCBmcm9tIGhlYWRlciBpdGVtIGRhdGFcbiAgICAgICAgICAgIGRyYWdTb3VyY2U6ICdoZWFkZXInXG4gICAgICAgIH07XG4gICAgICAgIC8vIFN0YXJ0IGluIGhlYWRlciBtb2RlXG4gICAgICAgIHRoaXMuaW5IZWFkZXIgPSB0cnVlO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBJbml0aWFsaXplIGRyYWcgZm9yIGEgZ3JpZCBldmVudFxuICAgICAqL1xuICAgIGluaXRpYWxpemVHcmlkRXZlbnREcmFnKGVsZW1lbnQsIG1vdXNlT2Zmc2V0LCBlLCBjb2x1bW5FbGVtZW50LCBldmVudElkKSB7XG4gICAgICAgIC8vIENhbGN1bGF0ZSBhYnNvbHV0ZSBZIHBvc2l0aW9uIHVzaW5nIGdldEJvdW5kaW5nQ2xpZW50UmVjdFxuICAgICAgICBjb25zdCBlbGVtZW50UmVjdCA9IGVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG4gICAgICAgIGNvbnN0IGNvbHVtblJlY3QgPSBjb2x1bW5FbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgICAgICBjb25zdCBzdGFydFkgPSBlbGVtZW50UmVjdC50b3AgLSBjb2x1bW5SZWN0LnRvcDtcbiAgICAgICAgLy8gSWYgZXZlbnQgaXMgaW5zaWRlIGEgZ3JvdXAsIG1vdmUgaXQgdG8gZXZlbnRzLWxheWVyIGZvciBjb3JyZWN0IHBvc2l0aW9uaW5nIGR1cmluZyBkcmFnXG4gICAgICAgIGNvbnN0IGdyb3VwID0gZWxlbWVudC5jbG9zZXN0KCdzd3AtZXZlbnQtZ3JvdXAnKTtcbiAgICAgICAgaWYgKGdyb3VwKSB7XG4gICAgICAgICAgICBjb25zdCBldmVudHNMYXllciA9IGNvbHVtbkVsZW1lbnQucXVlcnlTZWxlY3Rvcignc3dwLWV2ZW50cy1sYXllcicpO1xuICAgICAgICAgICAgaWYgKGV2ZW50c0xheWVyKSB7XG4gICAgICAgICAgICAgICAgZXZlbnRzTGF5ZXIuYXBwZW5kQ2hpbGQoZWxlbWVudCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgLy8gU2V0IGNvbnNpc3RlbnQgcG9zaXRpb25pbmcgZm9yIGRyYWcgKHdvcmtzIGZvciBib3RoIGdyb3VwZWQgYW5kIHN0YWNrZWQgZXZlbnRzKVxuICAgICAgICBlbGVtZW50LnN0eWxlLnBvc2l0aW9uID0gJ2Fic29sdXRlJztcbiAgICAgICAgZWxlbWVudC5zdHlsZS50b3AgPSBgJHtzdGFydFl9cHhgO1xuICAgICAgICBlbGVtZW50LnN0eWxlLmxlZnQgPSAnMnB4JztcbiAgICAgICAgZWxlbWVudC5zdHlsZS5yaWdodCA9ICcycHgnO1xuICAgICAgICBlbGVtZW50LnN0eWxlLm1hcmdpbkxlZnQgPSAnMCc7IC8vIFJlc2V0IHN0YWNraW5nIG1hcmdpblxuICAgICAgICAvLyBDcmVhdGUgZ2hvc3QgY2xvbmVcbiAgICAgICAgY29uc3QgZ2hvc3RFbGVtZW50ID0gZWxlbWVudC5jbG9uZU5vZGUodHJ1ZSk7XG4gICAgICAgIGdob3N0RWxlbWVudC5jbGFzc0xpc3QuYWRkKCdkcmFnLWdob3N0Jyk7XG4gICAgICAgIGdob3N0RWxlbWVudC5zdHlsZS5vcGFjaXR5ID0gJzAuMyc7XG4gICAgICAgIGdob3N0RWxlbWVudC5zdHlsZS5wb2ludGVyRXZlbnRzID0gJ25vbmUnO1xuICAgICAgICAvLyBJbnNlcnQgZ2hvc3QgYmVmb3JlIG9yaWdpbmFsXG4gICAgICAgIGVsZW1lbnQucGFyZW50Tm9kZT8uaW5zZXJ0QmVmb3JlKGdob3N0RWxlbWVudCwgZWxlbWVudCk7XG4gICAgICAgIC8vIFNldHVwIGVsZW1lbnQgZm9yIGRyYWdnaW5nXG4gICAgICAgIGVsZW1lbnQuY2xhc3NMaXN0LmFkZCgnZHJhZ2dpbmcnKTtcbiAgICAgICAgLy8gQ2FsY3VsYXRlIGluaXRpYWwgdGFyZ2V0IGZyb20gbW91c2UgcG9zaXRpb25cbiAgICAgICAgY29uc3QgdGFyZ2V0WSA9IGUuY2xpZW50WSAtIGNvbHVtblJlY3QudG9wIC0gbW91c2VPZmZzZXQueTtcbiAgICAgICAgLy8gSW5pdGlhbGl6ZSBkcmFnIHN0YXRlXG4gICAgICAgIHRoaXMuZHJhZ1N0YXRlID0ge1xuICAgICAgICAgICAgZXZlbnRJZCxcbiAgICAgICAgICAgIGVsZW1lbnQsXG4gICAgICAgICAgICBnaG9zdEVsZW1lbnQsXG4gICAgICAgICAgICBzdGFydFksXG4gICAgICAgICAgICBtb3VzZU9mZnNldCxcbiAgICAgICAgICAgIGNvbHVtbkVsZW1lbnQsXG4gICAgICAgICAgICBjdXJyZW50Q29sdW1uOiBjb2x1bW5FbGVtZW50LFxuICAgICAgICAgICAgdGFyZ2V0WTogTWF0aC5tYXgoMCwgdGFyZ2V0WSksXG4gICAgICAgICAgICBjdXJyZW50WTogc3RhcnRZLFxuICAgICAgICAgICAgYW5pbWF0aW9uSWQ6IDAsXG4gICAgICAgICAgICBzb3VyY2VDb2x1bW5LZXk6IGNvbHVtbkVsZW1lbnQuZGF0YXNldC5jb2x1bW5LZXkgfHwgJycsXG4gICAgICAgICAgICBkcmFnU291cmNlOiAnZ3JpZCdcbiAgICAgICAgfTtcbiAgICAgICAgLy8gRW1pdCBkcmFnOnN0YXJ0XG4gICAgICAgIGNvbnN0IHBheWxvYWQgPSB7XG4gICAgICAgICAgICBldmVudElkLFxuICAgICAgICAgICAgZWxlbWVudCxcbiAgICAgICAgICAgIGdob3N0RWxlbWVudCxcbiAgICAgICAgICAgIHN0YXJ0WSxcbiAgICAgICAgICAgIG1vdXNlT2Zmc2V0LFxuICAgICAgICAgICAgY29sdW1uRWxlbWVudFxuICAgICAgICB9O1xuICAgICAgICB0aGlzLmV2ZW50QnVzLmVtaXQoQ29yZUV2ZW50cy5FVkVOVF9EUkFHX1NUQVJULCBwYXlsb2FkKTtcbiAgICAgICAgLy8gU3RhcnQgYW5pbWF0aW9uIGxvb3BcbiAgICAgICAgdGhpcy5hbmltYXRlRHJhZygpO1xuICAgIH1cbiAgICB1cGRhdGVEcmFnVGFyZ2V0KGUpIHtcbiAgICAgICAgaWYgKCF0aGlzLmRyYWdTdGF0ZSlcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgLy8gQ2hlY2sgaGVhZGVyIHpvbmUgZmlyc3RcbiAgICAgICAgdGhpcy5jaGVja0hlYWRlclpvbmUoZSk7XG4gICAgICAgIC8vIFNraXAgbm9ybWFsIGdyaWQgaGFuZGxpbmcgaWYgaW4gaGVhZGVyXG4gICAgICAgIGlmICh0aGlzLmluSGVhZGVyKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAvLyBDaGVjayBmb3IgY29sdW1uIGNoYW5nZVxuICAgICAgICBjb25zdCBjb2x1bW5BdFBvaW50ID0gdGhpcy5nZXRDb2x1bW5BdFBvaW50KGUuY2xpZW50WCk7XG4gICAgICAgIC8vIEZvciBoZWFkZXIgaXRlbXMgZW50ZXJpbmcgZ3JpZCwgc2V0IGluaXRpYWwgY29sdW1uXG4gICAgICAgIGlmICh0aGlzLmRyYWdTdGF0ZS5kcmFnU291cmNlID09PSAnaGVhZGVyJyAmJiBjb2x1bW5BdFBvaW50ICYmICF0aGlzLmRyYWdTdGF0ZS5jdXJyZW50Q29sdW1uKSB7XG4gICAgICAgICAgICB0aGlzLmRyYWdTdGF0ZS5jdXJyZW50Q29sdW1uID0gY29sdW1uQXRQb2ludDtcbiAgICAgICAgICAgIHRoaXMuZHJhZ1N0YXRlLmNvbHVtbkVsZW1lbnQgPSBjb2x1bW5BdFBvaW50O1xuICAgICAgICB9XG4gICAgICAgIGlmIChjb2x1bW5BdFBvaW50ICYmIGNvbHVtbkF0UG9pbnQgIT09IHRoaXMuZHJhZ1N0YXRlLmN1cnJlbnRDb2x1bW4gJiYgdGhpcy5kcmFnU3RhdGUuY3VycmVudENvbHVtbikge1xuICAgICAgICAgICAgY29uc3QgcGF5bG9hZCA9IHtcbiAgICAgICAgICAgICAgICBldmVudElkOiB0aGlzLmRyYWdTdGF0ZS5ldmVudElkLFxuICAgICAgICAgICAgICAgIGVsZW1lbnQ6IHRoaXMuZHJhZ1N0YXRlLmVsZW1lbnQsXG4gICAgICAgICAgICAgICAgcHJldmlvdXNDb2x1bW46IHRoaXMuZHJhZ1N0YXRlLmN1cnJlbnRDb2x1bW4sXG4gICAgICAgICAgICAgICAgbmV3Q29sdW1uOiBjb2x1bW5BdFBvaW50LFxuICAgICAgICAgICAgICAgIGN1cnJlbnRZOiB0aGlzLmRyYWdTdGF0ZS5jdXJyZW50WVxuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHRoaXMuZXZlbnRCdXMuZW1pdChDb3JlRXZlbnRzLkVWRU5UX0RSQUdfQ09MVU1OX0NIQU5HRSwgcGF5bG9hZCk7XG4gICAgICAgICAgICB0aGlzLmRyYWdTdGF0ZS5jdXJyZW50Q29sdW1uID0gY29sdW1uQXRQb2ludDtcbiAgICAgICAgICAgIHRoaXMuZHJhZ1N0YXRlLmNvbHVtbkVsZW1lbnQgPSBjb2x1bW5BdFBvaW50O1xuICAgICAgICB9XG4gICAgICAgIC8vIFNraXAgZ3JpZCBwb3NpdGlvbiB1cGRhdGVzIGlmIG5vIGNvbHVtbiB5ZXRcbiAgICAgICAgaWYgKCF0aGlzLmRyYWdTdGF0ZS5jb2x1bW5FbGVtZW50KVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICBjb25zdCBjb2x1bW5SZWN0ID0gdGhpcy5kcmFnU3RhdGUuY29sdW1uRWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcbiAgICAgICAgY29uc3QgdGFyZ2V0WSA9IGUuY2xpZW50WSAtIGNvbHVtblJlY3QudG9wIC0gdGhpcy5kcmFnU3RhdGUubW91c2VPZmZzZXQueTtcbiAgICAgICAgdGhpcy5kcmFnU3RhdGUudGFyZ2V0WSA9IE1hdGgubWF4KDAsIHRhcmdldFkpO1xuICAgICAgICAvLyBTdGFydCBhbmltYXRpb24gaWYgbm90IHJ1bm5pbmdcbiAgICAgICAgaWYgKCF0aGlzLmRyYWdTdGF0ZS5hbmltYXRpb25JZCkge1xuICAgICAgICAgICAgdGhpcy5hbmltYXRlRHJhZygpO1xuICAgICAgICB9XG4gICAgfVxuICAgIC8qKlxuICAgICAqIENoZWNrIGlmIHBvaW50ZXIgaXMgaW4gaGVhZGVyIHpvbmUgYW5kIGVtaXQgYXBwcm9wcmlhdGUgZXZlbnRzXG4gICAgICovXG4gICAgY2hlY2tIZWFkZXJab25lKGUpIHtcbiAgICAgICAgaWYgKCF0aGlzLmRyYWdTdGF0ZSlcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgY29uc3QgaGVhZGVyVmlld3BvcnQgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdzd3AtaGVhZGVyLXZpZXdwb3J0Jyk7XG4gICAgICAgIGlmICghaGVhZGVyVmlld3BvcnQpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIGNvbnN0IHJlY3QgPSBoZWFkZXJWaWV3cG9ydC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcbiAgICAgICAgY29uc3QgaXNJbkhlYWRlciA9IGUuY2xpZW50WSA8IHJlY3QuYm90dG9tO1xuICAgICAgICBpZiAoaXNJbkhlYWRlciAmJiAhdGhpcy5pbkhlYWRlcikge1xuICAgICAgICAgICAgLy8gRW50ZXJlZCBoZWFkZXIgKGZyb20gZ3JpZClcbiAgICAgICAgICAgIHRoaXMuaW5IZWFkZXIgPSB0cnVlO1xuICAgICAgICAgICAgaWYgKHRoaXMuZHJhZ1N0YXRlLmRyYWdTb3VyY2UgPT09ICdncmlkJyAmJiB0aGlzLmRyYWdTdGF0ZS5jb2x1bW5FbGVtZW50KSB7XG4gICAgICAgICAgICAgICAgY29uc3QgcGF5bG9hZCA9IHtcbiAgICAgICAgICAgICAgICAgICAgZXZlbnRJZDogdGhpcy5kcmFnU3RhdGUuZXZlbnRJZCxcbiAgICAgICAgICAgICAgICAgICAgZWxlbWVudDogdGhpcy5kcmFnU3RhdGUuZWxlbWVudCxcbiAgICAgICAgICAgICAgICAgICAgc291cmNlQ29sdW1uSW5kZXg6IHRoaXMuZ2V0Q29sdW1uSW5kZXgodGhpcy5kcmFnU3RhdGUuY29sdW1uRWxlbWVudCksXG4gICAgICAgICAgICAgICAgICAgIHNvdXJjZUNvbHVtbktleTogdGhpcy5kcmFnU3RhdGUuY29sdW1uRWxlbWVudC5kYXRhc2V0LmNvbHVtbktleSB8fCAnJyxcbiAgICAgICAgICAgICAgICAgICAgdGl0bGU6IHRoaXMuZHJhZ1N0YXRlLmVsZW1lbnQucXVlcnlTZWxlY3Rvcignc3dwLWV2ZW50LXRpdGxlJyk/LnRleHRDb250ZW50IHx8ICcnLFxuICAgICAgICAgICAgICAgICAgICBjb2xvckNsYXNzOiBbLi4udGhpcy5kcmFnU3RhdGUuZWxlbWVudC5jbGFzc0xpc3RdLmZpbmQoYyA9PiBjLnN0YXJ0c1dpdGgoJ2lzLScpKSxcbiAgICAgICAgICAgICAgICAgICAgaXRlbVR5cGU6ICdldmVudCcsXG4gICAgICAgICAgICAgICAgICAgIGR1cmF0aW9uOiAxXG4gICAgICAgICAgICAgICAgfTtcbiAgICAgICAgICAgICAgICB0aGlzLmV2ZW50QnVzLmVtaXQoQ29yZUV2ZW50cy5FVkVOVF9EUkFHX0VOVEVSX0hFQURFUiwgcGF5bG9hZCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBGb3IgaGVhZGVyIHNvdXJjZSByZS1lbnRlcmluZyBoZWFkZXIsIGp1c3QgdXBkYXRlIGluSGVhZGVyIGZsYWdcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmICghaXNJbkhlYWRlciAmJiB0aGlzLmluSGVhZGVyKSB7XG4gICAgICAgICAgICAvLyBMZWZ0IGhlYWRlciAoZW50ZXJpbmcgZ3JpZClcbiAgICAgICAgICAgIHRoaXMuaW5IZWFkZXIgPSBmYWxzZTtcbiAgICAgICAgICAgIGNvbnN0IHRhcmdldENvbHVtbiA9IHRoaXMuZ2V0Q29sdW1uQXRQb2ludChlLmNsaWVudFgpO1xuICAgICAgICAgICAgaWYgKHRoaXMuZHJhZ1N0YXRlLmRyYWdTb3VyY2UgPT09ICdoZWFkZXInKSB7XG4gICAgICAgICAgICAgICAgLy8gSGVhZGVyIGl0ZW0gbGVhdmluZyBoZWFkZXIgXHUyMTkyIGNyZWF0ZSBzd3AtZXZlbnQgaW4gZ3JpZFxuICAgICAgICAgICAgICAgIGNvbnN0IHBheWxvYWQgPSB7XG4gICAgICAgICAgICAgICAgICAgIGV2ZW50SWQ6IHRoaXMuZHJhZ1N0YXRlLmV2ZW50SWQsXG4gICAgICAgICAgICAgICAgICAgIHNvdXJjZTogJ2hlYWRlcicsXG4gICAgICAgICAgICAgICAgICAgIGVsZW1lbnQ6IHRoaXMuZHJhZ1N0YXRlLmVsZW1lbnQsXG4gICAgICAgICAgICAgICAgICAgIHRhcmdldENvbHVtbjogdGFyZ2V0Q29sdW1uIHx8IHVuZGVmaW5lZCxcbiAgICAgICAgICAgICAgICAgICAgc3RhcnQ6IHRoaXMuZHJhZ1N0YXRlLmVsZW1lbnQuZGF0YXNldC5zdGFydCA/IG5ldyBEYXRlKHRoaXMuZHJhZ1N0YXRlLmVsZW1lbnQuZGF0YXNldC5zdGFydCkgOiB1bmRlZmluZWQsXG4gICAgICAgICAgICAgICAgICAgIGVuZDogdGhpcy5kcmFnU3RhdGUuZWxlbWVudC5kYXRhc2V0LmVuZCA/IG5ldyBEYXRlKHRoaXMuZHJhZ1N0YXRlLmVsZW1lbnQuZGF0YXNldC5lbmQpIDogdW5kZWZpbmVkLFxuICAgICAgICAgICAgICAgICAgICB0aXRsZTogdGhpcy5kcmFnU3RhdGUuZWxlbWVudC50ZXh0Q29udGVudCB8fCAnJyxcbiAgICAgICAgICAgICAgICAgICAgY29sb3JDbGFzczogWy4uLnRoaXMuZHJhZ1N0YXRlLmVsZW1lbnQuY2xhc3NMaXN0XS5maW5kKGMgPT4gYy5zdGFydHNXaXRoKCdpcy0nKSlcbiAgICAgICAgICAgICAgICB9O1xuICAgICAgICAgICAgICAgIHRoaXMuZXZlbnRCdXMuZW1pdChDb3JlRXZlbnRzLkVWRU5UX0RSQUdfTEVBVkVfSEVBREVSLCBwYXlsb2FkKTtcbiAgICAgICAgICAgICAgICAvLyBSZS1hdHRhY2ggdG8gdGhlIG5ldyBzd3AtZXZlbnQgY3JlYXRlZCBieSBFdmVudFJlbmRlcmVyXG4gICAgICAgICAgICAgICAgaWYgKHRhcmdldENvbHVtbikge1xuICAgICAgICAgICAgICAgICAgICBjb25zdCBuZXdFbGVtZW50ID0gdGFyZ2V0Q29sdW1uLnF1ZXJ5U2VsZWN0b3IoYHN3cC1ldmVudFtkYXRhLWV2ZW50LWlkPVwiJHt0aGlzLmRyYWdTdGF0ZS5ldmVudElkfVwiXWApO1xuICAgICAgICAgICAgICAgICAgICBpZiAobmV3RWxlbWVudCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5kcmFnU3RhdGUuZWxlbWVudCA9IG5ld0VsZW1lbnQ7XG4gICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmRyYWdTdGF0ZS5jb2x1bW5FbGVtZW50ID0gdGFyZ2V0Q29sdW1uO1xuICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5kcmFnU3RhdGUuY3VycmVudENvbHVtbiA9IHRhcmdldENvbHVtbjtcbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIFN0YXJ0IGFuaW1hdGlvbiBmb3IgdGhlIG5ldyBlbGVtZW50XG4gICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmFuaW1hdGVEcmFnKCk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICAvLyBHcmlkIGV2ZW50IGxlYXZpbmcgaGVhZGVyIFx1MjE5MiByZXN0b3JlIHRvIGdyaWRcbiAgICAgICAgICAgICAgICBjb25zdCBwYXlsb2FkID0ge1xuICAgICAgICAgICAgICAgICAgICBldmVudElkOiB0aGlzLmRyYWdTdGF0ZS5ldmVudElkLFxuICAgICAgICAgICAgICAgICAgICBzb3VyY2U6ICdncmlkJ1xuICAgICAgICAgICAgICAgIH07XG4gICAgICAgICAgICAgICAgdGhpcy5ldmVudEJ1cy5lbWl0KENvcmVFdmVudHMuRVZFTlRfRFJBR19MRUFWRV9IRUFERVIsIHBheWxvYWQpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKGlzSW5IZWFkZXIpIHtcbiAgICAgICAgICAgIC8vIE1vdmluZyB3aXRoaW4gaGVhZGVyXG4gICAgICAgICAgICBjb25zdCBjb2x1bW4gPSB0aGlzLmdldENvbHVtbkF0WChlLmNsaWVudFgpO1xuICAgICAgICAgICAgaWYgKGNvbHVtbikge1xuICAgICAgICAgICAgICAgIGNvbnN0IHBheWxvYWQgPSB7XG4gICAgICAgICAgICAgICAgICAgIGV2ZW50SWQ6IHRoaXMuZHJhZ1N0YXRlLmV2ZW50SWQsXG4gICAgICAgICAgICAgICAgICAgIGNvbHVtbkluZGV4OiB0aGlzLmdldENvbHVtbkluZGV4KGNvbHVtbiksXG4gICAgICAgICAgICAgICAgICAgIGNvbHVtbktleTogY29sdW1uLmRhdGFzZXQuY29sdW1uS2V5IHx8ICcnXG4gICAgICAgICAgICAgICAgfTtcbiAgICAgICAgICAgICAgICB0aGlzLmV2ZW50QnVzLmVtaXQoQ29yZUV2ZW50cy5FVkVOVF9EUkFHX01PVkVfSEVBREVSLCBwYXlsb2FkKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBHZXQgY29sdW1uIGluZGV4ICgwLWJhc2VkKSBmb3IgYSBjb2x1bW4gZWxlbWVudFxuICAgICAqL1xuICAgIGdldENvbHVtbkluZGV4KGNvbHVtbikge1xuICAgICAgICBpZiAoIXRoaXMuY29udGFpbmVyIHx8ICFjb2x1bW4pXG4gICAgICAgICAgICByZXR1cm4gMDtcbiAgICAgICAgY29uc3QgY29sdW1ucyA9IEFycmF5LmZyb20odGhpcy5jb250YWluZXIucXVlcnlTZWxlY3RvckFsbCgnc3dwLWRheS1jb2x1bW4nKSk7XG4gICAgICAgIHJldHVybiBjb2x1bW5zLmluZGV4T2YoY29sdW1uKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IGNvbHVtbiBhdCBYIGNvb3JkaW5hdGUgKGFsaWFzIGZvciBnZXRDb2x1bW5BdFBvaW50KVxuICAgICAqL1xuICAgIGdldENvbHVtbkF0WChjbGllbnRYKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmdldENvbHVtbkF0UG9pbnQoY2xpZW50WCk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEZpbmQgY29sdW1uIGVsZW1lbnQgYXQgZ2l2ZW4gWCBjb29yZGluYXRlXG4gICAgICovXG4gICAgZ2V0Q29sdW1uQXRQb2ludChjbGllbnRYKSB7XG4gICAgICAgIGlmICghdGhpcy5jb250YWluZXIpXG4gICAgICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgICAgY29uc3QgY29sdW1ucyA9IHRoaXMuY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3JBbGwoJ3N3cC1kYXktY29sdW1uJyk7XG4gICAgICAgIGZvciAoY29uc3QgY29sIG9mIGNvbHVtbnMpIHtcbiAgICAgICAgICAgIGNvbnN0IHJlY3QgPSBjb2wuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG4gICAgICAgICAgICBpZiAoY2xpZW50WCA+PSByZWN0LmxlZnQgJiYgY2xpZW50WCA8PSByZWN0LnJpZ2h0KSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIGNvbDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG4gICAgLyoqXG4gICAgICogQ2FuY2VsIGRyYWcgYW5kIGFuaW1hdGUgYmFjayB0byBzdGFydCBwb3NpdGlvblxuICAgICAqL1xuICAgIGNhbmNlbERyYWcoKSB7XG4gICAgICAgIGlmICghdGhpcy5kcmFnU3RhdGUpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIC8vIFN0b3AgYW5pbWF0aW9uXG4gICAgICAgIGNhbmNlbEFuaW1hdGlvbkZyYW1lKHRoaXMuZHJhZ1N0YXRlLmFuaW1hdGlvbklkKTtcbiAgICAgICAgY29uc3QgeyBlbGVtZW50LCBnaG9zdEVsZW1lbnQsIHN0YXJ0WSwgZXZlbnRJZCB9ID0gdGhpcy5kcmFnU3RhdGU7XG4gICAgICAgIC8vIEFuaW1hdGUgYmFjayB0byBzdGFydFxuICAgICAgICBlbGVtZW50LnN0eWxlLnRyYW5zaXRpb24gPSAndG9wIDIwMG1zIGVhc2Utb3V0JztcbiAgICAgICAgZWxlbWVudC5zdHlsZS50b3AgPSBgJHtzdGFydFl9cHhgO1xuICAgICAgICAvLyBSZW1vdmUgZ2hvc3QgYWZ0ZXIgYW5pbWF0aW9uIChpZiBleGlzdHMpXG4gICAgICAgIHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgICAgICAgZ2hvc3RFbGVtZW50Py5yZW1vdmUoKTtcbiAgICAgICAgICAgIGVsZW1lbnQuc3R5bGUudHJhbnNpdGlvbiA9ICcnO1xuICAgICAgICAgICAgZWxlbWVudC5jbGFzc0xpc3QucmVtb3ZlKCdkcmFnZ2luZycpO1xuICAgICAgICB9LCAyMDApO1xuICAgICAgICAvLyBFbWl0IGRyYWc6Y2FuY2VsXG4gICAgICAgIGNvbnN0IHBheWxvYWQgPSB7XG4gICAgICAgICAgICBldmVudElkLFxuICAgICAgICAgICAgZWxlbWVudCxcbiAgICAgICAgICAgIHN0YXJ0WVxuICAgICAgICB9O1xuICAgICAgICB0aGlzLmV2ZW50QnVzLmVtaXQoQ29yZUV2ZW50cy5FVkVOVF9EUkFHX0NBTkNFTCwgcGF5bG9hZCk7XG4gICAgICAgIHRoaXMuZHJhZ1N0YXRlID0gbnVsbDtcbiAgICAgICAgdGhpcy5pbkhlYWRlciA9IGZhbHNlO1xuICAgIH1cbn1cbiIsICJpbXBvcnQgeyBDb3JlRXZlbnRzIH0gZnJvbSAnLi4vY29uc3RhbnRzL0NvcmVFdmVudHMnO1xuZXhwb3J0IGNsYXNzIEVkZ2VTY3JvbGxNYW5hZ2VyIHtcbiAgICBjb25zdHJ1Y3RvcihldmVudEJ1cykge1xuICAgICAgICB0aGlzLmV2ZW50QnVzID0gZXZlbnRCdXM7XG4gICAgICAgIHRoaXMuc2Nyb2xsYWJsZUNvbnRlbnQgPSBudWxsO1xuICAgICAgICB0aGlzLnRpbWVHcmlkID0gbnVsbDtcbiAgICAgICAgdGhpcy5kcmFnZ2VkRWxlbWVudCA9IG51bGw7XG4gICAgICAgIHRoaXMuc2Nyb2xsUkFGID0gbnVsbDtcbiAgICAgICAgdGhpcy5tb3VzZVkgPSAwO1xuICAgICAgICB0aGlzLmlzRHJhZ2dpbmcgPSBmYWxzZTtcbiAgICAgICAgdGhpcy5pc1Njcm9sbGluZyA9IGZhbHNlO1xuICAgICAgICB0aGlzLmxhc3RUcyA9IDA7XG4gICAgICAgIHRoaXMucmVjdCA9IG51bGw7XG4gICAgICAgIHRoaXMuaW5pdGlhbFNjcm9sbFRvcCA9IDA7XG4gICAgICAgIHRoaXMuT1VURVJfWk9ORSA9IDEwMDtcbiAgICAgICAgdGhpcy5JTk5FUl9aT05FID0gNTA7XG4gICAgICAgIHRoaXMuU0xPV19TUEVFRCA9IDE0MDtcbiAgICAgICAgdGhpcy5GQVNUX1NQRUVEID0gNjQwO1xuICAgICAgICB0aGlzLnRyYWNrTW91c2UgPSAoZSkgPT4ge1xuICAgICAgICAgICAgaWYgKHRoaXMuaXNEcmFnZ2luZykge1xuICAgICAgICAgICAgICAgIHRoaXMubW91c2VZID0gZS5jbGllbnRZO1xuICAgICAgICAgICAgfVxuICAgICAgICB9O1xuICAgICAgICB0aGlzLnNjcm9sbFRpY2sgPSAodHMpID0+IHtcbiAgICAgICAgICAgIGlmICghdGhpcy5pc0RyYWdnaW5nIHx8ICF0aGlzLnNjcm9sbGFibGVDb250ZW50KVxuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIGNvbnN0IGR0ID0gdGhpcy5sYXN0VHMgPyAodHMgLSB0aGlzLmxhc3RUcykgLyAxMDAwIDogMDtcbiAgICAgICAgICAgIHRoaXMubGFzdFRzID0gdHM7XG4gICAgICAgICAgICB0aGlzLnJlY3QgPz8gKHRoaXMucmVjdCA9IHRoaXMuc2Nyb2xsYWJsZUNvbnRlbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkpO1xuICAgICAgICAgICAgY29uc3QgdmVsb2NpdHkgPSB0aGlzLmNhbGN1bGF0ZVZlbG9jaXR5KCk7XG4gICAgICAgICAgICBpZiAodmVsb2NpdHkgIT09IDAgJiYgIXRoaXMuaXNBdEJvdW5kYXJ5KHZlbG9jaXR5KSkge1xuICAgICAgICAgICAgICAgIGNvbnN0IHNjcm9sbERlbHRhID0gdmVsb2NpdHkgKiBkdDtcbiAgICAgICAgICAgICAgICB0aGlzLnNjcm9sbGFibGVDb250ZW50LnNjcm9sbFRvcCArPSBzY3JvbGxEZWx0YTtcbiAgICAgICAgICAgICAgICB0aGlzLnJlY3QgPSBudWxsO1xuICAgICAgICAgICAgICAgIHRoaXMuZXZlbnRCdXMuZW1pdChDb3JlRXZlbnRzLkVER0VfU0NST0xMX1RJQ0ssIHsgc2Nyb2xsRGVsdGEgfSk7XG4gICAgICAgICAgICAgICAgdGhpcy5zZXRTY3JvbGxpbmdTdGF0ZSh0cnVlKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIHRoaXMuc2V0U2Nyb2xsaW5nU3RhdGUoZmFsc2UpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdGhpcy5zY3JvbGxSQUYgPSByZXF1ZXN0QW5pbWF0aW9uRnJhbWUodGhpcy5zY3JvbGxUaWNrKTtcbiAgICAgICAgfTtcbiAgICAgICAgdGhpcy5zdWJzY3JpYmVUb0V2ZW50cygpO1xuICAgICAgICBkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdwb2ludGVybW92ZScsIHRoaXMudHJhY2tNb3VzZSk7XG4gICAgfVxuICAgIGluaXQoc2Nyb2xsYWJsZUNvbnRlbnQpIHtcbiAgICAgICAgdGhpcy5zY3JvbGxhYmxlQ29udGVudCA9IHNjcm9sbGFibGVDb250ZW50O1xuICAgICAgICB0aGlzLnRpbWVHcmlkID0gc2Nyb2xsYWJsZUNvbnRlbnQucXVlcnlTZWxlY3Rvcignc3dwLXRpbWUtZ3JpZCcpO1xuICAgICAgICB0aGlzLnNjcm9sbGFibGVDb250ZW50LnN0eWxlLnNjcm9sbEJlaGF2aW9yID0gJ2F1dG8nO1xuICAgIH1cbiAgICBzdWJzY3JpYmVUb0V2ZW50cygpIHtcbiAgICAgICAgdGhpcy5ldmVudEJ1cy5vbihDb3JlRXZlbnRzLkVWRU5UX0RSQUdfU1RBUlQsIChldmVudCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgcGF5bG9hZCA9IGV2ZW50LmRldGFpbDtcbiAgICAgICAgICAgIHRoaXMuZHJhZ2dlZEVsZW1lbnQgPSBwYXlsb2FkLmVsZW1lbnQ7XG4gICAgICAgICAgICB0aGlzLnN0YXJ0RHJhZygpO1xuICAgICAgICB9KTtcbiAgICAgICAgdGhpcy5ldmVudEJ1cy5vbihDb3JlRXZlbnRzLkVWRU5UX0RSQUdfRU5ELCAoKSA9PiB0aGlzLnN0b3BEcmFnKCkpO1xuICAgICAgICB0aGlzLmV2ZW50QnVzLm9uKENvcmVFdmVudHMuRVZFTlRfRFJBR19DQU5DRUwsICgpID0+IHRoaXMuc3RvcERyYWcoKSk7XG4gICAgfVxuICAgIHN0YXJ0RHJhZygpIHtcbiAgICAgICAgdGhpcy5pc0RyYWdnaW5nID0gdHJ1ZTtcbiAgICAgICAgdGhpcy5pc1Njcm9sbGluZyA9IGZhbHNlO1xuICAgICAgICB0aGlzLmxhc3RUcyA9IDA7XG4gICAgICAgIHRoaXMuaW5pdGlhbFNjcm9sbFRvcCA9IHRoaXMuc2Nyb2xsYWJsZUNvbnRlbnQ/LnNjcm9sbFRvcCA/PyAwO1xuICAgICAgICBpZiAodGhpcy5zY3JvbGxSQUYgPT09IG51bGwpIHtcbiAgICAgICAgICAgIHRoaXMuc2Nyb2xsUkFGID0gcmVxdWVzdEFuaW1hdGlvbkZyYW1lKHRoaXMuc2Nyb2xsVGljayk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgc3RvcERyYWcoKSB7XG4gICAgICAgIHRoaXMuaXNEcmFnZ2luZyA9IGZhbHNlO1xuICAgICAgICB0aGlzLnNldFNjcm9sbGluZ1N0YXRlKGZhbHNlKTtcbiAgICAgICAgaWYgKHRoaXMuc2Nyb2xsUkFGICE9PSBudWxsKSB7XG4gICAgICAgICAgICBjYW5jZWxBbmltYXRpb25GcmFtZSh0aGlzLnNjcm9sbFJBRik7XG4gICAgICAgICAgICB0aGlzLnNjcm9sbFJBRiA9IG51bGw7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5yZWN0ID0gbnVsbDtcbiAgICAgICAgdGhpcy5sYXN0VHMgPSAwO1xuICAgICAgICB0aGlzLmluaXRpYWxTY3JvbGxUb3AgPSAwO1xuICAgIH1cbiAgICBjYWxjdWxhdGVWZWxvY2l0eSgpIHtcbiAgICAgICAgaWYgKCF0aGlzLnJlY3QpXG4gICAgICAgICAgICByZXR1cm4gMDtcbiAgICAgICAgY29uc3QgZGlzdFRvcCA9IHRoaXMubW91c2VZIC0gdGhpcy5yZWN0LnRvcDtcbiAgICAgICAgY29uc3QgZGlzdEJvdCA9IHRoaXMucmVjdC5ib3R0b20gLSB0aGlzLm1vdXNlWTtcbiAgICAgICAgaWYgKGRpc3RUb3AgPCB0aGlzLklOTkVSX1pPTkUpXG4gICAgICAgICAgICByZXR1cm4gLXRoaXMuRkFTVF9TUEVFRDtcbiAgICAgICAgaWYgKGRpc3RUb3AgPCB0aGlzLk9VVEVSX1pPTkUpXG4gICAgICAgICAgICByZXR1cm4gLXRoaXMuU0xPV19TUEVFRDtcbiAgICAgICAgaWYgKGRpc3RCb3QgPCB0aGlzLklOTkVSX1pPTkUpXG4gICAgICAgICAgICByZXR1cm4gdGhpcy5GQVNUX1NQRUVEO1xuICAgICAgICBpZiAoZGlzdEJvdCA8IHRoaXMuT1VURVJfWk9ORSlcbiAgICAgICAgICAgIHJldHVybiB0aGlzLlNMT1dfU1BFRUQ7XG4gICAgICAgIHJldHVybiAwO1xuICAgIH1cbiAgICBpc0F0Qm91bmRhcnkodmVsb2NpdHkpIHtcbiAgICAgICAgaWYgKCF0aGlzLnNjcm9sbGFibGVDb250ZW50IHx8ICF0aGlzLnRpbWVHcmlkIHx8ICF0aGlzLmRyYWdnZWRFbGVtZW50KVxuICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICBjb25zdCBhdFRvcCA9IHRoaXMuc2Nyb2xsYWJsZUNvbnRlbnQuc2Nyb2xsVG9wIDw9IDAgJiYgdmVsb2NpdHkgPCAwO1xuICAgICAgICBjb25zdCBhdEJvdHRvbSA9IHZlbG9jaXR5ID4gMCAmJlxuICAgICAgICAgICAgdGhpcy5kcmFnZ2VkRWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS5ib3R0b20gPj1cbiAgICAgICAgICAgICAgICB0aGlzLnRpbWVHcmlkLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLmJvdHRvbTtcbiAgICAgICAgcmV0dXJuIGF0VG9wIHx8IGF0Qm90dG9tO1xuICAgIH1cbiAgICBzZXRTY3JvbGxpbmdTdGF0ZShzY3JvbGxpbmcpIHtcbiAgICAgICAgaWYgKHRoaXMuaXNTY3JvbGxpbmcgPT09IHNjcm9sbGluZylcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgdGhpcy5pc1Njcm9sbGluZyA9IHNjcm9sbGluZztcbiAgICAgICAgaWYgKHNjcm9sbGluZykge1xuICAgICAgICAgICAgdGhpcy5ldmVudEJ1cy5lbWl0KENvcmVFdmVudHMuRURHRV9TQ1JPTExfU1RBUlRFRCwge30pO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgdGhpcy5pbml0aWFsU2Nyb2xsVG9wID0gdGhpcy5zY3JvbGxhYmxlQ29udGVudD8uc2Nyb2xsVG9wID8/IDA7XG4gICAgICAgICAgICB0aGlzLmV2ZW50QnVzLmVtaXQoQ29yZUV2ZW50cy5FREdFX1NDUk9MTF9TVE9QUEVELCB7fSk7XG4gICAgICAgIH1cbiAgICB9XG59XG4iLCAiaW1wb3J0IHsgcGl4ZWxzVG9NaW51dGVzLCBtaW51dGVzVG9QaXhlbHMsIHNuYXBUb0dyaWQgfSBmcm9tICcuLi91dGlscy9Qb3NpdGlvblV0aWxzJztcbmltcG9ydCB7IENvcmVFdmVudHMgfSBmcm9tICcuLi9jb25zdGFudHMvQ29yZUV2ZW50cyc7XG5pbXBvcnQgeyBTd3BFdmVudCB9IGZyb20gJy4uL3R5cGVzL1N3cEV2ZW50JztcbmV4cG9ydCBjbGFzcyBSZXNpemVNYW5hZ2VyIHtcbiAgICBjb25zdHJ1Y3RvcihldmVudEJ1cywgZ3JpZENvbmZpZywgZGF0ZVNlcnZpY2UpIHtcbiAgICAgICAgdGhpcy5ldmVudEJ1cyA9IGV2ZW50QnVzO1xuICAgICAgICB0aGlzLmdyaWRDb25maWcgPSBncmlkQ29uZmlnO1xuICAgICAgICB0aGlzLmRhdGVTZXJ2aWNlID0gZGF0ZVNlcnZpY2U7XG4gICAgICAgIHRoaXMuY29udGFpbmVyID0gbnVsbDtcbiAgICAgICAgdGhpcy5yZXNpemVTdGF0ZSA9IG51bGw7XG4gICAgICAgIHRoaXMuWl9JTkRFWF9SRVNJWklORyA9ICcxMDAwJztcbiAgICAgICAgdGhpcy5BTklNQVRJT05fU1BFRUQgPSAwLjM1O1xuICAgICAgICB0aGlzLk1JTl9IRUlHSFRfTUlOVVRFUyA9IDE1O1xuICAgICAgICAvKipcbiAgICAgICAgICogSGFuZGxlIG1vdXNlb3ZlciAtIGNyZWF0ZSByZXNpemUgaGFuZGxlIGlmIG5vdCBleGlzdHNcbiAgICAgICAgICovXG4gICAgICAgIHRoaXMuaGFuZGxlTW91c2VPdmVyID0gKGUpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHRhcmdldCA9IGUudGFyZ2V0O1xuICAgICAgICAgICAgY29uc3QgZXZlbnRFbGVtZW50ID0gdGFyZ2V0LmNsb3Nlc3QoJ3N3cC1ldmVudCcpO1xuICAgICAgICAgICAgaWYgKCFldmVudEVsZW1lbnQgfHwgdGhpcy5yZXNpemVTdGF0ZSlcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAvLyBDaGVjayBpZiBoYW5kbGUgYWxyZWFkeSBleGlzdHNcbiAgICAgICAgICAgIGlmICghZXZlbnRFbGVtZW50LnF1ZXJ5U2VsZWN0b3IoJzpzY29wZSA+IHN3cC1yZXNpemUtaGFuZGxlJykpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBoYW5kbGUgPSB0aGlzLmNyZWF0ZVJlc2l6ZUhhbmRsZSgpO1xuICAgICAgICAgICAgICAgIGV2ZW50RWxlbWVudC5hcHBlbmRDaGlsZChoYW5kbGUpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9O1xuICAgICAgICAvKipcbiAgICAgICAgICogSGFuZGxlIHBvaW50ZXJkb3duIC0gc3RhcnQgcmVzaXplIGlmIG9uIGhhbmRsZVxuICAgICAgICAgKi9cbiAgICAgICAgdGhpcy5oYW5kbGVQb2ludGVyRG93biA9IChlKSA9PiB7XG4gICAgICAgICAgICBjb25zdCBoYW5kbGUgPSBlLnRhcmdldC5jbG9zZXN0KCdzd3AtcmVzaXplLWhhbmRsZScpO1xuICAgICAgICAgICAgaWYgKCFoYW5kbGUpXG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgY29uc3QgZWxlbWVudCA9IGhhbmRsZS5wYXJlbnRFbGVtZW50O1xuICAgICAgICAgICAgaWYgKCFlbGVtZW50KVxuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIGNvbnN0IGV2ZW50SWQgPSBlbGVtZW50LmRhdGFzZXQuZXZlbnRJZCB8fCAnJztcbiAgICAgICAgICAgIGNvbnN0IHN0YXJ0SGVpZ2h0ID0gZWxlbWVudC5vZmZzZXRIZWlnaHQ7XG4gICAgICAgICAgICBjb25zdCBzdGFydER1cmF0aW9uTWludXRlcyA9IHBpeGVsc1RvTWludXRlcyhzdGFydEhlaWdodCwgdGhpcy5ncmlkQ29uZmlnKTtcbiAgICAgICAgICAgIC8vIFN0b3JlIHByZXZpb3VzIHotaW5kZXhcbiAgICAgICAgICAgIGNvbnN0IGNvbnRhaW5lciA9IGVsZW1lbnQuY2xvc2VzdCgnc3dwLWV2ZW50LWdyb3VwJykgPz8gZWxlbWVudDtcbiAgICAgICAgICAgIGNvbnN0IHByZXZaSW5kZXggPSBjb250YWluZXIuc3R5bGUuekluZGV4O1xuICAgICAgICAgICAgLy8gU2V0IHJlc2l6ZSBzdGF0ZVxuICAgICAgICAgICAgdGhpcy5yZXNpemVTdGF0ZSA9IHtcbiAgICAgICAgICAgICAgICBldmVudElkLFxuICAgICAgICAgICAgICAgIGVsZW1lbnQsXG4gICAgICAgICAgICAgICAgaGFuZGxlRWxlbWVudDogaGFuZGxlLFxuICAgICAgICAgICAgICAgIHN0YXJ0WTogZS5jbGllbnRZLFxuICAgICAgICAgICAgICAgIHN0YXJ0SGVpZ2h0LFxuICAgICAgICAgICAgICAgIHN0YXJ0RHVyYXRpb25NaW51dGVzLFxuICAgICAgICAgICAgICAgIHBvaW50ZXJJZDogZS5wb2ludGVySWQsXG4gICAgICAgICAgICAgICAgcHJldlpJbmRleCxcbiAgICAgICAgICAgICAgICAvLyBBbmltYXRpb24gc3RhdGVcbiAgICAgICAgICAgICAgICBjdXJyZW50SGVpZ2h0OiBzdGFydEhlaWdodCxcbiAgICAgICAgICAgICAgICB0YXJnZXRIZWlnaHQ6IHN0YXJ0SGVpZ2h0LFxuICAgICAgICAgICAgICAgIGFuaW1hdGlvbklkOiBudWxsXG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgLy8gRWxldmF0ZSB6LWluZGV4XG4gICAgICAgICAgICBjb250YWluZXIuc3R5bGUuekluZGV4ID0gdGhpcy5aX0lOREVYX1JFU0laSU5HO1xuICAgICAgICAgICAgLy8gQ2FwdHVyZSBwb2ludGVyIGZvciBzbW9vdGggdHJhY2tpbmdcbiAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgaGFuZGxlLnNldFBvaW50ZXJDYXB0dXJlKGUucG9pbnRlcklkKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNhdGNoIChlcnIpIHtcbiAgICAgICAgICAgICAgICBjb25zb2xlLndhcm4oJ1BvaW50ZXIgY2FwdHVyZSBmYWlsZWQ6JywgZXJyKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIEFkZCBnbG9iYWwgcmVzaXppbmcgY2xhc3NcbiAgICAgICAgICAgIGRvY3VtZW50LmRvY3VtZW50RWxlbWVudC5jbGFzc0xpc3QuYWRkKCdzd3AtLXJlc2l6aW5nJyk7XG4gICAgICAgICAgICAvLyBFbWl0IHJlc2l6ZSBzdGFydCBldmVudFxuICAgICAgICAgICAgdGhpcy5ldmVudEJ1cy5lbWl0KENvcmVFdmVudHMuRVZFTlRfUkVTSVpFX1NUQVJULCB7XG4gICAgICAgICAgICAgICAgZXZlbnRJZCxcbiAgICAgICAgICAgICAgICBlbGVtZW50LFxuICAgICAgICAgICAgICAgIHN0YXJ0SGVpZ2h0XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIGUucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgfTtcbiAgICAgICAgLyoqXG4gICAgICAgICAqIEhhbmRsZSBwb2ludGVybW92ZSAtIHVwZGF0ZSB0YXJnZXQgaGVpZ2h0IGR1cmluZyByZXNpemVcbiAgICAgICAgICovXG4gICAgICAgIHRoaXMuaGFuZGxlUG9pbnRlck1vdmUgPSAoZSkgPT4ge1xuICAgICAgICAgICAgaWYgKCF0aGlzLnJlc2l6ZVN0YXRlKVxuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIGNvbnN0IGRlbHRhWSA9IGUuY2xpZW50WSAtIHRoaXMucmVzaXplU3RhdGUuc3RhcnRZO1xuICAgICAgICAgICAgY29uc3QgbWluSGVpZ2h0ID0gKHRoaXMuTUlOX0hFSUdIVF9NSU5VVEVTIC8gNjApICogdGhpcy5ncmlkQ29uZmlnLmhvdXJIZWlnaHQ7XG4gICAgICAgICAgICBjb25zdCBuZXdIZWlnaHQgPSBNYXRoLm1heChtaW5IZWlnaHQsIHRoaXMucmVzaXplU3RhdGUuc3RhcnRIZWlnaHQgKyBkZWx0YVkpO1xuICAgICAgICAgICAgLy8gU2V0IHRhcmdldCBoZWlnaHQgZm9yIGFuaW1hdGlvblxuICAgICAgICAgICAgdGhpcy5yZXNpemVTdGF0ZS50YXJnZXRIZWlnaHQgPSBuZXdIZWlnaHQ7XG4gICAgICAgICAgICAvLyBTdGFydCBhbmltYXRpb24gaWYgbm90IHJ1bm5pbmdcbiAgICAgICAgICAgIGlmICh0aGlzLnJlc2l6ZVN0YXRlLmFuaW1hdGlvbklkID09PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5hbmltYXRlSGVpZ2h0KCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH07XG4gICAgICAgIC8qKlxuICAgICAgICAgKiBSQUYgYW5pbWF0aW9uIGxvb3AgZm9yIHNtb290aCBoZWlnaHQgaW50ZXJwb2xhdGlvblxuICAgICAgICAgKi9cbiAgICAgICAgdGhpcy5hbmltYXRlSGVpZ2h0ID0gKCkgPT4ge1xuICAgICAgICAgICAgaWYgKCF0aGlzLnJlc2l6ZVN0YXRlKVxuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIGNvbnN0IGRpZmYgPSB0aGlzLnJlc2l6ZVN0YXRlLnRhcmdldEhlaWdodCAtIHRoaXMucmVzaXplU3RhdGUuY3VycmVudEhlaWdodDtcbiAgICAgICAgICAgIC8vIFN0b3AgYW5pbWF0aW9uIHdoZW4gY2xvc2UgZW5vdWdoXG4gICAgICAgICAgICBpZiAoTWF0aC5hYnMoZGlmZikgPCAwLjUpIHtcbiAgICAgICAgICAgICAgICB0aGlzLnJlc2l6ZVN0YXRlLmFuaW1hdGlvbklkID0gbnVsbDtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBJbnRlcnBvbGF0ZSB0b3dhcmRzIHRhcmdldCAoMzUlIHBlciBmcmFtZSBsaWtlIFYxKVxuICAgICAgICAgICAgdGhpcy5yZXNpemVTdGF0ZS5jdXJyZW50SGVpZ2h0ICs9IGRpZmYgKiB0aGlzLkFOSU1BVElPTl9TUEVFRDtcbiAgICAgICAgICAgIHRoaXMucmVzaXplU3RhdGUuZWxlbWVudC5zdHlsZS5oZWlnaHQgPSBgJHt0aGlzLnJlc2l6ZVN0YXRlLmN1cnJlbnRIZWlnaHR9cHhgO1xuICAgICAgICAgICAgLy8gVXBkYXRlIHRpbWVzdGFtcCBkaXNwbGF5IChzbmFwcGVkKVxuICAgICAgICAgICAgdGhpcy51cGRhdGVUaW1lc3RhbXBEaXNwbGF5KCk7XG4gICAgICAgICAgICAvLyBDb250aW51ZSBhbmltYXRpb25cbiAgICAgICAgICAgIHRoaXMucmVzaXplU3RhdGUuYW5pbWF0aW9uSWQgPSByZXF1ZXN0QW5pbWF0aW9uRnJhbWUodGhpcy5hbmltYXRlSGVpZ2h0KTtcbiAgICAgICAgfTtcbiAgICAgICAgLyoqXG4gICAgICAgICAqIEhhbmRsZSBwb2ludGVydXAgLSBmaW5pc2ggcmVzaXplXG4gICAgICAgICAqL1xuICAgICAgICB0aGlzLmhhbmRsZVBvaW50ZXJVcCA9IChlKSA9PiB7XG4gICAgICAgICAgICBpZiAoIXRoaXMucmVzaXplU3RhdGUpXG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgLy8gQ2FuY2VsIGFueSBwZW5kaW5nIGFuaW1hdGlvblxuICAgICAgICAgICAgaWYgKHRoaXMucmVzaXplU3RhdGUuYW5pbWF0aW9uSWQgIT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICBjYW5jZWxBbmltYXRpb25GcmFtZSh0aGlzLnJlc2l6ZVN0YXRlLmFuaW1hdGlvbklkKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIFJlbGVhc2UgcG9pbnRlciBjYXB0dXJlXG4gICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgIHRoaXMucmVzaXplU3RhdGUuaGFuZGxlRWxlbWVudC5yZWxlYXNlUG9pbnRlckNhcHR1cmUoZS5wb2ludGVySWQpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY2F0Y2ggKGVycikge1xuICAgICAgICAgICAgICAgIGNvbnNvbGUud2FybignUG9pbnRlciByZWxlYXNlIGZhaWxlZDonLCBlcnIpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gU25hcCBmaW5hbCBoZWlnaHQgdG8gZ3JpZFxuICAgICAgICAgICAgdGhpcy5zbmFwVG9HcmlkRmluYWwoKTtcbiAgICAgICAgICAgIC8vIFVwZGF0ZSB0aW1lc3RhbXAgb25lIGZpbmFsIHRpbWVcbiAgICAgICAgICAgIHRoaXMudXBkYXRlVGltZXN0YW1wRGlzcGxheSgpO1xuICAgICAgICAgICAgLy8gUmVzdG9yZSB6LWluZGV4XG4gICAgICAgICAgICBjb25zdCBjb250YWluZXIgPSB0aGlzLnJlc2l6ZVN0YXRlLmVsZW1lbnQuY2xvc2VzdCgnc3dwLWV2ZW50LWdyb3VwJykgPz8gdGhpcy5yZXNpemVTdGF0ZS5lbGVtZW50O1xuICAgICAgICAgICAgY29udGFpbmVyLnN0eWxlLnpJbmRleCA9IHRoaXMucmVzaXplU3RhdGUucHJldlpJbmRleDtcbiAgICAgICAgICAgIC8vIFJlbW92ZSBnbG9iYWwgcmVzaXppbmcgY2xhc3NcbiAgICAgICAgICAgIGRvY3VtZW50LmRvY3VtZW50RWxlbWVudC5jbGFzc0xpc3QucmVtb3ZlKCdzd3AtLXJlc2l6aW5nJyk7XG4gICAgICAgICAgICAvLyBHZXQgY29sdW1uS2V5IGFuZCBkYXRlIGZyb20gcGFyZW50IGNvbHVtblxuICAgICAgICAgICAgY29uc3QgY29sdW1uID0gdGhpcy5yZXNpemVTdGF0ZS5lbGVtZW50LmNsb3Nlc3QoJ3N3cC1kYXktY29sdW1uJyk7XG4gICAgICAgICAgICBjb25zdCBjb2x1bW5LZXkgPSBjb2x1bW4/LmRhdGFzZXQuY29sdW1uS2V5IHx8ICcnO1xuICAgICAgICAgICAgY29uc3QgZGF0ZSA9IGNvbHVtbj8uZGF0YXNldC5kYXRlIHx8ICcnO1xuICAgICAgICAgICAgLy8gQ3JlYXRlIFN3cEV2ZW50IGZyb20gZWxlbWVudCAocmVhZHMgdG9wL2hlaWdodC9ldmVudElkIGZyb20gZWxlbWVudClcbiAgICAgICAgICAgIGNvbnN0IHN3cEV2ZW50ID0gU3dwRXZlbnQuZnJvbUVsZW1lbnQodGhpcy5yZXNpemVTdGF0ZS5lbGVtZW50LCBjb2x1bW5LZXksIGRhdGUsIHRoaXMuZ3JpZENvbmZpZyk7XG4gICAgICAgICAgICAvLyBFbWl0IHJlc2l6ZSBlbmQgZXZlbnRcbiAgICAgICAgICAgIHRoaXMuZXZlbnRCdXMuZW1pdChDb3JlRXZlbnRzLkVWRU5UX1JFU0laRV9FTkQsIHtcbiAgICAgICAgICAgICAgICBzd3BFdmVudFxuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAvLyBSZXNldCBzdGF0ZVxuICAgICAgICAgICAgdGhpcy5yZXNpemVTdGF0ZSA9IG51bGw7XG4gICAgICAgIH07XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEluaXRpYWxpemUgcmVzaXplIGZ1bmN0aW9uYWxpdHkgb24gY29udGFpbmVyXG4gICAgICovXG4gICAgaW5pdChjb250YWluZXIpIHtcbiAgICAgICAgdGhpcy5jb250YWluZXIgPSBjb250YWluZXI7XG4gICAgICAgIC8vIE1vdXNlb3ZlciBsaXN0ZW5lciBmb3IgaGFuZGxlIGNyZWF0aW9uIChjYXB0dXJlIHBoYXNlIGxpa2UgVjEpXG4gICAgICAgIGNvbnRhaW5lci5hZGRFdmVudExpc3RlbmVyKCdtb3VzZW92ZXInLCB0aGlzLmhhbmRsZU1vdXNlT3ZlciwgdHJ1ZSk7XG4gICAgICAgIC8vIFBvaW50ZXIgbGlzdGVuZXJzIGZvciByZXNpemUgKGNhcHR1cmUgcGhhc2UgbGlrZSBWMSlcbiAgICAgICAgZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcigncG9pbnRlcmRvd24nLCB0aGlzLmhhbmRsZVBvaW50ZXJEb3duLCB0cnVlKTtcbiAgICAgICAgZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcigncG9pbnRlcm1vdmUnLCB0aGlzLmhhbmRsZVBvaW50ZXJNb3ZlLCB0cnVlKTtcbiAgICAgICAgZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcigncG9pbnRlcnVwJywgdGhpcy5oYW5kbGVQb2ludGVyVXAsIHRydWUpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBDcmVhdGUgcmVzaXplIGhhbmRsZSBlbGVtZW50XG4gICAgICovXG4gICAgY3JlYXRlUmVzaXplSGFuZGxlKCkge1xuICAgICAgICBjb25zdCBoYW5kbGUgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzd3AtcmVzaXplLWhhbmRsZScpO1xuICAgICAgICBoYW5kbGUuc2V0QXR0cmlidXRlKCdhcmlhLWxhYmVsJywgJ1Jlc2l6ZSBldmVudCcpO1xuICAgICAgICBoYW5kbGUuc2V0QXR0cmlidXRlKCdyb2xlJywgJ3NlcGFyYXRvcicpO1xuICAgICAgICByZXR1cm4gaGFuZGxlO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBVcGRhdGUgdGltZXN0YW1wIGRpc3BsYXkgd2l0aCBzbmFwcGVkIGVuZCB0aW1lXG4gICAgICovXG4gICAgdXBkYXRlVGltZXN0YW1wRGlzcGxheSgpIHtcbiAgICAgICAgaWYgKCF0aGlzLnJlc2l6ZVN0YXRlKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICBjb25zdCB0aW1lRWwgPSB0aGlzLnJlc2l6ZVN0YXRlLmVsZW1lbnQucXVlcnlTZWxlY3Rvcignc3dwLWV2ZW50LXRpbWUnKTtcbiAgICAgICAgaWYgKCF0aW1lRWwpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIC8vIEdldCBzdGFydCB0aW1lIGZyb20gZWxlbWVudCBwb3NpdGlvblxuICAgICAgICBjb25zdCB0b3AgPSBwYXJzZUZsb2F0KHRoaXMucmVzaXplU3RhdGUuZWxlbWVudC5zdHlsZS50b3ApIHx8IDA7XG4gICAgICAgIGNvbnN0IHN0YXJ0TWludXRlc0Zyb21HcmlkID0gcGl4ZWxzVG9NaW51dGVzKHRvcCwgdGhpcy5ncmlkQ29uZmlnKTtcbiAgICAgICAgY29uc3Qgc3RhcnRNaW51dGVzID0gKHRoaXMuZ3JpZENvbmZpZy5kYXlTdGFydEhvdXIgKiA2MCkgKyBzdGFydE1pbnV0ZXNGcm9tR3JpZDtcbiAgICAgICAgLy8gQ2FsY3VsYXRlIHNuYXBwZWQgZW5kIHRpbWUgZnJvbSBjdXJyZW50IGhlaWdodFxuICAgICAgICBjb25zdCBzbmFwcGVkSGVpZ2h0ID0gc25hcFRvR3JpZCh0aGlzLnJlc2l6ZVN0YXRlLmN1cnJlbnRIZWlnaHQsIHRoaXMuZ3JpZENvbmZpZyk7XG4gICAgICAgIGNvbnN0IGR1cmF0aW9uTWludXRlcyA9IHBpeGVsc1RvTWludXRlcyhzbmFwcGVkSGVpZ2h0LCB0aGlzLmdyaWRDb25maWcpO1xuICAgICAgICBjb25zdCBlbmRNaW51dGVzID0gc3RhcnRNaW51dGVzICsgZHVyYXRpb25NaW51dGVzO1xuICAgICAgICAvLyBGb3JtYXQgYW5kIHVwZGF0ZVxuICAgICAgICBjb25zdCBzdGFydCA9IHRoaXMubWludXRlc1RvRGF0ZShzdGFydE1pbnV0ZXMpO1xuICAgICAgICBjb25zdCBlbmQgPSB0aGlzLm1pbnV0ZXNUb0RhdGUoZW5kTWludXRlcyk7XG4gICAgICAgIHRpbWVFbC50ZXh0Q29udGVudCA9IHRoaXMuZGF0ZVNlcnZpY2UuZm9ybWF0VGltZVJhbmdlKHN0YXJ0LCBlbmQpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBDb252ZXJ0IG1pbnV0ZXMgc2luY2UgbWlkbmlnaHQgdG8gRGF0ZVxuICAgICAqL1xuICAgIG1pbnV0ZXNUb0RhdGUobWludXRlcykge1xuICAgICAgICBjb25zdCBkYXRlID0gbmV3IERhdGUoKTtcbiAgICAgICAgZGF0ZS5zZXRIb3VycyhNYXRoLmZsb29yKG1pbnV0ZXMgLyA2MCkgJSAyNCwgbWludXRlcyAlIDYwLCAwLCAwKTtcbiAgICAgICAgcmV0dXJuIGRhdGU7XG4gICAgfVxuICAgIDtcbiAgICAvKipcbiAgICAgKiBTbmFwIGZpbmFsIGhlaWdodCB0byBncmlkIGludGVydmFsXG4gICAgICovXG4gICAgc25hcFRvR3JpZEZpbmFsKCkge1xuICAgICAgICBpZiAoIXRoaXMucmVzaXplU3RhdGUpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIGNvbnN0IGN1cnJlbnRIZWlnaHQgPSB0aGlzLnJlc2l6ZVN0YXRlLmVsZW1lbnQub2Zmc2V0SGVpZ2h0O1xuICAgICAgICBjb25zdCBzbmFwcGVkSGVpZ2h0ID0gc25hcFRvR3JpZChjdXJyZW50SGVpZ2h0LCB0aGlzLmdyaWRDb25maWcpO1xuICAgICAgICBjb25zdCBtaW5IZWlnaHQgPSBtaW51dGVzVG9QaXhlbHModGhpcy5NSU5fSEVJR0hUX01JTlVURVMsIHRoaXMuZ3JpZENvbmZpZyk7XG4gICAgICAgIGNvbnN0IGZpbmFsSGVpZ2h0ID0gTWF0aC5tYXgobWluSGVpZ2h0LCBzbmFwcGVkSGVpZ2h0KTtcbiAgICAgICAgdGhpcy5yZXNpemVTdGF0ZS5lbGVtZW50LnN0eWxlLmhlaWdodCA9IGAke2ZpbmFsSGVpZ2h0fXB4YDtcbiAgICAgICAgdGhpcy5yZXNpemVTdGF0ZS5jdXJyZW50SGVpZ2h0ID0gZmluYWxIZWlnaHQ7XG4gICAgfVxufVxuIiwgImltcG9ydCB7IENvcmVFdmVudHMgfSBmcm9tICcuLi9jb25zdGFudHMvQ29yZUV2ZW50cyc7XG5leHBvcnQgY2xhc3MgRXZlbnRQZXJzaXN0ZW5jZU1hbmFnZXIge1xuICAgIGNvbnN0cnVjdG9yKGV2ZW50U2VydmljZSwgZXZlbnRCdXMsIGRhdGVTZXJ2aWNlKSB7XG4gICAgICAgIHRoaXMuZXZlbnRTZXJ2aWNlID0gZXZlbnRTZXJ2aWNlO1xuICAgICAgICB0aGlzLmV2ZW50QnVzID0gZXZlbnRCdXM7XG4gICAgICAgIHRoaXMuZGF0ZVNlcnZpY2UgPSBkYXRlU2VydmljZTtcbiAgICAgICAgLyoqXG4gICAgICAgICAqIEhhbmRsZSBkcmFnIGVuZCAtIHVwZGF0ZSBldmVudCBwb3NpdGlvbiBpbiBJbmRleGVkREJcbiAgICAgICAgICovXG4gICAgICAgIHRoaXMuaGFuZGxlRHJhZ0VuZCA9IGFzeW5jIChlKSA9PiB7XG4gICAgICAgICAgICBjb25zdCBwYXlsb2FkID0gZS5kZXRhaWw7XG4gICAgICAgICAgICBjb25zdCB7IHN3cEV2ZW50IH0gPSBwYXlsb2FkO1xuICAgICAgICAgICAgLy8gR2V0IGV4aXN0aW5nIGV2ZW50IHRvIG1lcmdlIHdpdGhcbiAgICAgICAgICAgIGNvbnN0IGV2ZW50ID0gYXdhaXQgdGhpcy5ldmVudFNlcnZpY2UuZ2V0KHN3cEV2ZW50LmV2ZW50SWQpO1xuICAgICAgICAgICAgaWYgKCFldmVudCkge1xuICAgICAgICAgICAgICAgIGNvbnNvbGUud2FybihgRXZlbnRQZXJzaXN0ZW5jZU1hbmFnZXI6IEV2ZW50ICR7c3dwRXZlbnQuZXZlbnRJZH0gbm90IGZvdW5kYCk7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gUGFyc2UgcmVzb3VyY2VJZCBmcm9tIGNvbHVtbktleSBpZiBwcmVzZW50XG4gICAgICAgICAgICBjb25zdCB7IHJlc291cmNlIH0gPSB0aGlzLmRhdGVTZXJ2aWNlLnBhcnNlQ29sdW1uS2V5KHN3cEV2ZW50LmNvbHVtbktleSk7XG4gICAgICAgICAgICAvLyBVcGRhdGUgYW5kIHNhdmUgLSBzdGFydC9lbmQgYWxyZWFkeSBjYWxjdWxhdGVkIGluIFN3cEV2ZW50XG4gICAgICAgICAgICAvLyBTZXQgYWxsRGF5IGJhc2VkIG9uIGRyb3AgdGFyZ2V0OlxuICAgICAgICAgICAgLy8gLSBoZWFkZXI6IGFsbERheSA9IHRydWVcbiAgICAgICAgICAgIC8vIC0gZ3JpZDogYWxsRGF5ID0gZmFsc2UgKGNvbnZlcnRzIGFsbERheSBldmVudCB0byB0aW1lZClcbiAgICAgICAgICAgIGNvbnN0IHVwZGF0ZWRFdmVudCA9IHtcbiAgICAgICAgICAgICAgICAuLi5ldmVudCxcbiAgICAgICAgICAgICAgICBzdGFydDogc3dwRXZlbnQuc3RhcnQsXG4gICAgICAgICAgICAgICAgZW5kOiBzd3BFdmVudC5lbmQsXG4gICAgICAgICAgICAgICAgcmVzb3VyY2VJZDogcmVzb3VyY2UgPz8gZXZlbnQucmVzb3VyY2VJZCxcbiAgICAgICAgICAgICAgICBhbGxEYXk6IHBheWxvYWQudGFyZ2V0ID09PSAnaGVhZGVyJyxcbiAgICAgICAgICAgICAgICBzeW5jU3RhdHVzOiAncGVuZGluZydcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICBhd2FpdCB0aGlzLmV2ZW50U2VydmljZS5zYXZlKHVwZGF0ZWRFdmVudCk7XG4gICAgICAgICAgICAvLyBFbWl0IEVWRU5UX1VQREFURUQgZm9yIEV2ZW50UmVuZGVyZXIgdG8gcmUtcmVuZGVyIGFmZmVjdGVkIGNvbHVtbnNcbiAgICAgICAgICAgIGNvbnN0IHVwZGF0ZVBheWxvYWQgPSB7XG4gICAgICAgICAgICAgICAgZXZlbnRJZDogdXBkYXRlZEV2ZW50LmlkLFxuICAgICAgICAgICAgICAgIHNvdXJjZUNvbHVtbktleTogcGF5bG9hZC5zb3VyY2VDb2x1bW5LZXksXG4gICAgICAgICAgICAgICAgdGFyZ2V0Q29sdW1uS2V5OiBzd3BFdmVudC5jb2x1bW5LZXlcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICB0aGlzLmV2ZW50QnVzLmVtaXQoQ29yZUV2ZW50cy5FVkVOVF9VUERBVEVELCB1cGRhdGVQYXlsb2FkKTtcbiAgICAgICAgfTtcbiAgICAgICAgLyoqXG4gICAgICAgICAqIEhhbmRsZSByZXNpemUgZW5kIC0gdXBkYXRlIGV2ZW50IGR1cmF0aW9uIGluIEluZGV4ZWREQlxuICAgICAgICAgKi9cbiAgICAgICAgdGhpcy5oYW5kbGVSZXNpemVFbmQgPSBhc3luYyAoZSkgPT4ge1xuICAgICAgICAgICAgY29uc3QgcGF5bG9hZCA9IGUuZGV0YWlsO1xuICAgICAgICAgICAgY29uc3QgeyBzd3BFdmVudCB9ID0gcGF5bG9hZDtcbiAgICAgICAgICAgIC8vIEdldCBleGlzdGluZyBldmVudCB0byBtZXJnZSB3aXRoXG4gICAgICAgICAgICBjb25zdCBldmVudCA9IGF3YWl0IHRoaXMuZXZlbnRTZXJ2aWNlLmdldChzd3BFdmVudC5ldmVudElkKTtcbiAgICAgICAgICAgIGlmICghZXZlbnQpIHtcbiAgICAgICAgICAgICAgICBjb25zb2xlLndhcm4oYEV2ZW50UGVyc2lzdGVuY2VNYW5hZ2VyOiBFdmVudCAke3N3cEV2ZW50LmV2ZW50SWR9IG5vdCBmb3VuZGApO1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIFVwZGF0ZSBhbmQgc2F2ZSAtIGVuZCBhbHJlYWR5IGNhbGN1bGF0ZWQgaW4gU3dwRXZlbnRcbiAgICAgICAgICAgIGNvbnN0IHVwZGF0ZWRFdmVudCA9IHtcbiAgICAgICAgICAgICAgICAuLi5ldmVudCxcbiAgICAgICAgICAgICAgICBlbmQ6IHN3cEV2ZW50LmVuZCxcbiAgICAgICAgICAgICAgICBzeW5jU3RhdHVzOiAncGVuZGluZydcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICBhd2FpdCB0aGlzLmV2ZW50U2VydmljZS5zYXZlKHVwZGF0ZWRFdmVudCk7XG4gICAgICAgICAgICAvLyBFbWl0IEVWRU5UX1VQREFURUQgZm9yIEV2ZW50UmVuZGVyZXIgdG8gcmUtcmVuZGVyIHRoZSBjb2x1bW5cbiAgICAgICAgICAgIC8vIFJlc2l6ZSBzdGF5cyBpbiBzYW1lIGNvbHVtbiwgc28gc291cmNlIGFuZCB0YXJnZXQgYXJlIHRoZSBzYW1lXG4gICAgICAgICAgICBjb25zdCB1cGRhdGVQYXlsb2FkID0ge1xuICAgICAgICAgICAgICAgIGV2ZW50SWQ6IHVwZGF0ZWRFdmVudC5pZCxcbiAgICAgICAgICAgICAgICBzb3VyY2VDb2x1bW5LZXk6IHN3cEV2ZW50LmNvbHVtbktleSxcbiAgICAgICAgICAgICAgICB0YXJnZXRDb2x1bW5LZXk6IHN3cEV2ZW50LmNvbHVtbktleVxuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHRoaXMuZXZlbnRCdXMuZW1pdChDb3JlRXZlbnRzLkVWRU5UX1VQREFURUQsIHVwZGF0ZVBheWxvYWQpO1xuICAgICAgICB9O1xuICAgICAgICB0aGlzLnNldHVwTGlzdGVuZXJzKCk7XG4gICAgfVxuICAgIHNldHVwTGlzdGVuZXJzKCkge1xuICAgICAgICB0aGlzLmV2ZW50QnVzLm9uKENvcmVFdmVudHMuRVZFTlRfRFJBR19FTkQsIHRoaXMuaGFuZGxlRHJhZ0VuZCk7XG4gICAgICAgIHRoaXMuZXZlbnRCdXMub24oQ29yZUV2ZW50cy5FVkVOVF9SRVNJWkVfRU5ELCB0aGlzLmhhbmRsZVJlc2l6ZUVuZCk7XG4gICAgfVxufVxuIiwgImltcG9ydCB7IENvbnRhaW5lciB9IGZyb20gJ0Bub3ZhZGkvY29yZSc7XG5pbXBvcnQgeyBEYXRlUmVuZGVyZXIgfSBmcm9tICcuL2ZlYXR1cmVzL2RhdGUvRGF0ZVJlbmRlcmVyJztcbmltcG9ydCB7IERhdGVTZXJ2aWNlIH0gZnJvbSAnLi9jb3JlL0RhdGVTZXJ2aWNlJztcbmltcG9ydCB7IFJlc291cmNlUmVuZGVyZXIgfSBmcm9tICcuL2ZlYXR1cmVzL3Jlc291cmNlL1Jlc291cmNlUmVuZGVyZXInO1xuaW1wb3J0IHsgVGVhbVJlbmRlcmVyIH0gZnJvbSAnLi9mZWF0dXJlcy90ZWFtL1RlYW1SZW5kZXJlcic7XG5pbXBvcnQgeyBEZXBhcnRtZW50UmVuZGVyZXIgfSBmcm9tICcuL2ZlYXR1cmVzL2RlcGFydG1lbnQvRGVwYXJ0bWVudFJlbmRlcmVyJztcbmltcG9ydCB7IENhbGVuZGFyT3JjaGVzdHJhdG9yIH0gZnJvbSAnLi9jb3JlL0NhbGVuZGFyT3JjaGVzdHJhdG9yJztcbmltcG9ydCB7IENhbGVuZGFyQXBwIH0gZnJvbSAnLi9jb3JlL0NhbGVuZGFyQXBwJztcbmltcG9ydCB7IFRpbWVBeGlzUmVuZGVyZXIgfSBmcm9tICcuL2ZlYXR1cmVzL3RpbWVheGlzL1RpbWVBeGlzUmVuZGVyZXInO1xuaW1wb3J0IHsgU2Nyb2xsTWFuYWdlciB9IGZyb20gJy4vY29yZS9TY3JvbGxNYW5hZ2VyJztcbmltcG9ydCB7IEhlYWRlckRyYXdlck1hbmFnZXIgfSBmcm9tICcuL2NvcmUvSGVhZGVyRHJhd2VyTWFuYWdlcic7XG5pbXBvcnQgeyBNb2NrVGVhbVN0b3JlLCBNb2NrUmVzb3VyY2VTdG9yZSB9IGZyb20gJy4vZGVtby9Nb2NrU3RvcmVzJztcbmltcG9ydCB7IERlbW9BcHAgfSBmcm9tICcuL2RlbW8vRGVtb0FwcCc7XG4vLyBFdmVudCBzeXN0ZW1cbmltcG9ydCB7IEV2ZW50QnVzIH0gZnJvbSAnLi9jb3JlL0V2ZW50QnVzJztcbi8vIFN0b3JhZ2VcbmltcG9ydCB7IEluZGV4ZWREQkNvbnRleHQgfSBmcm9tICcuL3N0b3JhZ2UvSW5kZXhlZERCQ29udGV4dCc7XG5pbXBvcnQgeyBFdmVudFN0b3JlIH0gZnJvbSAnLi9zdG9yYWdlL2V2ZW50cy9FdmVudFN0b3JlJztcbmltcG9ydCB7IEV2ZW50U2VydmljZSB9IGZyb20gJy4vc3RvcmFnZS9ldmVudHMvRXZlbnRTZXJ2aWNlJztcbmltcG9ydCB7IFJlc291cmNlU3RvcmUgfSBmcm9tICcuL3N0b3JhZ2UvcmVzb3VyY2VzL1Jlc291cmNlU3RvcmUnO1xuaW1wb3J0IHsgUmVzb3VyY2VTZXJ2aWNlIH0gZnJvbSAnLi9zdG9yYWdlL3Jlc291cmNlcy9SZXNvdXJjZVNlcnZpY2UnO1xuaW1wb3J0IHsgQm9va2luZ1N0b3JlIH0gZnJvbSAnLi9zdG9yYWdlL2Jvb2tpbmdzL0Jvb2tpbmdTdG9yZSc7XG5pbXBvcnQgeyBCb29raW5nU2VydmljZSB9IGZyb20gJy4vc3RvcmFnZS9ib29raW5ncy9Cb29raW5nU2VydmljZSc7XG5pbXBvcnQgeyBDdXN0b21lclN0b3JlIH0gZnJvbSAnLi9zdG9yYWdlL2N1c3RvbWVycy9DdXN0b21lclN0b3JlJztcbmltcG9ydCB7IEN1c3RvbWVyU2VydmljZSB9IGZyb20gJy4vc3RvcmFnZS9jdXN0b21lcnMvQ3VzdG9tZXJTZXJ2aWNlJztcbmltcG9ydCB7IFRlYW1TdG9yZSB9IGZyb20gJy4vc3RvcmFnZS90ZWFtcy9UZWFtU3RvcmUnO1xuaW1wb3J0IHsgVGVhbVNlcnZpY2UgfSBmcm9tICcuL3N0b3JhZ2UvdGVhbXMvVGVhbVNlcnZpY2UnO1xuaW1wb3J0IHsgRGVwYXJ0bWVudFN0b3JlIH0gZnJvbSAnLi9zdG9yYWdlL2RlcGFydG1lbnRzL0RlcGFydG1lbnRTdG9yZSc7XG5pbXBvcnQgeyBEZXBhcnRtZW50U2VydmljZSB9IGZyb20gJy4vc3RvcmFnZS9kZXBhcnRtZW50cy9EZXBhcnRtZW50U2VydmljZSc7XG5pbXBvcnQgeyBTZXR0aW5nc1N0b3JlIH0gZnJvbSAnLi9zdG9yYWdlL3NldHRpbmdzL1NldHRpbmdzU3RvcmUnO1xuaW1wb3J0IHsgU2V0dGluZ3NTZXJ2aWNlIH0gZnJvbSAnLi9zdG9yYWdlL3NldHRpbmdzL1NldHRpbmdzU2VydmljZSc7XG5pbXBvcnQgeyBWaWV3Q29uZmlnU3RvcmUgfSBmcm9tICcuL3N0b3JhZ2Uvdmlld2NvbmZpZ3MvVmlld0NvbmZpZ1N0b3JlJztcbmltcG9ydCB7IFZpZXdDb25maWdTZXJ2aWNlIH0gZnJvbSAnLi9zdG9yYWdlL3ZpZXdjb25maWdzL1ZpZXdDb25maWdTZXJ2aWNlJztcbi8vIEF1ZGl0XG5pbXBvcnQgeyBBdWRpdFN0b3JlIH0gZnJvbSAnLi9zdG9yYWdlL2F1ZGl0L0F1ZGl0U3RvcmUnO1xuaW1wb3J0IHsgQXVkaXRTZXJ2aWNlIH0gZnJvbSAnLi9zdG9yYWdlL2F1ZGl0L0F1ZGl0U2VydmljZSc7XG5pbXBvcnQgeyBNb2NrRXZlbnRSZXBvc2l0b3J5IH0gZnJvbSAnLi9yZXBvc2l0b3JpZXMvTW9ja0V2ZW50UmVwb3NpdG9yeSc7XG5pbXBvcnQgeyBNb2NrUmVzb3VyY2VSZXBvc2l0b3J5IH0gZnJvbSAnLi9yZXBvc2l0b3JpZXMvTW9ja1Jlc291cmNlUmVwb3NpdG9yeSc7XG5pbXBvcnQgeyBNb2NrQm9va2luZ1JlcG9zaXRvcnkgfSBmcm9tICcuL3JlcG9zaXRvcmllcy9Nb2NrQm9va2luZ1JlcG9zaXRvcnknO1xuaW1wb3J0IHsgTW9ja0N1c3RvbWVyUmVwb3NpdG9yeSB9IGZyb20gJy4vcmVwb3NpdG9yaWVzL01vY2tDdXN0b21lclJlcG9zaXRvcnknO1xuaW1wb3J0IHsgTW9ja0F1ZGl0UmVwb3NpdG9yeSB9IGZyb20gJy4vcmVwb3NpdG9yaWVzL01vY2tBdWRpdFJlcG9zaXRvcnknO1xuaW1wb3J0IHsgTW9ja1RlYW1SZXBvc2l0b3J5IH0gZnJvbSAnLi9yZXBvc2l0b3JpZXMvTW9ja1RlYW1SZXBvc2l0b3J5JztcbmltcG9ydCB7IE1vY2tEZXBhcnRtZW50UmVwb3NpdG9yeSB9IGZyb20gJy4vcmVwb3NpdG9yaWVzL01vY2tEZXBhcnRtZW50UmVwb3NpdG9yeSc7XG5pbXBvcnQgeyBNb2NrU2V0dGluZ3NSZXBvc2l0b3J5IH0gZnJvbSAnLi9yZXBvc2l0b3JpZXMvTW9ja1NldHRpbmdzUmVwb3NpdG9yeSc7XG5pbXBvcnQgeyBNb2NrVmlld0NvbmZpZ1JlcG9zaXRvcnkgfSBmcm9tICcuL3JlcG9zaXRvcmllcy9Nb2NrVmlld0NvbmZpZ1JlcG9zaXRvcnknO1xuLy8gV29ya2Vyc1xuaW1wb3J0IHsgRGF0YVNlZWRlciB9IGZyb20gJy4vd29ya2Vycy9EYXRhU2VlZGVyJztcbi8vIEZlYXR1cmVzXG5pbXBvcnQgeyBFdmVudFJlbmRlcmVyIH0gZnJvbSAnLi9mZWF0dXJlcy9ldmVudC9FdmVudFJlbmRlcmVyJztcbmltcG9ydCB7IFNjaGVkdWxlUmVuZGVyZXIgfSBmcm9tICcuL2ZlYXR1cmVzL3NjaGVkdWxlL1NjaGVkdWxlUmVuZGVyZXInO1xuaW1wb3J0IHsgSGVhZGVyRHJhd2VyUmVuZGVyZXIgfSBmcm9tICcuL2ZlYXR1cmVzL2hlYWRlcmRyYXdlci9IZWFkZXJEcmF3ZXJSZW5kZXJlcic7XG4vLyBTY2hlZHVsZVxuaW1wb3J0IHsgU2NoZWR1bGVPdmVycmlkZVN0b3JlIH0gZnJvbSAnLi9zdG9yYWdlL3NjaGVkdWxlcy9TY2hlZHVsZU92ZXJyaWRlU3RvcmUnO1xuaW1wb3J0IHsgU2NoZWR1bGVPdmVycmlkZVNlcnZpY2UgfSBmcm9tICcuL3N0b3JhZ2Uvc2NoZWR1bGVzL1NjaGVkdWxlT3ZlcnJpZGVTZXJ2aWNlJztcbmltcG9ydCB7IFJlc291cmNlU2NoZWR1bGVTZXJ2aWNlIH0gZnJvbSAnLi9zdG9yYWdlL3NjaGVkdWxlcy9SZXNvdXJjZVNjaGVkdWxlU2VydmljZSc7XG4vLyBNYW5hZ2Vyc1xuaW1wb3J0IHsgRHJhZ0Ryb3BNYW5hZ2VyIH0gZnJvbSAnLi9tYW5hZ2Vycy9EcmFnRHJvcE1hbmFnZXInO1xuaW1wb3J0IHsgRWRnZVNjcm9sbE1hbmFnZXIgfSBmcm9tICcuL21hbmFnZXJzL0VkZ2VTY3JvbGxNYW5hZ2VyJztcbmltcG9ydCB7IFJlc2l6ZU1hbmFnZXIgfSBmcm9tICcuL21hbmFnZXJzL1Jlc2l6ZU1hbmFnZXInO1xuaW1wb3J0IHsgRXZlbnRQZXJzaXN0ZW5jZU1hbmFnZXIgfSBmcm9tICcuL21hbmFnZXJzL0V2ZW50UGVyc2lzdGVuY2VNYW5hZ2VyJztcbmNvbnN0IGRlZmF1bHRUaW1lRm9ybWF0Q29uZmlnID0ge1xuICAgIHRpbWV6b25lOiBJbnRsLkRhdGVUaW1lRm9ybWF0KCkucmVzb2x2ZWRPcHRpb25zKCkudGltZVpvbmUsXG4gICAgdXNlMjRIb3VyRm9ybWF0OiB0cnVlLFxuICAgIGxvY2FsZTogJ2RhLURLJyxcbiAgICBkYXRlRm9ybWF0OiAnbG9jYWxlJyxcbiAgICBzaG93U2Vjb25kczogZmFsc2Vcbn07XG5jb25zdCBkZWZhdWx0R3JpZENvbmZpZyA9IHtcbiAgICBob3VySGVpZ2h0OiA2NCxcbiAgICBkYXlTdGFydEhvdXI6IDYsXG4gICAgZGF5RW5kSG91cjogMTgsXG4gICAgc25hcEludGVydmFsOiAxNSxcbiAgICBncmlkU3RhcnRUaHJlc2hvbGRNaW51dGVzOiAzMFxufTtcbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVWMkNvbnRhaW5lcigpIHtcbiAgICBjb25zdCBjb250YWluZXIgPSBuZXcgQ29udGFpbmVyKCk7XG4gICAgY29uc3QgYnVpbGRlciA9IGNvbnRhaW5lci5idWlsZGVyKCk7XG4gICAgLy8gQ29uZmlnXG4gICAgYnVpbGRlci5yZWdpc3Rlckluc3RhbmNlKGRlZmF1bHRUaW1lRm9ybWF0Q29uZmlnKS5hcyhcIklUaW1lRm9ybWF0Q29uZmlnXCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJJbnN0YW5jZShkZWZhdWx0R3JpZENvbmZpZykuYXMoXCJJR3JpZENvbmZpZ1wiKTtcbiAgICAvLyBDb3JlIC0gRXZlbnRCdXNcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShFdmVudEJ1cykuYXMoXCJFdmVudEJ1c1wiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShFdmVudEJ1cykuYXMoXCJJRXZlbnRCdXNcIik7XG4gICAgLy8gU2VydmljZXNcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShEYXRlU2VydmljZSkuYXMoXCJEYXRlU2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSVRpbWVGb3JtYXRDb25maWdcIiksXG4gICAgICAgICAgICB1bmRlZmluZWRcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIC8vIFN0b3JhZ2UgaW5mcmFzdHJ1Y3R1cmVcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShJbmRleGVkREJDb250ZXh0KS5hcyhcIkluZGV4ZWREQkNvbnRleHRcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZUFsbChcIklTdG9yZVwiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgLy8gU3RvcmVzIChmb3IgSW5kZXhlZERCIHNjaGVtYSBjcmVhdGlvbilcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShFdmVudFN0b3JlKS5hcyhcIklTdG9yZVwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShSZXNvdXJjZVN0b3JlKS5hcyhcIklTdG9yZVwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShCb29raW5nU3RvcmUpLmFzKFwiSVN0b3JlXCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKEN1c3RvbWVyU3RvcmUpLmFzKFwiSVN0b3JlXCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFRlYW1TdG9yZSkuYXMoXCJJU3RvcmVcIik7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoRGVwYXJ0bWVudFN0b3JlKS5hcyhcIklTdG9yZVwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShTY2hlZHVsZU92ZXJyaWRlU3RvcmUpLmFzKFwiSVN0b3JlXCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKEF1ZGl0U3RvcmUpLmFzKFwiSVN0b3JlXCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFNldHRpbmdzU3RvcmUpLmFzKFwiSVN0b3JlXCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFZpZXdDb25maWdTdG9yZSkuYXMoXCJJU3RvcmVcIik7XG4gICAgLy8gRW50aXR5IHNlcnZpY2VzIChmb3IgRGF0YVNlZWRlciBwb2x5bW9ycGhpYyBhcnJheSlcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShFdmVudFNlcnZpY2UpLmFzKFwiSUVudGl0eVNlcnZpY2VcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkluZGV4ZWREQkNvbnRleHRcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKEV2ZW50U2VydmljZSkuYXMoXCJJRW50aXR5U2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSW5kZXhlZERCQ29udGV4dFwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklFdmVudEJ1c1wiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoRXZlbnRTZXJ2aWNlKS5hcyhcIkV2ZW50U2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSW5kZXhlZERCQ29udGV4dFwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklFdmVudEJ1c1wiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoUmVzb3VyY2VTZXJ2aWNlKS5hcyhcIklFbnRpdHlTZXJ2aWNlXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJbmRleGVkREJDb250ZXh0XCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShSZXNvdXJjZVNlcnZpY2UpLmFzKFwiSUVudGl0eVNlcnZpY2VcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkluZGV4ZWREQkNvbnRleHRcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFJlc291cmNlU2VydmljZSkuYXMoXCJSZXNvdXJjZVNlcnZpY2VcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkluZGV4ZWREQkNvbnRleHRcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKEJvb2tpbmdTZXJ2aWNlKS5hcyhcIklFbnRpdHlTZXJ2aWNlXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJbmRleGVkREJDb250ZXh0XCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShCb29raW5nU2VydmljZSkuYXMoXCJJRW50aXR5U2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSW5kZXhlZERCQ29udGV4dFwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklFdmVudEJ1c1wiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoQm9va2luZ1NlcnZpY2UpLmFzKFwiQm9va2luZ1NlcnZpY2VcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkluZGV4ZWREQkNvbnRleHRcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKEN1c3RvbWVyU2VydmljZSkuYXMoXCJJRW50aXR5U2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSW5kZXhlZERCQ29udGV4dFwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklFdmVudEJ1c1wiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoQ3VzdG9tZXJTZXJ2aWNlKS5hcyhcIklFbnRpdHlTZXJ2aWNlXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJbmRleGVkREJDb250ZXh0XCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShDdXN0b21lclNlcnZpY2UpLmFzKFwiQ3VzdG9tZXJTZXJ2aWNlXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJbmRleGVkREJDb250ZXh0XCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShUZWFtU2VydmljZSkuYXMoXCJJRW50aXR5U2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSW5kZXhlZERCQ29udGV4dFwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklFdmVudEJ1c1wiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoVGVhbVNlcnZpY2UpLmFzKFwiSUVudGl0eVNlcnZpY2VcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkluZGV4ZWREQkNvbnRleHRcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFRlYW1TZXJ2aWNlKS5hcyhcIlRlYW1TZXJ2aWNlXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJbmRleGVkREJDb250ZXh0XCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShEZXBhcnRtZW50U2VydmljZSkuYXMoXCJJRW50aXR5U2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSW5kZXhlZERCQ29udGV4dFwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklFdmVudEJ1c1wiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoRGVwYXJ0bWVudFNlcnZpY2UpLmFzKFwiSUVudGl0eVNlcnZpY2VcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkluZGV4ZWREQkNvbnRleHRcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKERlcGFydG1lbnRTZXJ2aWNlKS5hcyhcIkRlcGFydG1lbnRTZXJ2aWNlXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJbmRleGVkREJDb250ZXh0XCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShTZXR0aW5nc1NlcnZpY2UpLmFzKFwiSUVudGl0eVNlcnZpY2VcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkluZGV4ZWREQkNvbnRleHRcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFNldHRpbmdzU2VydmljZSkuYXMoXCJJRW50aXR5U2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSW5kZXhlZERCQ29udGV4dFwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklFdmVudEJ1c1wiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoU2V0dGluZ3NTZXJ2aWNlKS5hcyhcIlNldHRpbmdzU2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSW5kZXhlZERCQ29udGV4dFwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklFdmVudEJ1c1wiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoVmlld0NvbmZpZ1NlcnZpY2UpLmFzKFwiSUVudGl0eVNlcnZpY2VcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkluZGV4ZWREQkNvbnRleHRcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFZpZXdDb25maWdTZXJ2aWNlKS5hcyhcIklFbnRpdHlTZXJ2aWNlXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJbmRleGVkREJDb250ZXh0XCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShWaWV3Q29uZmlnU2VydmljZSkuYXMoXCJWaWV3Q29uZmlnU2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSW5kZXhlZERCQ29udGV4dFwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklFdmVudEJ1c1wiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgLy8gUmVwb3NpdG9yaWVzIChmb3IgRGF0YVNlZWRlciBwb2x5bW9ycGhpYyBhcnJheSlcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShNb2NrRXZlbnRSZXBvc2l0b3J5KS5hcyhcIklBcGlSZXBvc2l0b3J5XCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKE1vY2tFdmVudFJlcG9zaXRvcnkpLmFzKFwiSUFwaVJlcG9zaXRvcnlcIik7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoTW9ja1Jlc291cmNlUmVwb3NpdG9yeSkuYXMoXCJJQXBpUmVwb3NpdG9yeVwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShNb2NrUmVzb3VyY2VSZXBvc2l0b3J5KS5hcyhcIklBcGlSZXBvc2l0b3J5XCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKE1vY2tCb29raW5nUmVwb3NpdG9yeSkuYXMoXCJJQXBpUmVwb3NpdG9yeVwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShNb2NrQm9va2luZ1JlcG9zaXRvcnkpLmFzKFwiSUFwaVJlcG9zaXRvcnlcIik7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoTW9ja0N1c3RvbWVyUmVwb3NpdG9yeSkuYXMoXCJJQXBpUmVwb3NpdG9yeVwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShNb2NrQ3VzdG9tZXJSZXBvc2l0b3J5KS5hcyhcIklBcGlSZXBvc2l0b3J5XCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKE1vY2tBdWRpdFJlcG9zaXRvcnkpLmFzKFwiSUFwaVJlcG9zaXRvcnlcIik7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoTW9ja0F1ZGl0UmVwb3NpdG9yeSkuYXMoXCJJQXBpUmVwb3NpdG9yeVwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShNb2NrVGVhbVJlcG9zaXRvcnkpLmFzKFwiSUFwaVJlcG9zaXRvcnlcIik7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoTW9ja1RlYW1SZXBvc2l0b3J5KS5hcyhcIklBcGlSZXBvc2l0b3J5XCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKE1vY2tEZXBhcnRtZW50UmVwb3NpdG9yeSkuYXMoXCJJQXBpUmVwb3NpdG9yeVwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShNb2NrRGVwYXJ0bWVudFJlcG9zaXRvcnkpLmFzKFwiSUFwaVJlcG9zaXRvcnlcIik7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoTW9ja1NldHRpbmdzUmVwb3NpdG9yeSkuYXMoXCJJQXBpUmVwb3NpdG9yeVwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShNb2NrU2V0dGluZ3NSZXBvc2l0b3J5KS5hcyhcIklBcGlSZXBvc2l0b3J5XCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKE1vY2tWaWV3Q29uZmlnUmVwb3NpdG9yeSkuYXMoXCJJQXBpUmVwb3NpdG9yeVwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShNb2NrVmlld0NvbmZpZ1JlcG9zaXRvcnkpLmFzKFwiSUFwaVJlcG9zaXRvcnlcIik7XG4gICAgLy8gQXVkaXQgc2VydmljZSAobGlzdGVucyB0byBFTlRJVFlfU0FWRUQvREVMRVRFRCBldmVudHMgYXV0b21hdGljYWxseSlcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShBdWRpdFNlcnZpY2UpLmFzKFwiQXVkaXRTZXJ2aWNlXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJbmRleGVkREJDb250ZXh0XCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICAvLyBXb3JrZXJzXG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoRGF0YVNlZWRlcikuYXMoXCJEYXRhU2VlZGVyXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGVBbGwoXCJJRW50aXR5U2VydmljZVwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZUFsbChcIklBcGlSZXBvc2l0b3J5XCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICAvLyBTY2hlZHVsZSBzZXJ2aWNlc1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFNjaGVkdWxlT3ZlcnJpZGVTZXJ2aWNlKS5hcyhcIlNjaGVkdWxlT3ZlcnJpZGVTZXJ2aWNlXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJbmRleGVkREJDb250ZXh0XCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShSZXNvdXJjZVNjaGVkdWxlU2VydmljZSkuYXMoXCJSZXNvdXJjZVNjaGVkdWxlU2VydmljZVwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiUmVzb3VyY2VTZXJ2aWNlXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiU2NoZWR1bGVPdmVycmlkZVNlcnZpY2VcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJEYXRlU2VydmljZVwiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgLy8gRmVhdHVyZXNcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShFdmVudFJlbmRlcmVyKS5hcyhcIkV2ZW50UmVuZGVyZXJcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkV2ZW50U2VydmljZVwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkRhdGVTZXJ2aWNlXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUdyaWRDb25maWdcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFNjaGVkdWxlUmVuZGVyZXIpLmFzKFwiU2NoZWR1bGVSZW5kZXJlclwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiUmVzb3VyY2VTY2hlZHVsZVNlcnZpY2VcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJEYXRlU2VydmljZVwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklHcmlkQ29uZmlnXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShIZWFkZXJEcmF3ZXJSZW5kZXJlcikuYXMoXCJIZWFkZXJEcmF3ZXJSZW5kZXJlclwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUdyaWRDb25maWdcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJIZWFkZXJEcmF3ZXJNYW5hZ2VyXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiRXZlbnRTZXJ2aWNlXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiRGF0ZVNlcnZpY2VcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIC8vIFJlbmRlcmVycyAtIHJlZ2lzdHJlcmVzIHNvbSBSZW5kZXJlciAoYXJyYXkgaW5qZWN0aW9uIHRpbCBDYWxlbmRhck9yY2hlc3RyYXRvcilcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShEYXRlUmVuZGVyZXIpLmFzKFwiSVJlbmRlcmVyXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJEYXRlU2VydmljZVwiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoUmVzb3VyY2VSZW5kZXJlcikuYXMoXCJJUmVuZGVyZXJcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIlJlc291cmNlU2VydmljZVwiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoVGVhbVJlbmRlcmVyKS5hcyhcIklSZW5kZXJlclwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiVGVhbVNlcnZpY2VcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKERlcGFydG1lbnRSZW5kZXJlcikuYXMoXCJJUmVuZGVyZXJcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkRlcGFydG1lbnRTZXJ2aWNlXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICAvLyBTdG9yZXMgLSByZWdpc3RyZXJlcyBzb20gSUdyb3VwaW5nU3RvcmVcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShNb2NrVGVhbVN0b3JlKS5hcyhcIklHcm91cGluZ1N0b3JlXCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKE1vY2tSZXNvdXJjZVN0b3JlKS5hcyhcIklHcm91cGluZ1N0b3JlXCIpO1xuICAgIC8vIENhbGVuZGFyT3JjaGVzdHJhdG9yIG1vZHRhZ2VyIElHcm91cGluZ1N0b3JlW10gYXV0b21hdGlzayAoYXJyYXkgaW5qZWN0aW9uKVxuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKENhbGVuZGFyT3JjaGVzdHJhdG9yKS5hcyhcIkNhbGVuZGFyT3JjaGVzdHJhdG9yXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGVBbGwoXCJJUmVuZGVyZXJcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJFdmVudFJlbmRlcmVyXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiU2NoZWR1bGVSZW5kZXJlclwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkhlYWRlckRyYXdlclJlbmRlcmVyXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiRGF0ZVNlcnZpY2VcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGVBbGwoXCJJRW50aXR5U2VydmljZVwiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoVGltZUF4aXNSZW5kZXJlcikuYXMoXCJUaW1lQXhpc1JlbmRlcmVyXCIpO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFNjcm9sbE1hbmFnZXIpLmFzKFwiU2Nyb2xsTWFuYWdlclwiKTtcbiAgICBidWlsZGVyLnJlZ2lzdGVyVHlwZShIZWFkZXJEcmF3ZXJNYW5hZ2VyKS5hcyhcIkhlYWRlckRyYXdlck1hbmFnZXJcIik7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoRHJhZ0Ryb3BNYW5hZ2VyKS5hcyhcIkRyYWdEcm9wTWFuYWdlclwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUdyaWRDb25maWdcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKEVkZ2VTY3JvbGxNYW5hZ2VyKS5hcyhcIkVkZ2VTY3JvbGxNYW5hZ2VyXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIGJ1aWxkZXIucmVnaXN0ZXJUeXBlKFJlc2l6ZU1hbmFnZXIpLmFzKFwiUmVzaXplTWFuYWdlclwiKS5hdXRvV2lyZSh7XG4gICAgICAgIG1hcFJlc29sdmVyczogW1xuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUV2ZW50QnVzXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiSUdyaWRDb25maWdcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJEYXRlU2VydmljZVwiKVxuICAgICAgICBdXG4gICAgfSk7XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoRXZlbnRQZXJzaXN0ZW5jZU1hbmFnZXIpLmFzKFwiRXZlbnRQZXJzaXN0ZW5jZU1hbmFnZXJcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkV2ZW50U2VydmljZVwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIklFdmVudEJ1c1wiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkRhdGVTZXJ2aWNlXCIpXG4gICAgICAgIF1cbiAgICB9KTtcbiAgICAvLyBDYWxlbmRhckFwcCAtIGdlbmJydWdlbGlnIGthbGVuZGVya29tcG9uZW50XG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoQ2FsZW5kYXJBcHApLmFzKFwiQ2FsZW5kYXJBcHBcIikuYXV0b1dpcmUoe1xuICAgICAgICBtYXBSZXNvbHZlcnM6IFtcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkNhbGVuZGFyT3JjaGVzdHJhdG9yXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiVGltZUF4aXNSZW5kZXJlclwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkRhdGVTZXJ2aWNlXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiU2Nyb2xsTWFuYWdlclwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkhlYWRlckRyYXdlck1hbmFnZXJcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJEcmFnRHJvcE1hbmFnZXJcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJFZGdlU2Nyb2xsTWFuYWdlclwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIlJlc2l6ZU1hbmFnZXJcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJIZWFkZXJEcmF3ZXJSZW5kZXJlclwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkV2ZW50UGVyc2lzdGVuY2VNYW5hZ2VyXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiU2V0dGluZ3NTZXJ2aWNlXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiVmlld0NvbmZpZ1NlcnZpY2VcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIC8vIERlbW8gYXBwXG4gICAgYnVpbGRlci5yZWdpc3RlclR5cGUoRGVtb0FwcCkuYXMoXCJEZW1vQXBwXCIpLmF1dG9XaXJlKHtcbiAgICAgICAgbWFwUmVzb2x2ZXJzOiBbXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJbmRleGVkREJDb250ZXh0XCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiRGF0YVNlZWRlclwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkF1ZGl0U2VydmljZVwiKSxcbiAgICAgICAgICAgIGMgPT4gYy5yZXNvbHZlVHlwZShcIkNhbGVuZGFyQXBwXCIpLFxuICAgICAgICAgICAgYyA9PiBjLnJlc29sdmVUeXBlKFwiRGF0ZVNlcnZpY2VcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJSZXNvdXJjZVNlcnZpY2VcIiksXG4gICAgICAgICAgICBjID0+IGMucmVzb2x2ZVR5cGUoXCJJRXZlbnRCdXNcIilcbiAgICAgICAgXVxuICAgIH0pO1xuICAgIHJldHVybiBidWlsZGVyLmJ1aWxkKCk7XG59XG4iLCAiaW1wb3J0IHsgY3JlYXRlVjJDb250YWluZXIgfSBmcm9tICcuLi9WMkNvbXBvc2l0aW9uUm9vdCc7XG5jb25zdCBjb250YWluZXIgPSBjcmVhdGVWMkNvbnRhaW5lcigpO1xuY29udGFpbmVyLnJlc29sdmVUeXBlKFwiRGVtb0FwcFwiKS5pbml0KCkuY2F0Y2goY29uc29sZS5lcnJvcik7XG4iXSwKICAibWFwcGluZ3MiOiAiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUE7QUFBQTtBQUFBLEtBQUMsU0FBUyxHQUFFLEdBQUU7QUFBQyxrQkFBVSxPQUFPLFdBQVMsZUFBYSxPQUFPLFNBQU8sT0FBTyxVQUFRLEVBQUUsSUFBRSxjQUFZLE9BQU8sVUFBUSxPQUFPLE1BQUksT0FBTyxDQUFDLEtBQUcsSUFBRSxlQUFhLE9BQU8sYUFBVyxhQUFXLEtBQUcsTUFBTSxRQUFNLEVBQUU7QUFBQSxJQUFDLEVBQUUsU0FBTSxXQUFVO0FBQUM7QUFBYSxVQUFJLElBQUUsS0FBSSxJQUFFLEtBQUksSUFB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V0FBVztBQUFBLElBQ2xDO0FBQUEsRUFDSixDQUFDO0FBRUQsVUFBUSxhQUFhLE9BQU8sRUFBRSxHQUFHLFNBQVMsRUFBRSxTQUFTO0FBQUEsSUFDakQsY0FBYztBQUFBLE1BQ1YsT0FBSyxFQUFFLFlBQVksa0JBQWtCO0FBQUEsTUFDckMsT0FBSyxFQUFFLFlBQVksWUFBWTtBQUFBLE1BQy9CLE9BQUssRUFBRSxZQUFZLGNBQWM7QUFBQSxNQUNqQyxPQUFLLEVBQUUsWUFBWSxhQUFhO0FBQUEsTUFDaEMsT0FBSyxFQUFFLFlBQVksYUFBYTtBQUFBLE1BQ2hDLE9BQUssRUFBRSxZQUFZLGlCQUFpQjtBQUFBLE1BQ3BDLE9BQUssRUFBRSxZQUFZLFdBQVc7QUFBQSxJQUNsQztBQUFBLEVBQ0osQ0FBQztBQUNELFNBQU8sUUFBUSxNQUFNO0FBQ3pCO0FBdlZnQjs7O0FDekVoQixJQUFNLFlBQVksa0JBQWtCO0FBQ3BDLFVBQVUsWUFBWSxTQUFTLEVBQUUsS0FBSyxFQUFFLE1BQU0sUUFBUSxLQUFLOyIsCiAgIm5hbWVzIjogWyJ0IiwgImUiLCAibiIsICJyIiwgImkiLCAicyIsICJ1IiwgImEiLCAiTSIsICJtIiwgImYiLCAibCIsICIkIiwgInkiLCAidiIsICJnIiwgIkQiLCAibyIsICJkIiwgImMiLCAiaCIsICJ0IiwgImkiLCAiZSIsICJzIiwgImYiLCAibiIsICJ1IiwgInIiLCAibyIsICJ0IiwgIm4iLCAiaSIsICJvIiwgInIiLCAiZSIsICJ1IiwgImYiLCAicyIsICJhIiwgInQiLCAiaSIsICJkIiwgIm4iLCAiZSIsICJzIiwgInRva2VuIiwgImNvbnRhaW5lciIsICJjb250YWluZXIiLCAidG9rZW4iLCAiY29udGFpbmVyIiwgImRheWpzIiwgInV0YyIsICJ0aW1lem9uZSIsICJpc29XZWVrIiwgImNvbnRhaW5lciIsICJjb250YWluZXIiLCAiY29udGFpbmVyIiwgImNvbnRhaW5lciIsICJjb250YWluZXIiLCAiY29udGFpbmVyIiwgImdldEtleSIsICJza2lwUGF0aCIsICJrZXkiLCAiY29udGFpbmVyIiwgImNvbnRhaW5lciIsICJjb250YWluZXIiLCAiZGlmZiIsICJjb250YWluZXIiLCAiY29udGFpbmVyIiwgImRpZmYiLCAiY29udGFpbmVyIl0KfQo= diff --git a/wwwroot/js/workers/SyncManager.d.ts b/wwwroot/js/workers/SyncManager.d.ts new file mode 100644 index 0000000..dfc7f40 --- /dev/null +++ b/wwwroot/js/workers/SyncManager.d.ts @@ -0,0 +1,78 @@ +import { IEventBus } from '../types/CalendarTypes'; +import { OperationQueue } from '../storage/OperationQueue'; +import { IndexedDBService } from '../storage/IndexedDBService'; +import { ApiEventRepository } from '../repositories/ApiEventRepository'; +/** + * SyncManager - Background sync worker + * Processes operation queue and syncs with API when online + * + * Features: + * - Monitors online/offline status + * - Processes queue with FIFO order + * - Exponential backoff retry logic + * - Updates syncStatus in IndexedDB after successful sync + * - Emits sync events for UI feedback + */ +export declare class SyncManager { + private eventBus; + private queue; + private indexedDB; + private apiRepository; + private isOnline; + private isSyncing; + private syncInterval; + private maxRetries; + private intervalId; + constructor(eventBus: IEventBus, queue: OperationQueue, indexedDB: IndexedDBService, apiRepository: ApiEventRepository); + /** + * Setup online/offline event listeners + */ + private setupNetworkListeners; + /** + * Start background sync worker + */ + startSync(): void; + /** + * Stop background sync worker + */ + stopSync(): void; + /** + * Process operation queue + * Sends pending operations to API + */ + private processQueue; + /** + * Process a single operation + */ + private processOperation; + /** + * Mark event as synced in IndexedDB + */ + private markEventAsSynced; + /** + * Mark event as error in IndexedDB + */ + private markEventAsError; + /** + * Calculate exponential backoff delay + * @param retryCount Current retry count + * @returns Delay in milliseconds + */ + private calculateBackoff; + /** + * Manually trigger sync (for testing or manual sync button) + */ + triggerManualSync(): Promise; + /** + * Get current sync status + */ + getSyncStatus(): { + isOnline: boolean; + isSyncing: boolean; + isRunning: boolean; + }; + /** + * Cleanup - stop sync and remove listeners + */ + destroy(): void; +} diff --git a/wwwroot/js/workers/SyncManager.js b/wwwroot/js/workers/SyncManager.js new file mode 100644 index 0000000..4e67e87 --- /dev/null +++ b/wwwroot/js/workers/SyncManager.js @@ -0,0 +1,229 @@ +import { CoreEvents } from '../constants/CoreEvents'; +/** + * SyncManager - Background sync worker + * Processes operation queue and syncs with API when online + * + * Features: + * - Monitors online/offline status + * - Processes queue with FIFO order + * - Exponential backoff retry logic + * - Updates syncStatus in IndexedDB after successful sync + * - Emits sync events for UI feedback + */ +export class SyncManager { + constructor(eventBus, queue, indexedDB, apiRepository) { + this.isOnline = navigator.onLine; + this.isSyncing = false; + this.syncInterval = 5000; // 5 seconds + this.maxRetries = 5; + this.intervalId = null; + this.eventBus = eventBus; + this.queue = queue; + this.indexedDB = indexedDB; + this.apiRepository = apiRepository; + this.setupNetworkListeners(); + this.startSync(); + console.log('SyncManager initialized and started'); + } + /** + * Setup online/offline event listeners + */ + setupNetworkListeners() { + window.addEventListener('online', () => { + this.isOnline = true; + this.eventBus.emit(CoreEvents.OFFLINE_MODE_CHANGED, { + isOnline: true + }); + console.log('SyncManager: Network online - starting sync'); + this.startSync(); + }); + window.addEventListener('offline', () => { + this.isOnline = false; + this.eventBus.emit(CoreEvents.OFFLINE_MODE_CHANGED, { + isOnline: false + }); + console.log('SyncManager: Network offline - pausing sync'); + this.stopSync(); + }); + } + /** + * Start background sync worker + */ + startSync() { + if (this.intervalId) { + return; // Already running + } + console.log('SyncManager: Starting background sync'); + // Process immediately + this.processQueue(); + // Then poll every syncInterval + this.intervalId = window.setInterval(() => { + this.processQueue(); + }, this.syncInterval); + } + /** + * Stop background sync worker + */ + stopSync() { + if (this.intervalId) { + window.clearInterval(this.intervalId); + this.intervalId = null; + console.log('SyncManager: Stopped background sync'); + } + } + /** + * Process operation queue + * Sends pending operations to API + */ + async processQueue() { + // Don't sync if offline + if (!this.isOnline) { + return; + } + // Don't start new sync if already syncing + if (this.isSyncing) { + return; + } + // Check if queue is empty + if (await this.queue.isEmpty()) { + return; + } + this.isSyncing = true; + try { + const operations = await this.queue.getAll(); + this.eventBus.emit(CoreEvents.SYNC_STARTED, { + operationCount: operations.length + }); + // Process operations one by one (FIFO) + for (const operation of operations) { + await this.processOperation(operation); + } + this.eventBus.emit(CoreEvents.SYNC_COMPLETED, { + operationCount: operations.length + }); + } + catch (error) { + console.error('SyncManager: Queue processing error:', error); + this.eventBus.emit(CoreEvents.SYNC_FAILED, { + error: error instanceof Error ? error.message : 'Unknown error' + }); + } + finally { + this.isSyncing = false; + } + } + /** + * Process a single operation + */ + async processOperation(operation) { + // Check if max retries exceeded + if (operation.retryCount >= this.maxRetries) { + console.error(`SyncManager: Max retries exceeded for operation ${operation.id}`, operation); + await this.queue.remove(operation.id); + await this.markEventAsError(operation.eventId); + return; + } + try { + // Send to API based on operation type + switch (operation.type) { + case 'create': + await this.apiRepository.sendCreate(operation.data); + break; + case 'update': + await this.apiRepository.sendUpdate(operation.eventId, operation.data); + break; + case 'delete': + await this.apiRepository.sendDelete(operation.eventId); + break; + default: + console.error(`SyncManager: Unknown operation type ${operation.type}`); + await this.queue.remove(operation.id); + return; + } + // Success - remove from queue and mark as synced + await this.queue.remove(operation.id); + await this.markEventAsSynced(operation.eventId); + console.log(`SyncManager: Successfully synced operation ${operation.id}`); + } + catch (error) { + console.error(`SyncManager: Failed to sync operation ${operation.id}:`, error); + // Increment retry count + await this.queue.incrementRetryCount(operation.id); + // Calculate backoff delay + const backoffDelay = this.calculateBackoff(operation.retryCount + 1); + this.eventBus.emit(CoreEvents.SYNC_RETRY, { + operationId: operation.id, + retryCount: operation.retryCount + 1, + nextRetryIn: backoffDelay + }); + } + } + /** + * Mark event as synced in IndexedDB + */ + async markEventAsSynced(eventId) { + try { + const event = await this.indexedDB.getEvent(eventId); + if (event) { + event.syncStatus = 'synced'; + await this.indexedDB.saveEvent(event); + } + } + catch (error) { + console.error(`SyncManager: Failed to mark event ${eventId} as synced:`, error); + } + } + /** + * Mark event as error in IndexedDB + */ + async markEventAsError(eventId) { + try { + const event = await this.indexedDB.getEvent(eventId); + if (event) { + event.syncStatus = 'error'; + await this.indexedDB.saveEvent(event); + } + } + catch (error) { + console.error(`SyncManager: Failed to mark event ${eventId} as error:`, error); + } + } + /** + * Calculate exponential backoff delay + * @param retryCount Current retry count + * @returns Delay in milliseconds + */ + calculateBackoff(retryCount) { + // Exponential backoff: 2^retryCount * 1000ms + // Retry 1: 2s, Retry 2: 4s, Retry 3: 8s, Retry 4: 16s, Retry 5: 32s + const baseDelay = 1000; + const exponentialDelay = Math.pow(2, retryCount) * baseDelay; + const maxDelay = 60000; // Max 1 minute + return Math.min(exponentialDelay, maxDelay); + } + /** + * Manually trigger sync (for testing or manual sync button) + */ + async triggerManualSync() { + console.log('SyncManager: Manual sync triggered'); + await this.processQueue(); + } + /** + * Get current sync status + */ + getSyncStatus() { + return { + isOnline: this.isOnline, + isSyncing: this.isSyncing, + isRunning: this.intervalId !== null + }; + } + /** + * Cleanup - stop sync and remove listeners + */ + destroy() { + this.stopSync(); + // Note: We don't remove window event listeners as they're global + } +} +//# sourceMappingURL=SyncManager.js.map \ No newline at end of file diff --git a/wwwroot/js/workers/SyncManager.js.map b/wwwroot/js/workers/SyncManager.js.map new file mode 100644 index 0000000..3bdd938 --- /dev/null +++ b/wwwroot/js/workers/SyncManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"SyncManager.js","sourceRoot":"","sources":["../../../src/workers/SyncManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/wwwroot/plantempus-sales.html b/wwwroot/plantempus-sales.html new file mode 100644 index 0000000..9439cd8 --- /dev/null +++ b/wwwroot/plantempus-sales.html @@ -0,0 +1,1896 @@ + + + + + + + Plantempus - Din salon digitaliseret og optimeret + + + + + + + + + + +
+
+
+ + Til professionelle frisører og skønhedssaloner +
+

Mere tid til det du er god til

+

+ Plantempus tager sig af booking, administration og papirarbejde - så du kan fokusere på dine kunder og dit håndværk. +

+ +
+
+ + +
+
+
10t
+
Mere tid til kunder om ugen
+
+
+
0 kr
+
I tabte bookinger
+
+
+
24/7
+
Kunderne kan selv booke
+
+
+
5 min
+
At lukke kassen dagligt
+
+
+ + +
+
+
+
For professionelle frisører
+

Gør det du elsker - ikke papirarbejde

+
+ +
+

+ Du blev frisør for at klippe hår - ikke for at sidde med papirarbejde. + Plantempus håndterer booking, påmindelser og løn automatisk. + Kunderne booker selv online. Du får tid til det du er god til. +

+

+ 10 timer mere om ugen til dine kunder. + Ingen dobbeltbookinger. Ingen glemte påmindelser. Ingen timer ved Excel. + Bare en fyldt kalender og tilfredse kunder. +

+

+ Gratis i 14 dage. + Ingen binding. Intet kreditkort. +

+
+
+
+ + +
+
+
+
Kernefunktionalitet
+

Alt hvad din salon har brug for - i ét system

+

+ Fra online booking til lønudbetaling. Plantempus dækker hele din forretning. +

+
+ +
+
+
+ +
+

Booking der kører sig selv

+

+ Kunderne booker selv online når det passer dem - også klokken 22 om aftenen. + Systemet sender selv påmindelser, så du slipper for no-shows. +

+
+ +
+
+ +
+

Husk hver kunde

+

+ Gem farveformler, noter og præferencer direkte i systemet. + Næste gang kunden kommer, har du alt ved hånden - selv om det er 6 måneder siden. +

+
+ +
+
+ +
+

Nem betaling

+

+ Tag imod kort, kontant, MobilePay og gavekort - alt sammen i samme system. + Scan stregkoder på produkter og de kommer automatisk med på kvitteringen. +

+
+ +
+
+ +
+

Styr på medarbejdere

+

+ Planlæg vagter, hold styr på ferie og sygedage. + Løn og provision bliver beregnet automatisk - eksporter direkte til dit lønsystem. +

+
+ +
+
+ +
+

Nemt kassearbejde

+

+ Luk kassen på 5 minutter i stedet for 30. + Systemet tæller op for dig og viser hvor meget der skal være i skuffen. +

+
+ +
+
+ +
+

Se hvordan det går

+

+ Simpelt dashboard der viser hvad du tjener, hvilke kunder der kommer tilbage, + og hvor meget hver medarbejder omsætter for. Ingen komplicerede rapporter. +

+
+
+
+
+ + +
+
+
+
+
+ + Smart hjælp +
+

Systemet arbejder for dig - ikke omvendt

+

+ Plantempus gør det tunge arbejde. Det foreslår de bedste tider til dine kunder (så de ikke ringer midt i en klipning), + giver dig besked når du har huller i kalenderen der kan fyldes, og husker hvilke produkter dine kunder plejer at købe. + Det er som at have en ekstra medarbejder - der aldrig har fri. +

+
+
+
10t
+
Mere tid til kunder/uge
+
+
+
0
+
Glemte påmindelser
+
+
+
+8
+
Ekstra kunder/uge
+
+
+
+
+ AI Dashboard +
+
+
+
+ + +
+
+
+
Alt du har brug for
+

Ét system der klarer det hele

+

+ Fra booking til løn. Fra kundejournal til kasseafstemning. Alt samlet ét sted. +

+
+ +
+ +
+
+
+ +
+

Booking & Kalender

+
+
    +
  • Kunderne booker selv online
  • +
  • Forslag til ledige tider
  • +
  • Venteliste når I er fyldt op
  • +
  • Flyt bookinger med træk-og-slip
  • +
  • Book flere medarbejdere til samme kunde
  • +
  • Automatiske påmindelser
  • +
+
+ + +
+
+
+ +
+

Kunde-management

+
+
    +
  • Alle kunder ét sted
  • +
  • Marker VIP-kunder og stamkunder
  • +
  • Gem noter og observationer
  • +
  • Farveformler der følger med
  • +
  • Link familie-medlemmer
  • +
  • Håndter marketing-tilladelser
  • +
+
+ + +
+
+
+ +
+

Medarbejdere

+
+
    +
  • Planlæg vagter for hele ugen
  • +
  • Løn regnes automatisk ud
  • +
  • Gem kontrakter og dokumenter
  • +
  • Hold styr på ferie og sygdom
  • +
  • Påmindelser om certificeringer
  • +
  • Se hvem der tjener mest
  • +
+
+ + +
+
+
+ +
+

Økonomi & Kasse

+
+
    +
  • Tag imod kort, kontant, MobilePay
  • +
  • Sælg og administrer gavekort
  • +
  • Kasseafstemning på 5 minutter
  • +
  • Del betalingen på flere metoder
  • +
  • Print eller email kvitteringer
  • +
  • Scan produkter ind
  • +
+
+ + +
+
+
+ +
+

Smart hjælp

+
+
    +
  • Foreslår de bedste booking-tider
  • +
  • Finder huller i din kalender
  • +
  • Advarer når kunder er ved at stoppe
  • +
  • Anbefaler produkter til kunder
  • +
  • Viser hvad der sælger bedst
  • +
  • Giver dig overblik hver dag
  • +
+
+ + +
+
+
+ +
+

Ting der sker selv

+
+
    +
  • SMS går automatisk ud
  • +
  • Emails når noget ændres
  • +
  • Send løn direkte til revisor
  • +
  • Kvitteringer til sygeforsikring
  • +
  • Synk med din telefon-kalender
  • +
  • Kvitteringer printes eller sendes
  • +
+
+ + +
+
+
+ +
+

Virker med det du har

+
+
    +
  • Intect, Proløn, Zenegy og flere
  • +
  • Sender til "danmark" sygeforsikring
  • +
  • Tæl besøgende på hjemmesiden
  • +
  • Beskyt kundernes privatliv
  • +
  • Synk med Google/Apple kalender
  • +
  • Kan kobles til andre systemer
  • +
+
+ + +
+
+
+ +
+

Gør det til dit eget

+
+
    +
  • Slå funktioner til og fra
  • +
  • Dit logo og dine farver
  • +
  • Skriv dine egne beskeder
  • +
  • Sæt åbningstider og helligdage
  • +
  • Flere saloner i samme system
  • +
  • Mørkt tema til aftenvagter
  • +
+
+
+
+
+ + +
+
+
+
Resultater
+

Det virker i praksis

+

+ Tal fra saloner der allerede bruger Plantempus +

+
+ +
+
+
10t
+
Mere tid til kunder hver uge
+
+
+
95%
+
Kunder møder op (ingen no-shows)
+
+
+
8
+
Ekstra kunder om ugen
+
+
+
0 kr
+
Tabte bookinger om måneden
+
+
+
+
+ + +
+
+
+
Fordele
+

Hvorfor vælge Plantempus?

+
+ +
+
+
+ +
+
+

Start i dag

+

+ Intet at installere. Ingen kompliceret opsætning. + Du kan være klar til at modtage bookinger samme dag du starter. +

+
+
+ +
+
+ +
+
+

Ingen overraskelser

+

+ Fast pris hver måned. Ingen skjulte gebyrer. Ubegrænsede bookinger og SMS inkluderet. + Du betaler kun for antallet af medarbejdere. +

+
+
+ +
+
+ +
+
+

Sikkerhed & GDPR

+

+ Dine data er krypteret og hostet sikkert i EU. Fuld GDPR-compliance, + automatisk backup og 99.9% uptime garanti. +

+
+
+ +
+
+ +
+
+

Hjælp når du har brug for det

+

+ Skriv eller ring på dansk til folk der kender til frisørbranchen. + Vi svarer hurtigt og forklarer tingene i et sprog du forstår. +

+
+
+ +
+
+ +
+
+

Virker på mobilen

+

+ Kunderne booker fra deres telefon. Du kan tjekke kalenderen og ændre bookinger fra din tablet. + Intet kræver at du sidder ved en computer. +

+
+
+ +
+
+ +
+
+

Bliver bedre hele tiden

+

+ Nye funktioner tilføjes løbende uden du skal betale mere. + Vi lytter til hvad I har brug for og bygger det ind. +

+
+
+
+
+
+ + +
+
+
+
Priser
+

Vælg den plan der passer til dig

+

+ Alle planer inkluderer 14 dages gratis prøveperiode. Ingen binding. Opsig når som helst. +

+
+ +
+ +
+

Basis

+

1-3 brugere

+
+ 299 + kr/md +
+
    +
  • Op til 3 brugere
  • +
  • Online booking
  • +
  • Kalender & aftalestyring
  • +
  • Kundekartotek
  • +
  • SMS-påmindelser
  • +
  • Email-notifikationer
  • +
  • Basis rapporter
  • +
  • Email support
  • +
+ +
+ + + + + +
+

Enterprise

+

8+ brugere

+
+ Kontakt os +
+
    +
  • Ubegrænset brugere
  • +
  • Alt fra Pro
  • +
  • Flere lokationer
  • +
  • Tilpasset integration
  • +
  • Dedikeret kontaktperson
  • +
  • SLA & uptime garanti
  • +
  • On-premise option
  • +
  • 24/7 prioriteret support
  • +
+ +
+
+
+
+ + +
+
+
+
Sammenligning
+

Plantempus vs. Traditionelle Systemer

+

+ Se forskellen på et system bygget til moderne saloner +

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeaturePlantempusAndre systemer
Avanceret kundesøgning
Detaljerede statistikker med grafer
Farveformler & håranalyser
Automatisk lønberegning med provision
Multi-payment checkout (flere metoder)
Kasseafstemning
Stregkodescanner med produkt-genkendelse
Smart tidsforslag til kunder
Moderne, hurtig grænseflade
Eksport til 5+ lønsystemer
+
+ ✓ = Inkluderet · △ = Begrænset · — = Ikke tilgængelig +
+
+
+
+ + +
+
+
+
Se det rigtige system
+

Ikke bare infographics - det her er rigtigt

+

+ Vi er stolte af vores UX og performance. Se hvordan systemet faktisk ser ud. +

+
+ +
+
+

+ + Dashboard med rigtige data +

+

+ Se dagens omsætning, bookinger og medarbejder-status på ét blik. Ikke bare tal - visuelle grafer der giver mening. +

+ + Se live demo + +
+ +
+

+ + Kunde-søgning der virker +

+

+ Find kunder på navn, telefon, email - med det samme. Filtrér på VIP, stamkunder eller nye kunder. Hurtigt og præcist. +

+ + Se live demo + +
+ +
+

+ + Kundeprofiler med detaljer +

+

+ Farveformler, håranalyser, præferencer og købs-historie. Alt organiseret og nemt at finde. +

+ + Se live demo + +
+ +
+

+ + Løn & Provision +

+

+ Se præcis hvad hver medarbejder har tjent. Eksportér direkte til Intect, Proløn eller Zenegy med et klik. +

+ + Se live demo + +
+
+ +
+

+ + Alle demos er interaktive - klik og prøv dem +

+

+ Ikke mock-ups. Ikke static billeder. Det her er det rigtige system i aktion. +

+
+
+
+ + +
+
+
+
Performance & Design
+

Bygget med omtanke - mærkes i hverdagen

+
+ +
+
+
+ +
+
+

Lynhurtigt

+

+ Under 1 sekund load-tid. Ingen langsomme overgang + +er. + Systemet reagerer med det samme - som det skal være. +

+
+
+ +
+
+ +
+
+

Gennemtænkt UX

+

+ Hver knap, hver farve, hver animation er testet. + Designet så det er intuitivt - også for den der ikke er teknisk. +

+
+
+ +
+
+ +
+
+

Statistik der giver mening

+

+ Ikke bare tal på en side. Visuelle grafer, trends og indsigter. + Se med det samme hvad der virker og hvad der ikke gør. +

+
+
+ +
+
+ +
+
+

Moderne design

+

+ Ikke et system fra 2010. Rent, moderne interface der matcher + det professionelle udtryk din salon har. +

+
+
+
+
+
+ + +
+
+
+
Kundeudtalelser
+

Hvad siger vores brugere?

+
+ +
+
+
+ "Nu har jeg faktisk tid til at snakke med mine kunder i stedet for at rende efter telefonen. + Bookinger sker automatisk, påmindelser går selv ud, og jeg kan se hele ugen med et blik. Det er befriende." +
+
+
KK
+
+
Karina Knudsen
+
Ejer, KARINA KNUDSEN®
+
+
+
+ +
+
+ "Mine kunder elsker at de kan booke online klokken 22 om aftenen når de lige kommer i tanke om det. + Og jeg slipper for at svare på telefonen hele dagen. Win-win." +
+
+
MH
+
+
Maria Hansen
+
Salonchef, Salon Bellezza
+
+
+
+ +
+
+ "Før brugte jeg en hel formiddag hver måned på løn. Nu tager det 10 minutter. + Systemet har styr på hvem der har arbejdet hvornår, og sender det hele til min revisor." +
+
+
AS
+
+
Anna Sørensen
+
Økonomiansvarlig, Beauty Studio
+
+
+
+
+
+
+ + +
+
+

Klar til at optimere din salon?

+

Start din gratis 14-dages prøveperiode i dag. Ingen kreditkort påkrævet.

+ +
+
+ + + + + + + + +
  • Om os \ No newline at end of file diff --git a/wwwroot/poc-booking.html b/wwwroot/poc-booking.html deleted file mode 100644 index 4de81c9..0000000 --- a/wwwroot/poc-booking.html +++ /dev/null @@ -1,1806 +0,0 @@ - - - - - - Book tid - KARINA KNUDSEN® - - - - - - - - - - - - - SB - -

    KARINA KNUDSEN®

    -

    Amager Strandvej 22f, 2300 Kbh S

    -
    -
    - - - - - - - -

    Vælg ydelse

    -

    Vælg hvad du vil have lavet

    -
    - Rediger - -
    - - -
    -
    -
    -
    - - - - - - -

    Vælg medarbejder

    -

    Valgfrit

    -
    - Rediger - -
    - - -
    -
    -
    -
    - - - - - - -

    Vælg dato og tid

    -

    Find en ledig tid

    -
    - Rediger - -
    - - -
    -
    -
    -
    - - - - - - -

    Dine oplysninger

    -

    Kontaktinformation

    -
    - Rediger - -
    - - - - - Fornavn - - - - Efternavn - - - - Telefon - - - - Email - - - - Noter (valgfrit) - - - - - -
    -
    -
    - - - - Din booking - - - - Vælg en ydelse for at starte - - - - - - - - - - - Book tid - - -
    - - - - Videre - - - - - - - - -

    Booking bekræftet!

    -

    Du modtager en bekræftelse på email og SMS. Vi glæder os til at se dig!

    -
    -
    - - - - - diff --git a/wwwroot/poc-checkout.html b/wwwroot/poc-checkout.html index c843b6f..210d46f 100644 --- a/wwwroot/poc-checkout.html +++ b/wwwroot/poc-checkout.html @@ -943,6 +943,416 @@ border-color: #ccc; } + /* ========================================== + BARCODE SCANNER + ========================================== */ + .scan-btn { + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + width: 100%; + padding: 14px 20px; + margin-top: 12px; + font-size: 13px; + font-weight: 600; + color: var(--color-teal); + background: color-mix(in srgb, var(--color-teal) 8%, white); + border: 2px dashed var(--color-teal); + border-radius: 6px; + cursor: pointer; + transition: all 200ms ease; + } + + .scan-btn:hover { + background: color-mix(in srgb, var(--color-teal) 15%, white); + border-style: solid; + } + + .scan-btn.scanning { + border-color: #1976d2; + color: #1976d2; + background: color-mix(in srgb, #1976d2 8%, white); + animation: pulse-border 1.5s ease-in-out infinite; + } + + @keyframes pulse-border { + 0%, 100% { border-color: #1976d2; } + 50% { border-color: #64b5f6; } + } + + .scan-btn svg { + width: 18px; + height: 18px; + fill: currentColor; + } + + .scanner-input-hidden { + position: absolute; + left: -9999px; + opacity: 0; + } + + /* Debug codes in sidebar */ + .debug-codes { + display: flex; + flex-direction: column; + gap: 4px; + padding: 12px 16px; + border-top: 1px solid var(--color-border); + font-size: 11px; + color: var(--color-text-secondary); + } + + .debug-codes strong { + color: var(--color-text); + margin-bottom: 4px; + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.5px; + } + + .debug-codes div { + display: flex; + align-items: center; + gap: 6px; + } + + .debug-codes code { + font-family: var(--font-mono); + font-size: 10px; + background: var(--color-surface); + padding: 2px 6px; + border-radius: 3px; + cursor: pointer; + user-select: all; + } + + .debug-codes code:hover { + background: var(--color-teal); + color: white; + } + + /* ========================================== + RECEIPT + ========================================== */ + .receipt-container { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0,0,0,0.3); + z-index: 10; + display: flex; + justify-content: center; + padding-top: 20px; + opacity: 0; + visibility: hidden; + transition: opacity 300ms ease, visibility 300ms ease; + overflow-y: auto; + } + + .receipt-container.visible { + opacity: 1; + visibility: visible; + } + + .receipt { + width: 280px; + background: #fff; + font-family: 'Courier New', monospace; + font-size: 12px; + color: #000; + padding: 20px 16px; + box-shadow: 0 4px 20px rgba(0,0,0,0.3); + transform: translateY(-100%); + transition: transform 500ms cubic-bezier(0.34, 1.56, 0.64, 1); + position: relative; + margin-bottom: 20px; + } + + .receipt-container.visible .receipt { + transform: translateY(0); + } + + /* Torn paper effect at bottom */ + .receipt::after { + content: ''; + position: absolute; + bottom: -10px; + left: 0; + right: 0; + height: 10px; + background: linear-gradient(135deg, #fff 25%, transparent 25%), + linear-gradient(-135deg, #fff 25%, transparent 25%); + background-size: 10px 10px; + } + + .receipt-header { + text-align: center; + padding-bottom: 12px; + border-bottom: 1px dashed #000; + margin-bottom: 12px; + } + + .receipt-logo { + font-size: 18px; + font-weight: bold; + letter-spacing: 2px; + margin-bottom: 4px; + } + + .receipt-address { + font-size: 10px; + line-height: 1.4; + } + + .receipt-info { + display: flex; + justify-content: space-between; + font-size: 10px; + padding: 8px 0; + border-bottom: 1px dashed #000; + margin-bottom: 12px; + } + + .receipt-items { + margin-bottom: 12px; + } + + .receipt-item { + display: flex; + justify-content: space-between; + padding: 4px 0; + font-size: 11px; + } + + .receipt-item-name { + flex: 1; + padding-right: 8px; + } + + .receipt-item-qty { + width: 30px; + text-align: center; + } + + .receipt-item-price { + width: 70px; + text-align: right; + font-family: 'JetBrains Mono', monospace; + } + + .receipt-divider { + border-top: 1px dashed #000; + margin: 8px 0; + } + + .receipt-totals { + margin-bottom: 12px; + } + + .receipt-total-row { + display: flex; + justify-content: space-between; + padding: 3px 0; + font-size: 11px; + } + + .receipt-total-row.grand { + font-size: 14px; + font-weight: bold; + padding: 8px 0; + border-top: 2px solid #000; + border-bottom: 2px solid #000; + margin: 8px 0; + } + + .receipt-payment { + font-size: 10px; + padding: 8px 0; + border-bottom: 1px dashed #000; + } + + .receipt-payment-row { + display: flex; + justify-content: space-between; + padding: 2px 0; + } + + .receipt-footer { + text-align: center; + padding-top: 12px; + font-size: 10px; + } + + .receipt-footer-msg { + font-size: 12px; + font-weight: bold; + margin-bottom: 8px; + } + + .receipt-qr { + margin: 12px auto 8px; + display: flex; + justify-content: center; + } + + .receipt-qr svg { + border: 2px solid #000; + } + + .receipt-id { + font-size: 9px; + letter-spacing: 1px; + } + + .receipt-close { + position: absolute; + top: 8px; + right: 8px; + width: 24px; + height: 24px; + border: none; + background: #f0f0f0; + border-radius: 50%; + cursor: pointer; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + } + + .receipt-close:hover { + background: #e0e0e0; + } + + .receipt-actions { + display: flex; + gap: 8px; + margin-top: 16px; + padding-top: 12px; + border-top: 1px solid #e0e0e0; + } + + .receipt-actions button { + flex: 1; + padding: 8px; + font-size: 11px; + border-radius: 4px; + cursor: pointer; + font-family: var(--font-family); + } + + .receipt-actions .btn-print { + background: #000; + color: #fff; + border: none; + } + + .receipt-actions .btn-close { + background: #fff; + border: 1px solid #ccc; + color: #333; + } + + /* ========================================== + PRINT STYLES + ========================================== */ + @media print { + /* White background */ + html, body { + background: white !important; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; + } + + /* Hide everything by default */ + .demo-trigger, + .overlay, + .header, + .sidebar, + .cart-area, + .payment-total, + .payment-methods, + .payment-input-section, + .registered-payments, + .payment-footer { + display: none !important; + } + + /* Reset panel positioning */ + .panel { + position: static !important; + width: 100% !important; + transform: none !important; + box-shadow: none !important; + background: white !important; + } + + .main { + display: block !important; + } + + .payment-panel { + display: block !important; + position: static !important; + border: none !important; + background: white !important; + } + + /* Show receipt */ + .receipt-container { + display: flex !important; + justify-content: center !important; + visibility: visible !important; + opacity: 1 !important; + position: static !important; + background: white !important; + padding: 0 !important; + } + + .receipt { + display: block !important; + margin: 0 !important; + box-shadow: none !important; + transform: none !important; + text-align: left !important; + } + + /* Preserve item layout */ + .receipt-item { + display: flex !important; + justify-content: space-between !important; + text-align: left !important; + } + + .receipt-item-name { + text-align: left !important; + } + + .receipt-item-price { + text-align: right !important; + } + + .receipt-total-row { + display: flex !important; + justify-content: space-between !important; + } + + /* Hide buttons in receipt */ + .receipt-close, + .receipt-actions { + display: none !important; + } + + /* Remove torn paper effect */ + .receipt::after { + display: none; + } + } + @@ -992,6 +1402,13 @@
    Styling
    Olaplex
    +
    + Test EAN-koder: +
    5012345678900 Olaplex No.4
    +
    8710447489109 Redken Shampoo
    +
    3474636610143 Kérastase
    +
    0000000000000 Ukendt
    +
    @@ -1176,6 +1593,14 @@
    + + + + + @@ -1198,7 +1623,178 @@ -
    +
    + +
    +
    + + +
    + +
    + Østergade 42, 1. sal
    + 1100 København K
    + Tlf: 33 12 34 56
    + CVR: 12345678 +
    +
    + +
    +
    +
    Dato: 16-12-2025
    +
    Tid: 14:32
    +
    +
    +
    Kvit: #4521
    +
    Kasse: 1
    +
    +
    + +
    +
    + Vare + Antal + Pris +
    +
    + Dameklip + 1 + 725,00 +
    +
    + Gloss langt hår + 1 + 900,00 +
    +
    + Olaplex No. 3 + 1 + 300,00 +
    +
    + India mask 250ml + 2 + 350,00 +
    +
    + +
    + +
    +
    + Subtotal + 1.820,00 +
    +
    + Moms (25%) + 455,00 +
    +
    + TOTAL + 2.275,00 DKK +
    +
    + +
    +
    + Betalt med Kort + 2.275,00 +
    +
    + + + +
    + + +
    +
    +
    2.275 kr
    Total at betale
    @@ -1264,7 +1860,7 @@
    - +
    @@ -1653,6 +2249,251 @@ document.addEventListener('keydown', e => { if (e.key === 'Escape') closePanel(); }); + + // ========================================== + // BARCODE SCANNER + // ========================================== + + // Mock product database for scanning + const scanProducts = { + '5012345678900': { + name: 'Olaplex No.4 Bond Maintenance Shampoo', + price: 299, + size: '250ml' + }, + '8710447489109': { + name: 'Redken All Soft Shampoo', + price: 249, + size: '300ml' + }, + '3474636610143': { + name: 'Kérastase Elixir Ultime', + price: 425, + size: '100ml' + }, + '0850018802239': { + name: 'Olaplex No.7 Bonding Oil', + price: 319, + size: '30ml' + } + }; + + // Scanner elements + const scanButton = document.getElementById('scanButton'); + const scannerInput = document.getElementById('scannerInput'); + + let isScanning = false; + let scannedCode = ''; + let scanTimeout = null; + + // Start scanning + scanButton.addEventListener('click', () => { + if (isScanning) return; + + isScanning = true; + scannedCode = ''; + scanButton.classList.add('scanning'); + scanButton.innerHTML = ` + + SCANNING... + `; + scannerInput.value = ''; + scannerInput.focus(); + }); + + // Handle scanner input + scannerInput.addEventListener('input', (e) => { + scannedCode = e.target.value; + + if (scanTimeout) clearTimeout(scanTimeout); + + // After 200ms of no input, consider scan complete + scanTimeout = setTimeout(() => { + if (scannedCode.length >= 8) { + processScannedCode(scannedCode); + } + }, 200); + }); + + // Handle Enter key from scanner + scannerInput.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === 'Tab') { + e.preventDefault(); + if (scannedCode.length >= 8) { + clearTimeout(scanTimeout); + processScannedCode(scannedCode); + } + } + }); + + // Handle blur - reset if no code scanned + scannerInput.addEventListener('blur', () => { + if (isScanning && scannedCode.length === 0) { + setTimeout(() => { + if (isScanning && scannedCode.length === 0) { + resetScanner(); + } + }, 300); + } + }); + + // Reset scanner to ready state + function resetScanner() { + isScanning = false; + scannedCode = ''; + scanButton.classList.remove('scanning'); + scanButton.innerHTML = ` + + SCAN PRODUKT + `; + } + + // Process scanned code + async function processScannedCode(code) { + // Show loading state + scanButton.innerHTML = ` + + HENTER... + `; + + // Simulate API delay + await new Promise(r => setTimeout(r, 600)); + + // Look up product + const foundProduct = scanProducts[code] || null; + + if (foundProduct) { + // Add to cart + addScannedProductToCart(foundProduct, code); + + // Show success briefly + scanButton.style.borderColor = '#43a047'; + scanButton.style.color = '#43a047'; + scanButton.innerHTML = ` + + TILFØJET! + `; + + setTimeout(() => { + scanButton.style.borderColor = ''; + scanButton.style.color = ''; + resetScanner(); + }, 800); + } else { + // Show not found + scanButton.style.borderColor = '#f59e0b'; + scanButton.style.color = '#f59e0b'; + scanButton.innerHTML = ` + + IKKE FUNDET + `; + + setTimeout(() => { + scanButton.style.borderColor = ''; + scanButton.style.color = ''; + resetScanner(); + }, 1500); + } + } + + // Add scanned product to cart visually + function addScannedProductToCart(product, ean) { + const cartSectionItems = document.querySelector('.cart-section:last-of-type .cart-section-items'); + + const newItem = document.createElement('div'); + newItem.className = 'cart-item'; + newItem.setAttribute('onclick', 'toggleCartItem(this)'); + newItem.setAttribute('data-base-price', product.price); + newItem.innerHTML = ` +
    +
    + + 1 + +
    +
    +
    ${product.name}
    +
    ${product.size}
    +
    +
    +
    ${product.price} kr
    +
    ${product.price} kr
    +
    -0 kr rabat
    +
    + +
    +
    +
    +
    +
    Pris
    + +
    +
    +
    Rabat
    +
    + +
    + + +
    +
    +
    +
    +
    + `; + + // Insert at end of items list + cartSectionItems.appendChild(newItem); + + // Flash the new item + newItem.style.animation = 'flash 0.5s ease'; + + // Re-attach discount type handlers + newItem.querySelectorAll('.discount-type-btn').forEach(btn => { + btn.addEventListener('click', (e) => { + e.stopPropagation(); + toggleDiscountType(btn); + }); + }); + } + + // Add animations + const scannerStyle = document.createElement('style'); + scannerStyle.textContent = ` + @keyframes flash { + 0% { background-color: rgba(0, 137, 123, 0.2); } + 100% { background-color: transparent; } + } + @keyframes spin { + to { transform: rotate(360deg); } + } + `; + document.head.appendChild(scannerStyle); + + // ========================================== + // RECEIPT + // ========================================== + + function showReceipt() { + const receiptContainer = document.getElementById('receiptContainer'); + receiptContainer.classList.add('visible'); + } + + function hideReceipt() { + const receiptContainer = document.getElementById('receiptContainer'); + receiptContainer.classList.remove('visible'); + } + + // Close receipt on Escape key + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + const receiptContainer = document.getElementById('receiptContainer'); + if (receiptContainer.classList.contains('visible')) { + hideReceipt(); + e.stopPropagation(); + } + } + }); diff --git a/wwwroot/poc-dashboard copy.html b/wwwroot/poc-dashboard copy.html deleted file mode 100644 index 5f93f61..0000000 --- a/wwwroot/poc-dashboard copy.html +++ /dev/null @@ -1,1385 +0,0 @@ - - - - - - Dashboard - Salon OS - - - - - - - - - Dashboard - - - - - Mandag, 30. december 2024 - - - - Ny booking - - - - - - - - - 12 - Bookinger i dag - - - 4 gennemført, 2 i gang - - - - 8.450 kr - Forventet omsætning - - - +12% vs. gennemsnit - - - - 78% - Belægningsgrad - - - God kapacitet - - - - 3 - Kræver opmærksomhed - - - - - - - - - - - - AI Analyse - - - Godt i gang! 4 af 12 bookinger er gennemført. 2 er i gang nu, og 6 venter. - Forventet omsætning: 8.450 kr – allerede realiseret 2.150 kr. - Tip: Ida Rasmussen (11:30) har ikke bekræftet – overvej at sende en påmindelse. - - - - - - - - - - Dagens bookinger - - Se alle - - - - - Nu: 10:45 - - - - - - - 08:00 - 08:30 - - - - Herreklip - Thomas Berg - - - MH - Maria - - Gennemført - - - - - 08:30 - 09:00 - - - - Dameklip - Katrine Holm - - - AS - Anna - - Gennemført - - - - - 09:00 - 09:30 - - - - Skægtrimning - Mikkel Skov - - - PK - Peter - - Gennemført - - - - - 09:00 - 10:30 - - - - Dameklip + Farve - Sofie Nielsen - - - AS - Anna - - Gennemført - - - - - - 10:30 - 11:00 - - - - Herreklip - Jonas Petersen - - - MH - Maria - - I gang - - - - - 10:00 - 11:00 - - - - Føn + Styling - Rikke Dam - - - LJ - Louise - - I gang - - - - - - 11:00 - 12:00 - - - - Balayage - Emma Christensen - - - AS - Anna - - Bekræftet - - - - - 11:30 - 12:00 - - - - Dameklip - Ida Rasmussen - - - MH - Maria - - Afventer - - - - - 13:00 - 14:00 - - - - Farve + Føn - Louise Andersen - - - AS - Anna - - Bekræftet - - - - - 14:00 - 14:30 - - - - Herreklip - Anders Møller - - - PK - Peter - - Bekræftet - - - - - 15:30 - 16:30 - - - - Extensions - Julie Lund - - - LJ - Louise - - Bekræftet - - - - - - - - - - Opmærksomheder - - - - - - - - - - Aflyst booking - Mette Hansen aflyste kl. 15:00 – tid nu ledig - - Fyld tid - - - - - - - - Ubekræftet booking - Ida Rasmussen har ikke bekræftet kl. 11:30 - - Send påmindelse - - - - - - - - Gavekort udløber snart - GC-D2R4-6TY9 udløber om 3 uger (200 DKK) - - Se gavekort - - - - - - - - - - - - - Notifikationer - - Marker alle som læst - - - - - - - - - - Ny booking fra Emma Christensen til Balayage - - For 15 min. siden - - - - - - - - - - Ny anmeldelse – 5 stjerner fra Sofie Nielsen - - For 1 time siden - - - - - - - - - - Aflysning – Mette Hansen aflyste kl. 15:00 - - For 2 timer siden - - - - - - - - - - Bekræftet – Louise Andersen bekræftede kl. 13:00 - - I går kl. 18:30 - - - - - - - - - - - Denne uge - - - - - - 47 - Bookinger - - - 38.200 kr - Omsætning - - - 8 - Nye kunder - - - 72% - Gns. belægning - - - - - - - - - - Medarbejdere - - - - - - AS - - Anna Sørensen - Ledig til kl. 11:00 (Balayage) - - Ledig - - - - MH - - Maria Hansen - Herreklip med Jonas - - Optaget - - - - LJ - - Louise Jensen - Føn + Styling med Rikke - - Optaget - - - - PK - - Peter Kristensen - Ledig til kl. 14:00 - - Ledig - - - - - - - - - - diff --git a/wwwroot/poc-dashboard.html b/wwwroot/poc-dashboard.html index dda530a..a4b3f63 100644 --- a/wwwroot/poc-dashboard.html +++ b/wwwroot/poc-dashboard.html @@ -392,6 +392,15 @@ + + + + + 4 + + På venteliste + + @@ -472,6 +481,178 @@ + + + + + + + + Venteliste (4) + + + + + + + + + + + EC + + Emma Christensen + +45 12 34 56 78 + + + Dameklip + Farve + + + Ønsker: + Mandag-Onsdag + Formiddag + + + + + Tilmeldt: 2. jan 2026 + + + + Udløber: 16. jan 2026 + + + + + + + Kontakt + + + + Book nu + + + + + + + + MS + + Mikkel Sørensen + +45 23 45 67 89 + + + Herreklip + + + Ønsker: + Weekend + + + + + Tilmeldt: 30. dec 2025 + + + + Udløber: 6. jan 2026 + + + + + + + Kontakt + + + + Book nu + + + + + + + + LA + + Lise Andersen + +45 34 56 78 90 + + + Balayage + + + Ønsker: + Tirsdag-Torsdag + Eftermiddag + + + + + Tilmeldt: 28. dec 2025 + + + + Udløber: 11. jan 2026 + + + + + + + Kontakt + + + + Book nu + + + + + + + + PH + + Peter Hansen + +45 45 67 89 01 + + + Herreklip + Skæg + + + Ønsker: + Fleksibel + + + + + Tilmeldt: 27. dec 2025 + + + + Udløber: 10. jan 2026 + + + + + + + Kontakt + + + + Book nu + + + + + + + diff --git a/wwwroot/poc-employee.html b/wwwroot/poc-employee.html index 4c5e5f6..36b7cbf 100644 --- a/wwwroot/poc-employee.html +++ b/wwwroot/poc-employee.html @@ -2154,6 +2154,264 @@ height: 14px; fill: currentColor; } + + /* ========================================== + HR TAB - DOCUMENTS + ========================================== */ + swp-document-list { + display: flex; + flex-direction: column; + gap: 8px; + margin-top: 16px; + } + + swp-document-item { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + background: var(--color-background-alt); + border-radius: 6px; + border: 1px solid var(--color-border); + } + + swp-document-icon { + font-size: 20px; + } + + swp-document-info { + flex: 1; + } + + swp-document-name { + display: block; + font-weight: 500; + color: var(--color-text); + } + + swp-document-meta { + display: block; + font-size: 12px; + color: var(--color-text-secondary); + margin-top: 2px; + } + + swp-document-actions { + display: flex; + gap: 8px; + } + + /* ========================================== + HR TAB - COURSES + ========================================== */ + swp-course-section { + margin-bottom: 20px; + } + + swp-course-section:last-of-type { + margin-bottom: 0; + } + + swp-course-section-title { + display: block; + font-size: 12px; + font-weight: 600; + color: var(--color-text-secondary); + margin-bottom: 8px; + } + + swp-course-list { + display: flex; + flex-direction: column; + gap: 6px; + } + + swp-course-item { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 14px; + background: var(--color-background-alt); + border-radius: 6px; + } + + swp-course-item.upcoming { + background: color-mix(in srgb, var(--color-teal) 8%, white); + border: 1px solid color-mix(in srgb, var(--color-teal) 20%, white); + } + + swp-course-info { + flex: 1; + } + + swp-course-name { + display: block; + font-size: 14px; + font-weight: 500; + color: var(--color-text); + } + + swp-course-meta { + display: block; + font-size: 12px; + color: var(--color-text-secondary); + margin-top: 2px; + } + + swp-course-status { + font-size: 11px; + font-weight: 500; + padding: 4px 10px; + border-radius: 12px; + background: color-mix(in srgb, var(--color-teal) 15%, white); + color: var(--color-teal); + } + + /* ========================================== + RATES DRAWER + ========================================== */ + swp-drawer-overlay { + display: none; + position: fixed; + inset: 0; + background: rgba(0,0,0,0.3); + z-index: 999; + } + + swp-drawer-overlay.open { + display: block; + } + + swp-rates-drawer { + display: none; + position: fixed; + top: 0; + right: 0; + bottom: 0; + width: 420px; + background: var(--color-surface); + box-shadow: -4px 0 20px rgba(0,0,0,0.15); + z-index: 1000; + flex-direction: column; + } + + swp-rates-drawer.open { + display: flex; + } + + swp-drawer-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + border-bottom: 1px solid var(--color-border); + } + + swp-drawer-title { + font-size: 16px; + font-weight: 600; + } + + swp-drawer-close { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 6px; + cursor: pointer; + color: var(--color-text-secondary); + transition: all 150ms ease; + } + + swp-drawer-close:hover { + background: var(--color-background); + color: var(--color-text); + } + + swp-drawer-body { + flex: 1; + overflow-y: auto; + padding: 16px 20px; + } + + swp-rate-row { + display: grid; + grid-template-columns: 28px 1fr 100px; + align-items: center; + gap: 12px; + padding: 12px 0; + border-bottom: 1px solid var(--color-border); + } + + swp-rate-row:last-child { + border-bottom: none; + } + + swp-rate-row input[type="checkbox"] { + width: 18px; + height: 18px; + accent-color: var(--color-teal); + } + + swp-rate-label { + font-size: 14px; + } + + swp-rate-label.disabled { + opacity: 0.4; + } + + swp-rate-input { + display: flex; + align-items: center; + gap: 4px; + font-size: 13px; + color: var(--color-text-secondary); + } + + swp-rate-input input { + width: 70px; + padding: 6px 8px; + border: 1px solid var(--color-border); + border-radius: 4px; + font-size: 13px; + font-family: var(--font-mono); + text-align: right; + } + + swp-rate-input.disabled input { + opacity: 0.4; + background: var(--color-background); + } + + .btn-link { + background: none; + border: none; + color: var(--color-teal); + font-size: 13px; + cursor: pointer; + padding: 0; + } + + .btn-link:hover { + text-decoration: underline; + } + + swp-card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + } + + swp-card-header swp-section-label { + margin-bottom: 0; + } + + .mono { + font-family: var(--font-mono); + } @@ -2225,6 +2483,7 @@ Arbejdstid Services Løn + HR Statistik @@ -2481,44 +2740,6 @@ - -
    - - Planlagt fravær - - - 23. dec – 2. jan 2026 - Ferie - - - 14. feb 2025 - Fri - - - 7. apr – 11. apr 2025 - Ferie - - - - - - Ferie-saldo - - - Optjente feriedage - 25 dage - - - Brugte feriedage - 12 dage - - - Resterende - 13 dage - - - -
    + +
    + +
    + + + Kontrakt & Dokumenter + + + + + Kontrakttype + + + + + + Opsigelsesvarsel + + + + + + Kontraktudløb + — (ingen udløb) + + + + + + + 📄 + + Ansættelseskontrakt.pdf + Uploadet 1. aug 2019 + + + Vis + + + + 📄 + + Tillæg - Lønforhøjelse 2023.pdf + Uploadet 15. jan 2023 + + + Vis + + + + + + + Upload dokument + + +
    + + +
    + + + Certificeringer + + + 🎓 + + Balayage Specialist + Udløber: 15. juni 2026 + + Gyldig + + + 🎓 + + Farvecertificering (Wella) + Udløber: 1. marts 2025 + + Udløber snart + + + + + Tilføj certificering + + + + + + Kurser + + + Gennemførte kurser + + + + Avanceret balayage teknikker + Wella Academy · Marts 2024 + + + + + Kundeservice & mersalg + SalonUp · November 2023 + + + + + + + Planlagte kurser + + + + Olaplex certificering + Olaplex DK · 15. februar 2026 + + Tilmeldt + + + + + + + Tilføj kursus + + +
    +
    + + +
    + + Ferie-saldo + + + Optjente feriedage + 25 dage + + + Brugte feriedage + 12 dage + + + Resterende + 13 dage + + + + + + Fravær & Sygdom + + + Sygefravær 2025 + 3 dage + + + Sygefravær 2024 + 7 dage + + + Børns sygdom 2025 + 1 dag + + + Barsel + — (ingen planlagt) + + + +
    + + + + Planlagt fravær + + + 23. dec – 2. jan 2026 + Ferie + + + 14. feb 2025 + Fri + + + 7. apr – 11. apr 2025 + Ferie + + + + + Tilføj fravær + + +
    + @@ -3603,5 +4037,114 @@ }); + + + + + Lønsatser + + + + + + Grundsatser + + + Normal (timeløn) + kr + + + + Overarbejde (100%) + kr + + + + Kursus/skole + kr + + + + Afspadsering + kr + + + + Fri m. løn + kr + + + + Ferie m. løn + kr + + + + Kontor + kr + + + + Barns 1. sygedag + kr + + + + Barns hospitalsindlæggelse + kr + + + + Barsel + kr + + +
    + Tillæg + + + 8-21 Hverdage (udenfor arbejdstid) + kr + + + + 8-21 Lørdage (udenfor arbejdstid) + kr + + + + Søndag + kr + +
    + +
    + Provisionsberegning + + + Provision på produktsalg + % + + + + Provision på servicesalg + % + +
    +
    +
    + + + diff --git a/wwwroot/poc-gavekort-detail.html b/wwwroot/poc-gavekort-detail.html index 970c620..7bf7a98 100644 --- a/wwwroot/poc-gavekort-detail.html +++ b/wwwroot/poc-gavekort-detail.html @@ -3,7 +3,7 @@ - Gavekort GC-A7X2-9KM4 - Salon OS + Fordelskort FK-A7X2-9KM4 - Salon OS @@ -1731,6 +1910,10 @@ Moduler + + + Tracking + + + + + + + + AI Kundeanalyse + Forstå dine kunders adfærd og forbliv proaktiv. AI'en analyserer bookingmønstre, forudser hvornår kunder har brug for en tid, og identificerer kunder der er ved at falde fra. + + + + Til + Fra + + + + + + + Booking-prediktion baseret på historik + + + + Churn-detektion – se hvem der er ved at falde fra + + + + Service-præference analyse pr. kunde + + + + Sæson-korrektion i forudsigelser + + + + Win-back kampagner for inaktive kunder + + + + Automatisk personlig beskedgenerering + + + + + -34% + Færre tabte kunder + + + +18% + Genbookinger + + + 3.8x + ROI på kampagner + + + + + +79 kr/md + Beta + + Prøv gratis i 14 dage + + + @@ -3134,8 +3383,379 @@ Tak for din handel! Indstillinger + + + + + + + + + Website Builder + Byg din salons hjemmeside med drag-and-drop blokke. Vælg mellem færdige designs, tilpas farver og fonte, og integrer din booking. + + + + Til + Fra + + + + + + +149 kr/md + Ny + + Åbn Builder + + + + + + + + + + + HR & Dokumenter + Komplet medarbejderstyring: Kontrakter, certificeringer, kurser, ferie-saldo, sygefravær og barsel. Upload dokumenter og få påmindelser om udløbsdatoer. + + + + Til + Fra + + + + + + Inkluderet + + Indstillinger + + + + + + + + + Integrationer + + + + + + + + + + + + Sygeforsikring "danmark" + Gør det nemt for dine kunder at få tilskud. Send automatisk kvitteringer til "danmark" så kunderne får deres penge tilbage uden selv at løfte en finger. + + + + Til + Fra + + + + + + + Automatisk indsendelse af kvitteringer + + + + Direkte integration via API + + + + Kunden får tilskud uden besvær + + + + Øget kundetilfredshed + + + + + 2.1M + Medlemmer i DK + + + 100% + Automatiseret + + + 0 kr + Ekstra gebyr + + + + + Inkluderet + + Opsæt integration + + + + + + + + + + + Kalenderintegration + Få dine bookinger synkroniseret til din private kalender automatisk. Se alle aftaler samlet ét sted - også når du er offline. + + + + Til + Fra + + + + + + + Google Kalender + + + + Apple iCloud Kalender + + + + Microsoft Outlook + + + + Tovejs-synkronisering + + + + + Inkluderet + + Indstillinger + + + + + + + + + + + + + + Meta Pixel (Facebook) + + + Ja + Nej + + + + + + Pixel ID + 123456789012345 + + + + + Bruges til Facebook og Instagram annoncering og remarketing + + + + + + + + + + Google Analytics (GA4) + + + Ja + Nej + + + + + + Measurement ID + G-ABC123XYZ + + + + + Google Analytics 4 til website trafik og brugeradfærd + + + + + + + + + + Google Tag Manager + + + Ja + Nej + + + + + + Container ID + GTM-XXXXXXX + + + + + Central styring af alle tracking-tags + + + + + + + + + + Plausible Analytics + + + Ja + Nej + + + + + + Domæne + minside.dk + + + + + Privacy-venlig analytics uden cookies - GDPR compliant + + + + + + + + + + Fathom Analytics + + + Ja + Nej + + + + + + Site ID + ABCDEFGH + + + + + Privacy-venlig analytics uden cookies - GDPR compliant + + + + + + + + + + Matomo + + + Ja + Nej + + + + + + Server URL + https://matomo.minside.dk + + + Site ID + 1 + + + + + Self-hosted analytics - fuld kontrol over dine data + + + + + + + + + + Brugerdefinerede Scripts + + + + + + + Scripts i <head> + + + + + + + Scripts før </body> + + + + + + + + + + + + Genereret Kode + + + + + + Denne kode indsættes automatisk i <head> på din online booking side + +
    +
    +
    - - - - - Brugere - - - - - - 5 af 8 brugere - - - - - - - Inviter bruger - - - - - - - - Bruger - Rolle - Status - Sidst aktiv - - - - - - - - - MJ - - Maria Jensen - maria@salonbeauty.dk - - - - - Ejer - - - - - Aktiv - - - I dag, 14:32 - - - - - - - - - - - - - - AS - - Anna Sørensen - anna@salonbeauty.dk - - - - - Admin - - - - - Aktiv - - - I dag, 12:15 - - - - - - - - - - - - - - - - - LP - - Louise Pedersen - louise@salonbeauty.dk - - - - - Medarbejder - - - - - Aktiv - - - I går, 17:45 - - - - - - - - - - - - - - - - - KN - - Katrine Nielsen - katrine@salonbeauty.dk - - - - - Medarbejder - - - - - Aktiv - - - 27. dec, 09:30 - - - - - - - - - - - - - - - - - SH - - Sofie Hansen - sofie@salonbeauty.dk - - - - - Medarbejder - - - - - Invitation sendt - - - - - - - - - - - - - - - - - - - - diff --git a/wwwroot/poc-layout copy.html b/wwwroot/poc-layout copy.html deleted file mode 100644 index a24a9a2..0000000 --- a/wwwroot/poc-layout copy.html +++ /dev/null @@ -1,2509 +0,0 @@ - - - - - - Salon OS - - - - - - - - - - - Salon OS - - - - - - - - - Dashboard - - - Dashboard - - - - Kalender - - - - Kasse - - - - - - Data - - - Produkter & Lager - - - - Leverandører - - - - Kunder - - - - Medarbejdere - - - - - - Analyse - - - Statistik & Rapporter - - - - - - System - - - Indstillinger - - - - Abonnement & Konto - - - - - - - - Lås skærm - - - - - - - - - - ⌘K - - - - - - - 3 - - - - - - - MJ - - Maria Jensen - Administrator - - - - - - - - - - Dashboard - - Vælg et menupunkt i venstre side for at navigere til den ønskede sektion. -

    - Prøv f.eks.
    Produkter & Lager eller Leverandører. - - - - - - - - - - Min profil - - - - - - - - MJ - Maria Jensen - Administrator - maria@salon.dk - - - - Konto - - - Rediger profil - - - - - Skift adgangskode - - - - - Notifikationer - 3 ulæste - - - - Mine opgaver - 2 i dag - - - - - Udseende - - - - - - - - - - - - Support - - - Hjælp & Support - - - - - Om Salon OS - v2.1.0 - - - - - - - - Log ud - - - - - - - - Notifikationer - - Marker alle læst - - - - - - - - - - - - - - Ny booking - Maria Hansen har booket klipning kl. 14:00 - For 5 min siden - - - - - - - - - - Kunde feedback - 5 stjerner fra Jonas Petersen - For 15 min siden - - - - - - - - - - Misset opkald - +45 12 34 56 78 - For 1 time siden - - - - - - - - - - Ny mail - Fra: leverandoer@produkt.dk - Ordrebekræftelse - For 2 timer siden - - - - - - - - - - Ny besked i chat - Kunde: "Hej, kan jeg ændre min tid?" - For 3 timer siden - - - - - - - - - - Påmindelse - Bestil varer fra Wella inden fredag - I går - - - - - - - - - - - - Mine opgaver - - - - - - - - - - - - - I dag - 3 - - - - - - - - Ring til leverandør om ordre - - - - 10:00 - - - - - - - - - - Bestil shampoo fra Wella - - - - - - - - Opdater prisliste for 2025 - - - - Høj - - - - - - - - - - - - Denne uge - 2 - - - - - - - - Rengør og vedligehold udstyr - - - - Onsdag - - - - - - - - - - Medarbejdersamtale med Jonas - - - - Fredag - - - - 14:00 - - - - - - - - - - - - - - - - - - - Ny opgave - - - - - - Opgave - - - - - - - Dato - - - - Tid - - - - - - - Prioritet - - - - - - Synlighed - - - - Kun mig - - - - Alle - - - - - - - Noter - - - - - - Annuller - Gem opgave - - - - - - - - - - Skærm låst - Indtast PIN for at fortsætte - - Låst kl. 14:32 - - - - - - - - - - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - - 0 - - - - - - Indtast 4 cifre for at låse op - - - - - - - - - diff --git a/wwwroot/poc-layout.html b/wwwroot/poc-layout.html index 362e29b..db76aca 100644 --- a/wwwroot/poc-layout.html +++ b/wwwroot/poc-layout.html @@ -1650,7 +1650,7 @@ Kunder - + Medarbejdere diff --git a/wwwroot/poc-loenspecifikation.html b/wwwroot/poc-loenspecifikation.html new file mode 100644 index 0000000..0d28ceb --- /dev/null +++ b/wwwroot/poc-loenspecifikation.html @@ -0,0 +1,533 @@ + + + + + + Lønspecifikation – Januar 2026 + + + + + + + +
    +
    +
    +

    Lønspecifikation

    +

    Periode: Januar 2026

    +
    + +
    +
    Medarbejdernr.: EMP-001
    +
    + Medarbejder:Emma Larsen + Afdeling:Frisør + Ansættelse:Fuldtid (37 t/uge) +
    +
    +
    + +
    +
    +
    Bruttoløn (Januar 2026)
    +

    34.063,50 kr

    +
    +
    + +
    +
    +
    +

    Samlet lønopgørelse

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    LøndelBeløb
    Grundløn inkl. overarbejde29.322,50 kr
    Provision i alt3.685,00 kr
    Tillæg i alt1.056,00 kr
    Bruttoløn34.063,50 kr
    +
    +
    + +
    +
    +

    Saldi

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    TypeOptjentAfholdtRest
    Ferie (dage)18,56,012,5
    Afspadsering (timer)12,04,08,0
    +

    Saldi er opgjort som angivet på lønspecifikationen.

    +
    +
    +
    + +
    +
    +

    Hurtigt resumé

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    NøglepunktVærdi
    Normaltimer148,0 t
    Overarbejde7,0 t
    Provision (services + produkter)3.685,00 kr
    Tillæg (aften + lørdag + søndag)1.056,00 kr
    +
    +
    + +
    +
    Overblik
    +
    Lønspecifikation · Januar 2026
    +
    + +
    + + +
    +
    +

    Arbejdstid pr. uge

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    UgeNormaltimerOvertidBeløb
    Uge 1 (30. dec – 5. jan)37,0 t2,0 t7.400,00 kr
    Uge 2 (6. – 12. jan)37,0 t3,5 t7.816,25 kr
    Uge 3 (13. – 19. jan)37,0 t0,0 t6.845,00 kr
    Uge 4 (20. – 26. jan)37,0 t1,5 t7.261,25 kr
    I alt148,0 t7,0 t29.322,50 kr
    +

    + Satser: Normal 185,00 kr/time. Overtid (50%) 277,50 kr/time. +

    +
    +
    + +
    +
    +

    Provision

    +
    +
    +

    + Services: 15% af omsætning over minimum (220 kr/time).
    + Produkter: 10% af salg. +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    UgeService prov.Produkt prov.I alt
    Uge 1573,00 kr210,00 kr783,00 kr
    Uge 2883,50 kr320,00 kr1.203,50 kr
    Uge 3459,00 kr180,00 kr639,00 kr
    Uge 4769,50 kr290,00 kr1.059,50 kr
    I alt2.685,00 kr1.000,00 kr3.685,00 kr
    +
    +
    + +
    +
    +

    Tillæg & fravær

    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TillægTimerBeløb
    Aftentillæg (hverdage 18–21)12,0336,00 kr
    Lørdagstillæg (før kl. 14)16,0720,00 kr
    Søndagstillæg0,00,00 kr
    Tillæg i alt1.056,00 kr
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FraværDageBeløb
    Ferie med løn00,00 kr
    Sygdom00,00 kr
    Barns sygedag00,00 kr
    +

    Ingen fravær registreret i perioden.

    +
    +
    +
    +
    + +
    +
    Detaljer
    +
    Lønspecifikation · Januar 2026
    +
    + +
    + + diff --git a/wwwroot/poc-medarbejdere.html b/wwwroot/poc-medarbejdere.html new file mode 100644 index 0000000..5a4a7c5 --- /dev/null +++ b/wwwroot/poc-medarbejdere.html @@ -0,0 +1,588 @@ + + + + + + Medarbejdere - Salon OS + + + + + + + + + + + + +

    Medarbejdere

    +

    Administrer brugere, roller og rettigheder

    +
    +
    + + + + + 4 + Aktive medarbejdere + + + 1 + Afventer invitation + + + 4 + Roller defineret + + + + + + + + Brugere + + + + Roller + + + + + + + + 5 af 8 brugere + + + + + + + Inviter bruger + + + + + + + + Bruger + Rolle + Status + Sidst aktiv + + + + + + + + + MJ + + Maria Jensen + maria@salonbeauty.dk + + + + + Ejer + + + + + Aktiv + + + I dag, 14:32 + + + + + + + + + + + + + + AS + + Anna Sørensen + anna@salonbeauty.dk + + + + + Admin + + + + + Aktiv + + + I dag, 12:15 + + + + + + + + + + + + + + + + + LP + + Louise Pedersen + louise@salonbeauty.dk + + + + + Leder + + + + + Aktiv + + + I går, 17:45 + + + + + + + + + + + + + + + + + KN + + Katrine Nielsen + katrine@salonbeauty.dk + + + + + Medarbejder + + + + + Aktiv + + + 27. dec, 09:30 + + + + + + + + + + + + + + + + + SH + + Sofie Hansen + sofie@salonbeauty.dk + + + + + Medarbejder + + + + + Invitation sendt + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    RettighedEjerAdminLederMedarbejder
    + + + Kalender + +
    + + + Medarbejdere + +
    + + + Kunder + +
    + + + Rapporter & Økonomi + +
    +
    +
    + +
    + + + + + diff --git a/wwwroot/poc-salg.html b/wwwroot/poc-salg.html new file mode 100644 index 0000000..75f91ee --- /dev/null +++ b/wwwroot/poc-salg.html @@ -0,0 +1,1133 @@ + + + + + + Salg - Salon OS + + + + + + + + + Salg + + + + + Eksporter + + + + + + + + + 12.450 kr + Omsætning i dag + + + 187.230 kr + Omsætning denne måned + + + 18 + Antal salg i dag + + + 692 kr + Gns. ordreværdi + + + + + + + + Omsætning pr. måned + Sidste 12 måneder + + + + + + Betalingsmetoder + Fordeling + + + + + + + + + + + + + Fra + + + + Til + + + + Status + + + + Betaling + + + + + + + + Faktura + Dato/tid + Kunde + Medarbejder + Ydelser + Beløb + Betaling + Status + + + + + + + #1847 + + + + + 6. jan 2025 + 14:32 + + + + + Maria Hansen + +45 23 45 67 89 + + + Louise P. + + + Dameklip, Farve + + 1 produkt + + + 1.450 kr + Kort + + + + + + + + #1846 + + + + + 6. jan 2025 + 13:15 + + + + + Jonas Petersen + +45 31 22 44 55 + + + Karina K. + + + Herreklip + + + 350 kr + MobilePay + + + + + + + + #1845 + + + + + 6. jan 2025 + 11:45 + + + + + Sofie Larsen + +45 42 33 55 66 + + + Louise P. + + + Balayage, Klip + + 2 produkter + + + 2.850 kr + Faktura + Afventer + + + + + + + #1844 + + + + + 6. jan 2025 + 10:30 + + + + + Anne Nielsen + +45 51 62 73 84 + + + Mette J. + + + Dameklip, Vask + + + 550 kr + Fordelskort + + + + + + + + #1843 + + + + + 6. jan 2025 + 09:15 + + + + + Peter Andersen + +45 61 72 83 94 + + + Karina K. + + + Herreklip, Skægtrim + + + 450 kr + Kontant + + + + + + + + #1842 + + + + + 5. jan 2025 + 16:45 + + + + + Camilla Holm + +45 71 82 93 04 + + + Louise P. + + + Extensions + + 1 produkt + + + 3.200 kr + Kort + + + + + + + + #1841 + + + + + 5. jan 2025 + 15:00 + + + + + Mikkel Jensen + +45 81 92 03 14 + + + Mette J. + + + Dameklip + + + -450 kr + Kort + Krediteret + + + + + + + #1840 + + + + + 5. jan 2025 + 13:30 + + + + + Louise Eriksen + +45 91 02 13 24 + + + Karina K. + + + Highlights, Klip, Kur + + + 1.950 kr + Kort + + + + + + Viser 1-8 af 1.847 fakturaer + + + 1 + 2 + 3 + ... + 231 + + + + + + + + + diff --git a/wwwroot/poc-website-builder.html b/wwwroot/poc-website-builder.html new file mode 100644 index 0000000..5fd5fdd --- /dev/null +++ b/wwwroot/poc-website-builder.html @@ -0,0 +1,3633 @@ + + + + + + Website Builder - Salon OS + + + + + + + + + + + + + + + Website Builder + + + + + + + + + + + + + + + + + + + + + + + + + Preview + + + + Gem + + + + Publicer + + + + + + + + + + + + Blokke + + + + Design + + + + + + + Layout + + + + Hero + + + + Kolonner + + + + Spacer + + + + Divider + + + + + + Indhold + + + + Tekst + + + + Ydelser + + + + Team + + + + Galleri + + + + Anmeldelser + + + + CTA Banner + + + + Prisliste + + + + FAQ + + + + + + Kontakt & Booking + + + + Kontakt + + + + Åbningstider + + + + Book tid + + + + Kort + + + + Social + + + + + + + + Farveskema + + + + + Typografi + + + Velkommen til salonen + Poppins — Moderne & venlig + + + Velkommen til salonen + Playfair Display — Elegant & klassisk + + + Velkommen til salonen + Inter — Ren & professionel + + + Velkommen til salonen + Montserrat — Bold & stilfuld + + + + Sociale medier + + + Instagram + + + + Facebook + + + + TikTok + + + + LinkedIn + + + + + + + + + + + + + + + + + + + + Indstillinger + + + + Indhold + Styling + + + + + +

    Vælg en blok for at redigere

    +
    +
    +
    +
    +
    + + + +