Some ignored filles was missing
This commit is contained in:
parent
7db22245e2
commit
fd5ab6bc0d
268 changed files with 31970 additions and 4 deletions
32
wwwroot/js/renderers/AllDayEventRenderer.d.ts
vendored
Normal file
32
wwwroot/js/renderers/AllDayEventRenderer.d.ts
vendored
Normal file
|
|
@ -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;
|
||||
}
|
||||
97
wwwroot/js/renderers/AllDayEventRenderer.js
Normal file
97
wwwroot/js/renderers/AllDayEventRenderer.js
Normal file
|
|
@ -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
|
||||
1
wwwroot/js/renderers/AllDayEventRenderer.js.map
Normal file
1
wwwroot/js/renderers/AllDayEventRenderer.js.map
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"AllDayEventRenderer.js","sourceRoot":"","sources":["../../../src/renderers/AllDayEventRenderer.ts"],"names":[],"mappings":"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"}
|
||||
26
wwwroot/js/renderers/ColumnRenderer.d.ts
vendored
Normal file
26
wwwroot/js/renderers/ColumnRenderer.d.ts
vendored
Normal file
|
|
@ -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;
|
||||
}
|
||||
44
wwwroot/js/renderers/ColumnRenderer.js
Normal file
44
wwwroot/js/renderers/ColumnRenderer.js
Normal file
|
|
@ -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
|
||||
1
wwwroot/js/renderers/ColumnRenderer.js.map
Normal file
1
wwwroot/js/renderers/ColumnRenderer.js.map
Normal file
|
|
@ -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"}
|
||||
21
wwwroot/js/renderers/DateHeaderRenderer.d.ts
vendored
Normal file
21
wwwroot/js/renderers/DateHeaderRenderer.d.ts
vendored
Normal file
|
|
@ -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;
|
||||
}
|
||||
35
wwwroot/js/renderers/DateHeaderRenderer.js
Normal file
35
wwwroot/js/renderers/DateHeaderRenderer.js
Normal file
|
|
@ -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 = `
|
||||
<swp-day-name>${dayName}</swp-day-name>
|
||||
<swp-day-date>${date.getDate()}</swp-day-date>
|
||||
`;
|
||||
header.dataset.date = this.dateService.formatISODate(date);
|
||||
calendarHeader.appendChild(header);
|
||||
});
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=DateHeaderRenderer.js.map
|
||||
1
wwwroot/js/renderers/DateHeaderRenderer.js.map
Normal file
1
wwwroot/js/renderers/DateHeaderRenderer.js.map
Normal file
|
|
@ -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"}
|
||||
96
wwwroot/js/renderers/EventRenderer.d.ts
vendored
Normal file
96
wwwroot/js/renderers/EventRenderer.d.ts
vendored
Normal file
|
|
@ -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[];
|
||||
}
|
||||
296
wwwroot/js/renderers/EventRenderer.js
Normal file
296
wwwroot/js/renderers/EventRenderer.js
Normal file
|
|
@ -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
|
||||
1
wwwroot/js/renderers/EventRenderer.js.map
Normal file
1
wwwroot/js/renderers/EventRenderer.js.map
Normal file
File diff suppressed because one or more lines are too long
55
wwwroot/js/renderers/EventRendererManager.d.ts
vendored
Normal file
55
wwwroot/js/renderers/EventRendererManager.d.ts
vendored
Normal file
|
|
@ -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<void>;
|
||||
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;
|
||||
}
|
||||
264
wwwroot/js/renderers/EventRendererManager.js
Normal file
264
wwwroot/js/renderers/EventRendererManager.js
Normal file
|
|
@ -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
|
||||
1
wwwroot/js/renderers/EventRendererManager.js.map
Normal file
1
wwwroot/js/renderers/EventRendererManager.js.map
Normal file
File diff suppressed because one or more lines are too long
180
wwwroot/js/renderers/GridRenderer.d.ts
vendored
Normal file
180
wwwroot/js/renderers/GridRenderer.d.ts
vendored
Normal file
|
|
@ -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:
|
||||
* =====================
|
||||
* <swp-calendar-container>
|
||||
* <swp-header-spacer /> ← GridRenderer
|
||||
* <swp-time-axis> ← GridRenderer
|
||||
* <swp-hour-marker>00:00</...> ← GridRenderer (iterates hours)
|
||||
* </swp-time-axis>
|
||||
* <swp-grid-container> ← GridRenderer
|
||||
* <swp-calendar-header> ← GridRenderer creates container
|
||||
* <swp-day-header /> ← DateHeaderRenderer (iterates dates)
|
||||
* </swp-calendar-header>
|
||||
* <swp-scrollable-content> ← GridRenderer
|
||||
* <swp-time-grid> ← GridRenderer
|
||||
* <swp-grid-lines /> ← GridRenderer
|
||||
* <swp-day-columns> ← GridRenderer creates container
|
||||
* <swp-day-column /> ← ColumnRenderer (iterates dates)
|
||||
* </swp-day-columns>
|
||||
* </swp-time-grid>
|
||||
* </swp-scrollable-content>
|
||||
* </swp-grid-container>
|
||||
* </swp-calendar-container>
|
||||
*
|
||||
* 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;
|
||||
}
|
||||
289
wwwroot/js/renderers/GridRenderer.js
Normal file
289
wwwroot/js/renderers/GridRenderer.js
Normal file
|
|
@ -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:
|
||||
* =====================
|
||||
* <swp-calendar-container>
|
||||
* <swp-header-spacer /> ← GridRenderer
|
||||
* <swp-time-axis> ← GridRenderer
|
||||
* <swp-hour-marker>00:00</...> ← GridRenderer (iterates hours)
|
||||
* </swp-time-axis>
|
||||
* <swp-grid-container> ← GridRenderer
|
||||
* <swp-calendar-header> ← GridRenderer creates container
|
||||
* <swp-day-header /> ← DateHeaderRenderer (iterates dates)
|
||||
* </swp-calendar-header>
|
||||
* <swp-scrollable-content> ← GridRenderer
|
||||
* <swp-time-grid> ← GridRenderer
|
||||
* <swp-grid-lines /> ← GridRenderer
|
||||
* <swp-day-columns> ← GridRenderer creates container
|
||||
* <swp-day-column /> ← ColumnRenderer (iterates dates)
|
||||
* </swp-day-columns>
|
||||
* </swp-time-grid>
|
||||
* </swp-scrollable-content>
|
||||
* </swp-grid-container>
|
||||
* </swp-calendar-container>
|
||||
*
|
||||
* 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
|
||||
1
wwwroot/js/renderers/GridRenderer.js.map
Normal file
1
wwwroot/js/renderers/GridRenderer.js.map
Normal file
File diff suppressed because one or more lines are too long
24
wwwroot/js/renderers/GridStyleManager.d.ts
vendored
Normal file
24
wwwroot/js/renderers/GridStyleManager.d.ts
vendored
Normal file
|
|
@ -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;
|
||||
}
|
||||
76
wwwroot/js/renderers/GridStyleManager.js
Normal file
76
wwwroot/js/renderers/GridStyleManager.js
Normal file
|
|
@ -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
|
||||
1
wwwroot/js/renderers/GridStyleManager.js.map
Normal file
1
wwwroot/js/renderers/GridStyleManager.js.map
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"GridStyleManager.js","sourceRoot":"","sources":["../../../src/renderers/GridStyleManager.ts"],"names":[],"mappings":"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"}
|
||||
29
wwwroot/js/renderers/HeaderRenderer.d.ts
vendored
Normal file
29
wwwroot/js/renderers/HeaderRenderer.d.ts
vendored
Normal file
|
|
@ -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;
|
||||
}
|
||||
56
wwwroot/js/renderers/HeaderRenderer.js
Normal file
56
wwwroot/js/renderers/HeaderRenderer.js
Normal file
|
|
@ -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 = `
|
||||
<swp-day-name>${dayName}</swp-day-name>
|
||||
<swp-day-date>${date.getDate()}</swp-day-date>
|
||||
`;
|
||||
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 = `
|
||||
<swp-resource-avatar>
|
||||
<img src="${resource.avatarUrl}" alt="${resource.displayName}" onerror="this.style.display='none'">
|
||||
</swp-resource-avatar>
|
||||
<swp-resource-name>${resource.displayName}</swp-resource-name>
|
||||
`;
|
||||
calendarHeader.appendChild(header);
|
||||
});
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=HeaderRenderer.js.map
|
||||
1
wwwroot/js/renderers/HeaderRenderer.js.map
Normal file
1
wwwroot/js/renderers/HeaderRenderer.js.map
Normal file
|
|
@ -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"}
|
||||
22
wwwroot/js/renderers/NavigationRenderer.d.ts
vendored
Normal file
22
wwwroot/js/renderers/NavigationRenderer.d.ts
vendored
Normal file
|
|
@ -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;
|
||||
}
|
||||
68
wwwroot/js/renderers/NavigationRenderer.js
Normal file
68
wwwroot/js/renderers/NavigationRenderer.js
Normal file
|
|
@ -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
|
||||
1
wwwroot/js/renderers/NavigationRenderer.js.map
Normal file
1
wwwroot/js/renderers/NavigationRenderer.js.map
Normal file
|
|
@ -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"}
|
||||
26
wwwroot/js/renderers/WeekInfoRenderer.d.ts
vendored
Normal file
26
wwwroot/js/renderers/WeekInfoRenderer.d.ts
vendored
Normal file
|
|
@ -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;
|
||||
}
|
||||
75
wwwroot/js/renderers/WeekInfoRenderer.js
Normal file
75
wwwroot/js/renderers/WeekInfoRenderer.js
Normal file
|
|
@ -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
|
||||
1
wwwroot/js/renderers/WeekInfoRenderer.js.map
Normal file
1
wwwroot/js/renderers/WeekInfoRenderer.js.map
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"WeekInfoRenderer.js","sourceRoot":"","sources":["../../../src/renderers/WeekInfoRenderer.ts"],"names":[],"mappings":"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"}
|
||||
Loading…
Add table
Add a link
Reference in a new issue