Moving away from Azure Devops #1
3 changed files with 182 additions and 132 deletions
|
|
@ -9,6 +9,7 @@ import { ResourceRenderer } from './features/resource/ResourceRenderer';
|
|||
import { TeamRenderer } from './features/team/TeamRenderer';
|
||||
import { DepartmentRenderer } from './features/department/DepartmentRenderer';
|
||||
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';
|
||||
|
|
@ -220,6 +221,9 @@ export function createV2Container(): Container {
|
|||
builder.registerType(ResizeManager).as<ResizeManager>();
|
||||
builder.registerType(EventPersistenceManager).as<EventPersistenceManager>();
|
||||
|
||||
// CalendarApp - genbrugelig kalenderkomponent
|
||||
builder.registerType(CalendarApp).as<CalendarApp>();
|
||||
|
||||
// Demo app
|
||||
builder.registerType(DemoApp).as<DemoApp>();
|
||||
|
||||
|
|
|
|||
140
src/v2/core/CalendarApp.ts
Normal file
140
src/v2/core/CalendarApp.ts
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
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 { ResourceService } from '../storage/resources/ResourceService';
|
||||
import { ViewConfigService } from '../storage/viewconfigs/ViewConfigService';
|
||||
import { IWorkweekPreset } from '../types/SettingsTypes';
|
||||
|
||||
export class CalendarApp {
|
||||
private animator!: NavigationAnimator;
|
||||
private container!: HTMLElement;
|
||||
private weekOffset = 0;
|
||||
private currentViewId = 'simple';
|
||||
private workweekPreset: IWorkweekPreset | null = null;
|
||||
|
||||
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 resourceService: ResourceService,
|
||||
private viewConfigService: ViewConfigService
|
||||
) {}
|
||||
|
||||
async init(container: HTMLElement): Promise<void> {
|
||||
this.container = container;
|
||||
|
||||
// Load default workweek preset from settings
|
||||
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
|
||||
);
|
||||
|
||||
// Render time axis (from settings later, hardcoded for now)
|
||||
this.timeAxisRenderer.render(
|
||||
container.querySelector('#time-axis') as HTMLElement,
|
||||
6,
|
||||
18
|
||||
);
|
||||
|
||||
// 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 {
|
||||
this.container.addEventListener('calendar:cmd:render', ((e: CustomEvent) => {
|
||||
const { viewId, direction } = e.detail;
|
||||
this.handleRenderCommand(viewId, direction);
|
||||
}) as EventListener);
|
||||
|
||||
this.container.addEventListener('calendar:cmd:navigate', ((e: CustomEvent) => {
|
||||
const { offset, direction } = e.detail;
|
||||
this.handleNavigateCommand(offset, direction);
|
||||
}) as EventListener);
|
||||
|
||||
this.container.addEventListener('calendar:cmd:drawer:toggle', (() => {
|
||||
this.headerDrawerManager.toggle();
|
||||
}) as EventListener);
|
||||
}
|
||||
|
||||
private async handleRenderCommand(viewId: string, direction?: 'left' | 'right'): Promise<void> {
|
||||
this.currentViewId = viewId;
|
||||
|
||||
if (direction) {
|
||||
await this.animator.slide(direction, () => this.render());
|
||||
} else {
|
||||
await this.render();
|
||||
}
|
||||
|
||||
this.emitStatus('rendered', { viewId });
|
||||
}
|
||||
|
||||
private async handleNavigateCommand(offset: number, direction: 'left' | 'right'): Promise<void> {
|
||||
this.weekOffset += offset;
|
||||
await this.animator.slide(direction, () => this.render());
|
||||
this.emitStatus('rendered', { viewId: this.currentViewId });
|
||||
}
|
||||
|
||||
private async render(): Promise<void> {
|
||||
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 and offset
|
||||
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);
|
||||
|
||||
// Clone config and populate dates
|
||||
const viewConfig: ViewConfig = {
|
||||
...storedConfig,
|
||||
groupings: storedConfig.groupings.map(g =>
|
||||
g.type === 'date' ? { ...g, values: dates } : 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
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,47 +1,19 @@
|
|||
import { CalendarOrchestrator } from '../core/CalendarOrchestrator';
|
||||
import { TimeAxisRenderer } from '../features/timeaxis/TimeAxisRenderer';
|
||||
import { NavigationAnimator } from '../core/NavigationAnimator';
|
||||
import { DateService } from '../core/DateService';
|
||||
import { ScrollManager } from '../core/ScrollManager';
|
||||
import { HeaderDrawerManager } from '../core/HeaderDrawerManager';
|
||||
import { IndexedDBContext } from '../storage/IndexedDBContext';
|
||||
import { DataSeeder } from '../workers/DataSeeder';
|
||||
import { ViewConfig } from '../core/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 { AuditService } from '../storage/audit/AuditService';
|
||||
import { SettingsService } from '../storage/settings/SettingsService';
|
||||
import { ResourceService } from '../storage/resources/ResourceService';
|
||||
import { ViewConfigService } from '../storage/viewconfigs/ViewConfigService';
|
||||
import { IWorkweekPreset } from '../types/SettingsTypes';
|
||||
import { CalendarApp } from '../core/CalendarApp';
|
||||
import { DateService } from '../core/DateService';
|
||||
|
||||
export class DemoApp {
|
||||
private animator!: NavigationAnimator;
|
||||
private container!: HTMLElement;
|
||||
private weekOffset = 0;
|
||||
private currentView: 'day' | 'simple' | 'resource' | 'picker' | 'team' | 'department' = 'simple';
|
||||
private workweekPreset: IWorkweekPreset | null = null;
|
||||
private currentView = 'simple';
|
||||
|
||||
constructor(
|
||||
private orchestrator: CalendarOrchestrator,
|
||||
private timeAxisRenderer: TimeAxisRenderer,
|
||||
private dateService: DateService,
|
||||
private scrollManager: ScrollManager,
|
||||
private headerDrawerManager: HeaderDrawerManager,
|
||||
private indexedDBContext: IndexedDBContext,
|
||||
private dataSeeder: DataSeeder,
|
||||
private dragDropManager: DragDropManager,
|
||||
private edgeScrollManager: EdgeScrollManager,
|
||||
private resizeManager: ResizeManager,
|
||||
private headerDrawerRenderer: HeaderDrawerRenderer,
|
||||
private eventPersistenceManager: EventPersistenceManager,
|
||||
private auditService: AuditService,
|
||||
private settingsService: SettingsService,
|
||||
private resourceService: ResourceService,
|
||||
private viewConfigService: ViewConfigService
|
||||
private calendarApp: CalendarApp,
|
||||
private dateService: DateService
|
||||
) {}
|
||||
|
||||
async init(): Promise<void> {
|
||||
|
|
@ -56,145 +28,79 @@ export class DemoApp {
|
|||
await this.dataSeeder.seedIfEmpty();
|
||||
console.log('[DemoApp] Data seeding complete');
|
||||
|
||||
// Load default workweek preset from settings
|
||||
this.workweekPreset = await this.settingsService.getDefaultWorkweekPreset();
|
||||
console.log('[DemoApp] Workweek preset loaded:', this.workweekPreset?.id);
|
||||
|
||||
this.container = document.querySelector('swp-calendar-container') as HTMLElement;
|
||||
|
||||
// NavigationAnimator har DOM-dependencies - tilladt med new
|
||||
this.animator = new NavigationAnimator(
|
||||
document.querySelector('swp-header-track') as HTMLElement,
|
||||
document.querySelector('swp-content-track') as HTMLElement
|
||||
);
|
||||
// Initialize CalendarApp
|
||||
await this.calendarApp.init(this.container);
|
||||
console.log('[DemoApp] CalendarApp initialized');
|
||||
|
||||
// Render time axis (06:00 - 18:00)
|
||||
this.timeAxisRenderer.render(document.getElementById('time-axis') as HTMLElement, 6, 18);
|
||||
|
||||
// Init scroll synkronisering
|
||||
this.scrollManager.init(this.container);
|
||||
|
||||
// Init header drawer
|
||||
this.headerDrawerManager.init(this.container);
|
||||
|
||||
// Init drag-drop
|
||||
this.dragDropManager.init(this.container);
|
||||
|
||||
// Init edge scroll
|
||||
const scrollableContent = this.container.querySelector('swp-scrollable-content') as HTMLElement;
|
||||
this.edgeScrollManager.init(scrollableContent);
|
||||
|
||||
// Init resize
|
||||
this.resizeManager.init(this.container);
|
||||
|
||||
// Setup event handlers
|
||||
// Setup demo UI handlers
|
||||
this.setupNavigation();
|
||||
this.setupDrawerToggle();
|
||||
this.setupViewSwitching();
|
||||
|
||||
// Setup resource selector for picker view
|
||||
await this.setupResourceSelector();
|
||||
// Listen for calendar status events
|
||||
this.setupStatusListeners();
|
||||
|
||||
// Initial render
|
||||
this.render();
|
||||
}
|
||||
|
||||
private async render(): Promise<void> {
|
||||
// Load ViewConfig from IndexedDB
|
||||
const storedConfig = await this.viewConfigService.getById(this.currentView);
|
||||
if (!storedConfig) {
|
||||
console.error(`[DemoApp] ViewConfig not found for templateId: ${this.currentView}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Populate date values based on workweek and offset
|
||||
const workDays = this.workweekPreset?.workDays || [1, 2, 3, 4, 5];
|
||||
const dates = this.currentView === 'day'
|
||||
? this.dateService.getWeekDates(this.weekOffset, 1)
|
||||
: this.dateService.getWorkWeekDates(this.weekOffset, workDays);
|
||||
|
||||
// Clone config and populate dates
|
||||
const viewConfig: ViewConfig = {
|
||||
...storedConfig,
|
||||
groupings: storedConfig.groupings.map(g =>
|
||||
g.type === 'date' ? { ...g, values: dates } : g
|
||||
)
|
||||
};
|
||||
|
||||
await this.orchestrator.render(viewConfig, this.container);
|
||||
this.emitRenderCommand(this.currentView);
|
||||
}
|
||||
|
||||
private setupNavigation(): void {
|
||||
document.getElementById('btn-prev')!.onclick = () => {
|
||||
this.weekOffset--;
|
||||
this.animator.slide('right', () => this.render());
|
||||
this.emitNavigateCommand(-1, 'right');
|
||||
};
|
||||
|
||||
document.getElementById('btn-next')!.onclick = () => {
|
||||
this.weekOffset++;
|
||||
this.animator.slide('left', () => this.render());
|
||||
this.emitNavigateCommand(1, 'left');
|
||||
};
|
||||
}
|
||||
|
||||
private setupViewSwitching(): void {
|
||||
// View chip buttons
|
||||
const chips = document.querySelectorAll('.view-chip');
|
||||
chips.forEach(chip => {
|
||||
chip.addEventListener('click', () => {
|
||||
// Update active state
|
||||
chips.forEach(c => c.classList.remove('active'));
|
||||
chip.classList.add('active');
|
||||
|
||||
// Switch view
|
||||
const view = (chip as HTMLElement).dataset.view as typeof this.currentView;
|
||||
const view = (chip as HTMLElement).dataset.view;
|
||||
if (view) {
|
||||
this.currentView = view;
|
||||
this.updateSelectorVisibility();
|
||||
this.render();
|
||||
this.emitRenderCommand(view);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Workweek preset dropdown
|
||||
const workweekSelect = document.getElementById('workweek-select') as HTMLSelectElement;
|
||||
workweekSelect?.addEventListener('change', async () => {
|
||||
const presetId = workweekSelect.value;
|
||||
const preset = await this.settingsService.getWorkweekPreset(presetId);
|
||||
if (preset) {
|
||||
this.workweekPreset = preset;
|
||||
this.render();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private setupDrawerToggle(): void {
|
||||
document.getElementById('btn-drawer')!.onclick = () => {
|
||||
this.headerDrawerManager.toggle();
|
||||
this.container.dispatchEvent(new CustomEvent('calendar:cmd:drawer:toggle'));
|
||||
};
|
||||
}
|
||||
|
||||
private async setupResourceSelector(): Promise<void> {
|
||||
const resources = await this.resourceService.getAll();
|
||||
const container = document.querySelector('.resource-checkboxes') as HTMLElement;
|
||||
if (!container) return;
|
||||
|
||||
// Clear existing
|
||||
container.innerHTML = '';
|
||||
|
||||
// Display resources (read-only, values are stored in ViewConfig)
|
||||
resources.forEach(r => {
|
||||
const label = document.createElement('label');
|
||||
label.innerHTML = `
|
||||
<input type="checkbox" value="${r.id}" checked disabled>
|
||||
${r.displayName}
|
||||
`;
|
||||
container.appendChild(label);
|
||||
private setupStatusListeners(): void {
|
||||
this.container.addEventListener('calendar:status:ready', () => {
|
||||
console.log('[DemoApp] Calendar ready');
|
||||
});
|
||||
|
||||
this.container.addEventListener('calendar:status:rendered', ((e: CustomEvent) => {
|
||||
console.log('[DemoApp] Calendar rendered:', e.detail.viewId);
|
||||
}) as EventListener);
|
||||
|
||||
this.container.addEventListener('calendar:status:error', ((e: CustomEvent) => {
|
||||
console.error('[DemoApp] Calendar error:', e.detail.message);
|
||||
}) as EventListener);
|
||||
}
|
||||
|
||||
private updateSelectorVisibility(): void {
|
||||
const selector = document.querySelector('swp-resource-selector');
|
||||
const showSelector = this.currentView === 'picker' || this.currentView === 'day';
|
||||
selector?.classList.toggle('hidden', !showSelector);
|
||||
private emitRenderCommand(viewId: string, direction?: 'left' | 'right'): void {
|
||||
this.container.dispatchEvent(new CustomEvent('calendar:cmd:render', {
|
||||
detail: { viewId, direction }
|
||||
}));
|
||||
}
|
||||
|
||||
private emitNavigateCommand(offset: number, direction: 'left' | 'right'): void {
|
||||
this.container.dispatchEvent(new CustomEvent('calendar:cmd:navigate', {
|
||||
detail: { offset, direction }
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue