@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,94 @@
|
|
|
1
|
+
import { DTimestamp } from "../common/DTimestamp";
|
|
2
|
+
import { AnsweredQuestion } from "../player/history-que";
|
|
3
|
+
import { DState } from "../state/Dstate";
|
|
4
|
+
|
|
5
|
+
type EventProducer =
|
|
6
|
+
| "DVideo"
|
|
7
|
+
| "DUser"
|
|
8
|
+
| "DAudio"
|
|
9
|
+
| "DImage"
|
|
10
|
+
| "MediaManager"
|
|
11
|
+
| "DPage"
|
|
12
|
+
| "Window"
|
|
13
|
+
| "HOST"
|
|
14
|
+
| "RuleEngine"
|
|
15
|
+
| "Engine"
|
|
16
|
+
| "STATE-SERVICE";
|
|
17
|
+
|
|
18
|
+
interface Ev<K extends EventKind, P extends EventProducer, T> {
|
|
19
|
+
readonly kind: K;
|
|
20
|
+
readonly timestamp: DTimestamp;
|
|
21
|
+
readonly producer: P;
|
|
22
|
+
readonly producerId: string;
|
|
23
|
+
readonly data: T;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type EventKind = `${Uppercase<string>}_EVENT`;
|
|
27
|
+
|
|
28
|
+
export type QueryChangedEvent = Ev<"STATE_QUERY_RESULT_CHANGED_EVENT", "STATE-SERVICE", DState.StateQueryResult>;
|
|
29
|
+
|
|
30
|
+
export type AudioPlayEvent = Ev<"AUDIO_PLAY_EVENT", "DAudio", {}>;
|
|
31
|
+
// export type AudioWillPlayEvent = Ev<"AUDIO_WILL_PLAY_EVENT", "DAudio", {}>;
|
|
32
|
+
|
|
33
|
+
export type DAudioEvent =
|
|
34
|
+
| AudioPlayEvent
|
|
35
|
+
// | AudioWillPlayEvent
|
|
36
|
+
| Ev<"AUDIO_PAUSED_EVENT", "DAudio", {}>
|
|
37
|
+
| Ev<"AUDIO_ENDED_EVENT", "DAudio", { url: string }>
|
|
38
|
+
| Ev<"AUDIO_ERROR_EVENT", "DAudio", { error: unknown }>
|
|
39
|
+
| Ev<"AUDIO_METADATA_LOADED_EVENT", "DAudio", {}>
|
|
40
|
+
| Ev<"AUDIO_LOAD_EVENT", "DAudio", {}>
|
|
41
|
+
| Ev<"AUDIO_CAN_PLAY_THROUGH_EVENT", "DAudio", {}>
|
|
42
|
+
| Ev<"AUDIO_DURATION_CHANGE_EVENT", "DAudio", { duration: number; isInfinity: boolean }>
|
|
43
|
+
| Ev<"AUDIO_PROGRESS_EVENT", "DAudio", {}>;
|
|
44
|
+
|
|
45
|
+
export type DVideoEvent =
|
|
46
|
+
| Ev<"VIDEO_PLAY_EVENT", "DVideo", {}>
|
|
47
|
+
| Ev<"VIDEO_PAUSED_EVENT", "DVideo", {}>
|
|
48
|
+
| Ev<"VIDEO_ERROR_EVENT", "DVideo", { error: unknown }>
|
|
49
|
+
| Ev<"VIDEO_EVENT", "DVideo", { error: unknown }>
|
|
50
|
+
| Ev<"VIDEO_LOADED_METADATA_EVENT", "DVideo", { duration: number; isInfinity: boolean }>
|
|
51
|
+
| Ev<"VIDEO_DURATION_CHANGE_EVENT", "DVideo", { duration: number; isInfinity: boolean }>
|
|
52
|
+
| Ev<"VIDEO_PROGRESS_EVENT", "DVideo", { duration: number; progress: number }>
|
|
53
|
+
| Ev<"VIDEO_ENDED_EVENT", "DVideo", {}>;
|
|
54
|
+
|
|
55
|
+
// export type MediaManagerEvent =
|
|
56
|
+
// | Ev<"MEDIA_B
|
|
57
|
+
//
|
|
58
|
+
//
|
|
59
|
+
// LOCKING_START_EVENT", "MediaManager", {}>
|
|
60
|
+
// | Ev<"MEDIA_BLOCKING_END_EVENT", "MediaManager", {}>
|
|
61
|
+
// | Ev<"INPUT_BLOCKING_MEDIA_START_EVENT", "MediaManager", {}>
|
|
62
|
+
// | Ev<"INPUT_BLOCKING_MEDIA_END_EVENT", "MediaManager", {}>;
|
|
63
|
+
|
|
64
|
+
export type DImageEvent =
|
|
65
|
+
| Ev<
|
|
66
|
+
"IMAGE_LOADED_EVENT",
|
|
67
|
+
"DImage",
|
|
68
|
+
{ naturalHeight: number; naturalWidth: number; loadTime: DTimestamp.Diff; height: number; width: number }
|
|
69
|
+
>
|
|
70
|
+
| Ev<"IMAGE_ERROR_EVENT", "DImage", { error: unknown }>;
|
|
71
|
+
export type DPageEvents =
|
|
72
|
+
| Ev<"PAGE_ENTER_EVENT", "DPage", { pageId: string }>
|
|
73
|
+
| Ev<"PAGE_COMPLETED_EVENT", "DPage", { pageId: string; answers: AnsweredQuestion[] }>;
|
|
74
|
+
|
|
75
|
+
export type DWindowEvents =
|
|
76
|
+
| Ev<"WINDOW_VISIBILITY_CHANGE_EVENT", "Window", {}>
|
|
77
|
+
| Ev<"WINDOW_ONLINE_STATUS_CHANGE_EVENT", "Window", {}>;
|
|
78
|
+
|
|
79
|
+
export type DEvent =
|
|
80
|
+
| DImageEvent
|
|
81
|
+
| DAudioEvent
|
|
82
|
+
| DVideoEvent
|
|
83
|
+
| DPageEvents
|
|
84
|
+
// | MediaManagerEvent
|
|
85
|
+
| DWindowEvents
|
|
86
|
+
| QueryChangedEvent
|
|
87
|
+
| Ev<"USER_CLICKED_EVENT", "DUser", { elementId: string }>
|
|
88
|
+
| Ev<"RULE_MATCH_EVENT", "Window", { ruleId: string }>
|
|
89
|
+
| Ev<
|
|
90
|
+
"ENGINE_SCHEMA_LOADED_EVENT",
|
|
91
|
+
"Engine",
|
|
92
|
+
{ pageCount: number; hostHeight: number; hostWidth: number; scale: number }
|
|
93
|
+
>
|
|
94
|
+
| Ev<"HOST_SCALE_CHANGED_EVENT", "HOST", { scale: number }>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { EventBus } from "./event-bus";
|
|
2
|
+
import { DTimestamp } from "../common/DTimestamp";
|
|
3
|
+
describe("Event-bus-works", () => {
|
|
4
|
+
test("Can create event-bus", (done) => {
|
|
5
|
+
const bus = new EventBus();
|
|
6
|
+
|
|
7
|
+
bus.consoleLogEvents = false;
|
|
8
|
+
bus.subscribe((ev) => {
|
|
9
|
+
expect(ev.kind === "AUDIO_PLAY_EVENT").toBe(true);
|
|
10
|
+
done();
|
|
11
|
+
}, "TEST. CAN CREATE EVENTBUS");
|
|
12
|
+
|
|
13
|
+
bus.emit({
|
|
14
|
+
kind: "AUDIO_PLAY_EVENT",
|
|
15
|
+
timestamp: DTimestamp.now(),
|
|
16
|
+
data: {},
|
|
17
|
+
producerId: "",
|
|
18
|
+
producer: "DAudio",
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { DEvent } from "./DEvents";
|
|
2
|
+
|
|
3
|
+
interface SubscriberData {
|
|
4
|
+
readonly subscriberId: string;
|
|
5
|
+
readonly cb: (event: DEvent) => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface DEventStore {
|
|
9
|
+
subscribe(cb: (event: DEvent) => void, subscriberId: string): void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface DEventDispatcher {
|
|
13
|
+
emit(event: DEvent): void;
|
|
14
|
+
}
|
|
15
|
+
export class EventBus implements DEventStore, DEventDispatcher {
|
|
16
|
+
private readonly TAG = "[ EVENT_BUS ] :";
|
|
17
|
+
// private readonly subscribers = new Set<(event: DEvent) => void>();
|
|
18
|
+
private readonly subscribers = new Set<SubscriberData>();
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Emit on module end.
|
|
22
|
+
* @private
|
|
23
|
+
*/
|
|
24
|
+
private readonly eventLog: DEvent[] = [];
|
|
25
|
+
consoleLogEvents = false;
|
|
26
|
+
// readonly sub
|
|
27
|
+
|
|
28
|
+
subscribe(cb: (event: DEvent) => void, subscriberId: string) {
|
|
29
|
+
// this.subscribers.add(cb);
|
|
30
|
+
const subscriberData: SubscriberData = {
|
|
31
|
+
subscriberId,
|
|
32
|
+
cb,
|
|
33
|
+
};
|
|
34
|
+
this.subscribers.add(subscriberData);
|
|
35
|
+
if (this.consoleLogEvents) {
|
|
36
|
+
console.log(this.TAG + "subscription added: " + subscriberId);
|
|
37
|
+
console.log(this.TAG + "subscription count: " + this.subscribers.size);
|
|
38
|
+
}
|
|
39
|
+
return () => {
|
|
40
|
+
if (this.consoleLogEvents) {
|
|
41
|
+
console.log(this.TAG + "subscription removed: " + subscriberId);
|
|
42
|
+
console.log(this.TAG + "subscription count : " + this.subscribers.size);
|
|
43
|
+
}
|
|
44
|
+
// this.subscribers.delete(cb);
|
|
45
|
+
this.subscribers.delete(subscriberData);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getStats() {
|
|
50
|
+
return {
|
|
51
|
+
subscribersCount: this.subscribers.size,
|
|
52
|
+
eventLog: [...this.eventLog],
|
|
53
|
+
subscribers: [...this.subscribers],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
emit(event: DEvent) {
|
|
58
|
+
if (this.consoleLogEvents) {
|
|
59
|
+
this.logEvent(event);
|
|
60
|
+
}
|
|
61
|
+
this.eventLog.push(event);
|
|
62
|
+
this.subscribers.forEach((data) => {
|
|
63
|
+
// console.log('CALLING EMIT');
|
|
64
|
+
data.cb(event);
|
|
65
|
+
});
|
|
66
|
+
// this.logEvents();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private logEvent(event: DEvent) {
|
|
70
|
+
console.groupCollapsed(this.TAG + " " + event.kind);
|
|
71
|
+
console.log("ProducerId: " + event.producerId);
|
|
72
|
+
console.log(event.data);
|
|
73
|
+
console.groupEnd();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private logEvents() {
|
|
77
|
+
console.group(this.TAG + "LOGG");
|
|
78
|
+
console.table(this.eventLog);
|
|
79
|
+
console.groupEnd();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
class ContextMenuManager {
|
|
3
|
+
private static ROOT = document.createElement("div");
|
|
4
|
+
public static hasMenu = false;
|
|
5
|
+
private hasMenu = false;
|
|
6
|
+
|
|
7
|
+
createMenu() {
|
|
8
|
+
const root = document.createElement("div");
|
|
9
|
+
const menu = document.createElement("div");
|
|
10
|
+
const backDrop = document.createElement("div");
|
|
11
|
+
backDrop.style.position = "fixed";
|
|
12
|
+
backDrop.style.height = "100vh";
|
|
13
|
+
backDrop.style.width = "100vw";
|
|
14
|
+
backDrop.style.backgroundColor = "rgba(255, 0, 0, 0.25)";
|
|
15
|
+
backDrop.style.top = "0";
|
|
16
|
+
backDrop.style.right = "0";
|
|
17
|
+
backDrop.style.zIndex = "1000";
|
|
18
|
+
// backDrop.style.opacity = '0.5';
|
|
19
|
+
|
|
20
|
+
menu.style.height = "50%";
|
|
21
|
+
menu.style.width = "50%";
|
|
22
|
+
menu.style.top = "10%";
|
|
23
|
+
menu.style.right = "25%";
|
|
24
|
+
menu.style.backgroundColor = "green";
|
|
25
|
+
menu.style.position = "absolute";
|
|
26
|
+
menu.style.zIndex = "1001";
|
|
27
|
+
menu.contentEditable = "false";
|
|
28
|
+
const createOption = (text: string) => {
|
|
29
|
+
const p = document.createElement("p");
|
|
30
|
+
p.textContent = text;
|
|
31
|
+
return p;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const options = ["Select On-Click Actions", "Select On-Hover Actions"].map(
|
|
35
|
+
createOption
|
|
36
|
+
);
|
|
37
|
+
options.forEach((op) => {
|
|
38
|
+
menu.appendChild(op);
|
|
39
|
+
});
|
|
40
|
+
root.appendChild(backDrop);
|
|
41
|
+
root.appendChild(menu);
|
|
42
|
+
document.body.appendChild(root);
|
|
43
|
+
this.hasMenu = true;
|
|
44
|
+
backDrop.onclick = (ev: MouseEvent) => {
|
|
45
|
+
console.log("BACKDROP CLICKED");
|
|
46
|
+
console.log(ev.type);
|
|
47
|
+
root.removeChild(backDrop);
|
|
48
|
+
root.removeChild(menu);
|
|
49
|
+
this.hasMenu = false;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
menu.onclick = (_: MouseEvent) => {
|
|
53
|
+
console.log("NODE CLICKED");
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { DPlayer, DPlayerData } from "./dplayer";
|
|
2
|
+
import { PageDto, PageSequenceDto } from "../dto/SchemaDto";
|
|
3
|
+
import { Rule } from "../rules/rule";
|
|
4
|
+
import { PageHistory } from "./history-que";
|
|
5
|
+
import { DTimestamp } from "../common/DTimestamp";
|
|
6
|
+
import { PageQueCommand } from "../commands/DCommand";
|
|
7
|
+
|
|
8
|
+
const page = (id: number): PageDto => {
|
|
9
|
+
return { id: "" + id, elements: [] };
|
|
10
|
+
};
|
|
11
|
+
const p1 = page(1);
|
|
12
|
+
const p2 = page(2);
|
|
13
|
+
const p3 = page(3);
|
|
14
|
+
const p4 = page(4);
|
|
15
|
+
const p5 = page(5);
|
|
16
|
+
const p6 = page(6);
|
|
17
|
+
const all = [p1, p2, p3, p4, p5, p6];
|
|
18
|
+
const Seq1 = {
|
|
19
|
+
p10: page(10),
|
|
20
|
+
p11: page(11),
|
|
21
|
+
p12: page(12),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const seq1: PageSequenceDto = {
|
|
25
|
+
id: "s1",
|
|
26
|
+
pages: [Seq1.p10, Seq1.p11, Seq1.p12],
|
|
27
|
+
rules: [],
|
|
28
|
+
};
|
|
29
|
+
const data = (
|
|
30
|
+
pages: PageDto[],
|
|
31
|
+
pageSequences: PageSequenceDto[] = [],
|
|
32
|
+
rules: Rule<PageQueCommand, never>[] = []
|
|
33
|
+
): DPlayerData => ({ pages, pageSequences, rules });
|
|
34
|
+
// const seq = (pages: PageDto[], rules: Rule[]) => {};
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
// data = { pages: [], rules: [], pageSequences: [] };
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("DPlayer", () => {
|
|
40
|
+
it("should create an instance", () => {
|
|
41
|
+
const player = new DPlayer(data([p1, p1]));
|
|
42
|
+
expect(player).toBeTruthy();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("can app pages, and get next", () => {
|
|
46
|
+
const player = new DPlayer(data([p1, p2]));
|
|
47
|
+
|
|
48
|
+
expect(player.getNextPage()).toBe(p1);
|
|
49
|
+
expect(player.getNextPage()).toBe(p2);
|
|
50
|
+
expect(player.getNextPage()).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("Can insert a sequence", () => {
|
|
54
|
+
const player = new DPlayer(data(all, [seq1]));
|
|
55
|
+
|
|
56
|
+
expect(player.getNextPage()).toBe(p1);
|
|
57
|
+
expect(player.getNextPage()).toBe(p2);
|
|
58
|
+
player.handleNavigationCommand({
|
|
59
|
+
kind: "PAGE_QUE_GO_TO_SEQUENCE_COMMAND",
|
|
60
|
+
target: "PAGE_QUE",
|
|
61
|
+
targetId: "PAGE_QUE",
|
|
62
|
+
payload: {
|
|
63
|
+
sequenceId: seq1.id,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
expect(player.getNextPage()).toBe(Seq1.p10);
|
|
67
|
+
expect(player.getNextPage()).toBe(Seq1.p11);
|
|
68
|
+
expect(player.getNextPage()).toBe(Seq1.p12);
|
|
69
|
+
expect(player.getNextPage()).toBe(p3);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("Can jump forward to a pageId", () => {
|
|
73
|
+
const player = new DPlayer(data(all));
|
|
74
|
+
|
|
75
|
+
expect(player.getNextPage()).toBe(p1);
|
|
76
|
+
player.handleNavigationCommand({
|
|
77
|
+
kind: "PAGE_QUE_GO_TO_PAGE_COMMAND",
|
|
78
|
+
target: "PAGE_QUE",
|
|
79
|
+
targetId: "PAGE_QUE",
|
|
80
|
+
payload: { pageId: p4.id },
|
|
81
|
+
});
|
|
82
|
+
expect(player.getNextPage()).toBe(p4);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("Save history", () => {
|
|
86
|
+
const player = new DPlayer(data(all));
|
|
87
|
+
const curr = player.getNextPage() as PageDto;
|
|
88
|
+
|
|
89
|
+
const history: PageHistory = {
|
|
90
|
+
pageId: curr.id,
|
|
91
|
+
answeredQuestions: [
|
|
92
|
+
{
|
|
93
|
+
timestamp: DTimestamp.now(),
|
|
94
|
+
fact: {
|
|
95
|
+
referenceId: "as",
|
|
96
|
+
referenceLabel: "as-label",
|
|
97
|
+
kind: "numeric-fact",
|
|
98
|
+
label: "litt",
|
|
99
|
+
value: 2,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
player.saveHistory(history);
|
|
105
|
+
expect(player.getNextPage()).toBe(p2);
|
|
106
|
+
expect(player.getResults().length).toBe(1);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { AnsweredQuestion, HistoryQue, PageHistory } from "./history-que";
|
|
2
|
+
import { RuleEngine } from "../rules/rule-engine";
|
|
3
|
+
import { NextQue } from "./next-que";
|
|
4
|
+
import { PageDto, SchemaDto } from "../dto/SchemaDto";
|
|
5
|
+
import { NavigationCommand, PageQueCommand } from "../commands/DCommand";
|
|
6
|
+
import { DUtil } from "../utils/DUtil";
|
|
7
|
+
|
|
8
|
+
export type DPlayerData = Pick<SchemaDto, "pages" | "pageSequences" | "rules">;
|
|
9
|
+
export class DPlayer {
|
|
10
|
+
private history = new HistoryQue();
|
|
11
|
+
private ruleEngine = new RuleEngine<PageQueCommand, PageQueCommand>();
|
|
12
|
+
private nextQue = new NextQue();
|
|
13
|
+
private data: DPlayerData;
|
|
14
|
+
|
|
15
|
+
constructor(data: DPlayerData) {
|
|
16
|
+
this.data = data;
|
|
17
|
+
this.nextQue.resetQue(data.pages);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
saveHistory(pageHistory: PageHistory) {
|
|
21
|
+
this.history.addToHistory(pageHistory);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getResults(): AnsweredQuestion[] {
|
|
25
|
+
return this.history.getAnswers();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private goToPageById(pageId: string) {
|
|
29
|
+
this.nextQue.jumpToPageById(pageId);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
handleNavigationCommand(command: NavigationCommand) {
|
|
33
|
+
switch (command.kind) {
|
|
34
|
+
case "PAGE_QUE_NEXT_PAGE_COMMAND":
|
|
35
|
+
// NO NEED TO DO ANYTHING
|
|
36
|
+
break;
|
|
37
|
+
case "PAGE_QUE_GO_TO_PAGE_COMMAND":
|
|
38
|
+
this.goToPageById(command.payload.pageId);
|
|
39
|
+
break;
|
|
40
|
+
case "PAGE_QUE_GO_TO_SEQUENCE_COMMAND":
|
|
41
|
+
this.insertSequenceById(command.payload.sequenceId);
|
|
42
|
+
break;
|
|
43
|
+
|
|
44
|
+
default:
|
|
45
|
+
DUtil.neverCheck(command);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getNextPage(): PageDto | false {
|
|
50
|
+
// TODO HANDLE RULES!!
|
|
51
|
+
return this.nextQue.pop();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private insertSequenceById(id: string) {
|
|
55
|
+
const seq = this.data.pageSequences?.find((s) => s.id === id);
|
|
56
|
+
if (seq) {
|
|
57
|
+
this.nextQue.insertAsNextByForce([...seq.pages]);
|
|
58
|
+
} else {
|
|
59
|
+
// HOW TO HANDLE INVALID ID_REFS?? LOGGER??
|
|
60
|
+
// LOG INVALID COMMAND.
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Total number of pages.
|
|
66
|
+
*/
|
|
67
|
+
get pageCount(): number {
|
|
68
|
+
return this.nextQue.pageCount;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { AnsweredQuestion, HistoryQue, PageHistory } from "./history-que";
|
|
2
|
+
import { DTimestamp } from "../common/DTimestamp";
|
|
3
|
+
import { PageDto } from "../dto/SchemaDto";
|
|
4
|
+
|
|
5
|
+
const p = (id: number): PageDto => {
|
|
6
|
+
return { id: "" + id, elements: [] };
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const answer = (id: number, value: number): AnsweredQuestion => ({
|
|
10
|
+
timestamp: DTimestamp.now(),
|
|
11
|
+
fact: {
|
|
12
|
+
referenceId: "" + id,
|
|
13
|
+
referenceLabel: "label-for-" + id,
|
|
14
|
+
value,
|
|
15
|
+
label: "value-label " + value,
|
|
16
|
+
kind: "numeric-fact",
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
const h = (page: PageDto, answeredQuestions: AnsweredQuestion[]): PageHistory => ({
|
|
20
|
+
pageId: page.id,
|
|
21
|
+
answeredQuestions,
|
|
22
|
+
});
|
|
23
|
+
const p1 = p(1);
|
|
24
|
+
const p2 = p(2);
|
|
25
|
+
const p3 = p(3);
|
|
26
|
+
const p4 = p(4);
|
|
27
|
+
const p5 = p(5);
|
|
28
|
+
const p6 = p(6);
|
|
29
|
+
const all = [p1, p2, p3, p4, p5, p6];
|
|
30
|
+
|
|
31
|
+
let history = new HistoryQue();
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
history = new HistoryQue();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("HistoryQue", () => {
|
|
37
|
+
it("should create an instance", () => {
|
|
38
|
+
expect(history).toBeTruthy();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("Can add history, and get facts back", () => {
|
|
42
|
+
history.addToHistory(h(p1, [answer(1, 2)]));
|
|
43
|
+
expect(history.getFacts().length).toBe(1);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Fact } from "../rules/fact";
|
|
2
|
+
import { DTimestamp } from "../common/DTimestamp";
|
|
3
|
+
|
|
4
|
+
export interface AnsweredQuestion {
|
|
5
|
+
readonly timestamp: DTimestamp;
|
|
6
|
+
readonly fact: Fact;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export namespace AnsweredQuestion {
|
|
10
|
+
export const eq = (a: AnsweredQuestion, b: AnsweredQuestion): boolean => {
|
|
11
|
+
return a.fact === b.fact;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface PageHistory {
|
|
16
|
+
readonly pageId: string;
|
|
17
|
+
readonly answeredQuestions: AnsweredQuestion[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class HistoryQue {
|
|
21
|
+
private history: PageHistory[] = [];
|
|
22
|
+
|
|
23
|
+
getFacts(): Array<Fact> {
|
|
24
|
+
const answers = this.history.map((h) => h.answeredQuestions).flat(1);
|
|
25
|
+
const facts = answers.map((a) => a.fact);
|
|
26
|
+
// TODO FIND LATEST FACT (answer) if have multiple.
|
|
27
|
+
return facts;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getAnswers(): Array<AnsweredQuestion> {
|
|
31
|
+
const answers = this.history.map((h) => h.answeredQuestions).flat(1);
|
|
32
|
+
return answers;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
addToHistory(page: PageHistory) {
|
|
36
|
+
this.history.push(page);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { NextQue } from "./next-que";
|
|
2
|
+
import { PageDto } from "../dto/SchemaDto";
|
|
3
|
+
|
|
4
|
+
const tag1 = "tag1";
|
|
5
|
+
const tag2 = "tag2";
|
|
6
|
+
const tag3 = "tag3";
|
|
7
|
+
|
|
8
|
+
let que = new NextQue();
|
|
9
|
+
const createPage = () => {};
|
|
10
|
+
const p1: PageDto = { id: "1", elements: [], tags: [tag1] };
|
|
11
|
+
const p2: PageDto = { id: "2", elements: [], tags: [] };
|
|
12
|
+
const p3: PageDto = { id: "3", elements: [], tags: [tag2] };
|
|
13
|
+
const p4: PageDto = { id: "4", elements: [], tags: [] };
|
|
14
|
+
const p5: PageDto = { id: "5", elements: [], tags: [tag3, tag2] };
|
|
15
|
+
const p6: PageDto = { id: "6", elements: [], tags: [tag3] };
|
|
16
|
+
const all = [p1, p2, p3, p4, p5, p6];
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
que = new NextQue();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("NextQue testing", () => {
|
|
23
|
+
it("Can pop", () => {
|
|
24
|
+
que.resetQue(all);
|
|
25
|
+
expect(que.pop()).toBe(p1);
|
|
26
|
+
expect(que.pop()).toBe(p2);
|
|
27
|
+
expect(que.size).toBe(all.length - 2);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("Can remove by tag", () => {
|
|
31
|
+
que.resetQue(all);
|
|
32
|
+
expect(que.size).toBe(6);
|
|
33
|
+
que.removeByTag(tag3);
|
|
34
|
+
expect(que.size).toBe(4);
|
|
35
|
+
expect(que.peek()).toBe(p1);
|
|
36
|
+
que.pop();
|
|
37
|
+
expect(que.peek()).toBe(p2);
|
|
38
|
+
expect(que.size).toBe(3);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("Can remove by tags[]", () => {
|
|
42
|
+
que.resetQue(all);
|
|
43
|
+
que.removeByTag([tag3, tag2, tag1]);
|
|
44
|
+
expect(que.size).toBe(2);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("Can remove by pageIds[]", () => {
|
|
48
|
+
que.resetQue(all);
|
|
49
|
+
que.removeByPageId([p1.id, p2.id, p3.id]);
|
|
50
|
+
expect(que.pop()).toBe(p4);
|
|
51
|
+
expect(que.size).toBe(2);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("Can remove by pageId", () => {
|
|
55
|
+
que.resetQue(all);
|
|
56
|
+
que.removeByPageId(p1.id);
|
|
57
|
+
expect(que.peek()).toBe(p2);
|
|
58
|
+
que.pop();
|
|
59
|
+
expect(que.peek()).toBe(p3);
|
|
60
|
+
expect(que.size).toBe(4);
|
|
61
|
+
que.removeByPageId(p3.id);
|
|
62
|
+
expect(que.size).toBe(3);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("Can set pages in constuctor", () => {
|
|
66
|
+
que = new NextQue(all);
|
|
67
|
+
expect(que.size).toBe(all.length);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("Can set pages in constuctor", () => {
|
|
71
|
+
que = new NextQue(all);
|
|
72
|
+
expect(que.size).toBe(all.length);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("Can reset que to start over.", () => {
|
|
76
|
+
que = new NextQue(all);
|
|
77
|
+
expect(que.size).toBe(all.length);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("Can insert new pages in the middle of the test.", () => {
|
|
81
|
+
que = new NextQue(all);
|
|
82
|
+
que.pop();
|
|
83
|
+
que.pop();
|
|
84
|
+
que.pop();
|
|
85
|
+
expect(que.peek()).toBe(p4);
|
|
86
|
+
que.insertAsNextByForce([p1, p2]);
|
|
87
|
+
expect(que.peek()).toBe(p1);
|
|
88
|
+
expect(que.size).toBe(5);
|
|
89
|
+
expect(que.pop()).toBe(p1);
|
|
90
|
+
expect(que.pop()).toBe(p2);
|
|
91
|
+
expect(que.pop()).toBe(p4);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("Can jump to page by id", () => {
|
|
95
|
+
que = new NextQue(all);
|
|
96
|
+
que.pop();
|
|
97
|
+
que.jumpToPageById(p5.id);
|
|
98
|
+
// expect(que.size).toBe(5);
|
|
99
|
+
expect(que.peek()).toBe(p5);
|
|
100
|
+
que.jumpToPageById("noncence");
|
|
101
|
+
expect(que.peek()).toBe(p5);
|
|
102
|
+
// CAn not jump back
|
|
103
|
+
que.jumpToPageById(p2.id);
|
|
104
|
+
expect(que.peek()).toBe(p5);
|
|
105
|
+
que.jumpToPageById(p6.id);
|
|
106
|
+
expect(que.peek()).toBe(p6);
|
|
107
|
+
});
|
|
108
|
+
});
|