Moving away from Azure Devops #1
17 changed files with 331 additions and 134 deletions
16
build.js
16
build.js
|
|
@ -61,6 +61,22 @@ async function build() {
|
|||
|
||||
console.log('V2 bundle created: wwwroot/js/calendar-v2.js');
|
||||
|
||||
// V2 demo bundle (with DI transformer for autowiring)
|
||||
await esbuild.build({
|
||||
entryPoints: ['src/v2/demo/index.ts'],
|
||||
bundle: true,
|
||||
outfile: 'wwwroot/js/v2-demo.js',
|
||||
format: 'esm',
|
||||
sourcemap: 'inline',
|
||||
target: 'es2020',
|
||||
minify: false,
|
||||
keepNames: true,
|
||||
platform: 'browser',
|
||||
plugins: [NovadiUnplugin.esbuild({ debug: false, enableAutowiring: true })]
|
||||
});
|
||||
|
||||
console.log('V2 demo bundle created: wwwroot/js/v2-demo.js');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Build failed:', error);
|
||||
process.exit(1);
|
||||
|
|
|
|||
53
src/v2/V2CompositionRoot.ts
Normal file
53
src/v2/V2CompositionRoot.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import { Container } from '@novadi/core';
|
||||
import { IGroupingRenderer } from './core/IGroupingRenderer';
|
||||
import { IGroupingStore } from './core/IGroupingStore';
|
||||
import { DateRenderer } from './features/date/DateRenderer';
|
||||
import { DateService } from './core/DateService';
|
||||
import { ITimeFormatConfig } from './core/ITimeFormatConfig';
|
||||
import { ResourceRenderer } from './features/resource/ResourceRenderer';
|
||||
import { TeamRenderer } from './features/team/TeamRenderer';
|
||||
import { RendererRegistry } from './core/RendererRegistry';
|
||||
import { CalendarOrchestrator } from './core/CalendarOrchestrator';
|
||||
import { TimeAxisRenderer } from './features/timeaxis/TimeAxisRenderer';
|
||||
import { MockTeamStore, MockResourceStore } from './demo/MockStores';
|
||||
import { DemoApp } from './demo/DemoApp';
|
||||
|
||||
const defaultTimeFormatConfig: ITimeFormatConfig = {
|
||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
use24HourFormat: true,
|
||||
locale: 'da-DK',
|
||||
dateFormat: 'locale',
|
||||
showSeconds: false
|
||||
};
|
||||
|
||||
export function createV2Container(): Container {
|
||||
const container = new Container();
|
||||
const builder = container.builder();
|
||||
|
||||
// Config
|
||||
builder.registerInstance(defaultTimeFormatConfig).as<ITimeFormatConfig>();
|
||||
|
||||
// Services
|
||||
builder.registerType(DateService).as<DateService>();
|
||||
|
||||
// Renderers - registreres som IGroupingRenderer
|
||||
builder.registerType(DateRenderer).as<IGroupingRenderer>();
|
||||
builder.registerType(ResourceRenderer).as<IGroupingRenderer>();
|
||||
builder.registerType(TeamRenderer).as<IGroupingRenderer>();
|
||||
|
||||
// RendererRegistry modtager IGroupingRenderer[] automatisk (array injection)
|
||||
builder.registerType(RendererRegistry).as<RendererRegistry>();
|
||||
|
||||
// Stores - registreres som IGroupingStore
|
||||
builder.registerType(MockTeamStore).as<IGroupingStore>();
|
||||
builder.registerType(MockResourceStore).as<IGroupingStore>();
|
||||
|
||||
// CalendarOrchestrator modtager IGroupingStore[] automatisk (array injection)
|
||||
builder.registerType(CalendarOrchestrator).as<CalendarOrchestrator>();
|
||||
builder.registerType(TimeAxisRenderer).as<TimeAxisRenderer>();
|
||||
|
||||
// Demo app
|
||||
builder.registerType(DemoApp).as<DemoApp>();
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
84
src/v2/demo/DemoApp.ts
Normal file
84
src/v2/demo/DemoApp.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import { CalendarOrchestrator } from '../core/CalendarOrchestrator';
|
||||
import { TimeAxisRenderer } from '../features/timeaxis/TimeAxisRenderer';
|
||||
import { NavigationAnimator } from '../core/NavigationAnimator';
|
||||
import { DateService } from '../core/DateService';
|
||||
import { ViewConfig } from '../core/ViewConfig';
|
||||
|
||||
export class DemoApp {
|
||||
private animator!: NavigationAnimator;
|
||||
private container!: HTMLElement;
|
||||
private weekOffset = 0;
|
||||
private views!: Record<string, ViewConfig>;
|
||||
|
||||
constructor(
|
||||
private orchestrator: CalendarOrchestrator,
|
||||
private timeAxisRenderer: TimeAxisRenderer,
|
||||
private dateService: DateService
|
||||
) {}
|
||||
|
||||
init(): void {
|
||||
this.container = document.querySelector('swp-calendar-container') as HTMLElement;
|
||||
|
||||
// NavigationAnimator har DOM-dependencies - tilladt med new
|
||||
this.animator = new NavigationAnimator(
|
||||
document.querySelector('swp-header-track') as HTMLElement,
|
||||
document.querySelector('swp-content-track') as HTMLElement
|
||||
);
|
||||
|
||||
// View configs
|
||||
const dates = this.dateService.getWeekDates();
|
||||
this.views = {
|
||||
simple: { templateId: 'simple', groupings: [{ type: 'date', values: dates }] },
|
||||
resource: {
|
||||
templateId: 'resource',
|
||||
groupings: [
|
||||
{ type: 'resource', values: ['alice', 'bob', 'carol'] },
|
||||
{ type: 'date', values: dates.slice(0, 3) }
|
||||
]
|
||||
},
|
||||
team: {
|
||||
templateId: 'team',
|
||||
groupings: [
|
||||
{ type: 'team', values: ['alpha', 'beta'] },
|
||||
{ type: 'resource', values: ['alice', 'bob', 'carol', 'dave'], parentKey: 'teamId' },
|
||||
{ type: 'date', values: dates.slice(0, 3) }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
// Render time axis
|
||||
this.timeAxisRenderer.render(document.getElementById('time-axis') as HTMLElement);
|
||||
|
||||
// Setup event handlers
|
||||
this.setupNavigation();
|
||||
this.setupViewSwitchers();
|
||||
|
||||
// Initial render
|
||||
this.orchestrator.render(this.views.simple, this.container);
|
||||
}
|
||||
|
||||
private setupNavigation(): void {
|
||||
document.getElementById('btn-prev')!.onclick = () => {
|
||||
this.weekOffset--;
|
||||
this.views.simple.groupings[0].values = this.dateService.getWeekDates(this.weekOffset);
|
||||
this.animator.slide('right', () => this.orchestrator.render(this.views.simple, this.container));
|
||||
};
|
||||
|
||||
document.getElementById('btn-next')!.onclick = () => {
|
||||
this.weekOffset++;
|
||||
this.views.simple.groupings[0].values = this.dateService.getWeekDates(this.weekOffset);
|
||||
this.animator.slide('left', () => this.orchestrator.render(this.views.simple, this.container));
|
||||
};
|
||||
}
|
||||
|
||||
private setupViewSwitchers(): void {
|
||||
document.getElementById('btn-simple')!.onclick = () =>
|
||||
this.animator.slide('right', () => this.orchestrator.render(this.views.simple, this.container));
|
||||
|
||||
document.getElementById('btn-resource')!.onclick = () =>
|
||||
this.animator.slide('left', () => this.orchestrator.render(this.views.resource, this.container));
|
||||
|
||||
document.getElementById('btn-team')!.onclick = () =>
|
||||
this.animator.slide('left', () => this.orchestrator.render(this.views.team, this.container));
|
||||
}
|
||||
}
|
||||
40
src/v2/demo/MockStores.ts
Normal file
40
src/v2/demo/MockStores.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import { IGroupingStore } from '../core/IGroupingStore';
|
||||
|
||||
export interface Team {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Resource {
|
||||
id: string;
|
||||
name: string;
|
||||
teamId: string;
|
||||
}
|
||||
|
||||
export class MockTeamStore implements IGroupingStore<Team> {
|
||||
readonly type = 'team';
|
||||
|
||||
private teams: Team[] = [
|
||||
{ id: 'alpha', name: 'Team Alpha' },
|
||||
{ id: 'beta', name: 'Team Beta' }
|
||||
];
|
||||
|
||||
getByIds(ids: string[]): Team[] {
|
||||
return this.teams.filter(t => ids.includes(t.id));
|
||||
}
|
||||
}
|
||||
|
||||
export class MockResourceStore implements IGroupingStore<Resource> {
|
||||
readonly type = 'resource';
|
||||
|
||||
private resources: Resource[] = [
|
||||
{ id: 'alice', name: 'Alice', teamId: 'alpha' },
|
||||
{ id: 'bob', name: 'Bob', teamId: 'alpha' },
|
||||
{ id: 'carol', name: 'Carol', teamId: 'beta' },
|
||||
{ id: 'dave', name: 'Dave', teamId: 'beta' }
|
||||
];
|
||||
|
||||
getByIds(ids: string[]): Resource[] {
|
||||
return this.resources.filter(r => ids.includes(r.id));
|
||||
}
|
||||
}
|
||||
5
src/v2/demo/index.ts
Normal file
5
src/v2/demo/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { createV2Container } from '../V2CompositionRoot';
|
||||
import { DemoApp } from './DemoApp';
|
||||
|
||||
const app = createV2Container();
|
||||
app.resolveType<DemoApp>().init();
|
||||
|
|
@ -1,20 +1,11 @@
|
|||
import { IGroupingRenderer } from '../../core/IGroupingRenderer';
|
||||
import { RenderContext } from '../../core/RenderContext';
|
||||
|
||||
export interface IDateService {
|
||||
parseISO(dateStr: string): Date;
|
||||
getDayName(date: Date, format: 'short' | 'long'): string;
|
||||
}
|
||||
|
||||
export const defaultDateService: IDateService = {
|
||||
parseISO: (str) => new Date(str),
|
||||
getDayName: (date, format) => date.toLocaleDateString('da-DK', { weekday: format })
|
||||
};
|
||||
import { DateService } from '../../core/DateService';
|
||||
|
||||
export class DateRenderer implements IGroupingRenderer {
|
||||
readonly type = 'date';
|
||||
|
||||
constructor(private dateService: IDateService = defaultDateService) {}
|
||||
constructor(private dateService: DateService) {}
|
||||
|
||||
render(context: RenderContext): void {
|
||||
for (const dateStr of context.values) {
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export { DateRenderer, IDateService, defaultDateService } from './DateRenderer';
|
||||
export { DateRenderer } from './DateRenderer';
|
||||
|
|
|
|||
10
src/v2/features/timeaxis/TimeAxisRenderer.ts
Normal file
10
src/v2/features/timeaxis/TimeAxisRenderer.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
export class TimeAxisRenderer {
|
||||
render(container: HTMLElement, startHour = 6, endHour = 20): void {
|
||||
container.innerHTML = '';
|
||||
for (let hour = startHour; hour <= endHour; hour++) {
|
||||
const marker = document.createElement('swp-hour-marker');
|
||||
marker.textContent = `${hour.toString().padStart(2, '0')}:00`;
|
||||
container.appendChild(marker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,17 @@
|
|||
export { ViewTemplate, ViewConfig, GroupingConfig } from './core/ViewConfig';
|
||||
export { RenderContext } from './core/RenderContext';
|
||||
export { IGroupingRenderer } from './core/IGroupingRenderer';
|
||||
export { IGroupingStore, IStoreRegistry } from './core/IGroupingStore';
|
||||
export { IGroupingStore } from './core/IGroupingStore';
|
||||
export { RendererRegistry } from './core/RendererRegistry';
|
||||
export { StoreRegistry } from './core/StoreRegistry';
|
||||
export { CalendarOrchestrator } from './core/CalendarOrchestrator';
|
||||
export { NavigationAnimator } from './core/NavigationAnimator';
|
||||
|
||||
// Feature exports
|
||||
export { DateRenderer, IDateService, defaultDateService } from './features/date';
|
||||
export { DateRenderer } from './features/date';
|
||||
export { DateService } from './core/DateService';
|
||||
export { ITimeFormatConfig } from './core/ITimeFormatConfig';
|
||||
export { EventRenderer, IEventData, IEventStore } from './features/event';
|
||||
export { ResourceRenderer } from './features/resource';
|
||||
export { TeamRenderer } from './features/team';
|
||||
export { TimeAxisRenderer } from './features/timeaxis/TimeAxisRenderer';
|
||||
|
||||
|
|
|
|||
|
|
@ -107,6 +107,22 @@ swp-grid-container {
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Viewport/Track for slide animation */
|
||||
swp-header-viewport,
|
||||
swp-content-viewport {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
swp-header-track,
|
||||
swp-content-track {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
swp-header-track > swp-calendar-header,
|
||||
swp-content-track > swp-scrollable-content {
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
swp-calendar-header {
|
||||
display: grid;
|
||||
|
|
|
|||
101
wwwroot/v2.html
101
wwwroot/v2.html
|
|
@ -17,6 +17,8 @@
|
|||
<swp-week-number>V2</swp-week-number>
|
||||
<swp-date-range id="view-info"></swp-date-range>
|
||||
</swp-week-info>
|
||||
<swp-nav-button id="btn-prev">←</swp-nav-button>
|
||||
<swp-nav-button id="btn-next">→</swp-nav-button>
|
||||
</swp-calendar-nav>
|
||||
|
||||
<swp-calendar-container>
|
||||
|
|
@ -25,107 +27,26 @@
|
|||
<swp-time-axis-content id="time-axis"></swp-time-axis-content>
|
||||
</swp-time-axis>
|
||||
<swp-grid-container>
|
||||
<swp-header-viewport>
|
||||
<swp-header-track>
|
||||
<swp-calendar-header></swp-calendar-header>
|
||||
</swp-header-track>
|
||||
</swp-header-viewport>
|
||||
<swp-content-viewport>
|
||||
<swp-content-track>
|
||||
<swp-scrollable-content>
|
||||
<swp-time-grid>
|
||||
<swp-grid-lines></swp-grid-lines>
|
||||
<swp-day-columns></swp-day-columns>
|
||||
</swp-time-grid>
|
||||
</swp-scrollable-content>
|
||||
</swp-content-track>
|
||||
</swp-content-viewport>
|
||||
</swp-grid-container>
|
||||
</swp-calendar-container>
|
||||
</swp-calendar>
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
import {
|
||||
CalendarOrchestrator,
|
||||
RendererRegistry,
|
||||
StoreRegistry,
|
||||
DateRenderer,
|
||||
ResourceRenderer,
|
||||
TeamRenderer
|
||||
} from './js/calendar-v2.js';
|
||||
|
||||
const rendererRegistry = new RendererRegistry([
|
||||
new DateRenderer(),
|
||||
new ResourceRenderer(),
|
||||
new TeamRenderer()
|
||||
]);
|
||||
|
||||
const mockTeams = [
|
||||
{ id: 'alpha', name: 'Team Alpha' },
|
||||
{ id: 'beta', name: 'Team Beta' }
|
||||
];
|
||||
|
||||
const mockResources = [
|
||||
{ id: 'alice', name: 'Alice', teamId: 'alpha' },
|
||||
{ id: 'bob', name: 'Bob', teamId: 'alpha' },
|
||||
{ id: 'carol', name: 'Carol', teamId: 'beta' },
|
||||
{ id: 'dave', name: 'Dave', teamId: 'beta' }
|
||||
];
|
||||
|
||||
const storeRegistry = new StoreRegistry();
|
||||
storeRegistry.register('team', { getByIds: ids => mockTeams.filter(t => ids.includes(t.id)) });
|
||||
storeRegistry.register('resource', { getByIds: ids => mockResources.filter(r => ids.includes(r.id)) });
|
||||
|
||||
const orchestrator = new CalendarOrchestrator(rendererRegistry, storeRegistry);
|
||||
const container = document.querySelector('swp-calendar-container');
|
||||
const viewInfo = document.getElementById('view-info');
|
||||
|
||||
function getWeekDates() {
|
||||
const today = new Date();
|
||||
const mon = new Date(today);
|
||||
mon.setDate(today.getDate() - today.getDay() + 1);
|
||||
return Array.from({ length: 5 }, (_, i) => {
|
||||
const d = new Date(mon);
|
||||
d.setDate(mon.getDate() + i);
|
||||
return d.toISOString().split('T')[0];
|
||||
});
|
||||
}
|
||||
|
||||
const dates = getWeekDates();
|
||||
|
||||
const views = {
|
||||
simple: {
|
||||
templateId: 'simple',
|
||||
groupings: [{ type: 'date', values: dates }]
|
||||
},
|
||||
resource: {
|
||||
templateId: 'resource',
|
||||
groupings: [
|
||||
{ type: 'resource', values: ['alice', 'bob', 'carol'] },
|
||||
{ type: 'date', values: dates.slice(0, 3) }
|
||||
]
|
||||
},
|
||||
team: {
|
||||
templateId: 'team',
|
||||
groupings: [
|
||||
{ type: 'team', values: ['alpha', 'beta'] },
|
||||
{ type: 'resource', values: ['alice', 'bob', 'carol', 'dave'], parentKey: 'teamId' },
|
||||
{ type: 'date', values: dates.slice(0, 3) }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
function generateTimeAxis() {
|
||||
const el = document.getElementById('time-axis');
|
||||
el.innerHTML = Array.from({ length: 15 }, (_, i) =>
|
||||
`<swp-hour-marker>${(6 + i).toString().padStart(2, '0')}:00</swp-hour-marker>`
|
||||
).join('');
|
||||
}
|
||||
|
||||
async function render(view, label) {
|
||||
viewInfo.textContent = label;
|
||||
await orchestrator.render(view, container);
|
||||
}
|
||||
|
||||
document.getElementById('btn-simple').onclick = () => render(views.simple, '5 datoer');
|
||||
document.getElementById('btn-resource').onclick = () => render(views.resource, '3 resources × 3 datoer');
|
||||
document.getElementById('btn-team').onclick = () => render(views.team, '2 teams × 2 resources × 3 datoer');
|
||||
|
||||
generateTimeAxis();
|
||||
render(views.simple, '5 datoer');
|
||||
</script>
|
||||
<script type="module" src="js/v2-demo.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue