@media-quest/engine 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/public-api.d.mts +543 -0
- package/dist/public-api.d.ts +543 -0
- package/dist/public-api.js +2187 -0
- package/dist/public-api.mjs +2150 -0
- package/package.json +10 -3
- package/src/Delement/AudioContainer.ts +169 -0
- package/src/Delement/DAuto-play.ts +36 -0
- package/src/Delement/DElement.ts +263 -0
- package/src/Delement/DImg.ts +78 -0
- package/src/Delement/DStyle-utils.ts +616 -0
- package/src/Delement/DStyle.ts +165 -0
- package/src/Delement/DText.ts +29 -0
- package/src/Delement/Ddiv.ts +38 -0
- package/src/Delement/VideoContainer.ts +199 -0
- package/src/Delement/css.spec.ts +36 -0
- package/src/Delement/css.ts +46 -0
- package/src/commands/DCommand.ts +62 -0
- package/src/commands/DCommandBus.ts +60 -0
- package/src/common/DMaybe.ts +46 -0
- package/src/common/DTimestamp.ts +20 -0
- package/src/common/DTmestamp.spec.ts +11 -0
- package/src/common/result.ts +41 -0
- package/src/dto/AnimationDto.ts +4 -0
- package/src/dto/DElement.dto.ts +50 -0
- package/src/dto/SchemaDto.ts +65 -0
- package/src/engine/DPage.ts +55 -0
- package/src/engine/SchemaEngine.ts +210 -0
- package/src/engine/element-factory.ts +52 -0
- package/src/engine/scale.spec.ts +38 -0
- package/src/engine/scale.ts +70 -0
- package/src/event-handlers/DEventHandler.ts +29 -0
- package/src/events/DEvents.ts +94 -0
- package/src/events/event-bus.spec.ts +21 -0
- package/src/events/event-bus.ts +81 -0
- package/src/kladd/context-menu-manager.ts +56 -0
- package/src/player/dplayer.spec.ts +108 -0
- package/src/player/dplayer.ts +70 -0
- package/src/player/history-que.spec.ts +45 -0
- package/src/player/history-que.ts +38 -0
- package/src/player/next-que.spec.ts +108 -0
- package/src/player/next-que.ts +93 -0
- package/src/public-api.ts +18 -5
- package/src/rules/__test__/complex-condition.spec.ts +15 -0
- package/src/rules/__test__/conditon.spec.ts +124 -0
- package/src/rules/__test__/numeric-condition.spec.ts +84 -0
- package/src/rules/__test__/rule-engine.spec.ts +354 -0
- package/src/rules/__test__/rule-evaluation.spec.ts +140 -0
- package/src/rules/__test__/string-condition.spec.ts +41 -0
- package/src/rules/condition.ts +191 -0
- package/src/rules/fact.ts +18 -0
- package/src/rules/rule-engine.ts +46 -0
- package/src/rules/rule.ts +40 -0
- package/src/services/DMedia-manager.spec.ts +27 -0
- package/src/services/DMedia-manager.ts +182 -0
- package/src/services/resource-provider.ts +33 -0
- package/src/services/sequence-manager.spec.ts +168 -0
- package/src/services/sequence-manager.ts +132 -0
- package/src/state/Dstate.spec.ts +7 -0
- package/src/state/Dstate.ts +105 -0
- package/src/state/boolean-property.ts +69 -0
- package/src/state/state-service.spec.ts +307 -0
- package/src/state/state-service.ts +251 -0
- package/src/state/state-testing-helpers.ts +59 -0
- package/src/utils/DUtil.ts +109 -0
- package/tsconfig.json +4 -3
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type DTimestamp = number & { __timestamp__: true };
|
|
2
|
+
export namespace DTimestamp {
|
|
3
|
+
export const now = () => Date.now() as DTimestamp;
|
|
4
|
+
export type Diff = number & { __diff__: true };
|
|
5
|
+
|
|
6
|
+
export const addMills = (t: DTimestamp, ms: number): DTimestamp => {
|
|
7
|
+
const res = t + Math.abs(ms);
|
|
8
|
+
return res as DTimestamp;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const diff = (t1: DTimestamp, t2: DTimestamp): Diff => {
|
|
12
|
+
const t1Abs = Math.abs(t1);
|
|
13
|
+
const t2Abs = Math.abs(t2);
|
|
14
|
+
return Math.abs(t1Abs - t2Abs) as Diff;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const diffNow = (t: DTimestamp): Diff => {
|
|
18
|
+
return diff(t, now());
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { DTimestamp } from "./DTimestamp";
|
|
2
|
+
|
|
3
|
+
describe("DTimestamp works", () => {
|
|
4
|
+
test("Can add milliseconds to timestamp", () => {
|
|
5
|
+
const t0 = DTimestamp.now();
|
|
6
|
+
const plus1000 = DTimestamp.addMills(t0, 1000);
|
|
7
|
+
const diff = DTimestamp.diff(t0, plus1000);
|
|
8
|
+
// const pageDto: PageDto = {id}
|
|
9
|
+
expect(diff).toBe(1000);
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export class Ok<T> implements IResult<T> {
|
|
2
|
+
constructor(readonly value: T) {}
|
|
3
|
+
isOk(): this is Ok<T> {
|
|
4
|
+
return true;
|
|
5
|
+
}
|
|
6
|
+
isFailure(): this is Failure<T> {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
map<A>(f: (t: T) => A): IResult<A> {
|
|
11
|
+
const value = f(this.value);
|
|
12
|
+
return new Ok<A>(value);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class Failure<T> implements IResult<T> {
|
|
17
|
+
constructor(readonly message: string) {}
|
|
18
|
+
isFailure(): this is Failure<T> {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
isOk(): this is Ok<T> {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
map<A>(_f: (t: T) => A): IResult<A> {
|
|
27
|
+
return new Failure<A>(this.message);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface IResult<T> {
|
|
32
|
+
isOk(): this is Ok<T>;
|
|
33
|
+
isFailure(): this is Failure<T>;
|
|
34
|
+
map<A>(f: (t: T) => A): IResult<A>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type Result<T> = Failure<T> | Ok<T>;
|
|
38
|
+
export namespace Result {
|
|
39
|
+
export const ok = <T>(value: T) => new Ok<T>(value);
|
|
40
|
+
export const failure = <T>(message: string) => new Failure<T>(message);
|
|
41
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { DStyle } from "../Delement/DStyle";
|
|
2
|
+
import { DEventHandler, QueryChangedHandler } from "../event-handlers/DEventHandler";
|
|
3
|
+
import { DCommand } from "../commands/DCommand";
|
|
4
|
+
|
|
5
|
+
// export interface CanBlockMedia {
|
|
6
|
+
// readonly isMediaBlocking:
|
|
7
|
+
// }
|
|
8
|
+
export type DElementDto = DTextDto | DImgDto | DDivDto;
|
|
9
|
+
|
|
10
|
+
interface DStateListener {
|
|
11
|
+
readonly onStateChange?: ReadonlyArray<QueryChangedHandler>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface DElementBaseDto extends DStateListener {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
readonly style: Partial<DStyle>;
|
|
17
|
+
readonly eventHandlers?: ReadonlyArray<DEventHandler>;
|
|
18
|
+
readonly onClick?: ReadonlyArray<DCommand>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface DTextDto extends DElementBaseDto {
|
|
22
|
+
readonly _tag: "p";
|
|
23
|
+
readonly text: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DDivDto extends DElementBaseDto {
|
|
27
|
+
readonly _tag: "div";
|
|
28
|
+
readonly children: Array<DTextDto | DImgDto>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DImgDto extends DElementBaseDto {
|
|
32
|
+
readonly _tag: "img";
|
|
33
|
+
readonly url: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface DVideoDto extends DElementBaseDto {
|
|
37
|
+
readonly _tag: "video";
|
|
38
|
+
readonly url: string;
|
|
39
|
+
// readonly mode: "gif" | "autoplay" | "on-demand";
|
|
40
|
+
// readonly isMediaBlocking: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface DAudioDto {
|
|
44
|
+
readonly id: string;
|
|
45
|
+
readonly _tag: "audio";
|
|
46
|
+
readonly url: string;
|
|
47
|
+
// readonly eventHandlers: ReadonlyArray<DEventHandler>;
|
|
48
|
+
|
|
49
|
+
// readonly isMediaBlocking: boolean;
|
|
50
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { DAutoPlaySequence } from "../Delement/DAuto-play";
|
|
2
|
+
import { DAudioDto, DElementDto, DImgDto, DVideoDto } from "./DElement.dto";
|
|
3
|
+
import { Rule } from "../rules/rule";
|
|
4
|
+
import { Fact } from "../rules/fact";
|
|
5
|
+
import { PageQueCommand } from "../commands/DCommand";
|
|
6
|
+
import { DState } from "../state/Dstate";
|
|
7
|
+
|
|
8
|
+
export type PageQueRules = Rule<PageQueCommand, never>;
|
|
9
|
+
export interface PageDto {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly elements: Array<DElementDto>;
|
|
12
|
+
readonly tags?: string[];
|
|
13
|
+
readonly mainVideoId?: string;
|
|
14
|
+
readonly backgroundColor?: string;
|
|
15
|
+
readonly video?: Array<DVideoDto>;
|
|
16
|
+
readonly audio?: Array<DAudioDto>;
|
|
17
|
+
readonly autoPlaySequence?: DAutoPlaySequence;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface PageSequenceDto {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly rules: Array<PageQueRules>;
|
|
23
|
+
readonly pages: Array<PageDto>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SchemaDto {
|
|
27
|
+
readonly id: string;
|
|
28
|
+
readonly prefix: string;
|
|
29
|
+
readonly baseHeight: number;
|
|
30
|
+
readonly baseWidth: number;
|
|
31
|
+
readonly backgroundColor: string;
|
|
32
|
+
readonly pages: PageDto[];
|
|
33
|
+
readonly rules: Array<PageQueRules>;
|
|
34
|
+
readonly stateProps?: ReadonlyArray<DState.Prop>;
|
|
35
|
+
readonly stateQueries?: ReadonlyArray<DState.StateQuery>;
|
|
36
|
+
readonly stateFromEvent: ReadonlyArray<DState.fromEventHandler>;
|
|
37
|
+
readonly pageSequences?: Array<PageSequenceDto>;
|
|
38
|
+
readonly predefinedFacts?: ReadonlyArray<Fact>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export namespace SchemaDto {
|
|
42
|
+
export const getResources = (
|
|
43
|
+
schema: SchemaDto
|
|
44
|
+
): {
|
|
45
|
+
videoList: ReadonlyArray<DVideoDto>;
|
|
46
|
+
audioList: ReadonlyArray<DAudioDto>;
|
|
47
|
+
imageList: ReadonlyArray<DImgDto>;
|
|
48
|
+
} => {
|
|
49
|
+
const { pages } = schema;
|
|
50
|
+
const videoList = pages.reduce<Array<DVideoDto>>((acc, curr) => {
|
|
51
|
+
if (Array.isArray(curr.video)) {
|
|
52
|
+
acc.push(...curr.video);
|
|
53
|
+
}
|
|
54
|
+
return acc;
|
|
55
|
+
}, []);
|
|
56
|
+
const audioList: Array<DAudioDto> = pages.reduce<Array<DAudioDto>>((acc, curr) => {
|
|
57
|
+
if (Array.isArray(curr.audio)) {
|
|
58
|
+
acc.push(...curr.audio);
|
|
59
|
+
}
|
|
60
|
+
return acc;
|
|
61
|
+
}, []);
|
|
62
|
+
|
|
63
|
+
return { videoList, audioList, imageList: [] };
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { DElement } from "../Delement/DElement";
|
|
2
|
+
import { PageDto } from "../dto/SchemaDto";
|
|
3
|
+
import { createDElement } from "./element-factory";
|
|
4
|
+
import { EventBus } from "../events/event-bus";
|
|
5
|
+
import { DCommandBus } from "../commands/DCommandBus";
|
|
6
|
+
import { DTimestamp } from "../common/DTimestamp";
|
|
7
|
+
import { ScaleService } from "./scale";
|
|
8
|
+
|
|
9
|
+
export class DPage {
|
|
10
|
+
private readonly TAG = "[ DPage ]: ";
|
|
11
|
+
private elements: DElement<HTMLElement>[] = [];
|
|
12
|
+
private readonly eventBus;
|
|
13
|
+
private readonly commandBus;
|
|
14
|
+
private readonly scale: ScaleService;
|
|
15
|
+
// Todo GLOBAL EVENT_BUSS AND PAGE EVENTS:
|
|
16
|
+
|
|
17
|
+
constructor(private readonly dto: PageDto, eventBus: EventBus, commandBus: DCommandBus, scale: ScaleService) {
|
|
18
|
+
this.eventBus = eventBus;
|
|
19
|
+
this.commandBus = commandBus;
|
|
20
|
+
this.scale = scale;
|
|
21
|
+
this.elements = dto.elements.map((dto) => createDElement(dto, this.commandBus, this.eventBus, this.scale));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
appendYourself(parent: HTMLElement) {
|
|
25
|
+
// console.log(this.TAG + " APPENDING TO PARENT");
|
|
26
|
+
this.elements.forEach((el) => {
|
|
27
|
+
el.appendYourself(parent);
|
|
28
|
+
// parent.appendChild(el.el);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// if (!preloadMode) {
|
|
32
|
+
this.eventBus.emit({
|
|
33
|
+
kind: "PAGE_ENTER_EVENT",
|
|
34
|
+
timestamp: DTimestamp.now(),
|
|
35
|
+
producer: "DPage",
|
|
36
|
+
producerId: this.id,
|
|
37
|
+
data: { pageId: this.id },
|
|
38
|
+
});
|
|
39
|
+
// }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
destroy() {
|
|
43
|
+
this.elements.forEach((el) => {
|
|
44
|
+
el.destroy();
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
log(): void {
|
|
49
|
+
// console.log(this.TAG + 'scale - ' + this.scale);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get id() {
|
|
53
|
+
return this.dto.id;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { SchemaDto } from "../dto/SchemaDto";
|
|
2
|
+
import { DMediaManager } from "../services/DMedia-manager";
|
|
3
|
+
import { DCommandBus } from "../commands/DCommandBus";
|
|
4
|
+
import { EventBus } from "../events/event-bus";
|
|
5
|
+
import { DPlayer } from "../player/dplayer";
|
|
6
|
+
import { AnsweredQuestion } from "../player/history-que";
|
|
7
|
+
import { DTimestamp } from "../common/DTimestamp";
|
|
8
|
+
import { DPage } from "./DPage";
|
|
9
|
+
import { ScaleService } from "./scale";
|
|
10
|
+
import { ResourceProvider } from "../services/resource-provider";
|
|
11
|
+
import { StateService } from "../state/state-service";
|
|
12
|
+
import { DCommand, StateCommand } from "../commands/DCommand";
|
|
13
|
+
import { DEvent } from "../events/DEvents";
|
|
14
|
+
|
|
15
|
+
export interface EngineLogger {
|
|
16
|
+
error(message: string): void;
|
|
17
|
+
warn(message: string): void;
|
|
18
|
+
appEvent(event: DEvent): void;
|
|
19
|
+
appCommand(command: DCommand): void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SchemaResult {
|
|
23
|
+
readonly eventLog: ReadonlyArray<DEvent>;
|
|
24
|
+
readonly commandLog: ReadonlyArray<DCommand>;
|
|
25
|
+
readonly answers: ReadonlyArray<any>;
|
|
26
|
+
}
|
|
27
|
+
export interface ISchemaEngine {
|
|
28
|
+
onComplete(handler: (result: SchemaResult) => void): void;
|
|
29
|
+
onCommandOrEvent(item: DEvent | DCommand): void;
|
|
30
|
+
setSchema(schema: SchemaDto): void;
|
|
31
|
+
onFatalError(handler: (error: { message: string }) => void): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class SchemaEngine implements ISchemaEngine {
|
|
35
|
+
private readonly TAG = "[ SCHEMA_ENGINE ] :";
|
|
36
|
+
private readonly commandBus = new DCommandBus();
|
|
37
|
+
private readonly eventBus = new EventBus();
|
|
38
|
+
private readonly mediaManager: DMediaManager;
|
|
39
|
+
private readonly scale: ScaleService;
|
|
40
|
+
private readonly hostElement: HTMLDivElement;
|
|
41
|
+
private readonly uiContainer: HTMLDivElement = document.createElement("div");
|
|
42
|
+
private readonly mediaContainer: HTMLDivElement = document.createElement("div");
|
|
43
|
+
private readonly resourceProvider: ResourceProvider;
|
|
44
|
+
private readonly stateService: StateService;
|
|
45
|
+
private readonly globalEventToStateHandlers = new Map<string, ReadonlyArray<StateCommand>>();
|
|
46
|
+
private player: DPlayer;
|
|
47
|
+
private currentPage: DPage | false = false;
|
|
48
|
+
private readonly subs: Array<() => void> = [];
|
|
49
|
+
|
|
50
|
+
constructor(
|
|
51
|
+
hostEl: HTMLDivElement,
|
|
52
|
+
private readonly height: number,
|
|
53
|
+
private readonly width: number,
|
|
54
|
+
private readonly schema: SchemaDto
|
|
55
|
+
) {
|
|
56
|
+
this.hostElement = hostEl;
|
|
57
|
+
this.hostElement.appendChild(this.uiContainer);
|
|
58
|
+
this.hostElement.appendChild(this.mediaContainer);
|
|
59
|
+
const stateProps = this.schema.stateProps ?? [];
|
|
60
|
+
const stateQueries = this.schema.stateQueries ?? [];
|
|
61
|
+
this.stateService = new StateService(this.eventBus, this.commandBus, stateProps, stateQueries);
|
|
62
|
+
this.scale = new ScaleService({
|
|
63
|
+
baseHeight: schema.baseHeight,
|
|
64
|
+
baseWidth: schema.baseWidth,
|
|
65
|
+
containerWidth: width,
|
|
66
|
+
containerHeight: height,
|
|
67
|
+
});
|
|
68
|
+
// this.commandBus.logCommands = true;
|
|
69
|
+
const globalEventHandlers = schema.stateFromEvent ?? [];
|
|
70
|
+
|
|
71
|
+
globalEventHandlers.forEach((h) => {
|
|
72
|
+
this.globalEventToStateHandlers.set(h.onEvent, h.thenExecute);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const resources = SchemaDto.getResources(this.schema);
|
|
76
|
+
this.resourceProvider = new ResourceProvider({ videos: resources.videoList, audio: resources.audioList });
|
|
77
|
+
this.mediaManager = new DMediaManager(
|
|
78
|
+
this.mediaContainer,
|
|
79
|
+
this.commandBus,
|
|
80
|
+
this.eventBus,
|
|
81
|
+
this.resourceProvider,
|
|
82
|
+
this.scale
|
|
83
|
+
);
|
|
84
|
+
this.player = new DPlayer(this.schema);
|
|
85
|
+
this.styleSelf();
|
|
86
|
+
this.nextPage();
|
|
87
|
+
this.hookUpListeners();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private hookUpListeners() {
|
|
91
|
+
const eventSubscription = this.eventBus.subscribe((ev) => {
|
|
92
|
+
this.onCommandOrEvent(ev);
|
|
93
|
+
const globalHandlers = this.globalEventToStateHandlers.get(ev.kind) ?? [];
|
|
94
|
+
globalHandlers.forEach((stateCommand) => {
|
|
95
|
+
this.commandBus.emit(stateCommand);
|
|
96
|
+
});
|
|
97
|
+
}, this.TAG + "HOOK_UP_LISTENERS");
|
|
98
|
+
const commandSubscription = this.commandBus.subscribe((command) => {
|
|
99
|
+
// switch (command.kind) {
|
|
100
|
+
//
|
|
101
|
+
// }
|
|
102
|
+
this.onCommandOrEvent(command);
|
|
103
|
+
if (command.kind === "PAGE_QUE_NEXT_PAGE_COMMAND") {
|
|
104
|
+
this.nextPage();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (command.kind === "ENGINE_LEAVE_PAGE_COMMAND") {
|
|
108
|
+
const pageId = command.payload.pageId;
|
|
109
|
+
const facts = command.payload.factsCollected;
|
|
110
|
+
const timestamp = DTimestamp.now();
|
|
111
|
+
const ans: AnsweredQuestion[] = facts.map((f) => ({
|
|
112
|
+
timestamp,
|
|
113
|
+
fact: f,
|
|
114
|
+
}));
|
|
115
|
+
this.player.saveHistory({
|
|
116
|
+
answeredQuestions: ans,
|
|
117
|
+
pageId,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
this.nextPage();
|
|
121
|
+
// const history: PageHistory = { page: {}, answeredQuestions: [] };
|
|
122
|
+
}
|
|
123
|
+
}, this.TAG);
|
|
124
|
+
|
|
125
|
+
this.subs.push(commandSubscription);
|
|
126
|
+
this.subs.push(eventSubscription);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private styleSelf() {
|
|
130
|
+
this.hostElement.style.height = this.scale.pageHeight + "px";
|
|
131
|
+
this.hostElement.style.width = this.scale.pageWidth + "px";
|
|
132
|
+
this.hostElement.style.backgroundColor = this.schema.backgroundColor ?? "white";
|
|
133
|
+
this.hostElement.style.position = "relative";
|
|
134
|
+
// this.hostElement.style.overflow = "hidden";
|
|
135
|
+
const makeStatic = (div: HTMLDivElement) => {
|
|
136
|
+
div.style.height = "0px";
|
|
137
|
+
div.style.width = "0px";
|
|
138
|
+
div.style.position = "static";
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
makeStatic(this.uiContainer);
|
|
142
|
+
makeStatic(this.mediaContainer);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private nextPage() {
|
|
146
|
+
// TODO SHOULD THIS BE PART OF THE SCHEMA?? THIS IS CLEANUP LOGIC
|
|
147
|
+
// this.commandBus.emit({ kind: "VIDEO_PAUSE_COMMAND", target: "VIDEO", targetId: "VIDEO", payload: {} });
|
|
148
|
+
// this.commandBus.emit({ kind: "AUDIO_PAUSE_COMMAND", target: "AUDIO", targetId: "AUDIO", payload: {} });
|
|
149
|
+
const currPageId = this.currentPage ? this.currentPage.id : "NO_PAGE";
|
|
150
|
+
// console.groupCollapsed("NEXT_PAGE FROM: " + currPageId);
|
|
151
|
+
console.log(this.TAG + " NEXT_PAGE STARTED AT: " + currPageId);
|
|
152
|
+
this.mediaManager.clearAllMedia();
|
|
153
|
+
const nextPage = this.player.getNextPage();
|
|
154
|
+
const state = this.stateService.getState();
|
|
155
|
+
console.log(state);
|
|
156
|
+
// TODO CLEAN UP PAGE COMPONENTS this.page.CleanUp()
|
|
157
|
+
if (this.currentPage) {
|
|
158
|
+
this.currentPage.destroy();
|
|
159
|
+
this.uiContainer.innerHTML = "";
|
|
160
|
+
}
|
|
161
|
+
if (!nextPage) {
|
|
162
|
+
// TODO FIGURE OUT WHAQT TO DO AT END OF TEST!! Start over??
|
|
163
|
+
this.player = new DPlayer(this.schema);
|
|
164
|
+
if (this.schema.pages.length > 0) {
|
|
165
|
+
this.nextPage();
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
const newPage = new DPage(nextPage, this.eventBus, this.commandBus, this.scale);
|
|
170
|
+
|
|
171
|
+
this.currentPage = newPage;
|
|
172
|
+
newPage.appendYourself(this.uiContainer);
|
|
173
|
+
|
|
174
|
+
this.mediaManager.setPage(nextPage);
|
|
175
|
+
const s1 = this.stateService.getState();
|
|
176
|
+
console.log(s1);
|
|
177
|
+
console.log("Next-page: " + newPage.id);
|
|
178
|
+
// console.groupEnd();
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
destroy() {
|
|
183
|
+
if (this.currentPage) {
|
|
184
|
+
this.currentPage.destroy();
|
|
185
|
+
this.uiContainer.innerHTML = "";
|
|
186
|
+
}
|
|
187
|
+
this.mediaManager.destroy();
|
|
188
|
+
this.stateService.destroy();
|
|
189
|
+
this.subs.forEach((sub) => {
|
|
190
|
+
sub();
|
|
191
|
+
});
|
|
192
|
+
const evStats = this.eventBus.getStats();
|
|
193
|
+
const cmdStats = this.commandBus.getStats();
|
|
194
|
+
console.assert(evStats.subscribersCount === 0, this.TAG + " Eventbus should have no subscribers ", evStats);
|
|
195
|
+
console.assert(cmdStats.subscribersCount === 0, this.TAG + "Commandbus should have no subscribers", cmdStats);
|
|
196
|
+
}
|
|
197
|
+
onComplete(handler: (result: SchemaResult) => void) {
|
|
198
|
+
console.log(handler);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
onFatalError(handler: (error: { message: string }) => void): void {
|
|
202
|
+
console.log(handler);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
onCommandOrEvent(_event_or_command: DEvent | DCommand) {}
|
|
206
|
+
|
|
207
|
+
setSchema(schema: SchemaDto): void {
|
|
208
|
+
console.log(schema);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { DElement } from "../Delement/DElement";
|
|
2
|
+
import { DDiv } from "../Delement/Ddiv";
|
|
3
|
+
import { DImg } from "../Delement/DImg";
|
|
4
|
+
import { DText } from "../Delement/DText";
|
|
5
|
+
import { DDivDto, DElementDto } from "../dto/DElement.dto";
|
|
6
|
+
import { DCommandBus } from "../commands/DCommandBus";
|
|
7
|
+
import { EventBus } from "../events/event-bus";
|
|
8
|
+
import { ScaleService } from "./scale";
|
|
9
|
+
|
|
10
|
+
export const createDElement = (
|
|
11
|
+
dto: DElementDto,
|
|
12
|
+
actionBus: DCommandBus,
|
|
13
|
+
eventBus: EventBus,
|
|
14
|
+
scale: ScaleService
|
|
15
|
+
): DElement<any> => {
|
|
16
|
+
switch (dto._tag) {
|
|
17
|
+
case "div":
|
|
18
|
+
const childEls = createChildrenForDiv(dto, actionBus, eventBus, scale);
|
|
19
|
+
const newDiv = new DDiv(dto, eventBus, actionBus, scale, childEls);
|
|
20
|
+
return newDiv;
|
|
21
|
+
case "img":
|
|
22
|
+
return new DImg(dto, actionBus, eventBus, scale);
|
|
23
|
+
case "p":
|
|
24
|
+
return new DText(dto, eventBus, actionBus, scale);
|
|
25
|
+
default:
|
|
26
|
+
const check: never = dto;
|
|
27
|
+
throw new Error("Unknown dto given to the createDElement function.");
|
|
28
|
+
// TODO LOGGING or create any HTML-ELEMENT??
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const createChildrenForDiv = (
|
|
33
|
+
dto: DDivDto,
|
|
34
|
+
actionSubject: DCommandBus,
|
|
35
|
+
eventBus: EventBus,
|
|
36
|
+
scale: ScaleService
|
|
37
|
+
): DDiv["children"] => {
|
|
38
|
+
const childDto = dto.children;
|
|
39
|
+
const childEls: Array<DImg | DText> = [];
|
|
40
|
+
// console.log(childElements);
|
|
41
|
+
childDto.forEach((dto) => {
|
|
42
|
+
if (dto._tag === "p") {
|
|
43
|
+
const newText = new DText(dto, eventBus, actionSubject, scale);
|
|
44
|
+
childEls.push(newText);
|
|
45
|
+
}
|
|
46
|
+
if (dto._tag === "img") {
|
|
47
|
+
const newImage = new DImg(dto, actionSubject, eventBus, scale);
|
|
48
|
+
childEls.push(newImage);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return childEls;
|
|
52
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Scale, ScaleService } from "./scale";
|
|
2
|
+
|
|
3
|
+
describe("Scale works", () => {
|
|
4
|
+
test("scaleService", () => {
|
|
5
|
+
const scaleService = new ScaleService({
|
|
6
|
+
baseHeight: 1300,
|
|
7
|
+
baseWidth: 1024,
|
|
8
|
+
containerWidth: 600,
|
|
9
|
+
containerHeight: 650,
|
|
10
|
+
});
|
|
11
|
+
expect(scaleService.scale).toBe(0.5);
|
|
12
|
+
const unsub = scaleService.onChange((scale) => {
|
|
13
|
+
expect(scale).toBe(0.5);
|
|
14
|
+
}, "ScaleService test-function");
|
|
15
|
+
unsub();
|
|
16
|
+
|
|
17
|
+
scaleService.setContainerBounds({ height: 1170, width: 1000 });
|
|
18
|
+
|
|
19
|
+
expect(scaleService.scale).toBe(0.9);
|
|
20
|
+
});
|
|
21
|
+
test("scaleFn", () => {
|
|
22
|
+
const scaleFunction = Scale.calc(1200, 1200);
|
|
23
|
+
expect(scaleFunction({ height: 600, width: 600 })).toBe(0.5);
|
|
24
|
+
expect(scaleFunction({ height: 900, width: 600 })).toBe(0.5);
|
|
25
|
+
expect(scaleFunction({ height: 400, width: 600 })).toBe(4 / 12);
|
|
26
|
+
expect(scaleFunction({ height: 900, width: 300 })).toBe(0.25);
|
|
27
|
+
expect(scaleFunction({ height: 300, width: 400 })).toBe(0.25);
|
|
28
|
+
expect(scaleFunction({ height: 0, width: 0 })).toBe(0);
|
|
29
|
+
const scaleFunction2 = Scale.calc(1200, 1024);
|
|
30
|
+
expect(scaleFunction2({ height: 600, width: 6000 })).toBe(0.5);
|
|
31
|
+
expect(scaleFunction2({ height: 400, width: 800 })).toBe(4 / 12);
|
|
32
|
+
expect(scaleFunction2({ height: 800, width: 512 })).toBe(0.5);
|
|
33
|
+
const scale = Scale.scaleFunctionCreator(0.5);
|
|
34
|
+
|
|
35
|
+
expect(scale(500)).toBe(250);
|
|
36
|
+
expect(scale(200)).toBe(100);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export class ScaleService {
|
|
2
|
+
private readonly baseHeight;
|
|
3
|
+
private readonly baseWidth;
|
|
4
|
+
private containerHeight = 1300;
|
|
5
|
+
private containerWidth = 1300;
|
|
6
|
+
|
|
7
|
+
get scale() {
|
|
8
|
+
return this._scale;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
get pageHeight() {
|
|
12
|
+
return this.baseHeight * this.scale;
|
|
13
|
+
}
|
|
14
|
+
get pageWidth() {
|
|
15
|
+
return this.baseWidth * this._scale;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private _scale = 1;
|
|
19
|
+
|
|
20
|
+
private readonly subscribers = new Set<(scale: number) => void>();
|
|
21
|
+
|
|
22
|
+
constructor(config: { baseHeight: number; baseWidth: number; containerHeight: number; containerWidth: number }) {
|
|
23
|
+
this.baseHeight = config.baseHeight;
|
|
24
|
+
this.baseWidth = config.baseWidth;
|
|
25
|
+
this.containerHeight = config.containerHeight;
|
|
26
|
+
this.containerWidth = config.containerWidth;
|
|
27
|
+
this.updateScale();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
setContainerBounds(bounds: { height: number; width: number }) {
|
|
31
|
+
this.containerWidth = bounds.width;
|
|
32
|
+
this.containerHeight = bounds.height;
|
|
33
|
+
this.updateScale();
|
|
34
|
+
}
|
|
35
|
+
private updateScale() {
|
|
36
|
+
const scaleFn = Scale.calc(this.baseHeight, this.baseWidth);
|
|
37
|
+
const scale = scaleFn({ height: this.containerHeight, width: this.containerWidth });
|
|
38
|
+
const hasChanged = this.scale !== scale;
|
|
39
|
+
this._scale = scale;
|
|
40
|
+
if (hasChanged) {
|
|
41
|
+
this.subscribers.forEach((fn) => {
|
|
42
|
+
fn(this._scale);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
onChange(scaleChangeHandler: (scale: number) => void, subscriberId: string) {
|
|
48
|
+
// console.log(subscriberId);
|
|
49
|
+
this.subscribers.add(scaleChangeHandler);
|
|
50
|
+
scaleChangeHandler(this._scale);
|
|
51
|
+
return () => {
|
|
52
|
+
this.subscribers.delete(scaleChangeHandler);
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export namespace Scale {
|
|
57
|
+
export const calc = (baseHeight: number, baseWidth: number) => {
|
|
58
|
+
return (container: { height: number; width: number }) => {
|
|
59
|
+
const heightRatio = container.height / baseHeight;
|
|
60
|
+
const widthRatio = container.width / baseWidth;
|
|
61
|
+
return Math.min(heightRatio, widthRatio);
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
type ScalePosInput = { x: number; y: number; h: number; w: number };
|
|
66
|
+
|
|
67
|
+
export const scaleFunctionCreator = (scale: number) => {
|
|
68
|
+
return (value: number) => value * scale;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { DCommand, ElementCommand, StateCommand } from "../commands/DCommand";
|
|
2
|
+
import { DEvent } from "../events/DEvents";
|
|
3
|
+
import { Condition } from "../rules/condition";
|
|
4
|
+
|
|
5
|
+
export interface DEventHandler<E extends DEvent = DEvent> {
|
|
6
|
+
readonly onEvent: E["kind"];
|
|
7
|
+
readonly when?: { producerId?: string; condition?: Condition };
|
|
8
|
+
readonly thenExecute: ReadonlyArray<DCommand>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface QueryChangedHandler {
|
|
12
|
+
readonly queryName: string;
|
|
13
|
+
readonly whenTrue: ReadonlyArray<ElementCommand>;
|
|
14
|
+
readonly whenFalse: ReadonlyArray<ElementCommand>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export namespace DEventHandler {
|
|
18
|
+
export type LookUp = Map<DEventHandler["onEvent"], Array<DEventHandler>>;
|
|
19
|
+
export const createLookUp = (handlers?: ReadonlyArray<DEventHandler>): LookUp => {
|
|
20
|
+
const map = new Map<DEventHandler["onEvent"], Array<DEventHandler>>();
|
|
21
|
+
handlers?.forEach((h) => {
|
|
22
|
+
const kind = h.onEvent;
|
|
23
|
+
const current = map.get(kind);
|
|
24
|
+
const actions = current ? [...current, h] : [h];
|
|
25
|
+
map.set(kind, actions);
|
|
26
|
+
});
|
|
27
|
+
return map;
|
|
28
|
+
};
|
|
29
|
+
}
|