@player-ui/player 0.0.1-next.1
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/index.cjs.js +1394 -0
- package/dist/index.d.ts +434 -0
- package/dist/index.esm.js +1326 -0
- package/package.json +26 -0
- package/src/data.ts +247 -0
- package/src/index.ts +18 -0
- package/src/player.ts +497 -0
- package/src/plugins/flow-exp-plugin.ts +65 -0
- package/src/types.ts +114 -0
- package/src/utils/desc.d.ts +2 -0
- package/src/validation/binding-tracker.ts +239 -0
- package/src/validation/controller.ts +661 -0
- package/src/validation/index.ts +2 -0
- package/src/view/asset-transform.ts +147 -0
- package/src/view/controller.ts +148 -0
- package/src/view/index.ts +4 -0
- package/src/view/store.ts +94 -0
- package/src/view/types.ts +31 -0
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@player-ui/player",
|
|
3
|
+
"version": "0.0.1-next.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"registry": "https://registry.npmjs.org"
|
|
7
|
+
},
|
|
8
|
+
"peerDependencies": {},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@player-ui/binding-grammar": "0.0.1-next.1",
|
|
11
|
+
"@player-ui/logger": "0.0.1-next.1",
|
|
12
|
+
"@player-ui/partial-match-registry": "0.0.1-next.1",
|
|
13
|
+
"@player-ui/types": "0.0.1-next.1",
|
|
14
|
+
"@player-ui/utils": "0.0.1-next.1",
|
|
15
|
+
"babel-plugin-preval": "^5.0.0",
|
|
16
|
+
"dequal": "^2.0.2",
|
|
17
|
+
"p-defer": "^3.0.0",
|
|
18
|
+
"queue-microtask": "^1.2.3",
|
|
19
|
+
"tapable": "1.1.3",
|
|
20
|
+
"timm": "^1.6.2",
|
|
21
|
+
"@babel/runtime": "7.15.4"
|
|
22
|
+
},
|
|
23
|
+
"main": "dist/index.cjs.js",
|
|
24
|
+
"module": "dist/index.esm.js",
|
|
25
|
+
"typings": "dist/index.d.ts"
|
|
26
|
+
}
|
package/src/data.ts
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { SyncHook, SyncWaterfallHook, SyncBailHook } from 'tapable';
|
|
2
|
+
import type { Logger } from '@player-ui/logger';
|
|
3
|
+
import { omit, removeAt } from 'timm';
|
|
4
|
+
import { dequal } from 'dequal';
|
|
5
|
+
import type { BindingParser, BindingLike } from '@player-ui/binding';
|
|
6
|
+
import { BindingInstance } from '@player-ui/binding';
|
|
7
|
+
import type {
|
|
8
|
+
BatchSetTransaction,
|
|
9
|
+
Updates,
|
|
10
|
+
DataModelOptions,
|
|
11
|
+
DataModelWithParser,
|
|
12
|
+
DataPipeline,
|
|
13
|
+
DataModelMiddleware,
|
|
14
|
+
} from '@player-ui/data';
|
|
15
|
+
import { PipelinedDataModel, LocalModel } from '@player-ui/data';
|
|
16
|
+
import type { RawSetTransaction } from './types';
|
|
17
|
+
|
|
18
|
+
/** The orchestrator for player data */
|
|
19
|
+
export class DataController implements DataModelWithParser<DataModelOptions> {
|
|
20
|
+
public hooks = {
|
|
21
|
+
resolve: new SyncWaterfallHook(['binding']),
|
|
22
|
+
resolveDataStages: new SyncWaterfallHook<DataPipeline>(['pipeline']),
|
|
23
|
+
|
|
24
|
+
// On any set or get of an undefined value, redirect the value to be the default
|
|
25
|
+
resolveDefaultValue: new SyncBailHook<BindingInstance, any>(['binding']),
|
|
26
|
+
|
|
27
|
+
onDelete: new SyncHook<any, void>(['binding']),
|
|
28
|
+
onSet: new SyncHook<BatchSetTransaction, void>(['transaction']),
|
|
29
|
+
onGet: new SyncHook<any, any, void>(['binding', 'result']),
|
|
30
|
+
onUpdate: new SyncHook<Updates>(['updates']),
|
|
31
|
+
|
|
32
|
+
format: new SyncWaterfallHook<any, BindingInstance>(['value', 'binding']),
|
|
33
|
+
deformat: new SyncWaterfallHook<any, BindingInstance>(['value', 'binding']),
|
|
34
|
+
|
|
35
|
+
serialize: new SyncWaterfallHook<any>(['data']),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
private model?: PipelinedDataModel;
|
|
39
|
+
private trash: Set<BindingInstance>;
|
|
40
|
+
private pathResolver: BindingParser;
|
|
41
|
+
private baseMiddleware: Array<DataModelMiddleware>;
|
|
42
|
+
private logger?: Logger;
|
|
43
|
+
|
|
44
|
+
constructor(
|
|
45
|
+
model: Record<any, unknown> | undefined,
|
|
46
|
+
options: {
|
|
47
|
+
/** A means of parsing a raw binding to a Binding object */
|
|
48
|
+
pathResolver: BindingParser;
|
|
49
|
+
|
|
50
|
+
/** middleware to use. typically for validation */
|
|
51
|
+
middleware?: Array<DataModelMiddleware>;
|
|
52
|
+
|
|
53
|
+
/** A logger to use */
|
|
54
|
+
logger?: Logger;
|
|
55
|
+
}
|
|
56
|
+
) {
|
|
57
|
+
this.logger = options.logger;
|
|
58
|
+
const middleware = options.middleware || [];
|
|
59
|
+
this.baseMiddleware = [new LocalModel(model), ...middleware];
|
|
60
|
+
|
|
61
|
+
this.trash = new Set();
|
|
62
|
+
this.pathResolver = options.pathResolver;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public getModel(): PipelinedDataModel {
|
|
66
|
+
if (!this.model) {
|
|
67
|
+
const stages = this.hooks.resolveDataStages.call(this.baseMiddleware);
|
|
68
|
+
const model = new PipelinedDataModel();
|
|
69
|
+
model.setMiddleware(stages);
|
|
70
|
+
this.model = model;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return this.model;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private resolveDataValue(
|
|
77
|
+
binding: BindingInstance,
|
|
78
|
+
value: any,
|
|
79
|
+
deformat: boolean
|
|
80
|
+
) {
|
|
81
|
+
if (deformat) {
|
|
82
|
+
return this.hooks.deformat.call(value, binding);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public set(
|
|
89
|
+
transaction: RawSetTransaction,
|
|
90
|
+
options?: DataModelOptions
|
|
91
|
+
): Updates {
|
|
92
|
+
let normalizedTransaction: BatchSetTransaction = [];
|
|
93
|
+
|
|
94
|
+
if (Array.isArray(transaction)) {
|
|
95
|
+
normalizedTransaction = transaction.map(([binding, value]) => {
|
|
96
|
+
const parsed = this.pathResolver.parse(binding);
|
|
97
|
+
|
|
98
|
+
return [
|
|
99
|
+
parsed,
|
|
100
|
+
this.resolveDataValue(parsed, value, Boolean(options?.formatted)),
|
|
101
|
+
];
|
|
102
|
+
}) as BatchSetTransaction;
|
|
103
|
+
} else {
|
|
104
|
+
normalizedTransaction = Object.keys(transaction).map(
|
|
105
|
+
(binding: string) => {
|
|
106
|
+
const parsed = this.pathResolver.parse(binding);
|
|
107
|
+
const val = transaction[binding];
|
|
108
|
+
|
|
109
|
+
return [
|
|
110
|
+
parsed,
|
|
111
|
+
this.resolveDataValue(parsed, val, Boolean(options?.formatted)),
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
) as BatchSetTransaction;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Figure out what the base changes being applied are
|
|
118
|
+
const setUpdates = normalizedTransaction.reduce<Updates>(
|
|
119
|
+
(updates, [binding, newVal]) => {
|
|
120
|
+
const oldVal = this.get(binding, { includeInvalid: true });
|
|
121
|
+
|
|
122
|
+
if (!dequal(oldVal, newVal)) {
|
|
123
|
+
updates.push({
|
|
124
|
+
binding,
|
|
125
|
+
newValue: newVal,
|
|
126
|
+
oldValue: oldVal,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this.logger?.debug(
|
|
131
|
+
`Setting path: ${binding.asString()} from: ${oldVal} to: ${newVal}`
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
return updates;
|
|
135
|
+
},
|
|
136
|
+
[]
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// Get the applied update
|
|
140
|
+
const result = this.getModel().set(normalizedTransaction, options);
|
|
141
|
+
|
|
142
|
+
// Add any extra bindings that were effected
|
|
143
|
+
const setUpdateBindings = new Set(setUpdates.map((su) => su.binding));
|
|
144
|
+
result.forEach((tr) => {
|
|
145
|
+
if (
|
|
146
|
+
!setUpdateBindings.has(tr.binding) &&
|
|
147
|
+
(tr.force === true || !dequal(tr.oldValue, tr.newValue))
|
|
148
|
+
) {
|
|
149
|
+
this.logger?.debug(
|
|
150
|
+
`Path: ${tr.binding.asString()} was changed from: ${
|
|
151
|
+
tr.oldValue
|
|
152
|
+
} to: ${tr.newValue}`
|
|
153
|
+
);
|
|
154
|
+
setUpdates.push(tr);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
this.hooks.onSet.call(normalizedTransaction);
|
|
159
|
+
|
|
160
|
+
if (setUpdates.length > 0) {
|
|
161
|
+
this.hooks.onUpdate.call(setUpdates);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private resolve(binding: BindingLike): BindingInstance {
|
|
168
|
+
return Array.isArray(binding) || typeof binding === 'string'
|
|
169
|
+
? this.pathResolver.parse(binding)
|
|
170
|
+
: binding;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
public get(binding: BindingLike, options?: DataModelOptions) {
|
|
174
|
+
const resolved =
|
|
175
|
+
binding instanceof BindingInstance ? binding : this.resolve(binding);
|
|
176
|
+
let result = this.getModel().get(resolved, options);
|
|
177
|
+
|
|
178
|
+
if (result === undefined && !options?.ignoreDefaultValue) {
|
|
179
|
+
const defaultVal = this.hooks.resolveDefaultValue.call(resolved);
|
|
180
|
+
|
|
181
|
+
if (defaultVal !== result) {
|
|
182
|
+
result = defaultVal;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (options?.formatted) {
|
|
187
|
+
result = this.hooks.format.call(result, resolved);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
this.hooks.onGet.call(binding, result);
|
|
191
|
+
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
public delete(binding: BindingLike) {
|
|
196
|
+
if (binding === undefined || binding === null) {
|
|
197
|
+
throw new Error(`Invalid arguments: delete expects a data path (string)`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const resolved = this.resolve(binding);
|
|
201
|
+
this.hooks.onDelete.call(resolved);
|
|
202
|
+
this.deleteData(resolved);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public getTrash(): Set<BindingInstance> {
|
|
206
|
+
return this.trash;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private addToTrash(binding: BindingInstance) {
|
|
210
|
+
this.trash.add(binding);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private deleteData(binding: BindingInstance) {
|
|
214
|
+
const parentBinding = binding.parent();
|
|
215
|
+
const parentPath = parentBinding.asString();
|
|
216
|
+
const property = binding.key();
|
|
217
|
+
|
|
218
|
+
const existedBeforeDelete = Object.prototype.hasOwnProperty.call(
|
|
219
|
+
this.get(parentBinding),
|
|
220
|
+
property
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
if (property !== undefined) {
|
|
224
|
+
const parent = parentBinding ? this.get(parentBinding) : undefined;
|
|
225
|
+
|
|
226
|
+
// If we're deleting an item in an array, we just splice it out
|
|
227
|
+
// Don't add it to the trash
|
|
228
|
+
if (parentPath && Array.isArray(parent)) {
|
|
229
|
+
if (parent.length > property) {
|
|
230
|
+
this.set([[parentBinding, removeAt(parent, property as number)]]);
|
|
231
|
+
}
|
|
232
|
+
} else if (parentPath && parent[property]) {
|
|
233
|
+
this.set([[parentBinding, omit(parent, property as string)]]);
|
|
234
|
+
} else if (!parentPath) {
|
|
235
|
+
this.getModel().reset(omit(this.get(''), property as string));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (existedBeforeDelete && !this.get(binding)) {
|
|
240
|
+
this.addToTrash(binding);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
public serialize(): object {
|
|
245
|
+
return this.hooks.serialize.call(this.get(''));
|
|
246
|
+
}
|
|
247
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Add the types export first so it's naming takes precedence
|
|
2
|
+
export * from '@player-ui/types';
|
|
3
|
+
export * from '@player-ui/binding';
|
|
4
|
+
export * from '@player-ui/data';
|
|
5
|
+
export * from '@player-ui/expressions';
|
|
6
|
+
export * from '@player-ui/flow';
|
|
7
|
+
export * from '@player-ui/logger';
|
|
8
|
+
export * from '@player-ui/schema';
|
|
9
|
+
export * from '@player-ui/string-resolver';
|
|
10
|
+
export * from '@player-ui/validator';
|
|
11
|
+
export * from '@player-ui/view';
|
|
12
|
+
|
|
13
|
+
export * from './player';
|
|
14
|
+
export * from './validation';
|
|
15
|
+
export * from './view';
|
|
16
|
+
export * from './data';
|
|
17
|
+
export * from './types';
|
|
18
|
+
export * from './plugins/flow-exp-plugin';
|