@awayfl/awayfl-player 0.2.37 → 0.2.38

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.
Files changed (39) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +36 -36
  3. package/awayfl.config.js +85 -85
  4. package/builtins/playerglobal.json +2752 -2752
  5. package/builtins/playerglobal_new.json +4169 -4169
  6. package/bundle/awayfl-player.umd.js +14 -131
  7. package/bundle/awayfl-player.umd.js.gz +0 -0
  8. package/bundle/awayfl-player.umd.js.map +1 -1
  9. package/dist/index.d.ts +8 -8
  10. package/dist/index.js +9 -9
  11. package/dist/lib/AVM1Player.d.ts +4 -4
  12. package/dist/lib/AVM1Player.js +13 -13
  13. package/dist/lib/AVM2Player.d.ts +4 -4
  14. package/dist/lib/AVM2Player.js +14 -14
  15. package/dist/lib/AVMDebugInterface.d.ts +21 -21
  16. package/dist/lib/AVMDebugInterface.js +279 -279
  17. package/dist/lib/AVMPlayer.d.ts +6 -6
  18. package/dist/lib/AVMPlayer.js +27 -27
  19. package/dist/src/Main.d.ts +1 -1
  20. package/dist/src/Main.js +19 -19
  21. package/index.ts +9 -9
  22. package/lib/AVM1Player.ts +9 -9
  23. package/lib/AVM2Player.ts +12 -12
  24. package/lib/AVMDebugInterface.ts +345 -345
  25. package/lib/AVMPlayer.ts +29 -29
  26. package/package.json +97 -97
  27. package/rollup.config.js +30 -30
  28. package/scripts/copyVersionToIndex.js +33 -33
  29. package/scripts/initAwayDev_mac.sh +177 -177
  30. package/scripts/initAwayDev_mac_pnpm.sh +177 -177
  31. package/scripts/initAwayDev_win.bat +198 -198
  32. package/scripts/unlinkAwayDev_mac.sh +140 -140
  33. package/scripts/unlinkAwayDev_mac_pnpm.sh +140 -140
  34. package/scripts/unlinkAwayDev_win.bat +140 -140
  35. package/scripts/updateAwayDev_mac.sh +86 -90
  36. package/scripts/updateAwayDev_win.bat +69 -69
  37. package/scripts/updateAway_any.bat +73 -73
  38. package/tsconfig.json +12 -12
  39. package/webpack.config.js +496 -496
@@ -1,346 +1,346 @@
1
-
2
- import { AVMStage, registerDebugMethod } from "@awayfl/swf-loader";
3
- import { DisplayObject } from '@awayjs/scene';
4
- import { PickGroup } from "@awayjs/view";
5
- import { SharedObjectDebug as SOavm2 } from "@awayfl/playerglobal";
6
- import { SharedObjectDebug as SOavm1 } from "@awayfl/avm1";
7
-
8
- function fullSerializer(obj: any) {
9
- const clone = Object.assign({}, obj);
10
-
11
- Object.keys(clone).forEach((key)=>{
12
- if(typeof clone[key] === 'object') {
13
- clone[key] = fullSerializer(clone[key]);
14
- } else if(typeof clone[key] === 'function') {
15
- // replace func with it string representation
16
- clone[key] = clone[key].toString();
17
- }
18
- });
19
-
20
- return clone;
21
- }
22
-
23
- const OBJECT_FIELDS = ['id','visible', 'index', 'assetType:type', 'name'];
24
-
25
- export class AVMDebug {
26
- private _rafState: 'stop' | 'next' | 'play' = 'play';
27
- private _defaultRaf: any = self.requestAnimationFrame;
28
- private _requestedCallbacks: FrameRequestCallback [] = [];
29
-
30
- constructor(public player: AVMStage) {
31
-
32
- registerDebugMethod(this._dirObjectByIds.bind(this), {
33
- name: "dirObjectByIds",
34
- description:"Export selected object to console",
35
- declaration: [{name: 'ids', type: "object"}]
36
- });
37
-
38
- registerDebugMethod(this._applyPropsByIds.bind(this), {
39
- name: "applyPropsByIds",
40
- description:"Apply propertyes by node ids",
41
- declaration: [{name: 'ids', type: "object"}, {name:'object', type:'object'}]
42
- });
43
-
44
- registerDebugMethod(this._removeObjectByIds.bind(this), {
45
- name: "removeObjectByIds",
46
- description:"Remove object from sceen tree",
47
- declaration: [{name: 'ids', type: "object"}]
48
- });
49
-
50
- registerDebugMethod(this._getInfo.bind(this), {
51
- name: "getInfo",
52
- description:"Get file info for app",
53
- declaration: [{name:"return", type:"object"}]
54
- });
55
-
56
- registerDebugMethod(this._getSceneTree.bind(this), {
57
- name: "getNodeTree",
58
- description:"Get sceen tree of app",
59
- declaration: [
60
- {name:"return", type:"object"},
61
- {name:"flat", type:"boolean"},
62
- {name:"from", type:"number"},
63
- {name:"rect", type:"object"}
64
- ]
65
- });
66
-
67
- registerDebugMethod(this._getStageCanvas.bind(this), {
68
- name: "getStageCanvas",
69
- description:"Get canvas attahed to stage",
70
- declaration: []
71
- });
72
-
73
- registerDebugMethod(this._setRAFState.bind(this), {
74
- name: "setRAFState",
75
- description: "Changed RAF state",
76
- declaration: [
77
- {name:'return', type: 'string'},
78
- {name:'state', type: 'string'}
79
- ]
80
- });
81
-
82
- registerDebugMethod(this._getRAFState.bind(this), {
83
- name: "getRAFState",
84
- description: "Changed RAF state",
85
- declaration: [
86
- {name:'return', type: 'string'},
87
- {name:'state', type: 'string'}
88
- ]
89
- });
90
-
91
- this._mokedRaf = this._mokedRaf.bind(this);
92
-
93
- //@ts-ignore
94
- window._AWAY_DEBUG_PLAYER_ = this;
95
- }
96
-
97
- public onAvmInit(version: number) {
98
-
99
- //@ts-ignore
100
- window._AWAY_DEBUG_STORAGE = version === 1 ? SOavm1 : SOavm2;
101
- }
102
-
103
- private _mokedRaf(callback: FrameRequestCallback) {
104
- if (this._requestedCallbacks.indexOf(callback) !== -1) return;
105
-
106
- this._requestedCallbacks.push(callback);
107
- return 0;
108
- }
109
-
110
- private _setRAFState(state: 'stop' | 'next' | 'play'): 'stop' | 'next' | 'play' {
111
- if (!state) return this._rafState;
112
- if (state === this._rafState)
113
- return;
114
-
115
- if(state === 'next' && this._rafState === 'stop') {
116
- const time = performance.now();
117
- const callbacks = this._requestedCallbacks.slice();
118
-
119
- this._rafState = 'next';
120
- this._requestedCallbacks.length = 0;
121
-
122
- callbacks.forEach((e) => e && e(time));
123
-
124
- return this._rafState = 'stop';
125
- }
126
-
127
- if (state === 'stop') {
128
- this._requestedCallbacks.length = 0;
129
- self.requestAnimationFrame = this._mokedRaf;
130
-
131
- return this._rafState = 'stop';
132
- }
133
-
134
- if (state === 'play') {
135
- const time = performance.now();
136
- const callbacks = this._requestedCallbacks.slice();
137
-
138
- this._rafState = 'play';
139
- this._requestedCallbacks.length = 0;
140
-
141
- self.requestAnimationFrame = this._defaultRaf;
142
-
143
- callbacks.forEach((e) => e && e(time));
144
- }
145
-
146
- return this._rafState;
147
- }
148
-
149
- private _getRAFState(): 'stop' | 'next' | 'play' {
150
- return this._rafState;
151
- }
152
-
153
- private _selectNode(ids: number[]): DisplayObject {
154
- let node = this.player.root as any;
155
-
156
- for(let i of ids) {
157
- node = node._children.find((e) => e.id === i);
158
- if(!node) {
159
- break;
160
- }
161
- }
162
-
163
- if(!node){
164
- throw new Error("Node not found");
165
- }
166
-
167
- return node;
168
- }
169
-
170
- private _getStageCanvas() {
171
- return this.player.view.stage.container;
172
- }
173
-
174
- private _dirObjectByIds(ids: number[]) {
175
- const node = this._selectNode(ids);
176
- //@ts-ignore
177
- const exposeID = window._lastTempNode = window._lastTempNode || 1;
178
- //@ts-ignore
179
- window._lastTempNode++;
180
-
181
- window['tempNode' + exposeID] = node
182
- console.log('tempNode' + exposeID, '=');
183
- console.dir(node);
184
- }
185
-
186
- private _getNodeBounds(node: DisplayObject) {
187
- const view = this.player.view;
188
-
189
- let box;
190
- const pool = AVMStage.instance && (<any>AVMStage.instance()).pool
191
- if (pool) {
192
- //@ts-ignore
193
- const partition = pool.getNode(node).partition;
194
- const picker = PickGroup.getInstance().getBoundsPicker(partition);
195
-
196
- //@ts-ignore
197
- box = picker.getBoxBounds(pool.getNode(this.player.root), true, true);
198
-
199
- } else {
200
- //@ts-ignore
201
- box = PickGroup.getInstance().getBoundsPicker(node.partition).getBoxBounds(this.player.root);
202
- }
203
-
204
- if (!box)
205
- return null;
206
-
207
- const sx = view.width / this.player.stageWidth;
208
- const sy = view.height / this.player.stageHeight;
209
-
210
- //console.log("DisplayObject:getRect not yet implemented");FromBounds
211
- return {
212
- x: box.x * sx,
213
- y: box.y * sy,
214
- width: box.width * sx,
215
- height: box.height * sy
216
- };
217
- }
218
-
219
- private _traverse(node: any, req = false, rect = false, visibleOnly = false) {
220
-
221
- const ret = {
222
- parentId: node.parent ? node.parent.id : -1,
223
- children: null,
224
- rect: null,
225
- }
226
-
227
- for(let name of OBJECT_FIELDS) {
228
- const sub = name.split(":");
229
- if(sub.length > 1) {
230
- ret[sub[1]] = node[sub[0]];
231
- } else{
232
- ret[name] = node[name];
233
- }
234
- }
235
-
236
- ret["globalVisible"] =
237
- node.parent ? (node.parent.visible && node.visible) : node.visible;
238
-
239
- if(rect) {
240
- ret.rect = this._getNodeBounds(node)
241
- }
242
-
243
- if(req) {
244
-
245
- ret.children = [];
246
- for(let c of node._children) {
247
- if(visibleOnly && c.visible || !visibleOnly){
248
- ret.children.push(this._traverse(c, req, rect, visibleOnly));
249
- }
250
- }
251
- }
252
-
253
- return ret;
254
- }
255
-
256
- private _removeObjectByIds(ids: number[]) {
257
- const node = this._selectNode(ids);
258
-
259
- node.parent.removeChild(node);
260
- }
261
-
262
- private _applyPropsByIds(ids: number[], object: any) {
263
- const node = this._selectNode(ids);
264
-
265
- Object.assign(node, object);
266
- }
267
-
268
- private _getSceneTree(params: {flat?: boolean, from?: number, rect?: boolean, visibleOnly?: boolean})
269
- private _getSceneTree(flat?: boolean, from?: number, rect?: boolean)
270
-
271
- private _getSceneTree(params: any, fromArg?: number, rectArg?: boolean) {
272
- if(typeof params !== 'object') {
273
- params = {
274
- flat: params || false,
275
- from: fromArg || 0,
276
- rect: rectArg || false,
277
- visibleOnly: false
278
- }
279
- }
280
-
281
- const {
282
- flat = false,
283
- from = 0,
284
- rect = false,
285
- visibleOnly = false
286
- } = params;
287
-
288
- const tree = [];
289
- //@ts-ignore
290
- const q: any[] = this.player.root._children.slice();
291
-
292
- while(true) {
293
- const node = q.pop();
294
-
295
- if(!node) {
296
- break;
297
- }
298
-
299
- tree.push(this._traverse(node, !flat, rect, visibleOnly));
300
-
301
- if(flat) {
302
- q.push.apply(q, node._children.reverse().filter(e => (e.visible && visibleOnly || !visibleOnly)));
303
- }
304
- }
305
-
306
- return tree;
307
- }
308
-
309
- private _getInfo() {
310
- const player = <any>this.player;
311
-
312
- const avm = player._avmHandler.avmVersion;
313
- const {
314
- swfVersion,
315
- fpVersion,
316
- frameCount,
317
- frameRate,
318
- compression,
319
- bytesTotal
320
- } = player._swfFile;
321
-
322
- let path: string = (<any>player._gameConfig).binary.filter(({resourceType}) => resourceType === 'GAME')[0]?.path;
323
-
324
- if(path && path.indexOf('?') > -1) {
325
- path = path.substring(0, path.indexOf('?'));
326
- }
327
-
328
- return {
329
- file: {
330
- name: (<any>player._gameConfig).title,
331
- path: path,
332
- size: bytesTotal
333
- },
334
- runtime: {
335
- swfVersion,
336
- fpVersion,
337
- frameCount,
338
- frameRate,
339
- compression,
340
- avm
341
- },
342
- config: fullSerializer(player._gameConfig)
343
- }
344
- }
345
- }
1
+
2
+ import { AVMStage, registerDebugMethod } from "@awayfl/swf-loader";
3
+ import { DisplayObject } from '@awayjs/scene';
4
+ import { PickGroup } from "@awayjs/view";
5
+ import { SharedObjectDebug as SOavm2 } from "@awayfl/playerglobal";
6
+ import { SharedObjectDebug as SOavm1 } from "@awayfl/avm1";
7
+
8
+ function fullSerializer(obj: any) {
9
+ const clone = Object.assign({}, obj);
10
+
11
+ Object.keys(clone).forEach((key)=>{
12
+ if(typeof clone[key] === 'object') {
13
+ clone[key] = fullSerializer(clone[key]);
14
+ } else if(typeof clone[key] === 'function') {
15
+ // replace func with it string representation
16
+ clone[key] = clone[key].toString();
17
+ }
18
+ });
19
+
20
+ return clone;
21
+ }
22
+
23
+ const OBJECT_FIELDS = ['id','visible', 'index', 'assetType:type', 'name'];
24
+
25
+ export class AVMDebug {
26
+ private _rafState: 'stop' | 'next' | 'play' = 'play';
27
+ private _defaultRaf: any = self.requestAnimationFrame;
28
+ private _requestedCallbacks: FrameRequestCallback [] = [];
29
+
30
+ constructor(public player: AVMStage) {
31
+
32
+ registerDebugMethod(this._dirObjectByIds.bind(this), {
33
+ name: "dirObjectByIds",
34
+ description:"Export selected object to console",
35
+ declaration: [{name: 'ids', type: "object"}]
36
+ });
37
+
38
+ registerDebugMethod(this._applyPropsByIds.bind(this), {
39
+ name: "applyPropsByIds",
40
+ description:"Apply propertyes by node ids",
41
+ declaration: [{name: 'ids', type: "object"}, {name:'object', type:'object'}]
42
+ });
43
+
44
+ registerDebugMethod(this._removeObjectByIds.bind(this), {
45
+ name: "removeObjectByIds",
46
+ description:"Remove object from sceen tree",
47
+ declaration: [{name: 'ids', type: "object"}]
48
+ });
49
+
50
+ registerDebugMethod(this._getInfo.bind(this), {
51
+ name: "getInfo",
52
+ description:"Get file info for app",
53
+ declaration: [{name:"return", type:"object"}]
54
+ });
55
+
56
+ registerDebugMethod(this._getSceneTree.bind(this), {
57
+ name: "getNodeTree",
58
+ description:"Get sceen tree of app",
59
+ declaration: [
60
+ {name:"return", type:"object"},
61
+ {name:"flat", type:"boolean"},
62
+ {name:"from", type:"number"},
63
+ {name:"rect", type:"object"}
64
+ ]
65
+ });
66
+
67
+ registerDebugMethod(this._getStageCanvas.bind(this), {
68
+ name: "getStageCanvas",
69
+ description:"Get canvas attahed to stage",
70
+ declaration: []
71
+ });
72
+
73
+ registerDebugMethod(this._setRAFState.bind(this), {
74
+ name: "setRAFState",
75
+ description: "Changed RAF state",
76
+ declaration: [
77
+ {name:'return', type: 'string'},
78
+ {name:'state', type: 'string'}
79
+ ]
80
+ });
81
+
82
+ registerDebugMethod(this._getRAFState.bind(this), {
83
+ name: "getRAFState",
84
+ description: "Changed RAF state",
85
+ declaration: [
86
+ {name:'return', type: 'string'},
87
+ {name:'state', type: 'string'}
88
+ ]
89
+ });
90
+
91
+ this._mokedRaf = this._mokedRaf.bind(this);
92
+
93
+ //@ts-ignore
94
+ window._AWAY_DEBUG_PLAYER_ = this;
95
+ }
96
+
97
+ public onAvmInit(version: number) {
98
+
99
+ //@ts-ignore
100
+ window._AWAY_DEBUG_STORAGE = version === 1 ? SOavm1 : SOavm2;
101
+ }
102
+
103
+ private _mokedRaf(callback: FrameRequestCallback) {
104
+ if (this._requestedCallbacks.indexOf(callback) !== -1) return;
105
+
106
+ this._requestedCallbacks.push(callback);
107
+ return 0;
108
+ }
109
+
110
+ private _setRAFState(state: 'stop' | 'next' | 'play'): 'stop' | 'next' | 'play' {
111
+ if (!state) return this._rafState;
112
+ if (state === this._rafState)
113
+ return;
114
+
115
+ if(state === 'next' && this._rafState === 'stop') {
116
+ const time = performance.now();
117
+ const callbacks = this._requestedCallbacks.slice();
118
+
119
+ this._rafState = 'next';
120
+ this._requestedCallbacks.length = 0;
121
+
122
+ callbacks.forEach((e) => e && e(time));
123
+
124
+ return this._rafState = 'stop';
125
+ }
126
+
127
+ if (state === 'stop') {
128
+ this._requestedCallbacks.length = 0;
129
+ self.requestAnimationFrame = this._mokedRaf;
130
+
131
+ return this._rafState = 'stop';
132
+ }
133
+
134
+ if (state === 'play') {
135
+ const time = performance.now();
136
+ const callbacks = this._requestedCallbacks.slice();
137
+
138
+ this._rafState = 'play';
139
+ this._requestedCallbacks.length = 0;
140
+
141
+ self.requestAnimationFrame = this._defaultRaf;
142
+
143
+ callbacks.forEach((e) => e && e(time));
144
+ }
145
+
146
+ return this._rafState;
147
+ }
148
+
149
+ private _getRAFState(): 'stop' | 'next' | 'play' {
150
+ return this._rafState;
151
+ }
152
+
153
+ private _selectNode(ids: number[]): DisplayObject {
154
+ let node = this.player.root as any;
155
+
156
+ for(let i of ids) {
157
+ node = node._children.find((e) => e.id === i);
158
+ if(!node) {
159
+ break;
160
+ }
161
+ }
162
+
163
+ if(!node){
164
+ throw new Error("Node not found");
165
+ }
166
+
167
+ return node;
168
+ }
169
+
170
+ private _getStageCanvas() {
171
+ return this.player.view.stage.container;
172
+ }
173
+
174
+ private _dirObjectByIds(ids: number[]) {
175
+ const node = this._selectNode(ids);
176
+ //@ts-ignore
177
+ const exposeID = window._lastTempNode = window._lastTempNode || 1;
178
+ //@ts-ignore
179
+ window._lastTempNode++;
180
+
181
+ window['tempNode' + exposeID] = node
182
+ console.log('tempNode' + exposeID, '=');
183
+ console.dir(node);
184
+ }
185
+
186
+ private _getNodeBounds(node: DisplayObject) {
187
+ const view = this.player.view;
188
+
189
+ let box;
190
+ const pool = AVMStage.instance && (<any>AVMStage.instance()).pool
191
+ if (pool) {
192
+ //@ts-ignore
193
+ const partition = pool.getNode(node).partition;
194
+ const picker = PickGroup.getInstance().getBoundsPicker(partition);
195
+
196
+ //@ts-ignore
197
+ box = picker.getBoxBounds(pool.getNode(this.player.root), true, true);
198
+
199
+ } else {
200
+ //@ts-ignore
201
+ box = PickGroup.getInstance().getBoundsPicker(node.partition).getBoxBounds(this.player.root);
202
+ }
203
+
204
+ if (!box)
205
+ return null;
206
+
207
+ const sx = view.width / this.player.stageWidth;
208
+ const sy = view.height / this.player.stageHeight;
209
+
210
+ //console.log("DisplayObject:getRect not yet implemented");FromBounds
211
+ return {
212
+ x: box.x * sx,
213
+ y: box.y * sy,
214
+ width: box.width * sx,
215
+ height: box.height * sy
216
+ };
217
+ }
218
+
219
+ private _traverse(node: any, req = false, rect = false, visibleOnly = false) {
220
+
221
+ const ret = {
222
+ parentId: node.parent ? node.parent.id : -1,
223
+ children: null,
224
+ rect: null,
225
+ }
226
+
227
+ for(let name of OBJECT_FIELDS) {
228
+ const sub = name.split(":");
229
+ if(sub.length > 1) {
230
+ ret[sub[1]] = node[sub[0]];
231
+ } else{
232
+ ret[name] = node[name];
233
+ }
234
+ }
235
+
236
+ ret["globalVisible"] =
237
+ node.parent ? (node.parent.visible && node.visible) : node.visible;
238
+
239
+ if(rect) {
240
+ ret.rect = this._getNodeBounds(node)
241
+ }
242
+
243
+ if(req) {
244
+
245
+ ret.children = [];
246
+ for(let c of node._children) {
247
+ if(visibleOnly && c.visible || !visibleOnly){
248
+ ret.children.push(this._traverse(c, req, rect, visibleOnly));
249
+ }
250
+ }
251
+ }
252
+
253
+ return ret;
254
+ }
255
+
256
+ private _removeObjectByIds(ids: number[]) {
257
+ const node = this._selectNode(ids);
258
+
259
+ node.parent.removeChild(node);
260
+ }
261
+
262
+ private _applyPropsByIds(ids: number[], object: any) {
263
+ const node = this._selectNode(ids);
264
+
265
+ Object.assign(node, object);
266
+ }
267
+
268
+ private _getSceneTree(params: {flat?: boolean, from?: number, rect?: boolean, visibleOnly?: boolean})
269
+ private _getSceneTree(flat?: boolean, from?: number, rect?: boolean)
270
+
271
+ private _getSceneTree(params: any, fromArg?: number, rectArg?: boolean) {
272
+ if(typeof params !== 'object') {
273
+ params = {
274
+ flat: params || false,
275
+ from: fromArg || 0,
276
+ rect: rectArg || false,
277
+ visibleOnly: false
278
+ }
279
+ }
280
+
281
+ const {
282
+ flat = false,
283
+ from = 0,
284
+ rect = false,
285
+ visibleOnly = false
286
+ } = params;
287
+
288
+ const tree = [];
289
+ //@ts-ignore
290
+ const q: any[] = this.player.root._children.slice();
291
+
292
+ while(true) {
293
+ const node = q.pop();
294
+
295
+ if(!node) {
296
+ break;
297
+ }
298
+
299
+ tree.push(this._traverse(node, !flat, rect, visibleOnly));
300
+
301
+ if(flat) {
302
+ q.push.apply(q, node._children.reverse().filter(e => (e.visible && visibleOnly || !visibleOnly)));
303
+ }
304
+ }
305
+
306
+ return tree;
307
+ }
308
+
309
+ private _getInfo() {
310
+ const player = <any>this.player;
311
+
312
+ const avm = player._avmHandler.avmVersion;
313
+ const {
314
+ swfVersion,
315
+ fpVersion,
316
+ frameCount,
317
+ frameRate,
318
+ compression,
319
+ bytesTotal
320
+ } = player._swfFile;
321
+
322
+ let path: string = (<any>player._gameConfig).binary.filter(({resourceType}) => resourceType === 'GAME')[0]?.path;
323
+
324
+ if(path && path.indexOf('?') > -1) {
325
+ path = path.substring(0, path.indexOf('?'));
326
+ }
327
+
328
+ return {
329
+ file: {
330
+ name: (<any>player._gameConfig).title,
331
+ path: path,
332
+ size: bytesTotal
333
+ },
334
+ runtime: {
335
+ swfVersion,
336
+ fpVersion,
337
+ frameCount,
338
+ frameRate,
339
+ compression,
340
+ avm
341
+ },
342
+ config: fullSerializer(player._gameConfig)
343
+ }
344
+ }
345
+ }
346
346