Refactor calendar V2 core with DI and new features
Introduces dependency injection container and composition root Adds core services like DateService and NavigationAnimator Simplifies CalendarOrchestrator with improved store handling Implements mock stores and demo application for V2 calendar
This commit is contained in:
parent
1ad7d10266
commit
a0c0ef9e8d
17 changed files with 331 additions and 134 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import { ViewConfig, GroupingConfig } from './ViewConfig';
|
||||
import { RenderContext } from './RenderContext';
|
||||
import { RendererRegistry } from './RendererRegistry';
|
||||
import { IStoreRegistry } from './IGroupingStore';
|
||||
import { IGroupingStore } from './IGroupingStore';
|
||||
|
||||
interface HierarchyNode {
|
||||
type: string;
|
||||
|
|
@ -19,9 +19,13 @@ interface GroupingData {
|
|||
export class CalendarOrchestrator {
|
||||
constructor(
|
||||
private rendererRegistry: RendererRegistry,
|
||||
private storeRegistry: IStoreRegistry
|
||||
private stores: IGroupingStore[]
|
||||
) {}
|
||||
|
||||
private getStore(type: string): IGroupingStore | undefined {
|
||||
return this.stores.find(s => s.type === type);
|
||||
}
|
||||
|
||||
async render(viewConfig: ViewConfig, container: HTMLElement): Promise<void> {
|
||||
const headerContainer = container.querySelector('swp-calendar-header') as HTMLElement;
|
||||
const columnContainer = container.querySelector('swp-day-columns') as HTMLElement;
|
||||
|
|
@ -66,7 +70,9 @@ export class CalendarOrchestrator {
|
|||
continue;
|
||||
}
|
||||
|
||||
const rawItems = this.storeRegistry.get(g.type).getByIds(g.values);
|
||||
const store = this.getStore(g.type);
|
||||
if (!store) continue;
|
||||
const rawItems = store.getByIds(g.values);
|
||||
const items = rawItems.map((item: any) => ({ id: item.id, data: item }));
|
||||
const byParent = g.parentKey
|
||||
? this.groupBy(items, item => (item.data as any)[g.parentKey!])
|
||||
|
|
|
|||
21
src/v2/core/DateService.ts
Normal file
21
src/v2/core/DateService.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import dayjs from 'dayjs';
|
||||
import { ITimeFormatConfig } from './ITimeFormatConfig';
|
||||
|
||||
export class DateService {
|
||||
constructor(private config: ITimeFormatConfig) {}
|
||||
|
||||
parseISO(isoString: string): Date {
|
||||
return dayjs(isoString).toDate();
|
||||
}
|
||||
|
||||
getDayName(date: Date, format: 'short' | 'long' = 'short'): string {
|
||||
return new Intl.DateTimeFormat(this.config.locale, { weekday: format }).format(date);
|
||||
}
|
||||
|
||||
getWeekDates(offset = 0): string[] {
|
||||
const monday = dayjs().startOf('week').add(1, 'day').add(offset, 'week');
|
||||
return Array.from({ length: 5 }, (_, i) =>
|
||||
monday.add(i, 'day').format('YYYY-MM-DD')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,4 @@
|
|||
export interface IGroupingStore<T = unknown> {
|
||||
readonly type: string;
|
||||
getByIds(ids: string[]): T[];
|
||||
}
|
||||
|
||||
export interface IStoreRegistry {
|
||||
get(type: string): IGroupingStore;
|
||||
}
|
||||
|
|
|
|||
7
src/v2/core/ITimeFormatConfig.ts
Normal file
7
src/v2/core/ITimeFormatConfig.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export interface ITimeFormatConfig {
|
||||
timezone: string;
|
||||
use24HourFormat: boolean;
|
||||
locale: string;
|
||||
dateFormat: 'locale' | 'technical';
|
||||
showSeconds: boolean;
|
||||
}
|
||||
41
src/v2/core/NavigationAnimator.ts
Normal file
41
src/v2/core/NavigationAnimator.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
export class NavigationAnimator {
|
||||
constructor(
|
||||
private headerTrack: HTMLElement,
|
||||
private contentTrack: HTMLElement
|
||||
) {}
|
||||
|
||||
async slide(direction: 'left' | 'right', renderFn: () => Promise<void>): Promise<void> {
|
||||
const out = direction === 'left' ? '-100%' : '100%';
|
||||
const into = direction === 'left' ? '100%' : '-100%';
|
||||
|
||||
await this.animateOut(out);
|
||||
await renderFn();
|
||||
await this.animateIn(into);
|
||||
}
|
||||
|
||||
private async animateOut(translate: string): Promise<void> {
|
||||
await Promise.all([
|
||||
this.headerTrack.animate(
|
||||
[{ transform: 'translateX(0)' }, { transform: `translateX(${translate})` }],
|
||||
{ duration: 200, easing: 'ease-in' }
|
||||
).finished,
|
||||
this.contentTrack.animate(
|
||||
[{ transform: 'translateX(0)' }, { transform: `translateX(${translate})` }],
|
||||
{ duration: 200, easing: 'ease-in' }
|
||||
).finished
|
||||
]);
|
||||
}
|
||||
|
||||
private async animateIn(translate: string): Promise<void> {
|
||||
await Promise.all([
|
||||
this.headerTrack.animate(
|
||||
[{ transform: `translateX(${translate})` }, { transform: 'translateX(0)' }],
|
||||
{ duration: 200, easing: 'ease-out' }
|
||||
).finished,
|
||||
this.contentTrack.animate(
|
||||
[{ transform: `translateX(${translate})` }, { transform: 'translateX(0)' }],
|
||||
{ duration: 200, easing: 'ease-out' }
|
||||
).finished
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import { IGroupingStore, IStoreRegistry } from './IGroupingStore';
|
||||
|
||||
export class StoreRegistry implements IStoreRegistry {
|
||||
private stores = new Map<string, IGroupingStore>();
|
||||
|
||||
register(type: string, store: IGroupingStore): void {
|
||||
this.stores.set(type, store);
|
||||
}
|
||||
|
||||
get(type: string): IGroupingStore {
|
||||
const store = this.stores.get(type);
|
||||
if (!store) throw new Error(`No store for type: ${type}`);
|
||||
return store;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue