@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,140 @@
|
|
|
1
|
+
import { Rule } from "../rule";
|
|
2
|
+
import { Fact } from "../fact";
|
|
3
|
+
import { Condition } from "../condition";
|
|
4
|
+
|
|
5
|
+
const xIs = (value: number): Fact.Numeric => ({
|
|
6
|
+
referenceId: "x",
|
|
7
|
+
referenceLabel: "x-label",
|
|
8
|
+
label: "label for x",
|
|
9
|
+
value,
|
|
10
|
+
kind: "numeric-fact",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const yIs = (value: number): Fact.Numeric => ({
|
|
14
|
+
kind: "numeric-fact",
|
|
15
|
+
referenceId: "y",
|
|
16
|
+
referenceLabel: "y-label",
|
|
17
|
+
label: "value-label-y",
|
|
18
|
+
// valueLabel: 'value-label-y',
|
|
19
|
+
value,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const conditionFactory =
|
|
23
|
+
(factId: string) =>
|
|
24
|
+
(op: Condition.NumericOperator) =>
|
|
25
|
+
(value: number): Condition.Numeric => ({
|
|
26
|
+
kind: "numeric-condition",
|
|
27
|
+
referenceId: factId,
|
|
28
|
+
operator: op,
|
|
29
|
+
value,
|
|
30
|
+
referenceLabel: "fact-label",
|
|
31
|
+
valueLabel: "value-label",
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Conditon helpers
|
|
35
|
+
const xConditionFactory = conditionFactory("x");
|
|
36
|
+
const xEq = xConditionFactory("eq");
|
|
37
|
+
const xLessThan = xConditionFactory("less-then");
|
|
38
|
+
const xGreaterThan = xConditionFactory("greater-then");
|
|
39
|
+
|
|
40
|
+
const yConditionFactory = conditionFactory("y");
|
|
41
|
+
const yEq = yConditionFactory("eq");
|
|
42
|
+
const yLessThan = yConditionFactory("less-then");
|
|
43
|
+
const yGreaterThan = yConditionFactory("greater-then");
|
|
44
|
+
|
|
45
|
+
const con = {
|
|
46
|
+
xEq,
|
|
47
|
+
xLessThan,
|
|
48
|
+
xGreaterThan,
|
|
49
|
+
yEq,
|
|
50
|
+
yLessThan,
|
|
51
|
+
yGreaterThan,
|
|
52
|
+
} as const;
|
|
53
|
+
|
|
54
|
+
const complex = (all: ReadonlyArray<Condition.Simple>, some: ReadonlyArray<Condition.Simple>): Condition.Complex => ({
|
|
55
|
+
name: "test-name",
|
|
56
|
+
kind: "complex-condition",
|
|
57
|
+
all,
|
|
58
|
+
some,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const createRule = (all: ReadonlyArray<Condition>, some: ReadonlyArray<Condition>): Rule<any, any> => {
|
|
62
|
+
const rule: Rule<any, any> = {
|
|
63
|
+
id: "xyz",
|
|
64
|
+
description: "",
|
|
65
|
+
some,
|
|
66
|
+
all,
|
|
67
|
+
onSuccess: [],
|
|
68
|
+
onFailure: [],
|
|
69
|
+
};
|
|
70
|
+
return rule;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
describe("Rule-evaluation works", () => {
|
|
74
|
+
it("A empty rule is not valid, and will always return false", () => {
|
|
75
|
+
expect(Rule.solve(createRule([], []), [])).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("One all condition is true => true", () => {
|
|
79
|
+
const all: ReadonlyArray<Condition> = [con.xEq(0)];
|
|
80
|
+
const some: ReadonlyArray<Condition> = [];
|
|
81
|
+
const facts: ReadonlyArray<Fact> = [xIs(0)];
|
|
82
|
+
const rule = createRule(all, some);
|
|
83
|
+
expect(Rule.solve(rule, facts)).toEqual(true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("one some condition is true => true", () => {
|
|
87
|
+
const all: ReadonlyArray<Condition> = [];
|
|
88
|
+
const some: ReadonlyArray<Condition> = [con.xEq(0)];
|
|
89
|
+
const facts: ReadonlyArray<Fact> = [xIs(0)];
|
|
90
|
+
const rule = createRule(all, some);
|
|
91
|
+
expect(Rule.solve(rule, facts)).toEqual(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("one some condition is true, but all condition is false => false", () => {
|
|
95
|
+
const all: ReadonlyArray<Condition> = [con.xEq(6)];
|
|
96
|
+
const some: ReadonlyArray<Condition> = [con.xEq(0)];
|
|
97
|
+
const facts: ReadonlyArray<Fact> = [xIs(0)];
|
|
98
|
+
const rule = createRule(all, some);
|
|
99
|
+
expect(Rule.solve(rule, facts)).toEqual(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("some is empty, and all is not all true => false", () => {
|
|
103
|
+
const all: ReadonlyArray<Condition> = [con.xEq(6), con.xEq(0)];
|
|
104
|
+
const some: ReadonlyArray<Condition> = [];
|
|
105
|
+
const facts: ReadonlyArray<Fact> = [xIs(0)];
|
|
106
|
+
const rule = createRule(all, some);
|
|
107
|
+
expect(Rule.solve(rule, facts)).toEqual(false);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("some is false, and all is not all true => false", () => {
|
|
111
|
+
const all: ReadonlyArray<Condition> = [con.xEq(6), con.xEq(0)];
|
|
112
|
+
const some: ReadonlyArray<Condition> = [con.xLessThan(-2)];
|
|
113
|
+
const facts: ReadonlyArray<Fact> = [xIs(0)];
|
|
114
|
+
const rule = createRule(all, some);
|
|
115
|
+
expect(Rule.solve(rule, facts)).toEqual(false);
|
|
116
|
+
});
|
|
117
|
+
it("If one all-condition is true -> true", () => {
|
|
118
|
+
const all: ReadonlyArray<Condition> = [con.xEq(0)];
|
|
119
|
+
const some: ReadonlyArray<Condition> = [];
|
|
120
|
+
const facts: ReadonlyArray<Fact> = [xIs(0)];
|
|
121
|
+
const rule = createRule(all, some);
|
|
122
|
+
expect(Rule.solve(rule, facts)).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("All all-condition is true, and some is empty -> true", () => {
|
|
126
|
+
const all: ReadonlyArray<Condition> = [con.xEq(0), con.xLessThan(8), con.xGreaterThan(-1)];
|
|
127
|
+
const some: ReadonlyArray<Condition> = [];
|
|
128
|
+
const facts: ReadonlyArray<Fact> = [xIs(0)];
|
|
129
|
+
const rule = createRule(all, some);
|
|
130
|
+
expect(Rule.solve(rule, facts)).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("All all-condition is true, and none in some is true -> false", () => {
|
|
134
|
+
const all: ReadonlyArray<Condition> = [con.xEq(0), con.xLessThan(8), con.xGreaterThan(-1), con.yEq(9)];
|
|
135
|
+
const some: ReadonlyArray<Condition> = [con.yLessThan(9)];
|
|
136
|
+
const facts: ReadonlyArray<Fact> = [xIs(0), yIs(9)];
|
|
137
|
+
const rule = createRule(all, some);
|
|
138
|
+
expect(Rule.solve(rule, facts)).toBe(false);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Condition } from "../condition";
|
|
2
|
+
import { Fact } from "../fact";
|
|
3
|
+
|
|
4
|
+
const helloWorldString = "hello-world";
|
|
5
|
+
const eqHelloWorld: Condition.String = {
|
|
6
|
+
operator: "eq",
|
|
7
|
+
referenceId: "not-important",
|
|
8
|
+
value: helloWorldString,
|
|
9
|
+
kind: "string-condition",
|
|
10
|
+
referenceLabel: "fact-label",
|
|
11
|
+
valueLabel: "value-label",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const strFact = (str: string): [Fact.String] => [
|
|
15
|
+
{
|
|
16
|
+
kind: "string-fact",
|
|
17
|
+
value: str,
|
|
18
|
+
label: "asdf",
|
|
19
|
+
referenceId: "not-important",
|
|
20
|
+
referenceLabel: "not-important-label",
|
|
21
|
+
},
|
|
22
|
+
// Fact.string('not-important', 'var-label-' + str, str, 'value-label-' + str),
|
|
23
|
+
];
|
|
24
|
+
const notEqHelloWorld: Condition.String = {
|
|
25
|
+
...eqHelloWorld,
|
|
26
|
+
operator: "not-eq",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
describe("string-conditions test-suite", () => {
|
|
30
|
+
// beforeEach(() => {});
|
|
31
|
+
|
|
32
|
+
it("string eq works", () => {
|
|
33
|
+
expect(Condition.evaluate(eqHelloWorld, strFact(helloWorldString))).toBe(true);
|
|
34
|
+
expect(Condition.evaluate(eqHelloWorld, strFact("not-hello-world"))).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("string not-eq works", () => {
|
|
38
|
+
expect(Condition.evaluate(eqHelloWorld, strFact("not-hello-world"))).toBe(false);
|
|
39
|
+
expect(Condition.evaluate(eqHelloWorld, strFact(helloWorldString))).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { Fact } from "./fact";
|
|
2
|
+
import { DUtil } from "../utils/DUtil";
|
|
3
|
+
export type Condition = Condition.String | Condition.Numeric | Condition.Complex;
|
|
4
|
+
|
|
5
|
+
export namespace Condition {
|
|
6
|
+
export type StringOperator = "eq" | "not-eq" | "longer-then" | "shorter-then";
|
|
7
|
+
|
|
8
|
+
export type NumericOperator =
|
|
9
|
+
| "eq"
|
|
10
|
+
| "not-eq"
|
|
11
|
+
| "greater-then"
|
|
12
|
+
| "less-then"
|
|
13
|
+
| "greater-then-inclusive"
|
|
14
|
+
| "less-then-inclusive";
|
|
15
|
+
|
|
16
|
+
export interface Numeric {
|
|
17
|
+
readonly referenceId: string;
|
|
18
|
+
readonly referenceLabel: string;
|
|
19
|
+
readonly valueLabel: string;
|
|
20
|
+
readonly kind: "numeric-condition";
|
|
21
|
+
readonly operator: NumericOperator;
|
|
22
|
+
readonly value: number;
|
|
23
|
+
}
|
|
24
|
+
export interface String {
|
|
25
|
+
readonly referenceId: string;
|
|
26
|
+
readonly referenceLabel: string;
|
|
27
|
+
readonly valueLabel: string;
|
|
28
|
+
readonly kind: "string-condition";
|
|
29
|
+
readonly operator: StringOperator;
|
|
30
|
+
readonly value: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface Complex {
|
|
34
|
+
readonly kind: "complex-condition";
|
|
35
|
+
readonly name: string;
|
|
36
|
+
readonly all: ReadonlyArray<Condition.Simple>;
|
|
37
|
+
readonly some: ReadonlyArray<Condition.Simple>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type Simple = Condition.String | Condition.Numeric;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* An empty condition will evaluate to false,
|
|
44
|
+
* @param condition: Condition.Any
|
|
45
|
+
* @param facts
|
|
46
|
+
*/
|
|
47
|
+
export const evaluate = (condition: Condition, facts: ReadonlyArray<Fact>) => {
|
|
48
|
+
let result: boolean = false;
|
|
49
|
+
switch (condition.kind) {
|
|
50
|
+
case "string-condition":
|
|
51
|
+
result = evaluateSimple(condition, facts);
|
|
52
|
+
break;
|
|
53
|
+
case "numeric-condition":
|
|
54
|
+
result = evaluateSimple(condition, facts);
|
|
55
|
+
break;
|
|
56
|
+
case "complex-condition":
|
|
57
|
+
result = evaluateComplex(condition, facts);
|
|
58
|
+
break;
|
|
59
|
+
default:
|
|
60
|
+
const check: never = condition;
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const evaluateComplex = (condition: Condition.Complex, facts: ReadonlyArray<Fact>): boolean => {
|
|
66
|
+
if (condition.some.length === 0 && condition.all.length === 0) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
const allSolved = condition.all.map((condition) => {
|
|
70
|
+
return evaluateSimple(condition, facts);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const someEvaluated = condition.some.map((condition) => {
|
|
74
|
+
return evaluateSimple(condition, facts);
|
|
75
|
+
});
|
|
76
|
+
const allResult = allSolved.every(DUtil.isTrue);
|
|
77
|
+
const someResult = someEvaluated.length === 0 || someEvaluated.some(DUtil.isTrue);
|
|
78
|
+
return someResult && allResult;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const evaluateSimple = (condition: Condition.Simple, facts: ReadonlyArray<Fact>): boolean => {
|
|
82
|
+
const fact = facts.find((f) => f.referenceId === condition.referenceId);
|
|
83
|
+
if (!fact) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
let res = false;
|
|
87
|
+
switch (condition.kind) {
|
|
88
|
+
case "numeric-condition":
|
|
89
|
+
if (fact.kind === "numeric-fact") {
|
|
90
|
+
res = evaluateNumeric(condition, fact.value);
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
case "string-condition":
|
|
94
|
+
if (fact.kind === "string-fact") {
|
|
95
|
+
res = evaluateString(condition, fact.value);
|
|
96
|
+
}
|
|
97
|
+
break;
|
|
98
|
+
default:
|
|
99
|
+
const check: never = condition;
|
|
100
|
+
}
|
|
101
|
+
return res;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const isEmpty = (complex: Complex) => {
|
|
105
|
+
return complex.all.length === 0 && complex.some.length === 0;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const evaluateString = (condition: Readonly<Condition.String>, value: string): boolean => {
|
|
109
|
+
const operator = condition.operator;
|
|
110
|
+
let result = false;
|
|
111
|
+
switch (operator) {
|
|
112
|
+
case "eq":
|
|
113
|
+
result = condition.value === value;
|
|
114
|
+
break;
|
|
115
|
+
case "not-eq":
|
|
116
|
+
result = condition.value !== value;
|
|
117
|
+
break;
|
|
118
|
+
case "shorter-then":
|
|
119
|
+
result = condition.value !== value;
|
|
120
|
+
break;
|
|
121
|
+
case "longer-then":
|
|
122
|
+
result = condition.value !== value;
|
|
123
|
+
break;
|
|
124
|
+
default:
|
|
125
|
+
const check: never = operator;
|
|
126
|
+
}
|
|
127
|
+
return result;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const evaluateNumeric = (condition: Numeric, value: number): boolean => {
|
|
131
|
+
const op = condition.operator;
|
|
132
|
+
const conditionValue = condition.value;
|
|
133
|
+
let result = false;
|
|
134
|
+
switch (op) {
|
|
135
|
+
case "eq":
|
|
136
|
+
result = value === conditionValue;
|
|
137
|
+
break;
|
|
138
|
+
case "not-eq":
|
|
139
|
+
result = value !== conditionValue;
|
|
140
|
+
break;
|
|
141
|
+
case "greater-then":
|
|
142
|
+
result = value > conditionValue;
|
|
143
|
+
break;
|
|
144
|
+
case "greater-then-inclusive":
|
|
145
|
+
result = value >= conditionValue;
|
|
146
|
+
break;
|
|
147
|
+
case "less-then":
|
|
148
|
+
result = value < conditionValue;
|
|
149
|
+
break;
|
|
150
|
+
case "less-then-inclusive":
|
|
151
|
+
result = value <= conditionValue;
|
|
152
|
+
break;
|
|
153
|
+
default:
|
|
154
|
+
const check: never = op;
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const _getAllSimple = (condition: Condition): ReadonlyArray<Condition.Simple> => {
|
|
160
|
+
const simple: Array<Condition.Simple> = [];
|
|
161
|
+
switch (condition.kind) {
|
|
162
|
+
case "complex-condition":
|
|
163
|
+
simple.push(...condition.all);
|
|
164
|
+
simple.push(...condition.some);
|
|
165
|
+
break;
|
|
166
|
+
case "numeric-condition":
|
|
167
|
+
simple.push(condition);
|
|
168
|
+
break;
|
|
169
|
+
case "string-condition":
|
|
170
|
+
simple.push(condition);
|
|
171
|
+
break;
|
|
172
|
+
default:
|
|
173
|
+
DUtil.neverCheck(condition);
|
|
174
|
+
}
|
|
175
|
+
return simple;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export const getAllSimpleConditions = (
|
|
179
|
+
condition: Condition | Array<Condition>
|
|
180
|
+
): ReadonlyArray<Condition.Simple> => {
|
|
181
|
+
const simple: Array<Condition.Simple> = [];
|
|
182
|
+
if (Array.isArray(condition)) {
|
|
183
|
+
condition.forEach((c) => {
|
|
184
|
+
simple.push(..._getAllSimple(c));
|
|
185
|
+
});
|
|
186
|
+
} else {
|
|
187
|
+
simple.push(..._getAllSimple(condition));
|
|
188
|
+
}
|
|
189
|
+
return simple;
|
|
190
|
+
};
|
|
191
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type Fact = Fact.Numeric | Fact.String;
|
|
2
|
+
export namespace Fact {
|
|
3
|
+
export interface Numeric {
|
|
4
|
+
readonly kind: "numeric-fact";
|
|
5
|
+
readonly value: number;
|
|
6
|
+
readonly label: string;
|
|
7
|
+
readonly referenceId: string;
|
|
8
|
+
readonly referenceLabel: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface String {
|
|
12
|
+
readonly kind: "string-fact";
|
|
13
|
+
readonly label: string;
|
|
14
|
+
readonly value: string;
|
|
15
|
+
readonly referenceId: string;
|
|
16
|
+
readonly referenceLabel: string;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Fact } from "./fact";
|
|
2
|
+
import { Rule } from "./rule";
|
|
3
|
+
import { PageQueCommand } from "../commands/DCommand";
|
|
4
|
+
|
|
5
|
+
export interface SolveResult<S, F> {
|
|
6
|
+
matching: ReadonlyArray<Match<S, F>>;
|
|
7
|
+
errors: ReadonlyArray<RuleEngineError>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface Match<S, F> {
|
|
11
|
+
readonly matchingRuleId: string;
|
|
12
|
+
readonly ruleDescription: string;
|
|
13
|
+
readonly actionList: ReadonlyArray<S> | ReadonlyArray<F>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface RuleEngineError {
|
|
17
|
+
readonly kind?: string;
|
|
18
|
+
readonly message: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class RuleEngine<S, F> {
|
|
22
|
+
constructor() {}
|
|
23
|
+
|
|
24
|
+
solveAll(rules: Rule<S, F>[], facts: Fact[]): SolveResult<S, F> {
|
|
25
|
+
const errors: RuleEngineError[] = [];
|
|
26
|
+
const matching: Match<S, F>[] = [];
|
|
27
|
+
rules.forEach((rule) => {
|
|
28
|
+
if (Rule.isEmpty(rule)) {
|
|
29
|
+
errors.push({ message: "Empty rule: " + rule.id });
|
|
30
|
+
} else if (Rule.solve(rule, facts)) {
|
|
31
|
+
const match: Match<S, F> = {
|
|
32
|
+
ruleDescription: rule.description,
|
|
33
|
+
matchingRuleId: rule.id,
|
|
34
|
+
actionList: [...rule.onSuccess],
|
|
35
|
+
};
|
|
36
|
+
matching.push(match);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return { matching, errors };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
solve(rule: Rule<S, F>, facts: Fact[]): boolean {
|
|
43
|
+
// TODO Validate, and Return result
|
|
44
|
+
return Rule.solve(rule, facts);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Condition } from "./condition";
|
|
2
|
+
import { Fact } from "./fact";
|
|
3
|
+
import { DUtil } from "../utils/DUtil";
|
|
4
|
+
|
|
5
|
+
export interface Rule<OnSuccessAction, OnFailureAction> {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly description: string;
|
|
8
|
+
readonly all: ReadonlyArray<Condition>;
|
|
9
|
+
readonly some: ReadonlyArray<Condition>;
|
|
10
|
+
readonly onSuccess: ReadonlyArray<OnSuccessAction>;
|
|
11
|
+
readonly onFailure: ReadonlyArray<OnFailureAction>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export namespace Rule {
|
|
15
|
+
/**
|
|
16
|
+
* Validates that the rule is valid.
|
|
17
|
+
* @param rule
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export const isEmpty = (rule: Rule<any, any>): boolean => {
|
|
21
|
+
const emptyConditions = rule.all.length === 0 && rule.some.length === 0;
|
|
22
|
+
const emptyActions = rule.onSuccess.length === 0 && rule.onFailure.length === 0;
|
|
23
|
+
return emptyConditions || emptyActions;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const solve = (rule: Rule<any, any>, facts: ReadonlyArray<Fact>): boolean => {
|
|
27
|
+
if (rule.some.length === 0 && rule.all.length === 0) {
|
|
28
|
+
// TODO RETURN WARNING? OR LOGGING ?
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const someSolved = rule.some.map((condition) => Condition.evaluate(condition, facts));
|
|
33
|
+
|
|
34
|
+
const someResult = someSolved.length === 0 || someSolved.some(DUtil.isTrue);
|
|
35
|
+
|
|
36
|
+
const allSolved = rule.all.map((condition) => Condition.evaluate(condition, facts)).every(DUtil.isTrue);
|
|
37
|
+
|
|
38
|
+
return allSolved && someResult;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DMediaManager } from "./DMedia-manager";
|
|
2
|
+
import { EventBus } from "../events/event-bus";
|
|
3
|
+
import { ScaleService } from "../engine/scale";
|
|
4
|
+
import { DCommandBus } from "../commands/DCommandBus";
|
|
5
|
+
import { ResourceProvider } from "./resource-provider";
|
|
6
|
+
|
|
7
|
+
describe("Media-manager to work", () => {
|
|
8
|
+
test("Can be instanciated", () => {
|
|
9
|
+
const host = document.createElement("div");
|
|
10
|
+
const eventBus = new EventBus();
|
|
11
|
+
const commandBus = new DCommandBus();
|
|
12
|
+
const scale = new ScaleService({
|
|
13
|
+
baseHeight: 1300,
|
|
14
|
+
baseWidth: 1024,
|
|
15
|
+
containerHeight: 650,
|
|
16
|
+
containerWidth: 600,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const resourceProvider = new ResourceProvider({
|
|
20
|
+
videos: [],
|
|
21
|
+
audio: [],
|
|
22
|
+
});
|
|
23
|
+
const mm = new DMediaManager(host, commandBus, eventBus, resourceProvider, scale);
|
|
24
|
+
// const pageDto: PageDto = {id}
|
|
25
|
+
expect(host.children.length).toBe(1);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { PageDto } from "../dto/SchemaDto";
|
|
2
|
+
import { VideoContainer } from "../Delement/VideoContainer";
|
|
3
|
+
import { AudioContainer } from "../Delement/AudioContainer";
|
|
4
|
+
import { AutoPlayElement, DAutoPlaySequence } from "../Delement/DAuto-play";
|
|
5
|
+
import { DCommandBus } from "../commands/DCommandBus";
|
|
6
|
+
import { DCommand } from "../commands/DCommand";
|
|
7
|
+
import { DTimestamp } from "../common/DTimestamp";
|
|
8
|
+
import { EventBus } from "../events/event-bus";
|
|
9
|
+
import { ScaleService } from "../engine/scale";
|
|
10
|
+
import { ResourceProvider } from "./resource-provider";
|
|
11
|
+
import { AutoplayTask, SequenceManager } from "./sequence-manager";
|
|
12
|
+
|
|
13
|
+
export interface IMediaManager {
|
|
14
|
+
setPage(page: PageDto): void;
|
|
15
|
+
destroy(): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class DMediaManager implements IMediaManager {
|
|
19
|
+
private static INSTANCE_COUNT = 0;
|
|
20
|
+
private readonly instanceNumber: number;
|
|
21
|
+
private readonly TAG = "[ MEDIA_MANAGER ] : ";
|
|
22
|
+
private readonly videoContainer: VideoContainer;
|
|
23
|
+
private readonly audioContainer: AudioContainer;
|
|
24
|
+
private pageEnter: DTimestamp;
|
|
25
|
+
private sincePageEnter: DTimestamp.Diff;
|
|
26
|
+
private currentPage: PageDto | null = null;
|
|
27
|
+
private readonly unsubscribeCommands: () => void;
|
|
28
|
+
private readonly tickerRef: number;
|
|
29
|
+
private readonly sequenceManager: SequenceManager | false = false;
|
|
30
|
+
constructor(
|
|
31
|
+
private hostEl: HTMLDivElement,
|
|
32
|
+
private readonly commandBus: DCommandBus,
|
|
33
|
+
private readonly eventBus: EventBus,
|
|
34
|
+
private readonly resourceProvider: ResourceProvider,
|
|
35
|
+
private readonly scale: ScaleService
|
|
36
|
+
) {
|
|
37
|
+
DMediaManager.INSTANCE_COUNT = DMediaManager.INSTANCE_COUNT + 1;
|
|
38
|
+
this.instanceNumber = DMediaManager.INSTANCE_COUNT;
|
|
39
|
+
const videoEl = document.createElement("video");
|
|
40
|
+
this.hostEl.append(videoEl);
|
|
41
|
+
this.videoContainer = new VideoContainer(videoEl, eventBus, this.scale);
|
|
42
|
+
this.audioContainer = new AudioContainer(this.eventBus);
|
|
43
|
+
this.tick = this.tick.bind(this);
|
|
44
|
+
this.videoContainer.setStyle({ visibility: "hidden" });
|
|
45
|
+
const now = DTimestamp.now();
|
|
46
|
+
this.pageEnter = now;
|
|
47
|
+
this.sincePageEnter = DTimestamp.diff(now, now);
|
|
48
|
+
this.unsubscribeCommands = this.commandBus.subscribe((action) => {
|
|
49
|
+
this.commandHandler(action);
|
|
50
|
+
}, this.TAG);
|
|
51
|
+
|
|
52
|
+
this.tickerRef = window.setInterval(() => {}, 1000);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Stop all media that is playing
|
|
57
|
+
*/
|
|
58
|
+
clearAllMedia() {
|
|
59
|
+
this.videoContainer.pause();
|
|
60
|
+
this.audioContainer.pause();
|
|
61
|
+
}
|
|
62
|
+
destroy() {
|
|
63
|
+
console.log("TODO DESTROY ALL MEDIA");
|
|
64
|
+
this.unsubscribeCommands();
|
|
65
|
+
// console.log(this.tickerRef);
|
|
66
|
+
window.clearInterval(this.tickerRef);
|
|
67
|
+
this.audioContainer.destroy();
|
|
68
|
+
this.videoContainer.destroy();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
setPage(page: PageDto) {
|
|
72
|
+
// TODO STOP VIDEO/AUDIO THAT MIGHT BE PLAYING IN THE DOM??
|
|
73
|
+
this.currentPage = page;
|
|
74
|
+
this.videoContainer.pause();
|
|
75
|
+
this.pageEnter = DTimestamp.now();
|
|
76
|
+
// TODO THis might be a bug if someone
|
|
77
|
+
|
|
78
|
+
const seq = page.autoPlaySequence;
|
|
79
|
+
// this.sequence = seq ? [...seq.items] : [];
|
|
80
|
+
const { mainVideoId, audio } = page;
|
|
81
|
+
const audioElements = page.audio;
|
|
82
|
+
|
|
83
|
+
if (mainVideoId) {
|
|
84
|
+
const dto = this.resourceProvider.getVideoById(mainVideoId);
|
|
85
|
+
if (dto) {
|
|
86
|
+
this.videoContainer.setDto(dto);
|
|
87
|
+
this.videoContainer.setStyle({ ...dto.style, visibility: "visible" });
|
|
88
|
+
}
|
|
89
|
+
// this.videoContainer.playToEnd();
|
|
90
|
+
} else {
|
|
91
|
+
this.videoContainer.setStyle({ visibility: "hidden" });
|
|
92
|
+
// HIDE?
|
|
93
|
+
}
|
|
94
|
+
if (audioElements) {
|
|
95
|
+
const first = audioElements[0];
|
|
96
|
+
if (first) {
|
|
97
|
+
this.audioContainer.setAudio(first);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (seq) {
|
|
101
|
+
this.playSequence(seq);
|
|
102
|
+
}
|
|
103
|
+
// const hasVideo =
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private commandHandler(command: DCommand) {
|
|
107
|
+
if (command.kind === "VIDEO_PLAY_COMMAND") {
|
|
108
|
+
const video = command.targetId;
|
|
109
|
+
const dto = this.videoContainer.getCurrentDto();
|
|
110
|
+
|
|
111
|
+
if (dto && dto.id === command.targetId) {
|
|
112
|
+
console.log(video);
|
|
113
|
+
this.videoContainer.play(dto.url);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (command.kind === "VIDEO_PAUSE_COMMAND") {
|
|
117
|
+
this.videoContainer.pause();
|
|
118
|
+
}
|
|
119
|
+
if (command.kind === "AUDIO_PLAY_COMMAND") {
|
|
120
|
+
this.audioContainer
|
|
121
|
+
.playToEnd()
|
|
122
|
+
.then(() => {})
|
|
123
|
+
.catch()
|
|
124
|
+
.finally(() => {});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (command.kind === "AUDIO_PAUSE_COMMAND") {
|
|
128
|
+
// this.audioContainer.
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private async playSequence(seq: DAutoPlaySequence) {
|
|
133
|
+
console.log(this.TAG + "SEQUENCE STARTED -- TODO EVENT FOR THIS??");
|
|
134
|
+
console.log(this.TAG + "SEQUENCE STARTED -- TODO EVENT FOR THIS??");
|
|
135
|
+
console.log(this.TAG + "SEQUENCE STARTED -- TODO EVENT FOR THIS??");
|
|
136
|
+
console.log(this.TAG + "SEQUENCE STARTED -- TODO EVENT FOR THIS??");
|
|
137
|
+
|
|
138
|
+
const elements = seq.items;
|
|
139
|
+
const testClone = [...elements];
|
|
140
|
+
const first = testClone.pop();
|
|
141
|
+
|
|
142
|
+
if (!first) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
const now = DTimestamp.now();
|
|
146
|
+
seq.startCommands.forEach((c) => {
|
|
147
|
+
console.log(c);
|
|
148
|
+
this.commandBus.emit(c);
|
|
149
|
+
});
|
|
150
|
+
// this.commandBus.emit(DStateProps.mediaBlockedBySequence.setTrueCommand);
|
|
151
|
+
// if (seq.blockUserInput) {
|
|
152
|
+
// this.commandBus.emit(DStateProps.inputBlockingBySequence.getSetTrueCommand());
|
|
153
|
+
// }
|
|
154
|
+
for (let i = 0; i < elements.length; i++) {
|
|
155
|
+
const item = elements[i];
|
|
156
|
+
if (item.kind === "autoplay-video") {
|
|
157
|
+
const dto = this.resourceProvider.getVideoById(item.videoId);
|
|
158
|
+
if (dto) this.videoContainer.setDto(dto);
|
|
159
|
+
await this.videoContainer.playToEnd();
|
|
160
|
+
}
|
|
161
|
+
if (item.kind === "autoplay-audio") {
|
|
162
|
+
console.log(item);
|
|
163
|
+
const dto = this.resourceProvider.getAudioById(item.audioId);
|
|
164
|
+
if (dto) this.audioContainer.setAudio(dto);
|
|
165
|
+
await this.audioContainer.playToEnd();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
seq.endCommands.forEach((c) => {
|
|
169
|
+
this.commandBus.emit(c);
|
|
170
|
+
});
|
|
171
|
+
// this.actionService.emit(DStateProps.mediaBlockedBySequence.setFalseCommand);
|
|
172
|
+
// this.actionService.emit(DStateProps.inputBlockingBySequence.setFalseCommand);
|
|
173
|
+
console.log(this.TAG + "SEQUENCE ENDED");
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// setPage()
|
|
178
|
+
|
|
179
|
+
private tick() {
|
|
180
|
+
// console.log(this.video1.getStats());
|
|
181
|
+
}
|
|
182
|
+
}
|